diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..86ef8ca --- /dev/null +++ b/.env.sample @@ -0,0 +1,19 @@ +# Django settings +DJANGO_SETTINGS_MODULE=config.settings.development +SECRET_KEY= your-secret-key +ALLOWED_HOSTS=api.your-domain.com,www.your-domain.com + +# Database URL (recommended for production) +DATABASE_URL=postgres://user:password@host:port/database_name + +# Frontend URL +FRONTEND_URL=http://frontend.your-domain.com + +# Email settings +EMAIL_HOST= +EMAIL_PORT= +EMAIL_USE_TLS= +EMAIL_HOST_USER= +EMAIL_HOST_PASSWORD= +DEFAULT_FROM_EMAIL= +SERVER_EMAIL= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ad2ba34 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +name: Django CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: 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 ${{ matrix.python-version }} uses: actions/setup-python@v5 + with: +python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Install dependencies + run: uv sync + + - name: Run linting + run: uv run ruff check . + + - name: Format check + run: uv run ruff format --check . + + - name: Set up PostgreSQL + env: + DB_NAME: test_db + DB_USER: postgres + DB_PASSWORD: postgres + DB_HOST: localhost + DB_PORT: 5432 + run: | + echo "PostgreSQL is ready" + + - name: Run Django tests + env: + DJANGO_SETTINGS_MODULE: config.settings.development + SECRET_KEY: test-secret-key-for-ci + ALLOWED_HOSTS: localhost + DB_NAME: test_db + DB_USER: postgres + DB_PASSWORD: postgres + DB_HOST: localhost + DB_PORT: 5432 + run: | + uv run python manage.py check + uv run python manage.py test + + - name: Check migrations + env: + DJANGO_SETTINGS_MODULE: config.settings.development + SECRET_KEY: test-secret-key-for-ci + ALLOWED_HOSTS: localhost + DB_NAME: test_db + DB_USER: postgres + DB_PASSWORD: postgres + DB_HOST: localhost + DB_PORT: 5432 + run: | + uv run python manage.py makemigrations --check --dry-run + + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Install dependencies + run: uv sync + + - name: Run security check + run: uv run python manage.py check --deploy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b5243c --- /dev/null +++ b/.gitignore @@ -0,0 +1,93 @@ +#--------------------------------------------------# +# The following was generated by djinit: # +#--------------------------------------------------# + +# Python +*.py[cod] +*$py.class +*.so +__pycache__/ +*.egg-info/ +dist/ +build/ +.eggs/ +*.egg + +# Django +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +*.sqlite3 + +# Environment +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ +.project +.pydevproject +.settings/ +.ropeproject/ + +# OS +.DS_Store +*.DS_Store +Thumbs.db +desktop.ini + +# Django specific +media/ +staticfiles/ +staticfiles/ +static_root/ +/static/ + +# Testing +.coverage +htmlcov/ +.pytest_cache/ +.tox/ +coverage.xml +*.cover +.hypothesis/ +.cache + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Node modules (if using frontend tools) +node_modules/ + +# Secret keys +secret.txt +secrets.json + +# Jupyter Notebook +.ipynb_checkpoints + +# Backup files +*.bak +*.tmp +*~ + +# Database +*.db +*.sql + +# Cloud +.cloud diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..7ebdf0b --- /dev/null +++ b/Procfile @@ -0,0 +1,20 @@ +# Procfile for config +# Used for deploying to PaaS platforms like Heroku, Railway, Render, etc. + +# Web server process (using Gunicorn) +# Adjust workers based on your needs: (2 x CPU cores) + 1 +web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 120 --access-logfile - --error-logfile - --log-level info + +# Alternative web server configurations (uncomment to use): +# web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 4 --threads 2 --timeout 120 +# web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --worker-class gevent --worker-connections 1000 --timeout 120 + +# Release task (runs migrations before deployment) +release: python manage.py migrate --noinput && python manage.py collectstatic --noinput + +# Uncomment below to run scheduled tasks (if using Celery) +# worker: celery -A config worker --loglevel=info +# beat: celery -A config beat --loglevel=info + +# Development server (for local testing with Foreman/Overmind) +# web: python manage.py runserver 0.0.0.0:$PORT diff --git a/README.md b/README.md new file mode 100644 index 0000000..1105a99 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# config + +A Django project created with django-setup. + +## Features + +- Modern project structure with environment-specific settings +- Pre-configured REST API with JWT authentication +- Essential dependencies and utilities +- Production-ready configuration + +## Setup + +1. Install dependencies: + ```bash + pip install -r requirements.txt + ``` + +2. Set environment variables in `.env` file + +3. Run migrations: + ```bash + python manage.py migrate + ``` + +4. Create superuser: + ```bash + python manage.py createsuperuser + ``` + +5. Start development server: + ```bash + python manage.py runserver + ``` + +## Project Structure + +``` +config/ +├── config/ +│ ├── settings/ +│ │ ├── base.py +│ │ ├── development.py +│ │ └── production.py +│ ├── urls.py +│ ├── wsgi.py +│ └── asgi.py +├── clients/ +├── core/ +├── finance/ +├── productivity/ +├── services/ +├── core/ +├── notifications/ +├── projects/ +├── manage.py +├── requirements.txt +├── pyproject.toml +├── .env.sample +└── README.md +``` + +## API Documentation + +When running in development mode, API documentation is available at: +- Swagger UI: http://localhost:8000/docs/ +- Schema: http://localhost:8000/schema/ diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/clients/__init__.py b/apps/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/clients/admin.py b/apps/clients/admin.py new file mode 100644 index 0000000..dc9bfc6 --- /dev/null +++ b/apps/clients/admin.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/clients/apps.py b/apps/clients/apps.py new file mode 100644 index 0000000..8f1a25f --- /dev/null +++ b/apps/clients/apps.py @@ -0,0 +1,8 @@ +"""generated with djinit""" + +from django.apps import AppConfig + + +class ClientsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'clients' diff --git a/apps/clients/migrations/__init__.py b/apps/clients/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/clients/models.py b/apps/clients/models.py new file mode 100644 index 0000000..980c8e5 --- /dev/null +++ b/apps/clients/models.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.db import models + +# Create your models here. diff --git a/apps/clients/routes.py b/apps/clients/routes.py new file mode 100644 index 0000000..01f74cc --- /dev/null +++ b/apps/clients/routes.py @@ -0,0 +1,18 @@ +""" +generated with djinit + +API routes for clients app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'clients', views.ClientsViewSet) + +# Get URL patterns +urlpatterns = router.urls diff --git a/apps/clients/serializers.py b/apps/clients/serializers.py new file mode 100644 index 0000000..1dde732 --- /dev/null +++ b/apps/clients/serializers.py @@ -0,0 +1,14 @@ +""" +generated with djinit +serializers for clients app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class ClientsSerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/apps/clients/tests.py b/apps/clients/tests.py new file mode 100644 index 0000000..7fdd344 --- /dev/null +++ b/apps/clients/tests.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/clients/urls.py b/apps/clients/urls.py new file mode 100644 index 0000000..d783bdc --- /dev/null +++ b/apps/clients/urls.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +from django.urls import path +from . import views + +app_name = 'clients' + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/apps/clients/views.py b/apps/clients/views.py new file mode 100644 index 0000000..7df6a0d --- /dev/null +++ b/apps/clients/views.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.shortcuts import render + +# Create your views here. diff --git a/apps/core/__init__.py b/apps/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/admin.py b/apps/core/admin.py new file mode 100644 index 0000000..dc9bfc6 --- /dev/null +++ b/apps/core/admin.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/core/apps.py b/apps/core/apps.py new file mode 100644 index 0000000..90b25b0 --- /dev/null +++ b/apps/core/apps.py @@ -0,0 +1,8 @@ +"""generated with djinit""" + +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/apps/core/migrations/__init__.py b/apps/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/core/models.py b/apps/core/models.py new file mode 100644 index 0000000..980c8e5 --- /dev/null +++ b/apps/core/models.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.db import models + +# Create your models here. diff --git a/apps/core/routes.py b/apps/core/routes.py new file mode 100644 index 0000000..b300b23 --- /dev/null +++ b/apps/core/routes.py @@ -0,0 +1,18 @@ +""" +generated with djinit + +API routes for core app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'core', views.CoreViewSet) + +# Get URL patterns +urlpatterns = router.urls diff --git a/apps/core/serializers.py b/apps/core/serializers.py new file mode 100644 index 0000000..90821de --- /dev/null +++ b/apps/core/serializers.py @@ -0,0 +1,14 @@ +""" +generated with djinit +serializers for core app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class CoreSerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/apps/core/tests.py b/apps/core/tests.py new file mode 100644 index 0000000..7fdd344 --- /dev/null +++ b/apps/core/tests.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/core/urls.py b/apps/core/urls.py new file mode 100644 index 0000000..bd6e2f2 --- /dev/null +++ b/apps/core/urls.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +from django.urls import path +from . import views + +app_name = 'core' + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/apps/core/views.py b/apps/core/views.py new file mode 100644 index 0000000..7df6a0d --- /dev/null +++ b/apps/core/views.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.shortcuts import render + +# Create your views here. diff --git a/apps/finance/__init__.py b/apps/finance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/finance/admin.py b/apps/finance/admin.py new file mode 100644 index 0000000..dc9bfc6 --- /dev/null +++ b/apps/finance/admin.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/finance/apps.py b/apps/finance/apps.py new file mode 100644 index 0000000..9c73f0a --- /dev/null +++ b/apps/finance/apps.py @@ -0,0 +1,8 @@ +"""generated with djinit""" + +from django.apps import AppConfig + + +class FinanceConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'finance' diff --git a/apps/finance/migrations/__init__.py b/apps/finance/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/finance/models.py b/apps/finance/models.py new file mode 100644 index 0000000..980c8e5 --- /dev/null +++ b/apps/finance/models.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.db import models + +# Create your models here. diff --git a/apps/finance/routes.py b/apps/finance/routes.py new file mode 100644 index 0000000..456116f --- /dev/null +++ b/apps/finance/routes.py @@ -0,0 +1,18 @@ +""" +generated with djinit + +API routes for finance app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'finance', views.FinanceViewSet) + +# Get URL patterns +urlpatterns = router.urls diff --git a/apps/finance/serializers.py b/apps/finance/serializers.py new file mode 100644 index 0000000..3c65e56 --- /dev/null +++ b/apps/finance/serializers.py @@ -0,0 +1,14 @@ +""" +generated with djinit +serializers for finance app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class FinanceSerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/apps/finance/tests.py b/apps/finance/tests.py new file mode 100644 index 0000000..7fdd344 --- /dev/null +++ b/apps/finance/tests.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/finance/urls.py b/apps/finance/urls.py new file mode 100644 index 0000000..c510689 --- /dev/null +++ b/apps/finance/urls.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +from django.urls import path +from . import views + +app_name = 'finance' + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/apps/finance/views.py b/apps/finance/views.py new file mode 100644 index 0000000..7df6a0d --- /dev/null +++ b/apps/finance/views.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.shortcuts import render + +# Create your views here. diff --git a/apps/notifications/__init__.py b/apps/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/notifications/admin.py b/apps/notifications/admin.py new file mode 100644 index 0000000..dc9bfc6 --- /dev/null +++ b/apps/notifications/admin.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py new file mode 100644 index 0000000..7535af1 --- /dev/null +++ b/apps/notifications/apps.py @@ -0,0 +1,8 @@ +"""generated with djinit""" + +from django.apps import AppConfig + + +class NotificationsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'notifications' diff --git a/apps/notifications/migrations/__init__.py b/apps/notifications/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/notifications/models.py b/apps/notifications/models.py new file mode 100644 index 0000000..980c8e5 --- /dev/null +++ b/apps/notifications/models.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.db import models + +# Create your models here. diff --git a/apps/notifications/routes.py b/apps/notifications/routes.py new file mode 100644 index 0000000..1325f95 --- /dev/null +++ b/apps/notifications/routes.py @@ -0,0 +1,18 @@ +""" +generated with djinit + +API routes for notifications app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'notifications', views.NotificationsViewSet) + +# Get URL patterns +urlpatterns = router.urls diff --git a/apps/notifications/serializers.py b/apps/notifications/serializers.py new file mode 100644 index 0000000..076b944 --- /dev/null +++ b/apps/notifications/serializers.py @@ -0,0 +1,14 @@ +""" +generated with djinit +serializers for notifications app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class NotificationsSerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/apps/notifications/tests.py b/apps/notifications/tests.py new file mode 100644 index 0000000..7fdd344 --- /dev/null +++ b/apps/notifications/tests.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/notifications/urls.py b/apps/notifications/urls.py new file mode 100644 index 0000000..f3195c5 --- /dev/null +++ b/apps/notifications/urls.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +from django.urls import path +from . import views + +app_name = 'notifications' + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/apps/notifications/views.py b/apps/notifications/views.py new file mode 100644 index 0000000..7df6a0d --- /dev/null +++ b/apps/notifications/views.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.shortcuts import render + +# Create your views here. diff --git a/apps/productivity/__init__.py b/apps/productivity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/productivity/admin.py b/apps/productivity/admin.py new file mode 100644 index 0000000..dc9bfc6 --- /dev/null +++ b/apps/productivity/admin.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/productivity/apps.py b/apps/productivity/apps.py new file mode 100644 index 0000000..029ed10 --- /dev/null +++ b/apps/productivity/apps.py @@ -0,0 +1,8 @@ +"""generated with djinit""" + +from django.apps import AppConfig + + +class ProductivityConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'productivity' diff --git a/apps/productivity/migrations/__init__.py b/apps/productivity/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/productivity/models.py b/apps/productivity/models.py new file mode 100644 index 0000000..980c8e5 --- /dev/null +++ b/apps/productivity/models.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.db import models + +# Create your models here. diff --git a/apps/productivity/routes.py b/apps/productivity/routes.py new file mode 100644 index 0000000..92042f7 --- /dev/null +++ b/apps/productivity/routes.py @@ -0,0 +1,18 @@ +""" +generated with djinit + +API routes for productivity app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'productivity', views.ProductivityViewSet) + +# Get URL patterns +urlpatterns = router.urls diff --git a/apps/productivity/serializers.py b/apps/productivity/serializers.py new file mode 100644 index 0000000..76c4ccf --- /dev/null +++ b/apps/productivity/serializers.py @@ -0,0 +1,14 @@ +""" +generated with djinit +serializers for productivity app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class ProductivitySerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/apps/productivity/tests.py b/apps/productivity/tests.py new file mode 100644 index 0000000..7fdd344 --- /dev/null +++ b/apps/productivity/tests.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/productivity/urls.py b/apps/productivity/urls.py new file mode 100644 index 0000000..52942c4 --- /dev/null +++ b/apps/productivity/urls.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +from django.urls import path +from . import views + +app_name = 'productivity' + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/apps/productivity/views.py b/apps/productivity/views.py new file mode 100644 index 0000000..7df6a0d --- /dev/null +++ b/apps/productivity/views.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.shortcuts import render + +# Create your views here. diff --git a/apps/projects/__init__.py b/apps/projects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/projects/admin.py b/apps/projects/admin.py new file mode 100644 index 0000000..dc9bfc6 --- /dev/null +++ b/apps/projects/admin.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/projects/apps.py b/apps/projects/apps.py new file mode 100644 index 0000000..829afd2 --- /dev/null +++ b/apps/projects/apps.py @@ -0,0 +1,8 @@ +"""generated with djinit""" + +from django.apps import AppConfig + + +class ProjectsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'projects' diff --git a/apps/projects/migrations/__init__.py b/apps/projects/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/projects/models.py b/apps/projects/models.py new file mode 100644 index 0000000..980c8e5 --- /dev/null +++ b/apps/projects/models.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.db import models + +# Create your models here. diff --git a/apps/projects/routes.py b/apps/projects/routes.py new file mode 100644 index 0000000..c7f59d1 --- /dev/null +++ b/apps/projects/routes.py @@ -0,0 +1,18 @@ +""" +generated with djinit + +API routes for projects app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'projects', views.ProjectsViewSet) + +# Get URL patterns +urlpatterns = router.urls diff --git a/apps/projects/serializers.py b/apps/projects/serializers.py new file mode 100644 index 0000000..e71aa58 --- /dev/null +++ b/apps/projects/serializers.py @@ -0,0 +1,14 @@ +""" +generated with djinit +serializers for projects app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class ProjectsSerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/apps/projects/tests.py b/apps/projects/tests.py new file mode 100644 index 0000000..7fdd344 --- /dev/null +++ b/apps/projects/tests.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/projects/urls.py b/apps/projects/urls.py new file mode 100644 index 0000000..6493af1 --- /dev/null +++ b/apps/projects/urls.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +from django.urls import path +from . import views + +app_name = 'projects' + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/apps/projects/views.py b/apps/projects/views.py new file mode 100644 index 0000000..7df6a0d --- /dev/null +++ b/apps/projects/views.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.shortcuts import render + +# Create your views here. diff --git a/apps/services/__init__.py b/apps/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/services/admin.py b/apps/services/admin.py new file mode 100644 index 0000000..dc9bfc6 --- /dev/null +++ b/apps/services/admin.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/services/apps.py b/apps/services/apps.py new file mode 100644 index 0000000..0a13a3b --- /dev/null +++ b/apps/services/apps.py @@ -0,0 +1,8 @@ +"""generated with djinit""" + +from django.apps import AppConfig + + +class ServicesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'services' diff --git a/apps/services/migrations/__init__.py b/apps/services/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/services/models.py b/apps/services/models.py new file mode 100644 index 0000000..980c8e5 --- /dev/null +++ b/apps/services/models.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.db import models + +# Create your models here. diff --git a/apps/services/routes.py b/apps/services/routes.py new file mode 100644 index 0000000..2cf76c3 --- /dev/null +++ b/apps/services/routes.py @@ -0,0 +1,18 @@ +""" +generated with djinit + +API routes for services app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'services', views.ServicesViewSet) + +# Get URL patterns +urlpatterns = router.urls diff --git a/apps/services/serializers.py b/apps/services/serializers.py new file mode 100644 index 0000000..1444a26 --- /dev/null +++ b/apps/services/serializers.py @@ -0,0 +1,14 @@ +""" +generated with djinit +serializers for services app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class ServicesSerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/apps/services/tests.py b/apps/services/tests.py new file mode 100644 index 0000000..7fdd344 --- /dev/null +++ b/apps/services/tests.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/services/urls.py b/apps/services/urls.py new file mode 100644 index 0000000..15b21ad --- /dev/null +++ b/apps/services/urls.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +from django.urls import path +from . import views + +app_name = 'services' + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/apps/services/views.py b/apps/services/views.py new file mode 100644 index 0000000..7df6a0d --- /dev/null +++ b/apps/services/views.py @@ -0,0 +1,5 @@ +"""generated with djinit""" + +from django.shortcuts import render + +# Create your views here. diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..80dff82 --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,10 @@ +"""generated with djinit""" + +import os +from dotenv import load_dotenv +from django.core.asgi import get_asgi_application + +load_dotenv() + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE", "core.settings.production")) +application = get_asgi_application() diff --git a/config/settings/__init__.py b/config/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/settings/base.py b/config/settings/base.py new file mode 100644 index 0000000..5af88b0 --- /dev/null +++ b/config/settings/base.py @@ -0,0 +1,184 @@ +""" +generated with djinit +Common settings shared between development and production environment +""" + +import os +from datetime import timedelta +from pathlib import Path + +from dotenv import load_dotenv + +load_dotenv() + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ + + +# Application definition +THIRD_PARTY_APPS = [ + "rest_framework", + "rest_framework_simplejwt", + "rest_framework_simplejwt.token_blacklist", + "corsheaders", + "drf_spectacular", +] + +USER_DEFINED_APPS = [ + "apps.clients", + "apps.core", + "apps.finance", + "apps.productivity", + "apps.services", + "apps.core", + "apps.notifications", + "apps.projects", +] + +BUILT_IN_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +INSTALLED_APPS = BUILT_IN_APPS + THIRD_PARTY_APPS + USER_DEFINED_APPS +INSTALLED_APPS.insert(0, "jazzmin") + +MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", + "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", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "config.wsgi.application" + + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +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", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + +LANGUAGE_CODE = "en-us" +TIME_ZONE = "UTC" +USE_I18N = True +USE_TZ = True + +# Static files and media files +STATIC_URL = "static/" +STATIC_ROOT = BASE_DIR / "staticfiles" + +MEDIA_URL = "media/" +MEDIA_ROOT = BASE_DIR / "media" + +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# REST Framework Settings +REST_FRAMEWORK = { + "DEFAULT_RENDERER_CLASSES": [ + "rest_framework.renderers.JSONRenderer", + ], + "DEFAULT_PARSER_CLASSES": [ + "rest_framework.parsers.JSONParser", + ], + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + "rest_framework.authentication.SessionAuthentication", + ), + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 20, +} + +# DRF Spectacular settings +SPECTACULAR_SETTINGS = { + "TITLE": "config API", + "DESCRIPTION": "API documentation for config", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, + "COMPONENT_SPLIT_REQUEST": True, + "SCHEMA_PATH_PREFIX": "/api/", +} + +# JWT Settings +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(days=1), + "REFRESH_TOKEN_LIFETIME": timedelta(days=3), + "ROTATE_REFRESH_TOKENS": True, + "BLACKLIST_AFTER_ROTATION": True, +} + +# CORS Settings +CORS_ALLOW_METHODS = [ + "DELETE", + "GET", + "OPTIONS", + "PATCH", + "POST", + "PUT", +] + +CORS_ALLOW_HEADERS = [ + "accept", + "accept-encoding", + "authorization", + "content-type", + "dnt", + "origin", + "user-agent", + "x-csrftoken", + "x-requested-with", +] + +CORS_PREFLIGHT_MAX_AGE = 86400 # 24 hours +# Security Settings +SECURE_BROWSER_XSS_FILTER = True +SECURE_CONTENT_TYPE_NOSNIFF = True +X_FRAME_OPTIONS = "DENY" diff --git a/config/settings/development.py b/config/settings/development.py new file mode 100644 index 0000000..dc52a7d --- /dev/null +++ b/config/settings/development.py @@ -0,0 +1,31 @@ +"""generated with djinit""" + +from .base import * + +SECRET_KEY = "" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ["*"] + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + +# Email backend for development +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +# CORS settings for development +# Allow all origins in development (convenient but not secure) +CORS_ALLOW_ALL_ORIGINS = True +CORS_ALLOW_CREDENTIALS = True + +# Disable HTTPS redirect in development +SECURE_SSL_REDIRECT = False diff --git a/config/settings/production.py b/config/settings/production.py new file mode 100644 index 0000000..0401b78 --- /dev/null +++ b/config/settings/production.py @@ -0,0 +1,57 @@ +"""generated with djinit""" + +from .base import * +import os +import dj_database_url + +DEBUG = False +SECRET_KEY = os.getenv('SECRET_KEY') +ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',') + +# Database +# Using individual database parameters +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('DB_NAME'), + 'USER': os.getenv('DB_USER'), + 'PASSWORD': os.getenv('DB_PASSWORD'), + 'HOST': os.getenv('DB_HOST'), + 'PORT': os.getenv('DB_PORT'), + } +} + +# CORS settings for production +# Filter out empty strings from FRONTEND_URL +frontend_urls = os.getenv("FRONTEND_URL", "") +CORS_ALLOWED_ORIGINS = [ + origin.strip() for origin in frontend_urls.split(",") if origin.strip() +] +CORS_ALLOW_CREDENTIALS = True + +# Additional CORS security for production +CORS_ALLOW_PRIVATE_NETWORK = False + +# Email settings +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = os.getenv('EMAIL_HOST') +EMAIL_PORT = int(os.getenv('EMAIL_PORT', 587)) +EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', 'True').lower() == 'true' +EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') +DEFAULT_FROM_EMAIL = os.environ.get("EMAIL_HOST_USER") +SERVER_EMAIL = os.environ.get("EMAIL_HOST_USER") + +# Security settings for production +SECURE_SSL_REDIRECT = True +SECURE_HSTS_SECONDS = 31536000 +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + +# Session settings +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True + +# Static files +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..b1f191f --- /dev/null +++ b/config/urls.py @@ -0,0 +1,38 @@ +"""generated with djinit""" + +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularSwaggerView, +) +from rest_framework_simplejwt.views import ( + TokenObtainPairView, + TokenRefreshView, + TokenBlacklistView, +) + +urlpatterns = [ + path("admin/", admin.site.urls), + # JWT tokens + path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), + path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), + path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"), + # App URLs + path("", include("apps.clients.urls")), + path("", include("apps.core.urls")), + path("", include("apps.finance.urls")), + path("", include("apps.productivity.urls")), + path("", include("apps.services.urls")), + path("", include("apps.core.urls")), + path("", include("apps.notifications.urls")), + path("", include("apps.projects.urls")), +] + +# Don't show schema in production +if settings.DEBUG: + urlpatterns += [ + path("schema/", SpectacularAPIView.as_view(), name="schema"), + path("docs/", SpectacularSwaggerView.as_view(url_name="schema")), + ] diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..8f48013 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,10 @@ +"""generated with djinit""" + +import os +from dotenv import load_dotenv +from django.core.wsgi import get_wsgi_application + +load_dotenv() +os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE", "core.settings.production")) + +application = get_wsgi_application() diff --git a/justfile b/justfile new file mode 100644 index 0000000..da6a127 --- /dev/null +++ b/justfile @@ -0,0 +1,154 @@ +# Justfile for config +# Task runner for Django development +# Install just: https://github.com/casey/just + +# Default task: show available commands +default: + @just --list + +# Run development server +dev: + uv run python manage.py runserver + +# Run development server with custom host and port +dev-server HOST="0.0.0.0" PORT="8000": + uv run python manage.py runserver {{HOST}}:{{PORT}} + +# Create migrations for all apps +makemigrations: + uv run python manage.py makemigrations + +# Create migrations for specific app +makemigrations-app APP: + uv run python manage.py makemigrations {{APP}} + +# Run all migrations +migrate: + uv run python manage.py migrate + +# Reset database (delete all migrations) +reset-db: + uv run python manage.py flush --noinput + uv run python manage.py migrate + +# Create superuser +createsuperuser: + uv run python manage.py createsuperuser + +# Collect static files +collectstatic: + uv run python manage.py collectstatic --noinput + +# Run Django shell +shell: + uv run python manage.py shell + +# Run Django shell plus (IPython) +shell-plus: + uv run python manage.py shell_plus + +# Run tests +test: + uv run python manage.py test + +# Run tests with coverage +test-coverage: + uv run pytest --cov=. --cov-report=html --cov-report=term + +# Lint code with ruff +lint: + uv run ruff check . + +# Format code with ruff +format: + uv run ruff format . + +# Lint and format code +style: + uv run ruff check . + uv run ruff format . + +# Check security issues +check: + uv run python manage.py check + +# Check security issues (production settings) +check-deploy: + uv run python manage.py check --deploy + +# Show all database migrations status +showmigrations: + uv run python manage.py showmigrations + +# Sync dependencies (uv equivalent of pip freeze) +requirements: + uv pip compile + +# Run all linting and checks +ci: + uv run ruff check . + uv run ruff format --check . + uv run python manage.py check + +# Start interactive shell +shell-ipython: + uv run python manage.py shell_plus + +# Clear all cache +clear-cache: + uv run python manage.py clear_cache + +# Show Django version +version: + uv run python manage.py version + +# Run database shell +dbshell: + uv run python manage.py dbshell + +# Start production server with gunicorn +server: + uv run gunicorn config.wsgi:application --bind 0.0.0.0:8000 + +# Start production server with gunicorn workers +server-prod WORKERS="4": + uv run gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers {{WORKERS}} + +# Start production server with gunicorn and reload on file changes +server-watch: + uv run gunicorn config.wsgi:application --bind 0.0.0.0:8000 --reload + +# Clean Python cache files +clean: + find . -type d -name "__pycache__" -exec rm -rf {} + + find . -type f -name "*.pyc" -delete + find . -type f -name "*.pyo" -delete + find . -type f -name ".coverage" -delete + find . -type d -name "*.egg-info" -exec rm -rf {} + + rm -rf htmlcov/ + rm -rf .pytest_cache/ + +# Setup project for development (using uv) +setup: + uv sync + uv run python manage.py migrate + uv run python manage.py createsuperuser + +# Complete setup including static files +setup-complete: + uv sync + uv run python manage.py migrate + uv run python manage.py collectstatic --noinput + uv run python manage.py createsuperuser + +# Install a new package +install PACKAGE: + uv add {{PACKAGE}} + +# Install a dev package +install-dev PACKAGE: + uv add --dev {{PACKAGE}} + +# Remove a package +remove PACKAGE: + uv remove {{PACKAGE}} diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..422a555 --- /dev/null +++ b/manage.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +"""generated with djinit""" + +"""Django's command-line utility for administrative tasks.""" +import os +import sys + +from dotenv import load_dotenv + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE")) + + 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) + + +if __name__ == "__main__": + load_dotenv() + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0b874f0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "backend" +version = "0.1.0" +description = "" +readme = "README.md" +requires-python = ">=3.13" +license = { text = "MIT" } +dependencies = [ + "django", + "python-dotenv", + "django-jazzmin", + "djangorestframework", + "djangorestframework-simplejwt", + "drf-spectacular", + "django-cors-headers", + "whitenoise", + "psycopg2-binary", + "gunicorn", +] + +[tool.ruff] +line-length = 88 +target-version = "py38" + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade +] +ignore = [ + "E501", # line too long, handled by formatter + "B008", # do not perform function calls in argument defaults +] + +[tool.ruff.lint.isort] +known-first-party = ["config"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5c099f4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,15 @@ +#--------------------------------------------------# +# The following was generated by djinit: # +#--------------------------------------------------# + +Django +python-dotenv +django-jazzmin +djangorestframework +djangorestframework_simplejwt +drf-spectacular +django-cors-headers +whitenoise +psycopg2-binary +gunicorn +dj-database-url \ No newline at end of file diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000..e912aa6 --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.13