feat(app_generator): support nested Django app structures and custom app module paths
This commit is contained in:
30 files changed
+109
-765
No files matched your search
@@ -5,11 +5,12 @@ Handles creation of Django apps and updating settings.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from src.scripts.console_ui import UIFormatter
|
||||
from src.scripts.template_engine import template_engine
|
||||
from src.utils import create_file_with_content
|
||||
from src.utils import change_cwd, create_file_with_content
|
||||
|
||||
|
||||
class AppManager:
|
||||
@@ -45,13 +46,42 @@ class AppManager:
|
||||
return os.path.exists(self.manage_py_path)
|
||||
|
||||
def _app_exists(self) -> bool:
|
||||
app_path = os.path.join(self.current_dir, self.app_name)
|
||||
# Detect project structure to check in correct location
|
||||
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
||||
app_path = os.path.join(apps_base_dir, self.app_name)
|
||||
return os.path.exists(app_path)
|
||||
|
||||
def _create_django_app(self) -> bool:
|
||||
result = subprocess.run(["django-admin", "startapp", self.app_name], capture_output=True, text=True, check=True)
|
||||
# Detect project structure
|
||||
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
||||
|
||||
# manage.py is always in the project root (current_dir), not in the project directory
|
||||
manage_py_path = os.path.join(self.current_dir, "manage.py")
|
||||
if not os.path.exists(manage_py_path):
|
||||
UIFormatter.print_error("Could not find manage.py file in project root")
|
||||
return False
|
||||
|
||||
# Create app in the correct location
|
||||
with change_cwd(apps_base_dir):
|
||||
UIFormatter.print_info(f"Creating app '{self.app_name}' in directory: {os.getcwd()}")
|
||||
|
||||
try:
|
||||
# Use django-admin instead of manage.py to avoid dependency issues
|
||||
result = subprocess.run(
|
||||
["django-admin", "startapp", self.app_name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
UIFormatter.print_error(f"Failed to create app '{self.app_name}': {e}")
|
||||
if e.stderr:
|
||||
UIFormatter.print_error(f"Error details: {e.stderr}")
|
||||
if e.stdout:
|
||||
UIFormatter.print_info(f"Output: {e.stdout}")
|
||||
return False
|
||||
|
||||
UIFormatter.print_success(f"Created Django app '{self.app_name}'")
|
||||
UIFormatter.print_success(f"Created Django app '{self.app_name}' in {apps_base_dir}")
|
||||
return True
|
||||
|
||||
def _add_to_installed_apps(self) -> bool:
|
||||
@@ -65,11 +95,38 @@ class AppManager:
|
||||
UIFormatter.print_error("Could not find base.py settings file")
|
||||
return False
|
||||
|
||||
UIFormatter.print_info(f"Found base.py at: {base_settings_path}")
|
||||
|
||||
with open(base_settings_path) as f:
|
||||
content = f.read()
|
||||
|
||||
if f'"{self.app_name}"' in content or f"'{self.app_name}'" in content:
|
||||
UIFormatter.print_info(f"App '{self.app_name}' is already in INSTALLED_APPS")
|
||||
# Detect project structure to determine the correct app module path
|
||||
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
||||
|
||||
# Determine the correct module path for the app
|
||||
if is_nested and nested_dir:
|
||||
app_module_path = f"{nested_dir}.{self.app_name}"
|
||||
else:
|
||||
app_module_path = self.app_name
|
||||
|
||||
UIFormatter.print_info(f"App module path: {app_module_path}")
|
||||
|
||||
# Check if app is already in USER_DEFINED_APPS section specifically
|
||||
lines = content.split("\n")
|
||||
in_user_apps = False
|
||||
app_already_exists = False
|
||||
|
||||
for line in lines:
|
||||
if "USER_DEFINED_APPS" in line and "=" in line:
|
||||
in_user_apps = True
|
||||
elif in_user_apps and line.strip() == "]":
|
||||
in_user_apps = False
|
||||
elif in_user_apps and (f'"{app_module_path}"' in line or f"'{app_module_path}'" in line):
|
||||
app_already_exists = True
|
||||
break
|
||||
|
||||
if app_already_exists:
|
||||
UIFormatter.print_info(f"App '{app_module_path}' is already in USER_DEFINED_APPS")
|
||||
return True
|
||||
|
||||
if "USER_DEFINED_APPS" not in content:
|
||||
@@ -86,7 +143,7 @@ class AppManager:
|
||||
in_user_apps = True
|
||||
new_lines.append(line)
|
||||
elif in_user_apps and line.strip() == "]":
|
||||
new_lines.append(f' "{self.app_name}",')
|
||||
new_lines.append(f' "{app_module_path}",')
|
||||
new_lines.append(line)
|
||||
in_user_apps = False
|
||||
apps_added = True
|
||||
@@ -100,7 +157,7 @@ class AppManager:
|
||||
with open(base_settings_path, "w") as f:
|
||||
f.write("\n".join(new_lines))
|
||||
|
||||
UIFormatter.print_success(f"Added '{self.app_name}' to USER_DEFINED_APPS in base.py")
|
||||
UIFormatter.print_success(f"Added '{app_module_path}' to USER_DEFINED_APPS in base.py")
|
||||
return True
|
||||
|
||||
def _find_settings_path(self) -> Optional[str]:
|
||||
@@ -122,9 +179,51 @@ class AppManager:
|
||||
|
||||
return None
|
||||
|
||||
def _detect_project_structure(self) -> tuple[bool, Optional[str], str]:
|
||||
"""
|
||||
Detect if the project uses nested apps and return the structure info.
|
||||
|
||||
Returns:
|
||||
tuple: (is_nested, nested_dir, apps_base_dir)
|
||||
"""
|
||||
# Look for project directories that contain settings
|
||||
for item in os.listdir(self.current_dir):
|
||||
if os.path.isdir(item) and not item.startswith(".") and item != "__pycache__":
|
||||
# Check if this directory contains Django project files
|
||||
project_path = os.path.join(self.current_dir, item)
|
||||
settings_path = os.path.join(project_path, "settings")
|
||||
|
||||
# Check if settings directory exists with base.py
|
||||
if os.path.exists(settings_path) and os.path.exists(os.path.join(settings_path, "base.py")):
|
||||
# Found the project directory, now check for nested apps
|
||||
project_name = item
|
||||
|
||||
# Look for common nested app directories
|
||||
for nested_dir in ["apps", "modules", "packages"]:
|
||||
nested_path = os.path.join(self.current_dir, nested_dir)
|
||||
if os.path.exists(nested_path) and os.path.isdir(nested_path):
|
||||
# Check if there are any Django apps in this directory
|
||||
for app_item in os.listdir(nested_path):
|
||||
app_path = os.path.join(nested_path, app_item)
|
||||
if os.path.isdir(app_path) and os.path.exists(os.path.join(app_path, "apps.py")):
|
||||
# Found nested apps structure
|
||||
return True, nested_dir, nested_path
|
||||
|
||||
# No nested structure found
|
||||
return False, None, self.current_dir
|
||||
|
||||
# Fallback: check current directory
|
||||
settings_path = os.path.join(self.current_dir, "settings")
|
||||
if os.path.exists(settings_path) and os.path.exists(os.path.join(settings_path, "base.py")):
|
||||
return False, None, self.current_dir
|
||||
|
||||
return False, None, self.current_dir
|
||||
|
||||
def _create_additional_files(self) -> bool:
|
||||
"""Create additional files for the app (serializers.py, routes.py)."""
|
||||
app_path = os.path.join(self.current_dir, self.app_name)
|
||||
# Detect project structure to get correct app path
|
||||
is_nested, nested_dir, apps_base_dir = self._detect_project_structure()
|
||||
app_path = os.path.join(apps_base_dir, self.app_name)
|
||||
|
||||
# Create serializers.py
|
||||
context = {"app_name": self.app_name}
|
||||
|
||||
@@ -98,7 +98,7 @@ def handle_app_command(args: argparse.Namespace) -> None:
|
||||
instructions.append("2. Create your models in the app's models.py\n")
|
||||
instructions.append("3. Run migrations: python manage.py makemigrations\n")
|
||||
instructions.append("4. Apply migrations: python manage.py migrate\n")
|
||||
instructions.append("5. Create views and URLs for your app\n")
|
||||
instructions.append("5. Create views, serializers and routes(URLs) for your app\n")
|
||||
|
||||
console.print(Panel(instructions, title="💡 What's Next", border_style="green"))
|
||||
else:
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Django settings
|
||||
DJANGO_SETTINGS_MODULE=core.settings.development
|
||||
SECRET_KEY= your-secret-key
|
||||
ALLOWED_HOSTS=api.your-domain.com,www.your-domain.com
|
||||
|
||||
# Database (individual parameters)
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
|
||||
# Email settings
|
||||
EMAIL_HOST=
|
||||
EMAIL_PORT=
|
||||
EMAIL_USE_TLS=
|
||||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
@@ -1,89 +0,0 @@
|
||||
# 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
|
||||
@@ -1,20 +0,0 @@
|
||||
# Procfile for core
|
||||
# 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 core.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 core.wsgi:application --bind 0.0.0.0:$PORT --workers 4 --threads 2 --timeout 120
|
||||
# web: gunicorn core.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 core worker --loglevel=info
|
||||
# beat: celery -A core beat --loglevel=info
|
||||
|
||||
# Development server (for local testing with Foreman/Overmind)
|
||||
# web: python manage.py runserver 0.0.0.0:$PORT
|
||||
@@ -1,60 +0,0 @@
|
||||
# core
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
core/
|
||||
├── core/
|
||||
│ ├── settings/
|
||||
│ │ ├── base.py
|
||||
│ │ ├── development.py
|
||||
│ │ └── production.py
|
||||
│ ├── urls.py
|
||||
│ ├── wsgi.py
|
||||
│ └── asgi.py
|
||||
├── store/
|
||||
├── 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/
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class StoreConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "store"
|
||||
Whitespace-only changes.
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -1,16 +0,0 @@
|
||||
"""
|
||||
API routes for store app.
|
||||
"""
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from . import views
|
||||
|
||||
# Create router instance
|
||||
router = DefaultRouter()
|
||||
|
||||
# Register viewsets with router
|
||||
# Example:
|
||||
# router.register(r'store', views.StoreViewSet)
|
||||
|
||||
# Get URL patterns
|
||||
urlpatterns = router.urls
|
||||
@@ -1,13 +0,0 @@
|
||||
"""
|
||||
Serializers for store app.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
# Add your serializers here
|
||||
# Example:
|
||||
# class StoreSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = YourModel
|
||||
# fields = '__all__'
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,9 +0,0 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = "store"
|
||||
|
||||
urlpatterns = [
|
||||
# Add your URL patterns here
|
||||
# path('', views.index, name='index'),
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Whitespace-only changes.
@@ -1,9 +0,0 @@
|
||||
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"))
|
||||
|
||||
application = get_asgi_application()
|
||||
Whitespace-only changes.
@@ -1,163 +0,0 @@
|
||||
"""
|
||||
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.store",
|
||||
"account",
|
||||
]
|
||||
|
||||
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",
|
||||
"account",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "core.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 = "core.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": "core API",
|
||||
"DESCRIPTION": "API documentation for core",
|
||||
"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_ALLOWED_ORIGINS = [
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:3000",
|
||||
]
|
||||
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
# Security Settings
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
X_FRAME_OPTIONS = "DENY"
|
||||
@@ -1,27 +0,0 @@
|
||||
from .base import *
|
||||
|
||||
SECRET_KEY = "+#JJBTL^S&N7FOYi8_WM$*(BvxD6VqB7JnLCvgO_4f9yN$FJP8"
|
||||
|
||||
# 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
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
|
||||
# Disable HTTPS redirect in development
|
||||
SECURE_SSL_REDIRECT = False
|
||||
@@ -1,43 +0,0 @@
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# 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")
|
||||
|
||||
# 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"
|
||||
@@ -1,29 +0,0 @@
|
||||
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.store.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")),
|
||||
]
|
||||
@@ -1,9 +0,0 @@
|
||||
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"))
|
||||
|
||||
application = get_wsgi_application()
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
# Justfile for core
|
||||
# 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 :
|
||||
|
||||
# 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
|
||||
|
||||
# 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 core.wsgi:application --bind 0.0.0.0:8000
|
||||
|
||||
# Start production server with gunicorn workers
|
||||
server-prod WORKERS="4":
|
||||
uv run gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers
|
||||
|
||||
# Start production server with gunicorn and reload on file changes
|
||||
server-watch:
|
||||
uv run gunicorn core.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
|
||||
|
||||
# Install a dev package
|
||||
install-dev PACKAGE:
|
||||
uv add --dev
|
||||
|
||||
# Remove a package
|
||||
remove PACKAGE:
|
||||
uv remove
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""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()
|
||||
@@ -1,41 +0,0 @@
|
||||
[project]
|
||||
name = "yoo"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
license = { text = "MIT" }
|
||||
dependencies = [
|
||||
"django>=5.2.7",
|
||||
"python-dotenv>=1.1.1",
|
||||
"django-jazzmin>=3.0.1",
|
||||
"djangorestframework>=3.16.1",
|
||||
"djangorestframework-simplejwt>=5.5.1",
|
||||
"drf-spectacular>=0.28.0",
|
||||
"django-cors-headers>=4.9.0",
|
||||
"whitenoise>=6.8.2",
|
||||
"psycopg2-binary>=2.9.9",
|
||||
"gunicorn>=21.2.0",
|
||||
]
|
||||
|
||||
[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 = ["core"]
|
||||
@@ -1,10 +0,0 @@
|
||||
Django
|
||||
python-dotenv
|
||||
django-jazzmin>
|
||||
djangorestframework
|
||||
djangorestframework_simplejwt
|
||||
drf-spectacular
|
||||
django-cors-headers
|
||||
whitenoise
|
||||
psycopg2-binary
|
||||
gunicorn
|
||||
@@ -1 +0,0 @@
|
||||
python-3.13
|
||||
Reference in new issue
Block a user