Initial commit: Django boilerplate with authentication, Docker, and Dokploy support

 Features:
- Complete Django project with authentication
- Email/password and social login (Google, Facebook)
- Role-based access control (admin, staff, user)
- Docker and Docker Compose setup
- Production-ready configuration
- Static file handling with WhiteNoise
- PostgreSQL database integration
- Comprehensive documentation
- GitHub CI/CD workflows
- Dokploy deployment configuration

🚀 Ready for deployment on Dokploy, Heroku, Railway, and other platforms
This commit is contained in:
Django Template 2025-09-11 09:36:55 +05:30
commit 4bbc56a08f
56 changed files with 2789 additions and 0 deletions

34
.env.example Normal file
View File

@ -0,0 +1,34 @@
# Django Configuration
SECRET_KEY=your-secret-key-here-make-it-long-and-random
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
# Database Configuration
DB_NAME=django_db
DB_USER=django_user
DB_PASSWORD=django_password
DB_HOST=db
DB_PORT=5432
# Email Configuration (for production)
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
DEFAULT_FROM_EMAIL=noreply@your-domain.com
# Social Authentication - Google
GOOGLE_OAUTH2_CLIENT_ID=your-google-client-id
GOOGLE_OAUTH2_CLIENT_SECRET=your-google-client-secret
# Social Authentication - Facebook
FACEBOOK_APP_ID=your-facebook-app-id
FACEBOOK_APP_SECRET=your-facebook-app-secret
# Production SSL Settings (set to True in production)
SECURE_SSL_REDIRECT=False
# Sentry (optional - for error tracking in production)
SENTRY_DSN=your-sentry-dsn-here

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- OS: [e.g. Ubuntu 20.04]
- Python version: [e.g. 3.11]
- Django version: [e.g. 4.2.7]
- Browser [e.g. chrome, safari]
- Docker version [if using Docker]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

32
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,32 @@
## Description
Please include a summary of the changes and the related issue. Please also include relevant motivation and context.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
- [ ] Test A
- [ ] Test B
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published

90
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,90 @@
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Cache pip dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements/development.txt
- name: Create .env file
run: |
echo "SECRET_KEY=test-secret-key-for-ci" >> .env
echo "DEBUG=True" >> .env
echo "DB_NAME=test_db" >> .env
echo "DB_USER=postgres" >> .env
echo "DB_PASSWORD=postgres" >> .env
echo "DB_HOST=localhost" >> .env
echo "DB_PORT=5432" >> .env
- name: Run migrations
run: |
python manage.py migrate --settings=django_project.settings.development
- name: Create test groups
run: |
python manage.py create_groups --settings=django_project.settings.development
- name: Run tests
run: |
python manage.py test --settings=django_project.settings.development
- name: Check for missing migrations
run: |
python manage.py makemigrations --check --dry-run --settings=django_project.settings.development
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
run: |
docker build --target development -t django-template:test .
- name: Test Docker image
run: |
docker run --rm django-template:test python manage.py check --settings=django_project.settings.development

149
.gitignore vendored Normal file
View File

@ -0,0 +1,149 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
Pipfile.lock
# PEP 582
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Django specific
media/
staticfiles/
static_cdn/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# SSL certificates
ssl/
# Docker
.dockerignore

273
DEPLOYMENT.md Normal file
View File

@ -0,0 +1,273 @@
# Deployment Guide
This guide covers how to deploy the Django Template application using various platforms including Dokploy.
## 🚀 Dokploy Deployment
Dokploy is a modern deployment platform that makes it easy to deploy applications with Docker.
### Prerequisites
1. A Dokploy account and server
2. A GitHub repository with your code
3. Domain name (optional but recommended)
### Quick Deploy
1. **Fork this repository** to your GitHub account
2. **Connect to Dokploy:**
- Log in to your Dokploy dashboard
- Click "New Application"
- Connect your GitHub repository
3. **Configure Environment Variables:**
```env
SECRET_KEY=your-very-long-random-secret-key
DEBUG=False
ALLOWED_HOSTS=your-domain.com,www.your-domain.com
# Database (Dokploy will provide these)
DB_NAME=django_db
DB_USER=django_user
DB_PASSWORD=secure_password
DB_HOST=postgres
DB_PORT=5432
# Email Configuration
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
DEFAULT_FROM_EMAIL=noreply@your-domain.com
# Social Authentication
GOOGLE_OAUTH2_CLIENT_ID=your-google-client-id
GOOGLE_OAUTH2_CLIENT_SECRET=your-google-client-secret
FACEBOOK_APP_ID=your-facebook-app-id
FACEBOOK_APP_SECRET=your-facebook-app-secret
# Security
SECURE_SSL_REDIRECT=True
```
4. **Configure Database:**
- Add a PostgreSQL database service
- Use PostgreSQL 15
- Database name: `django_db`
- Username: `django_user`
5. **Deploy:**
- Click "Deploy"
- Dokploy will build and deploy your application automatically
### Post-Deployment Steps
1. **Run initial setup:**
```bash
# Access your application console in Dokploy
python manage.py migrate
python manage.py create_groups
python manage.py createsuperuser
python manage.py collectstatic --noinput
```
2. **Configure Domain:**
- Add your domain in Dokploy dashboard
- Configure SSL certificate (Let's Encrypt recommended)
3. **Set up Social Authentication:**
- Configure Google OAuth2 callback URL: `https://your-domain.com/accounts/google/login/callback/`
- Configure Facebook OAuth2 callback URL: `https://your-domain.com/accounts/facebook/login/callback/`
### Environment Variables Reference
| Variable | Description | Required | Default |
|----------|-------------|----------|---------|
| `SECRET_KEY` | Django secret key | Yes | - |
| `DEBUG` | Debug mode | Yes | False |
| `ALLOWED_HOSTS` | Allowed hostnames | Yes | - |
| `DB_NAME` | Database name | Yes | django_db |
| `DB_USER` | Database user | Yes | django_user |
| `DB_PASSWORD` | Database password | Yes | - |
| `DB_HOST` | Database host | Yes | postgres |
| `DB_PORT` | Database port | Yes | 5432 |
| `EMAIL_HOST` | SMTP host | No | localhost |
| `EMAIL_PORT` | SMTP port | No | 587 |
| `EMAIL_HOST_USER` | SMTP username | No | - |
| `EMAIL_HOST_PASSWORD` | SMTP password | No | - |
| `GOOGLE_OAUTH2_CLIENT_ID` | Google OAuth2 ID | No | - |
| `GOOGLE_OAUTH2_CLIENT_SECRET` | Google OAuth2 secret | No | - |
| `FACEBOOK_APP_ID` | Facebook app ID | No | - |
| `FACEBOOK_APP_SECRET` | Facebook app secret | No | - |
## 🐳 Docker Deployment
### Production Docker Compose
```bash
# Create production environment file
cp .env.example .env.prod
# Edit .env.prod with production values
nano .env.prod
# Deploy with production compose
docker-compose -f docker-compose.prod.yml up -d --build
```
### Manual Docker Deployment
```bash
# Build production image
docker build --target production -t django-template:latest .
# Run with environment variables
docker run -d \
--name django-app \
-p 80:8000 \
--env-file .env.prod \
django-template:latest
```
## ☁️ Cloud Platform Deployment
### Heroku
1. **Prepare for Heroku:**
```bash
# Create Procfile
echo "web: gunicorn django_project.wsgi --bind 0.0.0.0:\$PORT" > Procfile
# Create runtime.txt
echo "python-3.11.0" > runtime.txt
```
2. **Deploy to Heroku:**
```bash
heroku create your-app-name
heroku addons:create heroku-postgresql:mini
heroku config:set SECRET_KEY=your-secret-key
heroku config:set DEBUG=False
git push heroku main
heroku run python manage.py migrate
heroku run python manage.py create_groups
heroku run python manage.py createsuperuser
```
### Railway
1. **Connect GitHub repository to Railway**
2. **Add PostgreSQL database**
3. **Configure environment variables**
4. **Deploy automatically**
### DigitalOcean App Platform
1. **Create new app from GitHub**
2. **Add managed database (PostgreSQL)**
3. **Configure environment variables**
4. **Deploy**
## 🔒 Security Checklist
Before deploying to production:
- [ ] Set `DEBUG=False`
- [ ] Use a strong, unique `SECRET_KEY`
- [ ] Configure `ALLOWED_HOSTS` properly
- [ ] Set up SSL/TLS certificate
- [ ] Enable `SECURE_SSL_REDIRECT=True`
- [ ] Configure proper database credentials
- [ ] Set up email backend for notifications
- [ ] Configure social authentication with production URLs
- [ ] Set up monitoring and logging
- [ ] Configure backup strategy for database
- [ ] Review and update all default passwords
## 📊 Monitoring
### Health Checks
The application provides a health check endpoint:
- URL: `/` (returns 200 if healthy)
- Database connectivity check included
### Logging
Logs are configured for production in `settings/production.py`:
- Application logs: `/var/log/django/django.log`
- Console output for container logs
### Metrics
Consider adding:
- Application Performance Monitoring (APM)
- Database monitoring
- Error tracking (Sentry is pre-configured)
## 🔄 Updates and Maintenance
### Updating the Application
1. **Pull latest changes:**
```bash
git pull origin main
```
2. **Rebuild and redeploy:**
```bash
docker-compose -f docker-compose.prod.yml up -d --build
```
3. **Run migrations if needed:**
```bash
docker-compose -f docker-compose.prod.yml exec web python manage.py migrate
```
### Backup Strategy
1. **Database backup:**
```bash
docker-compose -f docker-compose.prod.yml exec db pg_dump -U django_user django_db > backup.sql
```
2. **Media files backup:**
```bash
docker-compose -f docker-compose.prod.yml exec web tar -czf media_backup.tar.gz /app/media
```
## 🆘 Troubleshooting
### Common Issues
1. **500 Internal Server Error:**
- Check `DEBUG=False` and `ALLOWED_HOSTS`
- Verify database connection
- Check application logs
2. **Static files not loading:**
- Run `python manage.py collectstatic`
- Check `STATIC_URL` and `STATIC_ROOT` settings
3. **Social login not working:**
- Verify callback URLs in provider settings
- Check client ID and secret configuration
4. **Email not sending:**
- Verify SMTP settings
- Check firewall/security group settings
- Test email backend configuration
### Getting Help
- Check the application logs
- Review the GitHub Issues
- Consult the Django documentation
- Check provider-specific documentation (Dokploy, Heroku, etc.)
---
**Happy deploying!** 🚀

44
Dockerfile Normal file
View File

@ -0,0 +1,44 @@
FROM python:3.11-slim as base
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
postgresql-client \
build-essential \
libpq-dev \
gettext \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements/base.txt /app/requirements/
RUN pip install --no-cache-dir -r requirements/base.txt
FROM base as development
COPY requirements/development.txt /app/requirements/
RUN pip install --no-cache-dir -r requirements/development.txt
COPY . /app/
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
FROM base as production
COPY requirements/production.txt /app/requirements/
RUN pip install --no-cache-dir -r requirements/production.txt
RUN groupadd -r django && useradd -r -g django django
COPY . /app/
RUN chown -R django:django /app
USER django
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "django_project.wsgi:application"]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Django Template
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

362
README.md Normal file
View File

@ -0,0 +1,362 @@
# Django Boilerplate
A production-ready Django boilerplate with built-in authentication, social login, role-based permissions, and Docker support.
## ✨ Features
- 🔐 **Complete Authentication System**
- Email/password registration and login
- Email verification required for account activation
- Password reset workflows
- Django Allauth integration
- 🌐 **Social Authentication**
- Google OAuth2 integration
- Facebook OAuth2 integration
- Easy to extend for other providers
- 👥 **Role-Based Access Control**
- User groups: `admin`, `staff`, `user`
- Permission-based access control
- Custom decorators and mixins for role checking
- 🐳 **Docker & Production Ready**
- Multi-stage Dockerfile
- Docker Compose for development and production
- PostgreSQL database
- Redis for caching
- Nginx reverse proxy
- Gunicorn WSGI server
- 🎨 **Modern Frontend**
- Bootstrap 5 integration
- Responsive design
- Clean, professional UI
- ⚙️ **Environment Management**
- Separate settings for development/production
- Environment variables for sensitive data
- Comprehensive configuration
## 🚀 Quick Start
### Prerequisites
- Docker and Docker Compose
- Git
### 1. Clone the Repository
```bash
git clone https://github.com/yourusername/django-template.git
cd django-template
```
### 2. Environment Configuration
```bash
cp .env.example .env
```
Edit the `.env` file with your configuration:
```env
# Django Configuration
SECRET_KEY=your-very-long-and-random-secret-key
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
# Database Configuration
DB_NAME=django_db
DB_USER=django_user
DB_PASSWORD=secure_password
DB_HOST=db
DB_PORT=5432
# Email Configuration (for production)
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
DEFAULT_FROM_EMAIL=noreply@your-domain.com
# Social Authentication
GOOGLE_OAUTH2_CLIENT_ID=your-google-client-id
GOOGLE_OAUTH2_CLIENT_SECRET=your-google-client-secret
FACEBOOK_APP_ID=your-facebook-app-id
FACEBOOK_APP_SECRET=your-facebook-app-secret
```
### 3. Build and Run with Docker
```bash
# Build and start the services
docker-compose up --build
# Or run in detached mode
docker-compose up -d --build
```
### 4. Run Database Migrations
```bash
docker-compose exec web python manage.py migrate
```
### 5. Create Default User Groups
```bash
docker-compose exec web python manage.py create_groups
```
### 6. Create a Superuser
```bash
# Interactive creation
docker-compose exec web python manage.py createsuperuser
# Or use management command with defaults
docker-compose exec web python manage.py create_superuser --email admin@example.com --password admin123
```
### 7. Access the Application
- **Web Application**: http://localhost:8000
- **Django Admin**: http://localhost:8000/admin/
## 🔧 Development Setup
### Local Development (without Docker)
1. **Create a virtual environment:**
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\\Scripts\\activate
```
2. **Install dependencies:**
```bash
pip install -r requirements/development.txt
```
3. **Set up local database:**
```bash
# Install and start PostgreSQL
# Create database and user as configured in .env
```
4. **Run migrations:**
```bash
python manage.py migrate
python manage.py create_groups
python manage.py createsuperuser
```
5. **Start development server:**
```bash
python manage.py runserver
```
## 🌐 Social Authentication Setup
### Google OAuth2
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select existing
3. Enable Google+ API
4. Create OAuth2 credentials
5. Add authorized redirect URIs:
- `http://localhost:8000/accounts/google/login/callback/`
- `https://yourdomain.com/accounts/google/login/callback/`
6. Update `.env` with your client ID and secret
### Facebook OAuth2
1. Go to [Facebook Developers](https://developers.facebook.com/)
2. Create a new app
3. Add Facebook Login product
4. Configure Valid OAuth Redirect URIs:
- `http://localhost:8000/accounts/facebook/login/callback/`
- `https://yourdomain.com/accounts/facebook/login/callback/`
5. Update `.env` with your app ID and secret
## 📧 Email Configuration
For production email functionality:
1. **Gmail Setup:**
```env
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-gmail@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
```
2. **Generate App Password:**
- Enable 2FA on your Gmail account
- Generate an app-specific password
- Use this password in `EMAIL_HOST_PASSWORD`
## 🐳 Production Deployment
### Using Docker Compose (Production)
1. **Update environment variables:**
```bash
cp .env.example .env.prod
# Edit .env.prod with production values
```
2. **Deploy with production compose:**
```bash
docker-compose -f docker-compose.prod.yml up -d --build
```
3. **SSL Configuration:**
- Place SSL certificates in `ssl/` directory
- Update `nginx.prod.conf` with your domain
- Certificates should be named `cert.pem` and `key.pem`
### Environment Variables for Production
```env
DEBUG=False
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
SECURE_SSL_REDIRECT=True
SECRET_KEY=generate-a-new-secure-secret-key
# Database
DB_PASSWORD=use-a-strong-database-password
# Email
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
# Configure with your email provider
# Social Auth
# Configure with production callback URLs
```
## 📁 Project Structure
```
django-template/
├── django_project/ # Main Django project
│ ├── settings/ # Environment-specific settings
│ ├── urls.py # Main URL configuration
│ └── wsgi.py # WSGI application
├── apps/ # Django applications
│ ├── accounts/ # User authentication & profiles
│ └── core/ # Core application logic
├── templates/ # HTML templates
├── static/ # Static files (CSS, JS, images)
├── requirements/ # Python dependencies
├── Dockerfile # Docker configuration
├── docker-compose.yml # Development Docker Compose
├── docker-compose.prod.yml # Production Docker Compose
├── nginx.conf # Nginx configuration
└── entrypoint.sh # Docker entrypoint script
```
## 🔐 User Roles & Permissions
### Default User Groups
- **admin**: Full administrative access
- **staff**: Limited administrative access
- **user**: Basic user permissions
### Usage in Views
```python
from apps.core.views import admin_required, staff_required
@login_required
@admin_required
def admin_only_view(request):
return render(request, 'admin_only.html')
@login_required
@staff_required
def staff_view(request):
return render(request, 'staff.html')
```
### Usage in Templates
```html
{% if user.has_role:'admin' %}
<a href="{% url 'admin_dashboard' %}">Admin Panel</a>
{% endif %}
```
## 🛠️ Management Commands
```bash
# Create default user groups
python manage.py create_groups
# Create superuser with admin role
python manage.py create_superuser --email admin@example.com --password admin123
# Standard Django commands
python manage.py migrate
python manage.py collectstatic
python manage.py createsuperuser
```
## 🧪 Testing
```bash
# Run tests
docker-compose exec web python manage.py test
# With coverage
docker-compose exec web coverage run --source='.' manage.py test
docker-compose exec web coverage report
```
## 📚 API Documentation
The project is ready for API development. Consider adding:
- Django REST Framework
- API documentation with drf-yasg
- Authentication tokens
- Throttling and permissions
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🆘 Support
- Create an issue for bug reports or feature requests
- Check the Django documentation: https://docs.djangoproject.com/
- Django Allauth documentation: https://django-allauth.readthedocs.io/
## 🎉 What's Next?
Consider adding these features:
- [ ] API with Django REST Framework
- [ ] Celery for background tasks
- [ ] Monitoring with Sentry
- [ ] CI/CD pipeline
- [ ] Advanced user profiles
- [ ] Multi-tenant support
- [ ] Internationalization (i18n)
---
**Happy coding!** 🚀

0
apps/__init__.py Normal file
View File

View File

48
apps/accounts/admin.py Normal file
View File

@ -0,0 +1,48 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import Group
from .models import User, UserProfile
class UserProfileInline(admin.StackedInline):
model = UserProfile
can_delete = False
verbose_name_plural = 'Profile'
@admin.register(User)
class UserAdmin(BaseUserAdmin):
inlines = (UserProfileInline,)
list_display = ('email', 'username', 'first_name', 'last_name', 'is_staff', 'is_verified', 'created_at')
list_filter = ('is_staff', 'is_superuser', 'is_active', 'is_verified', 'groups')
search_fields = ('email', 'username', 'first_name', 'last_name')
ordering = ('email',)
filter_horizontal = ('groups', 'user_permissions')
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('username', 'first_name', 'last_name')}),
('Permissions', {
'fields': ('is_active', 'is_staff', 'is_superuser', 'is_verified', 'groups', 'user_permissions'),
}),
('Important dates', {'fields': ('last_login', 'date_joined')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'username', 'first_name', 'last_name', 'password1', 'password2'),
}),
)
def get_inline_instances(self, request, obj=None):
if not obj:
return list()
return super().get_inline_instances(request, obj)
@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('user', 'location', 'birth_date')
search_fields = ('user__email', 'user__first_name', 'user__last_name')
list_filter = ('location',)

9
apps/accounts/apps.py Normal file
View File

@ -0,0 +1,9 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.accounts'
def ready(self):
import apps.accounts.signals

View File

View File

@ -0,0 +1,53 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from apps.accounts.models import User
class Command(BaseCommand):
help = 'Create default user groups with appropriate permissions'
def handle(self, *args, **options):
groups_permissions = {
'admin': [
'add_user', 'change_user', 'delete_user', 'view_user',
'add_group', 'change_group', 'delete_group', 'view_group',
],
'staff': [
'view_user', 'change_user',
'view_group',
],
'user': [
'view_user',
]
}
for group_name, permissions in groups_permissions.items():
group, created = Group.objects.get_or_create(name=group_name)
if created:
self.stdout.write(
self.style.SUCCESS(f'Created group: {group_name}')
)
else:
self.stdout.write(
self.style.WARNING(f'Group already exists: {group_name}')
)
for permission_name in permissions:
try:
permission = Permission.objects.get(codename=permission_name)
group.permissions.add(permission)
except Permission.DoesNotExist:
self.stdout.write(
self.style.ERROR(f'Permission not found: {permission_name}')
)
group.save()
self.stdout.write(
self.style.SUCCESS(f'Configured permissions for {group_name}')
)
self.stdout.write(
self.style.SUCCESS('Successfully created all default groups')
)

View File

@ -0,0 +1,43 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group
from apps.accounts.models import User
from decouple import config
class Command(BaseCommand):
help = 'Create a superuser and assign admin role'
def add_arguments(self, parser):
parser.add_argument('--email', type=str, help='Superuser email')
parser.add_argument('--password', type=str, help='Superuser password')
parser.add_argument('--first_name', type=str, help='First name', default='Admin')
parser.add_argument('--last_name', type=str, help='Last name', default='User')
def handle(self, *args, **options):
email = options.get('email') or config('SUPERUSER_EMAIL', default='admin@example.com')
password = options.get('password') or config('SUPERUSER_PASSWORD', default='admin123')
first_name = options.get('first_name', 'Admin')
last_name = options.get('last_name', 'User')
if User.objects.filter(email=email).exists():
self.stdout.write(
self.style.WARNING(f'User with email {email} already exists')
)
return
user = User.objects.create_superuser(
email=email,
username=email,
password=password,
first_name=first_name,
last_name=last_name
)
admin_group, created = Group.objects.get_or_create(name='admin')
user.groups.add(admin_group)
self.stdout.write(
self.style.SUCCESS(
f'Successfully created superuser: {email} with admin role'
)
)

View File

@ -0,0 +1,59 @@
# Generated by Django 4.2.7 on 2025-09-10 18:41
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=254, unique=True)),
('first_name', models.CharField(max_length=30)),
('last_name', models.CharField(max_length=30)),
('is_verified', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'db_table': 'auth_user',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bio', models.TextField(blank=True, max_length=500)),
('location', models.CharField(blank=True, max_length=30)),
('birth_date', models.DateField(blank=True, null=True)),
('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')),
('phone_number', models.CharField(blank=True, max_length=15)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

50
apps/accounts/models.py Normal file
View File

@ -0,0 +1,50 @@
from django.contrib.auth.models import AbstractUser, Group
from django.db import models
class User(AbstractUser):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
is_verified = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
class Meta:
db_table = 'auth_user'
def __str__(self):
return self.email
@property
def full_name(self):
return f"{self.first_name} {self.last_name}".strip()
def has_role(self, role_name):
return self.groups.filter(name=role_name).exists()
def assign_role(self, role_name):
group, created = Group.objects.get_or_create(name=role_name)
self.groups.add(group)
def remove_role(self, role_name):
try:
group = Group.objects.get(name=role_name)
self.groups.remove(group)
except Group.DoesNotExist:
pass
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
phone_number = models.CharField(max_length=15, blank=True)
def __str__(self):
return f"{self.user.email} Profile"

19
apps/accounts/signals.py Normal file
View File

@ -0,0 +1,19 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import Group
from .models import User, UserProfile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
default_group, _ = Group.objects.get_or_create(name='user')
instance.groups.add(default_group)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
if hasattr(instance, 'profile'):
instance.profile.save()

10
apps/accounts/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from . import views
app_name = 'accounts'
urlpatterns = [
path('profile/', views.ProfileView.as_view(), name='profile'),
path('profile/edit/', views.ProfileUpdateView.as_view(), name='profile_edit'),
path('dashboard/', views.dashboard, name='dashboard'),
]

40
apps/accounts/views.py Normal file
View File

@ -0,0 +1,40 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render, redirect
from django.views.generic import TemplateView, UpdateView
from django.contrib import messages
from django.urls import reverse_lazy
from .models import User, UserProfile
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'account/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
model = UserProfile
template_name = 'account/profile_edit.html'
fields = ['bio', 'location', 'birth_date', 'avatar', 'phone_number']
success_url = reverse_lazy('accounts:profile')
def get_object(self):
profile, created = UserProfile.objects.get_or_create(user=self.request.user)
return profile
def form_valid(self, form):
messages.success(self.request, 'Your profile has been updated successfully!')
return super().form_valid(form)
@login_required
def dashboard(request):
context = {
'user': request.user,
'user_groups': request.user.groups.all(),
}
return render(request, 'account/dashboard.html', context)

0
apps/core/__init__.py Normal file
View File

6
apps/core/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.core'

10
apps/core/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from . import views
app_name = 'core'
urlpatterns = [
path('', views.home, name='home'),
path('admin-dashboard/', views.admin_dashboard, name='admin_dashboard'),
path('staff-dashboard/', views.staff_dashboard, name='staff_dashboard'),
]

53
apps/core/views.py Normal file
View File

@ -0,0 +1,53 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import TemplateView
from django.http import HttpResponseForbidden
def home(request):
return render(request, 'home.html')
class HomeView(TemplateView):
template_name = 'home.html'
class AdminRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.has_role('admin')
class StaffRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.has_role('staff') or self.request.user.has_role('admin')
def admin_required(function):
def wrap(request, *args, **kwargs):
if request.user.has_role('admin'):
return function(request, *args, **kwargs)
else:
return HttpResponseForbidden('Admin access required')
return wrap
def staff_required(function):
def wrap(request, *args, **kwargs):
if request.user.has_role('staff') or request.user.has_role('admin'):
return function(request, *args, **kwargs)
else:
return HttpResponseForbidden('Staff access required')
return wrap
@login_required
@admin_required
def admin_dashboard(request):
return render(request, 'admin_dashboard.html')
@login_required
@staff_required
def staff_dashboard(request):
return render(request, 'staff_dashboard.html')

View File

View File

View File

@ -0,0 +1,180 @@
"""
Base settings for Django project.
"""
import os
from pathlib import Path
from decouple import config
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = config('SECRET_KEY', default='django-insecure-change-this-in-production')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=lambda v: [s.strip() for s in v.split(',')])
DJANGO_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
]
THIRD_PARTY_APPS = [
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.facebook',
'whitenoise.runserver_nostatic',
]
LOCAL_APPS = [
'apps.accounts',
'apps.core',
]
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'allauth.account.middleware.AccountMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'django_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'django_project.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME', default='django_db'),
'USER': config('DB_USER', default='django_user'),
'PASSWORD': config('DB_PASSWORD', default='django_password'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'accounts.User'
SITE_ID = 1
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
ACCOUNT_LOGOUT_ON_GET = True
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = True
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 300
LOGIN_URL = '/accounts/login/'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='noreply@example.com')
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
],
'AUTH_PARAMS': {
'access_type': 'online',
},
'OAUTH_PKCE_ENABLED': True,
},
'facebook': {
'METHOD': 'oauth2',
'SCOPE': ['email', 'public_profile'],
'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
'INIT_PARAMS': {'cookie': True},
'FIELDS': [
'id',
'first_name',
'last_name',
'middle_name',
'name',
'name_format',
'picture',
'short_name'
],
'EXCHANGE_TOKEN': True,
'LOCALE_FUNC': 'path.to.callable',
'VERIFIED_EMAIL': False,
'VERSION': 'v7.0',
}
}
SOCIALACCOUNT_LOGIN_ON_GET = True

View File

@ -0,0 +1,41 @@
"""
Development settings for Django project.
"""
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME', default='django_db'),
'USER': config('DB_USER', default='django_user'),
'PASSWORD': config('DB_PASSWORD', default='django_password'),
'HOST': config('DB_HOST', default='db'),
'PORT': config('DB_PORT', default='5432'),
}
}
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
'propagate': False,
},
},
}

View File

@ -0,0 +1,57 @@
"""
Production settings for Django project.
"""
from .base import *
DEBUG = False
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=lambda v: [s.strip() for s in v.split(',')])
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_SECONDS = 31536000
SECURE_REDIRECT_EXEMPT = []
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=False, cast=bool)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_TZ = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_PRELOAD = True
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/log/django/django.log',
'formatter': 'verbose',
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'root': {
'handlers': ['console'],
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propagate': False,
},
},
}

16
django_project/urls.py Normal file
View File

@ -0,0 +1,16 @@
"""django_project URL Configuration"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),
path('accounts/', include('apps.accounts.urls')),
path('', include('apps.core.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

16
django_project/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for django_project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings.production')
application = get_wsgi_application()

51
docker-compose.prod.yml Normal file
View File

@ -0,0 +1,51 @@
services:
web:
build:
context: .
target: production
command: >
sh -c "chmod +x /app/entrypoint.sh &&
/app/entrypoint.sh &&
gunicorn --bind 0.0.0.0:8000 --workers 3 django_project.wsgi:application"
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/media
expose:
- 8000
env_file:
- .env
depends_on:
- db
- redis
environment:
- DJANGO_SETTINGS_MODULE=django_project.settings.production
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=${DB_NAME:-django_db}
- POSTGRES_USER=${DB_USER:-django_user}
- POSTGRES_PASSWORD=${DB_PASSWORD:-django_password}
redis:
image: redis:7-alpine
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.prod.conf:/etc/nginx/conf.d/default.conf
- static_volume:/app/staticfiles
- media_volume:/app/media
- ./ssl:/etc/nginx/ssl
depends_on:
- web
volumes:
postgres_data:
static_volume:
media_volume:

53
docker-compose.yml Normal file
View File

@ -0,0 +1,53 @@
services:
web:
build:
context: .
target: development
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
- static_volume:/app/staticfiles
- media_volume:/app/media
ports:
- "8001:8000"
env_file:
- .env
depends_on:
- db
- redis
environment:
- DJANGO_SETTINGS_MODULE=django_project.settings.development
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=${DB_NAME:-django_db}
- POSTGRES_USER=${DB_USER:-django_user}
- POSTGRES_PASSWORD=${DB_PASSWORD:-django_password}
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- static_volume:/app/staticfiles
- media_volume:/app/media
depends_on:
- web
profiles:
- production
volumes:
postgres_data:
static_volume:
media_volume:

143
dokploy.json Normal file
View File

@ -0,0 +1,143 @@
{
"name": "django-template",
"description": "Django boilerplate with authentication and Docker support",
"type": "application",
"buildType": "dockerfile",
"dockerfile": "Dockerfile",
"buildArgs": {
"TARGET": "production"
},
"env": [
{
"key": "SECRET_KEY",
"value": "your-production-secret-key-here",
"description": "Django secret key for production"
},
{
"key": "DEBUG",
"value": "False",
"description": "Set to False in production"
},
{
"key": "ALLOWED_HOSTS",
"value": "your-domain.com,www.your-domain.com",
"description": "Comma-separated list of allowed hosts"
},
{
"key": "DB_NAME",
"value": "django_db",
"description": "PostgreSQL database name"
},
{
"key": "DB_USER",
"value": "django_user",
"description": "PostgreSQL database user"
},
{
"key": "DB_PASSWORD",
"value": "secure_password_here",
"description": "PostgreSQL database password"
},
{
"key": "DB_HOST",
"value": "db",
"description": "PostgreSQL database host"
},
{
"key": "DB_PORT",
"value": "5432",
"description": "PostgreSQL database port"
},
{
"key": "EMAIL_BACKEND",
"value": "django.core.mail.backends.smtp.EmailBackend",
"description": "Email backend for production"
},
{
"key": "EMAIL_HOST",
"value": "smtp.gmail.com",
"description": "SMTP server host"
},
{
"key": "EMAIL_PORT",
"value": "587",
"description": "SMTP server port"
},
{
"key": "EMAIL_USE_TLS",
"value": "True",
"description": "Use TLS for email"
},
{
"key": "EMAIL_HOST_USER",
"value": "your-email@gmail.com",
"description": "Email account username"
},
{
"key": "EMAIL_HOST_PASSWORD",
"value": "your-app-password",
"description": "Email account password or app password"
},
{
"key": "DEFAULT_FROM_EMAIL",
"value": "noreply@your-domain.com",
"description": "Default from email address"
},
{
"key": "GOOGLE_OAUTH2_CLIENT_ID",
"value": "your-google-client-id",
"description": "Google OAuth2 client ID"
},
{
"key": "GOOGLE_OAUTH2_CLIENT_SECRET",
"value": "your-google-client-secret",
"description": "Google OAuth2 client secret"
},
{
"key": "FACEBOOK_APP_ID",
"value": "your-facebook-app-id",
"description": "Facebook app ID"
},
{
"key": "FACEBOOK_APP_SECRET",
"value": "your-facebook-app-secret",
"description": "Facebook app secret"
},
{
"key": "SECURE_SSL_REDIRECT",
"value": "True",
"description": "Force HTTPS redirects in production"
}
],
"ports": [
{
"containerPort": 8000,
"hostPort": 80,
"protocol": "tcp"
}
],
"volumes": [
{
"name": "static_files",
"mountPath": "/app/staticfiles"
},
{
"name": "media_files",
"mountPath": "/app/media"
}
],
"healthCheck": {
"path": "/",
"port": 8000,
"interval": 30,
"timeout": 10,
"retries": 3
},
"database": {
"type": "postgresql",
"version": "15",
"name": "django_db",
"username": "django_user",
"password": "secure_password_here"
}
}

17
entrypoint.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
set -e
echo "Waiting for PostgreSQL..."
sleep 5
echo "Running migrations..."
python manage.py migrate --noinput
echo "Creating default groups..."
python manage.py create_groups
echo "Collecting static files..."
python manage.py collectstatic --noinput
exec "$@"

16
manage.py Normal file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings.development')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

30
nginx.conf Normal file
View File

@ -0,0 +1,30 @@
upstream django {
server web:8000;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://django;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
alias /app/staticfiles/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /app/media/;
expires 7d;
add_header Cache-Control "public";
}
client_max_body_size 20M;
}

51
nginx.prod.conf Normal file
View File

@ -0,0 +1,51 @@
upstream django {
server web:8000;
}
server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
location / {
proxy_pass http://django;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
alias /app/staticfiles/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /app/media/;
expires 7d;
add_header Cache-Control "public";
}
client_max_body_size 20M;
}

7
requirements/base.txt Normal file
View File

@ -0,0 +1,7 @@
Django==4.2.7
django-allauth==0.57.0
python-decouple==3.8
psycopg2-binary==2.9.7
Pillow==10.0.1
whitenoise==6.6.0
gunicorn==21.2.0

View File

@ -0,0 +1,4 @@
-r base.txt
django-debug-toolbar==4.2.0
django-extensions==3.2.3

View File

@ -0,0 +1,3 @@
-r base.txt
sentry-sdk[django]==1.38.0

57
static/css/style.css Normal file
View File

@ -0,0 +1,57 @@
/* Custom styles for Django Boilerplate */
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.jumbotron {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
}
.card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
transition: box-shadow 0.15s ease-in-out;
}
.card:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.navbar-brand {
font-weight: bold;
font-size: 1.5rem;
}
footer {
margin-top: auto;
}
html, body {
height: 100%;
}
#root {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
}
.alert {
border: none;
border-radius: 0.5rem;
}
.btn {
border-radius: 0.375rem;
}
.form-control, .form-select {
border-radius: 0.375rem;
}

22
static/js/main.js Normal file
View File

@ -0,0 +1,22 @@
// Custom JavaScript for Django Boilerplate
document.addEventListener('DOMContentLoaded', function() {
// Auto-dismiss alerts after 5 seconds
setTimeout(function() {
const alerts = document.querySelectorAll('.alert');
alerts.forEach(function(alert) {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
});
}, 5000);
// Add smooth scrolling to anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
});

View File

@ -0,0 +1,83 @@
{% extends 'base.html' %}
{% block title %}Dashboard - Django Boilerplate{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<h1>Welcome to Your Dashboard</h1>
<p class="lead">Hello, <strong>{{ user.full_name|default:user.email }}</strong>!</p>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>Account Information</h5>
</div>
<div class="card-body">
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Name:</strong> {{ user.full_name|default:"Not set" }}</p>
<p><strong>Username:</strong> {{ user.username }}</p>
<p><strong>Member since:</strong> {{ user.date_joined|date:"M d, Y" }}</p>
<p><strong>Email verified:</strong>
{% if user.is_verified %}
<span class="badge bg-success">Yes</span>
{% else %}
<span class="badge bg-warning">No</span>
{% endif %}
</p>
<a href="{% url 'accounts:profile_edit' %}" class="btn btn-primary">Edit Profile</a>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>Your Roles</h5>
</div>
<div class="card-body">
{% if user_groups %}
{% for group in user_groups %}
<span class="badge bg-secondary me-1">{{ group.name|title }}</span>
{% endfor %}
{% else %}
<p class="text-muted">No roles assigned</p>
{% endif %}
<hr>
<h6>Access Levels:</h6>
<ul class="list-unstyled">
<li>
<i class="text-success"></i> User Dashboard
</li>
{% if user.has_role:'staff' or user.has_role:'admin' %}
<li>
<i class="text-success"></i>
<a href="{% url 'core:staff_dashboard' %}">Staff Dashboard</a>
</li>
{% else %}
<li>
<i class="text-muted"></i> Staff Dashboard
</li>
{% endif %}
{% if user.has_role:'admin' %}
<li>
<i class="text-success"></i>
<a href="{% url 'core:admin_dashboard' %}">Admin Dashboard</a>
</li>
{% else %}
<li>
<i class="text-muted"></i> Admin Dashboard
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load socialaccount %}
{% block title %}Login - Django Boilerplate{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Login to Your Account</h4>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Login</button>
</form>
<hr class="my-4">
<div class="text-center">
<h6>Or login with:</h6>
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
<a href="{% provider_login_url 'google' %}" class="btn btn-outline-danger">
Google
</a>
<a href="{% provider_login_url 'facebook' %}" class="btn btn-outline-primary">
Facebook
</a>
</div>
</div>
<hr class="my-4">
<div class="text-center">
<p>
Don't have an account?
<a href="{% url 'account_signup' %}">Sign up here</a>
</p>
<p>
<a href="{% url 'account_reset_password' %}">Forgot your password?</a>
</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,69 @@
{% extends 'base.html' %}
{% block title %}Profile - Django Boilerplate{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5>Your Profile</h5>
<a href="{% url 'accounts:profile_edit' %}" class="btn btn-primary btn-sm">Edit Profile</a>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4 text-center">
{% if user.profile.avatar %}
<img src="{{ user.profile.avatar.url }}" class="img-thumbnail" width="150" height="150">
{% else %}
<div class="bg-secondary d-flex align-items-center justify-content-center" style="width: 150px; height: 150px;">
<i class="text-white fs-1">👤</i>
</div>
{% endif %}
</div>
<div class="col-md-8">
<h4>{{ user.full_name|default:user.email }}</h4>
<p class="text-muted">{{ user.email }}</p>
{% if user.profile.bio %}
<p><strong>Bio:</strong> {{ user.profile.bio }}</p>
{% endif %}
{% if user.profile.location %}
<p><strong>Location:</strong> {{ user.profile.location }}</p>
{% endif %}
{% if user.profile.birth_date %}
<p><strong>Birth Date:</strong> {{ user.profile.birth_date }}</p>
{% endif %}
{% if user.profile.phone_number %}
<p><strong>Phone:</strong> {{ user.profile.phone_number }}</p>
{% endif %}
<p><strong>Member since:</strong> {{ user.date_joined|date:"M d, Y" }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>Account Status</h5>
</div>
<div class="card-body">
<p><strong>Email verified:</strong>
{% if user.is_verified %}
<span class="badge bg-success">Yes</span>
{% else %}
<span class="badge bg-warning">No</span>
{% endif %}
</p>
<p><strong>Account type:</strong> {{ user.groups.first.name|default:"User"|title }}</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends 'base.html' %}
{% block title %}Edit Profile - Django Boilerplate{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5>Edit Your Profile</h5>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<div class="d-flex justify-content-between">
<a href="{% url 'accounts:profile' %}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,46 @@
{% extends 'base.html' %}
{% load socialaccount %}
{% block title %}Sign Up - Django Boilerplate{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Create Your Account</h4>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Sign Up</button>
</form>
<hr class="my-4">
<div class="text-center">
<h6>Or sign up with:</h6>
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
<a href="{% provider_login_url 'google' %}" class="btn btn-outline-danger">
Google
</a>
<a href="{% provider_login_url 'facebook' %}" class="btn btn-outline-primary">
Facebook
</a>
</div>
</div>
<hr class="my-4">
<div class="text-center">
<p>
Already have an account?
<a href="{% url 'account_login' %}">Login here</a>
</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,70 @@
{% extends 'base.html' %}
{% block title %}Admin Dashboard - Django Boilerplate{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<h1>🔐 Admin Dashboard</h1>
<p class="lead">Administrative control panel</p>
</div>
</div>
<div class="row mt-4">
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<h5 class="card-title">User Management</h5>
<p class="card-text">Manage user accounts, roles, and permissions</p>
<a href="/admin/accounts/user/" class="btn btn-primary">Manage Users</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<h5 class="card-title">Group Management</h5>
<p class="card-text">Configure user groups and permissions</p>
<a href="/admin/auth/group/" class="btn btn-primary">Manage Groups</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body text-center">
<h5 class="card-title">Site Administration</h5>
<p class="card-text">Access full Django admin interface</p>
<a href="/admin/" class="btn btn-primary">Django Admin</a>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5>System Information</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6>Current User</h6>
<p><strong>Name:</strong> {{ user.full_name|default:user.email }}</p>
<p><strong>Role:</strong> Administrator</p>
<p><strong>Last Login:</strong> {{ user.last_login|date:"M d, Y H:i" }}</p>
</div>
<div class="col-md-6">
<h6>Quick Stats</h6>
<p><strong>Total Users:</strong> <span class="badge bg-info">{{ users_count|default:"N/A" }}</span></p>
<p><strong>Active Sessions:</strong> <span class="badge bg-success">{{ active_sessions|default:"N/A" }}</span></p>
<p><strong>System Status:</strong> <span class="badge bg-success">Online</span></p>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

93
templates/base.html Normal file
View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Django Boilerplate{% endblock %}</title>
{% load static %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'core:home' %}">Django App</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'core:home' %}">Home</a>
</li>
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'accounts:dashboard' %}">Dashboard</a>
</li>
{% if user.has_role %}
{% if user.has_role:'staff' or user.has_role:'admin' %}
<li class="nav-item">
<a class="nav-link" href="{% url 'core:staff_dashboard' %}">Staff</a>
</li>
{% endif %}
{% if user.has_role:'admin' %}
<li class="nav-item">
<a class="nav-link" href="{% url 'core:admin_dashboard' %}">Admin</a>
</li>
{% endif %}
{% endif %}
{% endif %}
</ul>
<ul class="navbar-nav">
{% if user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
{{ user.full_name|default:user.email }}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'accounts:profile' %}">Profile</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{% url 'account_logout' %}">Logout</a></li>
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'account_login' %}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'account_signup' %}">Sign Up</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<main class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</main>
<footer class="bg-dark text-light text-center py-3 mt-5">
<div class="container">
<p>&copy; 2024 Django Boilerplate. Built with Django & Bootstrap.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>

69
templates/home.html Normal file
View File

@ -0,0 +1,69 @@
{% extends 'base.html' %}
{% block title %}Home - Django Boilerplate{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 mx-auto">
<div class="jumbotron bg-light p-5 rounded">
<h1 class="display-4">Welcome to Django Boilerplate</h1>
<p class="lead">
A production-ready Django starter template with authentication, social login,
role-based permissions, and Docker support.
</p>
<hr class="my-4">
{% if user.is_authenticated %}
<p>Hello, <strong>{{ user.full_name|default:user.email }}</strong>!</p>
<a class="btn btn-primary btn-lg" href="{% url 'accounts:dashboard' %}" role="button">
Go to Dashboard
</a>
{% else %}
<p>Get started by creating an account or logging in.</p>
<a class="btn btn-primary btn-lg me-3" href="{% url 'account_signup' %}" role="button">
Sign Up
</a>
<a class="btn btn-outline-primary btn-lg" href="{% url 'account_login' %}" role="button">
Login
</a>
{% endif %}
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">🔐 Authentication</h5>
<p class="card-text">
Complete authentication system with email verification,
password reset, and social login (Google & Facebook).
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">👥 Role-Based Access</h5>
<p class="card-text">
Built-in user group system with admin, staff, and user roles
for granular permission control.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">🐳 Docker Ready</h5>
<p class="card-text">
Fully containerized with Docker Compose, PostgreSQL database,
and production-ready configuration.
</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,62 @@
{% extends 'base.html' %}
{% block title %}Staff Dashboard - Django Boilerplate{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<h1>👨‍💼 Staff Dashboard</h1>
<p class="lead">Staff management panel</p>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6">
<div class="card">
<div class="card-body text-center">
<h5 class="card-title">User Support</h5>
<p class="card-text">Assist users with account and technical issues</p>
<a href="#" class="btn btn-primary">Support Center</a>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body text-center">
<h5 class="card-title">Content Management</h5>
<p class="card-text">Manage and moderate platform content</p>
<a href="#" class="btn btn-primary">Content Panel</a>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5>Staff Information</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6>Current User</h6>
<p><strong>Name:</strong> {{ user.full_name|default:user.email }}</p>
<p><strong>Role:</strong> Staff Member</p>
<p><strong>Department:</strong> {{ user.profile.department|default:"General" }}</p>
</div>
<div class="col-md-6">
<h6>Quick Actions</h6>
<div class="d-grid gap-2">
<button class="btn btn-outline-primary btn-sm">View Reports</button>
<button class="btn btn-outline-secondary btn-sm">Export Data</button>
<button class="btn btn-outline-info btn-sm">Send Notifications</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}