refactor: improve project creation and app generation for better compatibility

This commit is contained in:
sankalp committed 2025-11-09 14:26:00 +05:45
commit cdaeede45a
41 files changed
+239 -810

No files matched your search

+5
View File
@@ -19,7 +19,12 @@ class Cli:
self.primary_app = primary_app
self.app_names = app_names
self.metadata = metadata
<<<<<<< HEAD
if project_dir == ".": # handle '.' for current directory
=======
# Handle '.' for current directory
if project_dir == ".":
>>>>>>> origin/main
self.project_root = os.getcwd()
else:
self.project_root = os.path.join(os.getcwd(), project_dir)
+14
View File
@@ -4,6 +4,11 @@ Handles creation of Django apps and updating settings.
"""
import os
<<<<<<< HEAD
=======
import subprocess
import sys
>>>>>>> origin/main
from typing import Optional
from djinit.scripts.console_ui import UIFormatter
@@ -70,9 +75,18 @@ class AppManager:
UIFormatter.print_error("Could not find manage.py file in project root")
return False
<<<<<<< HEAD
# If predefined structure detected (apps/ exists), scaffold nested layout using custom templates
if self._is_predefined_structure():
return self._create_predefined_app(os.path.join(self.current_dir, "apps"))
=======
# Create app in the correct location
# Use 'python -m django' instead of 'django-admin' for better compatibility
with change_cwd(apps_base_dir):
subprocess.run(
[sys.executable, "-m", "django", "startapp", self.app_name], capture_output=True, text=True, check=True
)
>>>>>>> origin/main
# Otherwise, use standard flat app generation
success = DjangoHelper.startapp(self.app_name, apps_base_dir)
+196
View File
@@ -204,6 +204,10 @@ class InputCollector:
console.print(f" [{UIColors.SUCCESS}]{option.key}[/{UIColors.SUCCESS}] {option.description}")
console.print()
<<<<<<< HEAD
=======
# Get valid keys from CICDOption enum
>>>>>>> origin/main
valid_keys = [option.key for option in CICDOption]
choice = CharReader.get_cicd_choice(
@@ -212,12 +216,24 @@ class InputCollector:
default=CICDOption.NONE.key,
)
<<<<<<< HEAD
choice_map = {
CICDOption.BOTH.key: (True, True),
CICDOption.GITHUB.key: (True, False),
CICDOption.GITLAB.key: (False, True),
}
return choice_map.get(choice, (False, False))
=======
# Parse choice
if choice == CICDOption.BOTH.key:
return True, True
elif choice == CICDOption.GITHUB.key:
return True, False
elif choice == CICDOption.GITLAB.key:
return False, True
else: # Default to none (should not happen due to validation, but kept as fallback)
return False, False
>>>>>>> origin/main
def get_nested_apps_config(self) -> Tuple[bool, str | None]:
UIFormatter.print_separator()
@@ -226,7 +242,13 @@ class InputCollector:
)
console.print()
<<<<<<< HEAD
choice = CharReader.get_yes_no(f"[{UIColors.HIGHLIGHT}]Nested Django apps? (y/N):[/{UIColors.HIGHLIGHT}]")
=======
choice = CharReader.get_yes_no(
f"[{UIColors.HIGHLIGHT}]Nested Django apps? (y/N):[/{UIColors.HIGHLIGHT}]", default="n"
)
>>>>>>> origin/main
if choice != "y":
return False, None
@@ -246,7 +268,16 @@ class InputCollector:
console.print(f" [{UIColors.SUCCESS}]N[/{UIColors.SUCCESS}] Use individual database parameters")
console.print()
<<<<<<< HEAD
return CharReader.get_yes_no(f"[{UIColors.HIGHLIGHT}]Use DATABASE_URL? (Y/n):[/{UIColors.HIGHLIGHT}]") == "y"
=======
choice = CharReader.get_yes_no(
f"[{UIColors.HIGHLIGHT}]Use DATABASE_URL? (Y/n):[/{UIColors.HIGHLIGHT}]", default="y"
)
# Return True if choice is 'y', False if 'n'
return choice == "y"
>>>>>>> origin/main
class CharReader:
@@ -259,6 +290,7 @@ class CharReader:
return CharReader._get_char_unix()
@staticmethod
<<<<<<< HEAD
def _is_enter_key(response: str) -> bool:
"""Check if response represents Enter key or control character."""
return not response or response in ("\r", "\n") or ord(response) < 32
@@ -291,10 +323,30 @@ class CharReader:
# Handle Enter key (default)
if CharReader._is_enter_key(response):
return CharReader._handle_default(default)
=======
def get_yes_no(prompt: str, default: str = "n") -> str:
max_attempts = 3
attempt = 0
while attempt < max_attempts:
try:
console.print(f"{prompt} ", end="")
sys.stdout.flush()
reader = CharReader()
response = reader.get_char()
# Handle Enter key (default) - in raw mode, Enter is '\r' (carriage return)
# Also handle empty string or other control characters as Enter
if not response or response == "\r" or response == "\n" or ord(response) < 32:
console.print(default.upper())
return default.lower()
>>>>>>> origin/main
response = response.lower()
# Validate response
<<<<<<< HEAD
if response in valid_keys_lower:
console.print(response.upper())
return response
@@ -307,11 +359,30 @@ class CharReader:
if attempt < CharReader.MAX_ATTEMPTS:
console.print(
f"[{UIColors.MUTED}]Please try again ({attempt}/{CharReader.MAX_ATTEMPTS}).[/{UIColors.MUTED}]"
=======
if response in ("y", "n"):
console.print(response.upper())
return response
# Invalid input - show error (only show if it's a printable character)
console.print()
if response.isprintable():
UIFormatter.print_error(f"Invalid input '{response}'. Please enter 'y' or 'n'.")
attempt += 1
if attempt < max_attempts:
console.print(
f"[{UIColors.MUTED}]Please try again ({attempt}/{max_attempts}).[/{UIColors.MUTED}]"
>>>>>>> origin/main
)
console.print()
else:
# Control character entered, treat as Enter
<<<<<<< HEAD
return CharReader._handle_default(default)
=======
console.print(default.upper())
return default.lower()
>>>>>>> origin/main
except KeyboardInterrupt:
console.print()
@@ -324,6 +395,7 @@ class CharReader:
return default.lower()
@staticmethod
<<<<<<< HEAD
def get_yes_no(prompt: str, default: str = None) -> str:
# Auto-detect default from prompt format (capital letter indicates default)
if default is None:
@@ -351,6 +423,70 @@ class CharReader:
default=default,
error_message=f"Invalid input '{{response}}'. Please enter one of: {valid_keys_str}.",
)
=======
def get_cicd_choice(prompt: str, valid_keys: list[str], default: str = "n") -> str:
"""
Get CI/CD choice input with validation and retries.
Args:
prompt: The prompt message to display
valid_keys: List of valid key characters (e.g., ['g', 'l', 'b', 'n'])
default: Default value if Enter is pressed
Returns:
Valid key (lowercase)
"""
max_attempts = 3
attempt = 0
valid_keys_lower = [k.lower() for k in valid_keys]
while attempt < max_attempts:
try:
console.print(f"{prompt} ", end="")
sys.stdout.flush()
reader = CharReader()
response = reader.get_char()
# Handle Enter key (default) - in raw mode, Enter is '\r' (carriage return)
# Also handle empty string or other control characters as Enter
if not response or response == "\r" or response == "\n" or ord(response) < 32:
console.print(default.upper())
return default.lower()
response = response.lower()
# Validate response
if response in valid_keys_lower:
console.print(response.upper())
return response
# Invalid input - show error (only show if it's a printable character)
console.print()
if response.isprintable():
valid_keys_str = ", ".join(f"'{k}'" for k in valid_keys)
UIFormatter.print_error(f"Invalid input '{response}'. Please enter one of: {valid_keys_str}.")
attempt += 1
if attempt < max_attempts:
console.print(
f"[{UIColors.MUTED}]Please try again ({attempt}/{max_attempts}).[/{UIColors.MUTED}]"
)
console.print()
else:
# Control character entered, treat as Enter
console.print(default.upper())
return default.lower()
except KeyboardInterrupt:
console.print()
UIFormatter.print_info("\nOperation cancelled by user.")
raise
# Max attempts reached - use default
console.print()
UIFormatter.print_warning(f"Maximum attempts reached. Using default: '{default.upper()}'.")
return default.lower()
>>>>>>> origin/main
@staticmethod
def _get_char_windows() -> str:
@@ -389,10 +525,64 @@ def get_user_input() -> Tuple[str, str, str, list, dict]:
# Pre-step: Ask for structure type first
UIFormatter.print_separator()
<<<<<<< HEAD
console.print(f"\n[{UIColors.INFO}]Choose Structure Type[/{UIColors.INFO}]\n")
console.print(f"[{UIColors.MUTED}]1. Standard structure (default Django layout)[/{UIColors.MUTED}]")
console.print(f"[{UIColors.MUTED}]2. Predefined structure (apps/users, apps/core, api/)[/{UIColors.MUTED}]")
console.print(f"[{UIColors.MUTED}]3. Unified structure (core/, apps/core, apps/api)[/{UIColors.MUTED}]")
=======
console.print(f"\n[{UIColors.INFO}]Step 1: Project Setup[/{UIColors.INFO}]\n")
console.print(f"[{UIColors.HIGHLIGHT}]Project Directory[/{UIColors.HIGHLIGHT}]")
console.print(f"[{UIColors.MUTED}]Where your project files will be created[/{UIColors.MUTED}]")
console.print(f"[{UIColors.MUTED}]Press Enter or enter '.' to create in current directory[/{UIColors.MUTED}]")
# Get project directory with special handling for '.' and empty input
attempt = 0
project_dir = None
while attempt < collector.MAX_ATTEMPTS and project_dir is None:
try:
user_input = console.input(
f"[{UIColors.HIGHLIGHT}]Enter project directory name:[/{UIColors.HIGHLIGHT}] "
).strip()
# Handle empty input or '.' - use current directory
if not user_input or user_input == ".":
project_dir = "."
UIFormatter.print_info(f"Creating project in current directory: {os.getcwd()}")
break
# Validate the input
is_valid, error_msg = validate_project_name(user_input)
if not is_valid:
UIFormatter.print_error(error_msg)
attempt += 1
collector._show_retry_message(attempt)
continue
# Check if directory exists and ask for alternative
if os.path.exists(user_input):
UIFormatter.print_error(f"Directory '{user_input}' already exists.")
UIFormatter.print_info(
"Please choose a different project directory name (or press Enter for current directory)."
)
attempt += 1
collector._show_retry_message(attempt)
continue
project_dir = user_input
break
except KeyboardInterrupt:
UIFormatter.print_info("\nSetup cancelled by user.")
sys.exit(0)
if project_dir is None:
UIFormatter.print_error("Maximum attempts reached for project directory name. Exiting.")
sys.exit(1)
>>>>>>> origin/main
console.print()
structure_choice = console.input(
@@ -544,7 +734,13 @@ def confirm_setup(project_dir: str, project_name: str, app_names: list, metadata
console.print()
try:
<<<<<<< HEAD
response = CharReader.get_yes_no(f"[{UIColors.WARNING}]Proceed with setup? (y/N):[/{UIColors.WARNING}]")
=======
response = CharReader.get_yes_no(
f"[{UIColors.WARNING}]Proceed with setup? (y/N):[/{UIColors.WARNING}]", default="n"
)
>>>>>>> origin/main
return response == "y"
except KeyboardInterrupt:
+20
View File
@@ -22,14 +22,23 @@ class ProjectManager:
self.project_name = project_name
self.app_names = app_names
self.metadata = metadata
<<<<<<< HEAD
self.project_root = os.getcwd() if project_dir == "." else os.path.join(os.getcwd(), project_dir)
# Support custom module name for the Django config package (e.g., 'config')
# Fall back to project_name if key present but None
self.module_name = metadata.get("project_module_name") or self.project_name
=======
# Handle '.' for current directory
if project_dir == ".":
self.project_root = os.getcwd()
else:
self.project_root = os.path.join(os.getcwd(), project_dir)
>>>>>>> origin/main
def create_project(self) -> bool:
os.makedirs(self.project_root, exist_ok=True)
<<<<<<< HEAD
unified = self.metadata.get("unified_structure", False)
success = DjangoHelper.startproject(self.module_name, self.project_root, unified=unified)
if success:
@@ -37,6 +46,17 @@ class ProjectManager:
else:
UIFormatter.print_error(f"Failed to create Django project '{self.project_name}'")
return success
=======
# Create Django project inside the directory
# Use 'python -m django' instead of 'django-admin' for better compatibility
subprocess.run(
[sys.executable, "-m", "django", "startproject", self.project_name, self.project_root],
check=True,
)
UIFormatter.print_success(f"Django project '{self.project_name}' created successfully!")
return True
>>>>>>> origin/main
def create_apps(self) -> bool:
apps_base_dir = self.project_root
+4
View File
@@ -14,6 +14,10 @@ from djinit.scripts.template_engine import template_engine
def format_file(filename: str) -> None:
"""Format Python file using Ruff formatter."""
<<<<<<< HEAD
=======
# Use 'python -m ruff' instead of 'ruff' for better compatibility
>>>>>>> origin/main
subprocess.run([sys.executable, "-m", "ruff", "format", filename], check=False, capture_output=True)
-20
View File
@@ -1,20 +0,0 @@
# Django settings
DJANGO_SETTINGS_MODULE=yoo.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=
DEFAULT_FROM_EMAIL=
SERVER_EMAIL=
-93
View File
@@ -1,93 +0,0 @@
#--------------------------------------------------#
# 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
-20
View File
@@ -1,20 +0,0 @@
# Procfile for yoo
# 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 yoo.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 yoo.wsgi:application --bind 0.0.0.0:$PORT --workers 4 --threads 2 --timeout 120
# web: gunicorn yoo.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 yoo worker --loglevel=info
# beat: celery -A yoo beat --loglevel=info
# Development server (for local testing with Foreman/Overmind)
# web: python manage.py runserver 0.0.0.0:$PORT
-84
View File
@@ -1,84 +0,0 @@
# yoo
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
```
yoo/
├── core/
│ ├── settings/
│ │ ├── base.py
│ │ ├── development.py
│ │ └── production.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── apps/
│ ├── core/
│ │ ├── apps.py
│ │ ├── models/
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ └── mixins.py
│ │ ├── utils/
│ │ │ ├── __init__.py
│ │ │ └── responses.py
│ │ ├── permissions/
│ │ ├── middleware/
│ │ └── __init__.py
│ └── api/
│ ├── apps.py
│ ├── models/
│ ├── serializers/
│ ├── views/
│ ├── services/
│ ├── tests/
│ ├── urls/
│ └── admin/
├── manage.py
├── requirements.txt
├── pyproject.toml
├── .env.sample
├── justfile
├── Procfile
├── runtime.txt
└── 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/
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
-8
View File
@@ -1,8 +0,0 @@
"""generated with djinit"""
from django.apps import AppConfig
class Apps.apiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.api'
View File
Whitespace-only changes.
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
-8
View File
@@ -1,8 +0,0 @@
"""generated with djinit"""
from django.apps import AppConfig
class Config(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = ""
Whitespace-only changes.
-5
View File
@@ -1,5 +0,0 @@
"""generated with djinit"""
from django.db import models
# Create your models here.
-13
View File
@@ -1,13 +0,0 @@
"""generated with djinit"""
from django.db import models
class AbstractBaseModel(models.Model):
"""Abstract base model with common fields."""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
-13
View File
@@ -1,13 +0,0 @@
"""generated with djinit"""
from django.db import models
class TimestampedModel(models.Model):
"""Mixin for models that need timestamp fields."""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
Whitespace-only changes.
-3
View File
@@ -1,3 +0,0 @@
from .responses import success, error
__all__ = ["success", "error"]
-13
View File
@@ -1,13 +0,0 @@
"""generated with djinit"""
from rest_framework.response import Response
def success(data=None, status_code=200):
"""Return a successful response."""
return Response({"success": True, "data": data}, status=status_code)
def error(message, status_code=400):
"""Return an error response."""
return Response({"success": False, "message": message}, status=status_code)
View File
Whitespace-only changes.
-11
View File
@@ -1,11 +0,0 @@
"""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"))
application = get_asgi_application()
View File
Whitespace-only changes.
-163
View File
@@ -1,163 +0,0 @@
"""
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.core",
"apps.api",
]
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 = "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"
-29
View File
@@ -1,29 +0,0 @@
"""generated with djinit"""
from .base import *
SECRET_KEY = "4RxQW##Ss0h(8&Q3snx(2Km(qw75kRjGM#-zyV7*KUV&3)rUGZ"
# 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
-47
View File
@@ -1,47 +0,0 @@
"""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"),
}
}
# 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"
-30
View File
@@ -1,30 +0,0 @@
"""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"),
path("api/", include("apps.api.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")),
]
-11
View File
@@ -1,11 +0,0 @@
"""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"))
application = get_wsgi_application()
-154
View File
@@ -1,154 +0,0 @@
# Justfile for yoo
# 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 yoo.wsgi:application --bind 0.0.0.0:8000
# Start production server with gunicorn workers
server-prod WORKERS="4":
uv run gunicorn yoo.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 yoo.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}}
-29
View File
@@ -1,29 +0,0 @@
#!/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()
-41
View File
@@ -1,41 +0,0 @@
[project]
name = "yoo"
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 = ["yoo"]
-14
View File
@@ -1,14 +0,0 @@
#--------------------------------------------------#
# The following was generated by djinit: #
#--------------------------------------------------#
Django
python-dotenv
django-jazzmin
djangorestframework
djangorestframework_simplejwt
drf-spectacular
django-cors-headers
whitenoise
psycopg2-binary
gunicorn
-1
View File
@@ -1 +0,0 @@
python-3.13