commit 4bbc56a08f7aeba44b9656cbb02809ddbc853631 Author: Django Template Date: Thu Sep 11 09:36:55 2025 +0530 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 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..86961b9 --- /dev/null +++ b/.env.example @@ -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 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..04ebe22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..6532412 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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. \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c2ef495 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0148144 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9be24a --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..5ad0b09 --- /dev/null +++ b/DEPLOYMENT.md @@ -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!** ๐Ÿš€ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1644b69 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2a62c71 --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ef92a5 --- /dev/null +++ b/README.md @@ -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' %} + Admin Panel +{% 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!** ๐Ÿš€ \ No newline at end of file diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/__init__.py b/apps/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/admin.py b/apps/accounts/admin.py new file mode 100644 index 0000000..9357d58 --- /dev/null +++ b/apps/accounts/admin.py @@ -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',) \ No newline at end of file diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py new file mode 100644 index 0000000..e6b68cb --- /dev/null +++ b/apps/accounts/apps.py @@ -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 \ No newline at end of file diff --git a/apps/accounts/management/__init__.py b/apps/accounts/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/management/commands/__init__.py b/apps/accounts/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/management/commands/create_groups.py b/apps/accounts/management/commands/create_groups.py new file mode 100644 index 0000000..e8ff29f --- /dev/null +++ b/apps/accounts/management/commands/create_groups.py @@ -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') + ) \ No newline at end of file diff --git a/apps/accounts/management/commands/create_superuser.py b/apps/accounts/management/commands/create_superuser.py new file mode 100644 index 0000000..695cacb --- /dev/null +++ b/apps/accounts/management/commands/create_superuser.py @@ -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' + ) + ) \ No newline at end of file diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..9e6514d --- /dev/null +++ b/apps/accounts/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/apps/accounts/migrations/__init__.py b/apps/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/models.py b/apps/accounts/models.py new file mode 100644 index 0000000..6359ff1 --- /dev/null +++ b/apps/accounts/models.py @@ -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" \ No newline at end of file diff --git a/apps/accounts/signals.py b/apps/accounts/signals.py new file mode 100644 index 0000000..b638a46 --- /dev/null +++ b/apps/accounts/signals.py @@ -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() \ No newline at end of file diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py new file mode 100644 index 0000000..3c4440b --- /dev/null +++ b/apps/accounts/urls.py @@ -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'), +] \ No newline at end of file diff --git a/apps/accounts/views.py b/apps/accounts/views.py new file mode 100644 index 0000000..d32d37c --- /dev/null +++ b/apps/accounts/views.py @@ -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) \ No newline at end of file diff --git a/apps/core/__init__.py b/apps/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/apps.py b/apps/core/apps.py new file mode 100644 index 0000000..bea492a --- /dev/null +++ b/apps/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.core' \ No newline at end of file diff --git a/apps/core/urls.py b/apps/core/urls.py new file mode 100644 index 0000000..795023b --- /dev/null +++ b/apps/core/urls.py @@ -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'), +] \ No newline at end of file diff --git a/apps/core/views.py b/apps/core/views.py new file mode 100644 index 0000000..5016558 --- /dev/null +++ b/apps/core/views.py @@ -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') \ No newline at end of file diff --git a/django_project/__init__.py b/django_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_project/settings/__init__.py b/django_project/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_project/settings/base.py b/django_project/settings/base.py new file mode 100644 index 0000000..2b24682 --- /dev/null +++ b/django_project/settings/base.py @@ -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 \ No newline at end of file diff --git a/django_project/settings/development.py b/django_project/settings/development.py new file mode 100644 index 0000000..f34272a --- /dev/null +++ b/django_project/settings/development.py @@ -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, + }, + }, +} \ No newline at end of file diff --git a/django_project/settings/production.py b/django_project/settings/production.py new file mode 100644 index 0000000..d3db806 --- /dev/null +++ b/django_project/settings/production.py @@ -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, + }, + }, +} \ No newline at end of file diff --git a/django_project/urls.py b/django_project/urls.py new file mode 100644 index 0000000..956d663 --- /dev/null +++ b/django_project/urls.py @@ -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) \ No newline at end of file diff --git a/django_project/wsgi.py b/django_project/wsgi.py new file mode 100644 index 0000000..33ebffd --- /dev/null +++ b/django_project/wsgi.py @@ -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() \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..747ed1e --- /dev/null +++ b/docker-compose.prod.yml @@ -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: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..024d794 --- /dev/null +++ b/docker-compose.yml @@ -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: \ No newline at end of file diff --git a/dokploy.json b/dokploy.json new file mode 100644 index 0000000..735603b --- /dev/null +++ b/dokploy.json @@ -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" + } +} \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..47f84ff --- /dev/null +++ b/entrypoint.sh @@ -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 "$@" \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..7172b96 --- /dev/null +++ b/manage.py @@ -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) \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..fdff9b9 --- /dev/null +++ b/nginx.conf @@ -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; +} \ No newline at end of file diff --git a/nginx.prod.conf b/nginx.prod.conf new file mode 100644 index 0000000..a0e378c --- /dev/null +++ b/nginx.prod.conf @@ -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; +} \ No newline at end of file diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..da1d0a6 --- /dev/null +++ b/requirements/base.txt @@ -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 \ No newline at end of file diff --git a/requirements/development.txt b/requirements/development.txt new file mode 100644 index 0000000..b185b40 --- /dev/null +++ b/requirements/development.txt @@ -0,0 +1,4 @@ +-r base.txt + +django-debug-toolbar==4.2.0 +django-extensions==3.2.3 \ No newline at end of file diff --git a/requirements/production.txt b/requirements/production.txt new file mode 100644 index 0000000..5e5215d --- /dev/null +++ b/requirements/production.txt @@ -0,0 +1,3 @@ +-r base.txt + +sentry-sdk[django]==1.38.0 \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..fd60a00 --- /dev/null +++ b/static/css/style.css @@ -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; +} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 0000000..95a7dcc --- /dev/null +++ b/static/js/main.js @@ -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' + }); + }); + }); +}); \ No newline at end of file diff --git a/templates/account/dashboard.html b/templates/account/dashboard.html new file mode 100644 index 0000000..9be4d44 --- /dev/null +++ b/templates/account/dashboard.html @@ -0,0 +1,83 @@ +{% extends 'base.html' %} + +{% block title %}Dashboard - Django Boilerplate{% endblock %} + +{% block content %} +
+
+

Welcome to Your Dashboard

+

Hello, {{ user.full_name|default:user.email }}!

+
+
+ +
+
+
+
+
Account Information
+
+
+

Email: {{ user.email }}

+

Name: {{ user.full_name|default:"Not set" }}

+

Username: {{ user.username }}

+

Member since: {{ user.date_joined|date:"M d, Y" }}

+

Email verified: + {% if user.is_verified %} + Yes + {% else %} + No + {% endif %} +

+ Edit Profile +
+
+
+ +
+
+
+
Your Roles
+
+
+ {% if user_groups %} + {% for group in user_groups %} + {{ group.name|title }} + {% endfor %} + {% else %} +

No roles assigned

+ {% endif %} + +
+ +
Access Levels:
+
    +
  • + โœ“ User Dashboard +
  • + {% if user.has_role:'staff' or user.has_role:'admin' %} +
  • + โœ“ + Staff Dashboard +
  • + {% else %} +
  • + โœ— Staff Dashboard +
  • + {% endif %} + + {% if user.has_role:'admin' %} +
  • + โœ“ + Admin Dashboard +
  • + {% else %} +
  • + โœ— Admin Dashboard +
  • + {% endif %} +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/account/login.html b/templates/account/login.html new file mode 100644 index 0000000..63d2d28 --- /dev/null +++ b/templates/account/login.html @@ -0,0 +1,49 @@ +{% extends 'base.html' %} +{% load socialaccount %} + +{% block title %}Login - Django Boilerplate{% endblock %} + +{% block content %} +
+
+
+
+

Login to Your Account

+
+
+
+ {% csrf_token %} + {{ form.as_p }} + +
+ +
+ +
+
Or login with:
+ +
+ +
+ +
+

+ Don't have an account? + Sign up here +

+

+ Forgot your password? +

+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/account/profile.html b/templates/account/profile.html new file mode 100644 index 0000000..81fbbf0 --- /dev/null +++ b/templates/account/profile.html @@ -0,0 +1,69 @@ +{% extends 'base.html' %} + +{% block title %}Profile - Django Boilerplate{% endblock %} + +{% block content %} +
+
+
+
+
Your Profile
+ Edit Profile +
+
+
+
+ {% if user.profile.avatar %} + + {% else %} +
+ ๐Ÿ‘ค +
+ {% endif %} +
+
+

{{ user.full_name|default:user.email }}

+

{{ user.email }}

+ + {% if user.profile.bio %} +

Bio: {{ user.profile.bio }}

+ {% endif %} + + {% if user.profile.location %} +

Location: {{ user.profile.location }}

+ {% endif %} + + {% if user.profile.birth_date %} +

Birth Date: {{ user.profile.birth_date }}

+ {% endif %} + + {% if user.profile.phone_number %} +

Phone: {{ user.profile.phone_number }}

+ {% endif %} + +

Member since: {{ user.date_joined|date:"M d, Y" }}

+
+
+
+
+
+ +
+
+
+
Account Status
+
+
+

Email verified: + {% if user.is_verified %} + Yes + {% else %} + No + {% endif %} +

+

Account type: {{ user.groups.first.name|default:"User"|title }}

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/account/profile_edit.html b/templates/account/profile_edit.html new file mode 100644 index 0000000..b8122f6 --- /dev/null +++ b/templates/account/profile_edit.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% block title %}Edit Profile - Django Boilerplate{% endblock %} + +{% block content %} +
+
+
+
+
Edit Your Profile
+
+
+
+ {% csrf_token %} + {{ form.as_p }} +
+ Cancel + +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/account/signup.html b/templates/account/signup.html new file mode 100644 index 0000000..9ed9577 --- /dev/null +++ b/templates/account/signup.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} +{% load socialaccount %} + +{% block title %}Sign Up - Django Boilerplate{% endblock %} + +{% block content %} +
+
+
+
+

Create Your Account

+
+
+
+ {% csrf_token %} + {{ form.as_p }} + +
+ +
+ +
+
Or sign up with:
+ +
+ +
+ +
+

+ Already have an account? + Login here +

+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/admin_dashboard.html b/templates/admin_dashboard.html new file mode 100644 index 0000000..073df27 --- /dev/null +++ b/templates/admin_dashboard.html @@ -0,0 +1,70 @@ +{% extends 'base.html' %} + +{% block title %}Admin Dashboard - Django Boilerplate{% endblock %} + +{% block content %} +
+
+

๐Ÿ” Admin Dashboard

+

Administrative control panel

+
+
+ +
+
+
+
+
User Management
+

Manage user accounts, roles, and permissions

+ Manage Users +
+
+
+ +
+
+
+
Group Management
+

Configure user groups and permissions

+ Manage Groups +
+
+
+ +
+
+
+
Site Administration
+

Access full Django admin interface

+ Django Admin +
+
+
+
+ +
+
+
+
+
System Information
+
+
+
+
+
Current User
+

Name: {{ user.full_name|default:user.email }}

+

Role: Administrator

+

Last Login: {{ user.last_login|date:"M d, Y H:i" }}

+
+
+
Quick Stats
+

Total Users: {{ users_count|default:"N/A" }}

+

Active Sessions: {{ active_sessions|default:"N/A" }}

+

System Status: Online

+
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..0f46e08 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,93 @@ + + + + + + {% block title %}Django Boilerplate{% endblock %} + {% load static %} + + + + + + + +
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + + {% block content %} + {% endblock %} +
+ + + + + + + \ No newline at end of file diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..c3357e5 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,69 @@ +{% extends 'base.html' %} + +{% block title %}Home - Django Boilerplate{% endblock %} + +{% block content %} +
+
+
+

Welcome to Django Boilerplate

+

+ A production-ready Django starter template with authentication, social login, + role-based permissions, and Docker support. +

+
+ + {% if user.is_authenticated %} +

Hello, {{ user.full_name|default:user.email }}!

+ + Go to Dashboard + + {% else %} +

Get started by creating an account or logging in.

+ + Sign Up + + + Login + + {% endif %} +
+
+
+ +
+
+
+
+
๐Ÿ” Authentication
+

+ Complete authentication system with email verification, + password reset, and social login (Google & Facebook). +

+
+
+
+
+
+
+
๐Ÿ‘ฅ Role-Based Access
+

+ Built-in user group system with admin, staff, and user roles + for granular permission control. +

+
+
+
+
+
+
+
๐Ÿณ Docker Ready
+

+ Fully containerized with Docker Compose, PostgreSQL database, + and production-ready configuration. +

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/staff_dashboard.html b/templates/staff_dashboard.html new file mode 100644 index 0000000..c95abab --- /dev/null +++ b/templates/staff_dashboard.html @@ -0,0 +1,62 @@ +{% extends 'base.html' %} + +{% block title %}Staff Dashboard - Django Boilerplate{% endblock %} + +{% block content %} +
+
+

๐Ÿ‘จโ€๐Ÿ’ผ Staff Dashboard

+

Staff management panel

+
+
+ +
+
+
+
+
User Support
+

Assist users with account and technical issues

+ Support Center +
+
+
+ +
+
+
+
Content Management
+

Manage and moderate platform content

+ Content Panel +
+
+
+
+ +
+
+
+
+
Staff Information
+
+
+
+
+
Current User
+

Name: {{ user.full_name|default:user.email }}

+

Role: Staff Member

+

Department: {{ user.profile.department|default:"General" }}

+
+
+
Quick Actions
+
+ + + +
+
+
+
+
+
+
+{% endblock %} \ No newline at end of file