A modern Django application for showcasing your development projects. Built with Django 5.1 and styled with Tailwind CSS, this application provides a clean and responsive interface to display your portfolio of projects.
All the About
, Skills
, Social links
and Projects
are fully dynamic
from the database, no code editing should be needed to get your profile up and
running.
There is a live example of this portfolio app at https://www.gnramsay.com
- Features
- Requirements
- Installation
- Configuration
- Usage
- Add your Projects, Personal Details and skills
- Contact Form
- Caching
- Production Deployment
- Project Structure
- Contributing
- Development
- License
- 🚀 Built with Django 5.1
- 💅 Modern UI with Tailwind CSS. We use
django-tailwind-cli
to make the integration easier. This uses the Tailwind CLI and does NOT require Node.js to be installed - 🧩 Component-based templates using
django-cotton
anddjango-shadcn
- 👤 Custom Models and Admin pages to customize the settings and text
- 📝 Contact form with Google reCAPTCHA v2 integration for spam protection and stored in the database as well as sent by email to the site owner
- 🔒 Environment-based configuration with customization from the database
- 🛠️ Modern development tools integration (
uv
,pre-commit
,ruff
,mypy
) - 📱 Fully responsive design
- 🌓 Light/Dark mode options, with a dropdown for user preference or system setting.
- 💾 Optional caching using memcached for improved performance
- 🔄 Live browser reload during development
- 🔐 Enhanced security features for production deployment
- 🚀 Production-ready with gunicorn integration
- 📄 Dynamic pagination of projects using HTMX for smooth, server-side loading
- 🚫 Custom error pages (400, 403, 404, 500) for better user experience
- Python 3.10+
Other package requirements will be automatically installed with the dependencies.
- Clone the repository:
git clone https://github.com/seapagan/django-projects
cd django-projects
- Install dependencies using uv:
uv venv
uv sync --no-dev
source .venv/bin/activate # On Windows: .venv\Scripts\activate
- Clone the repository:
git clone https://github.com/seapagan/django-projects
cd django-projects
- Create and activate a virtual environment:
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
- Install Python dependencies:
pip install -r requirements.txt
The application uses further environment variables for configuration and security. Most of them do have a default setting that will be used if not specified.
Key settings:
DJANGO_SECRET_KEY
: Your Django secret key. This MUST be set to a secure string.DJANGO_DEBUG
: Set to 1 for development, 0 for productionDJANGO_SECURE_MODE
: Set to 1 to enable enhanced security features for production (see Security Features section)DJANGO_CSRF_TRUSTED_ORIGINS
: JSON array of trusted origins for CSRF protection, e.g.["https://www.myserver.com"]
DJANGO_ALLOWED_HOSTS
: JSON array of allowed hosts, e.g.[".myserver.com"]
DJANGO_STATIC_ROOT
: Path where static files will be collected in production modeDJANGO_USE_CACHE
: Set to 1 to enable caching for the whole application. This usesmemcached
and that needs to be installed locally. Defaults to 0 (better for development) See the Caching secton below.DJANGO_CACHE_TIMEOUT
: Cache expiry length, in seconds (Defaults to 600, 10 minutes)DJANGO_PROTECT_ADMIN
: Set to 1 to enable IP-based admin access restriction. If not set or set to 0, all IPs can access the admin panelDJANGO_ADMIN_IPS_ALLOWED
: JSON array of IP addresses allowed to access the admin panel, e.g.["127.0.0.1", "192.168.1.100"]
. If not set, defaults to["127.0.0.1", "localhost"]
. Only used whenDJANGO_PROTECT_ADMIN
is set to 1
Note
If you do specify a value of DJANGO_ADMIN_IPS_ALLOWED
, the defaults are
REMOVED. Should you still want the defaults, they need to be manually added to
the env var along with your custom addresses.
DJANGO_SECURE_PROXY
: Set to 1 if you are using a secure reverse proxy (which passes theX_FORWARDED_FOR
header) to serve this appRECAPTCHA_SITE_KEY
: Your Google reCAPTCHA v2 site keyRECAPTCHA_SECRET_KEY
: Your Google reCAPTCHA v2 secret key
By default, the application uses SQLite for the database. However, you can configure it to use PostgreSQL instead by setting the following environment variables:
DJANGO_USE_POSTGRES
: Set to 1 to use PostgreSQL instead of SQLiteDJANGO_POSTGRES_DB
: PostgreSQL database nameDJANGO_POSTGRES_USER
: PostgreSQL usernameDJANGO_POSTGRES_PASSWORD
: PostgreSQL passwordDJANGO_POSTGRES_HOST
: PostgreSQL host (default: localhost)DJANGO_POSTGRES_PORT
: PostgreSQL port (default: 5432)
Note
The PostgreSQL user and database must already exist, and the user needs to have ownership (or access) to that database.
For SQLite, the local database file will be created when the migrations are run.
Important
This project uses the binary version of psycopg
which is not compatible with
some older systems (primarily older Macs or if using PyPy Python). If you
encounter compatibility issues, you should remove the psycopg[binary]
package and use the plain psycopg
package instead, or compile it locally.
For more information, see the psycopg documentation on supported
systems.
You can configure the email (SMTP) server to send contact emails using the below environment variables:
USE_LIVE_EMAIL
: Set to 1 to send actual emails, 0 or unset to output to consoleEMAIL_HOST
: SMTP server host (required if USE_LIVE_EMAIL=1)EMAIL_PORT
: SMTP server port (required if USE_LIVE_EMAIL=1)EMAIL_HOST_USER
: SMTP username (required if USE_LIVE_EMAIL=1)EMAIL_HOST_PASSWORD
: SMTP password (required if USE_LIVE_EMAIL=1)EMAIL_USE_TLS
: Set to 1 to use TLS for SMTP (optional, defaults to 1)DEFAULT_FROM_EMAIL
: Default sender email address (required if USE_LIVE_EMAIL=1)CONTACT_FORM_RECIPIENT
: Email address where contact form submissions will be sent (required if USE_LIVE_EMAIL=1)
Create an .env
file in the project root with the following content, or set the
environment variables directly:
DJANGO_SECRET_KEY=your-secret-key
DJANGO_DEBUG=1 # sets debug mode
DJANGO_SECURE_MODE=0 # set to 1 for production security features
# Database settings (optional - defaults to SQLite if not set)
DJANGO_USE_POSTGRES=0 # set to 1 to use PostgreSQL
DJANGO_POSTGRES_DB=mydatabase
DJANGO_POSTGRES_USER=myuser
DJANGO_POSTGRES_PASSWORD=your-postgres-password
DJANGO_POSTGRES_HOST=localhost
DJANGO_POSTGRES_PORT=5432
# Production settings (required when DJANGO_DEBUG=0)
DJANGO_CSRF_TRUSTED_ORIGINS=["https://www.myserver.com"]
DJANGO_ALLOWED_HOSTS=[".myserver.com"]
DJANGO_STATIC_ROOT="/var/www/myproject/static/"
# cache settings
DJANGO_USE_CACHE=0 # set to 0 for development, 1 for production when the database rarely changes
DJANGO_CACHE_TIMEOUT=3600 # defaults to 600 (10 minutes) if not set
# admin IP whitelist
DJANGO_PROTECT_ADMIN=1 # set to 1 to enable IP-based admin access restriction
DJANGO_ADMIN_IPS_ALLOWED=["127.0.0.1", "192.168.1.100"] # IP addresses allowed to access admin panel
# enable secure proxy forwarding. This ALSO needs 'DJANGO_SECURE_MODE=1'
DJANGO_SECURE_PROXY=0 # set to 1 if you are serving behind a secure proxy (ie nginx etc)
# recaptcha settings
RECAPTCHA_SITE_KEY=your-recaptcha-site-key
RECAPTCHA_SECRET_KEY=your-recaptcha-secret-key
# Email settings (optional - if USE_LIVE_EMAIL=0, emails will output to console only)
USE_LIVE_EMAIL=1
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_HOST_USER[email protected]
EMAIL_HOST_PASSWORD=your-smtp-password
EMAIL_USE_TLS=1
DEFAULT_FROM_EMAIL[email protected]
CONTACT_FORM_RECIPIENT[email protected]
Note
There is an .example.env
file in the project root you can rename and modify
to your own settings
To get your reCAPTCHA keys (required for the contact form functionality):
- Visit the Google reCAPTCHA Admin Console
- Sign in with your Google account
- Click the "+" button to create a new site
- Choose "reCAPTCHA v2" and "I'm not a robot" Checkbox
- Add your domain(s) to the list (you can use '127.0.0.1' for local testing, be sure to add the correct domain when you deploy)
- Copy the "Site Key" to
RECAPTCHA_SITE_KEY
and "Secret Key" toRECAPTCHA_SECRET_KEY
Note
For a Production or User-Facing project, ALWAYS set DJANGO_DEBUG=0
- Apply database migrations:
python manage.py migrate
- Create a superuser:
python manage.py createsuperuser
- Run the development server:
python manage.py tailwind runserver
- Visit http://localhost:8000/admin to add your projects
- View your portfolio at http://localhost:8000
You can customize the portfolio with your own skills and projects through the Django Admin interface at http://localhost:8000/admin
You can add and manage about sections through the Django admin interface in the Site Configuration section. Each about section has the following field:
- Content: The main text content for this section. This field supports multiple paragraphs and can include limited HTML tags.
About sections are linked to your site configuration and allow you to provide
detailed information about yourself, your background, skills, and experience.
Each about
record will be in a separate paragraph - you can use limited
HTML tags in this field (a
,strong
,em
,ul
,ol
,li
,sub
,sup
- all
others will be stripped.)
If you have no about sections added, the About area will not be displayed.
You can add projects by clicking on the Projects section in the admin pages. Each project has the following fields:
- Title: The name of your project (maximum 100 characters)
- Details: A detailed description of your project. This field supports multiple paragraphs and can be left blank if needed
- Priority: An optional integer value that determines the display order (lower numbers appear first)
- Repository URL: The URL to your project's source code repository (optional)
- Website URL: The URL to your project's live website or demo (optional)
- Tags: Associate relevant tags with your project to categorize it (optional). You first have to add the tags in the Tags section of the Admin page.
Projects are displayed on your portfolio page in the following order:
- Projects with a
priority
value are shown first, sorted by priority (lower numbers appear first and duplicate priorities are sorted by theircreated_at
date.) - Projects without a
priority
value are then displayed, sorted by creation date (oldest to newest)
You can set a project's priority in the admin interface. This allows you to highlight your most important projects by giving them lower priority numbers.
The application provides additional configuration options through the Django admin interface at the Site Configuration section . These settings allow you to customize various aspects of your portfolio:
-
Site Content
owner_name
: The Owner of the site. This will be the page title and the text in the header of the page content (default: "The Developer")hero_title
: The main title displayed on your portfolio (default: "Full Stack Developer")hero_info
: Primary text content for the hero section (up to 500 characters, no default)hero_secondary
: Secondary text content for the hero section (up to 500 characters, no default)
-
Social Media Links
github_username
: Your GitHub usernametwitter_username
: Your Twitter/X usernamelinkedin_username
: Your LinkedIn usernameyoutube_username
: Your YouTube channel usernamemedium_username
: Your Medium username
Any social media usernames left blank will not be displayed on your portfolio.
-
About Section
As mentioned above you can add/edit your 'About' info here too.
-
Languages and Frameworks
You can manage your programming languages and frameworks through the Django admin interface by editing the
Site Configuration
database.-
Languages : Add programming languages you work with
name
: Name of the programming language (e.g., "Python", "JavaScript")
-
Frameworks (
/admin/app/framework/
): Add frameworks and libraries you work withname
: Name of the framework (e.g., "Django", "React")
Both languages and frameworks will be displayed on your portfolio to showcase your technical stack. If you have none added, the whole My SKills section will not be displayed.
-
These settings can be modified at any time through the admin interface and changes will be reflected immediately on your portfolio (though, see the note below on Caching)
The contact form will save each contact into the database and you can view these in the Admin pages in the Contact Submissions section.
Optionally, if you have set up the email, each contact will also be emailed to you as it is submitted.
This Django application is set up for optional caching which is disabled by default. Generally it is not much use when you are setting up or customizing the database since any changes will take 10 minutes to actually be visible in the front-end. It is a bit overkill for this application but a good skill to learn.
Note
Probably best not to enable this until your database is set up to your liking, as the default is a 10 minute cache time-out. See below how to change this.
I have chosen to use memcached for this, though you can use Redis, File Caching, Local Memory or any other method that Django supports. See the Django docs on Caching for more info.
You need to install memcached for this to work. If this is not installed and the cache enabled your application will crash.
See the link above for how to install for your server, under Debian/Ubuntu it is available as a package:
sudo apt install memcached
Finally, to enable the caching, set the below variable in your .env
file or
environment:
DJANGO_USE_CACHE=1
If this variable is 0 or missing, caching will be disabled.
You can change the length of time that the database is cached by setting the below variable:
DJANGO_CACHE_TIMEOUT=1200 # Default is 600 (10 Minutes)
For production deployment, it's recommended to:
- Set
DJANGO_DEBUG=0
to disable debug mode - Set
DJANGO_SECURE_MODE=1
to enable security features
Caution
The Secure Mode settings may need tweaking for your exact use-case. As it
stands, they work well for an application served behind an nginx reverse-proxy
(which is a GREAT way to serve your Django App) but may cause issues in other
cases. Checkout the config/settings.py
file around the end to see what
settings are set and add/adjust any that you need for your specific setup.
- Use PostgreSQL instead of SQLite by setting
DJANGO_USE_POSTGRES=1
and configuring the related database settings - Set
DJANGO_PROTECT_ADMIN=1
to enable IP-based admin access restriction and configureDJANGO_ADMIN_IPS_ALLOWED
with trusted IP addresses - Configure the production-specific environment variables:
DJANGO_CSRF_TRUSTED_ORIGINS
: Your domain(s) as a JSON arrayDJANGO_ALLOWED_HOSTS
: Your domain(s) as a JSON arrayDJANGO_STATIC_ROOT
: The path where static files will be collected
You should also run the collectstatic files before deployment:
python manage.py collectstatic
Tip
This is a customized collectstatic
command which, as well as the usual
functionality will also:
- build the Tailwind production file
- minimize any Javascript files in the
assets/js
folder.
You no longer need to run the python manage.py tailwind build
command
seperately.
The application includes gunicorn for production deployment. A typical command to run the application in production would be:
gunicorn config.wsgi:application --bind 0.0.0.0:8000
For a proper production setup, you should:
- Run gunicorn as a systemd service for automatic startup and monitoring
- Use Nginx as a reverse proxy in front of gunicorn to handle static files, SSL, and more
For a detailed guide on setting up Django with Nginx and Gunicorn in production, see: https://realpython.com/django-nginx-gunicorn/
When DJANGO_SECURE_MODE=1
(and DJANGO_DEBUG=0
), the following security
features are enabled:
- HTTP Strict Transport Security (HSTS) - Initially set to 30 seconds for testing, with a commented option to increase to 15552000 seconds (180 days) once you've verified everything works correctly
Caution
Be very careful when setting the HSTS timeout to a longer value (like 180 days). Once set, it can be almost impossible to change later as browsers will remember this setting for the specified duration. However, setting a longer timeout is highly recommended once you've verified that HSTS is working correctly for your site.
- Secure cookies for CSRF and sessions
- SSL redirection
- Secure referrer policy
- Permissions policy headers that restrict potentially dangerous browser features
- IP-based admin access restriction - When
DJANGO_PROTECT_ADMIN=1
, only allows specified IP addresses to access the admin panel - Secure Proxy forwarding - When
DJANGO_SECURE_PROXY=1
, your proxy needs to support this.
These features follow Django security best practices and help protect your application against common web vulnerabilities.
├── app/ # Main application code
├── assets/ # Static assets
│ └── css/ # CSS files
│ └── js/ # JavaScript files
│ └── favicon.ico # Put your favicon here or use the existing one
├── config/ # Project configuration
├── templates/ # HTML templates
│ ├── app/ # Application templates
│ └── cotton/ # Component templates
└── manage.py # Django management script
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project uses several development tools to maintain code quality:
pre-commit
hooks for code formatting and lintingruff
for Python lintingmypy
for type-checkingdjango-browser-reload
for live developmentdjango-stubs
for improved type checking
Install development dependencies and set up pre-commit:
Using uv (Recommended):
uv sync
pre-commit install
Using pip:
pip install -r requirements-dev.txt
pre-commit install
This project is licensed under the MIT License - see the LICENSE file for details.