diff --git a/src/cli.py b/src/cli.py index 1676dca..080c950 100644 --- a/src/cli.py +++ b/src/cli.py @@ -42,6 +42,8 @@ class Cli: ("Configuring production settings", self.settings_manager.update_production_settings), ("Creating utility files", self._create_utility_files), ("Setting up app URLs", self.file_manager.create_app_urls), + ("Creating app serializers", self.file_manager.create_app_serializers), + ("Creating app routes", self.file_manager.create_app_routes), ("Configuring comprehensive URLs", self.file_manager.update_project_urls), ("Updating WSGI configuration", self.file_manager.update_wsgi_file), ("Updating ASGI configuration", self.file_manager.update_asgi_file), diff --git a/src/scripts/app_generator.py b/src/scripts/app_generator.py index 214e618..01bb692 100644 --- a/src/scripts/app_generator.py +++ b/src/scripts/app_generator.py @@ -8,6 +8,8 @@ import subprocess 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 class AppManager: @@ -33,6 +35,9 @@ class AppManager: if not self._add_to_installed_apps(): return False + if not self._create_additional_files(): + return False + UIFormatter.print_success(f"Django app '{self.app_name}' created and configured successfully!") return True @@ -116,3 +121,30 @@ class AppManager: return settings_path return None + + 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) + + # Create serializers.py + context = {"app_name": self.app_name} + serializers_content = template_engine.render_template("serializers.j2", context) + if not create_file_with_content( + os.path.join(app_path, "serializers.py"), + serializers_content, + f"Created {self.app_name}/serializers.py", + should_format=True, + ): + return False + + # Create routes.py + routes_content = template_engine.render_template("routes.j2", context) + if not create_file_with_content( + os.path.join(app_path, "routes.py"), + routes_content, + f"Created {self.app_name}/routes.py", + should_format=True, + ): + return False + + return True diff --git a/src/scripts/files.py b/src/scripts/files.py index 3ed3627..5a843ae 100644 --- a/src/scripts/files.py +++ b/src/scripts/files.py @@ -90,6 +90,44 @@ class FileManager: ) return True + def create_app_serializers(self) -> bool: + """Create serializers.py file for each app.""" + apps_base_dir = self.project_root + if self.metadata.get("nested_apps") and self.metadata.get("nested_dir"): + apps_base_dir = os.path.join(self.project_root, self.metadata.get("nested_dir")) + + for app_name in self.app_names: + app_path = os.path.join(apps_base_dir, app_name) + with change_cwd(app_path): + context = {"app_name": app_name} + serializers_content = template_engine.render_template("serializers.j2", context) + create_file_with_content( + "serializers.py", + serializers_content, + f"Created {app_name}/serializers.py", + should_format=True, + ) + return True + + def create_app_routes(self) -> bool: + """Create routes.py file for each app.""" + apps_base_dir = self.project_root + if self.metadata.get("nested_apps") and self.metadata.get("nested_dir"): + apps_base_dir = os.path.join(self.project_root, self.metadata.get("nested_dir")) + + for app_name in self.app_names: + app_path = os.path.join(apps_base_dir, app_name) + with change_cwd(app_path): + context = {"app_name": app_name} + routes_content = template_engine.render_template("routes.j2", context) + create_file_with_content( + "routes.py", + routes_content, + f"Created {app_name}/routes.py", + should_format=True, + ) + return True + def update_project_urls(self) -> bool: with change_cwd(self.project_configs): nested = bool(self.metadata.get("nested_apps")) diff --git a/src/templates/routes.j2 b/src/templates/routes.j2 new file mode 100644 index 0000000..25a1203 --- /dev/null +++ b/src/templates/routes.j2 @@ -0,0 +1,16 @@ +""" +API routes for {{ app_name }} app. +""" + +from rest_framework.routers import DefaultRouter +from . import views + +# Create router instance +router = DefaultRouter() + +# Register viewsets with router +# Example: +# router.register(r'{{ app_name }}', views.{{ app_name|title }}ViewSet) + +# Get URL patterns +urlpatterns = router.urls \ No newline at end of file diff --git a/src/templates/serializers.j2 b/src/templates/serializers.j2 new file mode 100644 index 0000000..7cf7f1a --- /dev/null +++ b/src/templates/serializers.j2 @@ -0,0 +1,13 @@ +""" +Serializers for {{ app_name }} app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class {{ app_name|title }}Serializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/yoo/.env.sample b/yoo/.env.sample new file mode 100644 index 0000000..281b616 --- /dev/null +++ b/yoo/.env.sample @@ -0,0 +1,18 @@ +# 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= \ No newline at end of file diff --git a/yoo/.gitignore b/yoo/.gitignore new file mode 100644 index 0000000..51c5a35 --- /dev/null +++ b/yoo/.gitignore @@ -0,0 +1,89 @@ +# 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/yoo/Procfile b/yoo/Procfile new file mode 100644 index 0000000..51f48af --- /dev/null +++ b/yoo/Procfile @@ -0,0 +1,20 @@ +# 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 diff --git a/yoo/README.md b/yoo/README.md new file mode 100644 index 0000000..1699752 --- /dev/null +++ b/yoo/README.md @@ -0,0 +1,60 @@ +# 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/ diff --git a/yoo/apps/__init__.py b/yoo/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yoo/apps/store/__init__.py b/yoo/apps/store/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yoo/apps/store/admin.py b/yoo/apps/store/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/yoo/apps/store/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/yoo/apps/store/apps.py b/yoo/apps/store/apps.py new file mode 100644 index 0000000..1a5be03 --- /dev/null +++ b/yoo/apps/store/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class StoreConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "store" diff --git a/yoo/apps/store/migrations/__init__.py b/yoo/apps/store/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yoo/apps/store/models.py b/yoo/apps/store/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/yoo/apps/store/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/yoo/apps/store/routes.py b/yoo/apps/store/routes.py new file mode 100644 index 0000000..276d801 --- /dev/null +++ b/yoo/apps/store/routes.py @@ -0,0 +1,16 @@ +""" +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 diff --git a/yoo/apps/store/serializers.py b/yoo/apps/store/serializers.py new file mode 100644 index 0000000..85ffff9 --- /dev/null +++ b/yoo/apps/store/serializers.py @@ -0,0 +1,13 @@ +""" +Serializers for store app. +""" + +from rest_framework import serializers + + +# Add your serializers here +# Example: +# class StoreSerializer(serializers.ModelSerializer): +# class Meta: +# model = YourModel +# fields = '__all__' diff --git a/yoo/apps/store/tests.py b/yoo/apps/store/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/yoo/apps/store/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/yoo/apps/store/urls.py b/yoo/apps/store/urls.py new file mode 100644 index 0000000..2b978dc --- /dev/null +++ b/yoo/apps/store/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from . import views + +app_name = "store" + +urlpatterns = [ + # Add your URL patterns here + # path('', views.index, name='index'), +] diff --git a/yoo/apps/store/views.py b/yoo/apps/store/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/yoo/apps/store/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/yoo/core/__init__.py b/yoo/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yoo/core/asgi.py b/yoo/core/asgi.py new file mode 100644 index 0000000..6380cd7 --- /dev/null +++ b/yoo/core/asgi.py @@ -0,0 +1,9 @@ +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() diff --git a/yoo/core/settings/__init__.py b/yoo/core/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yoo/core/settings/base.py b/yoo/core/settings/base.py new file mode 100644 index 0000000..014c4a2 --- /dev/null +++ b/yoo/core/settings/base.py @@ -0,0 +1,163 @@ +""" +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" diff --git a/yoo/core/settings/development.py b/yoo/core/settings/development.py new file mode 100644 index 0000000..02cde63 --- /dev/null +++ b/yoo/core/settings/development.py @@ -0,0 +1,27 @@ +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 diff --git a/yoo/core/settings/production.py b/yoo/core/settings/production.py new file mode 100644 index 0000000..05fcfbd --- /dev/null +++ b/yoo/core/settings/production.py @@ -0,0 +1,43 @@ +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" diff --git a/yoo/core/urls.py b/yoo/core/urls.py new file mode 100644 index 0000000..a9ad054 --- /dev/null +++ b/yoo/core/urls.py @@ -0,0 +1,29 @@ +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")), + ] diff --git a/yoo/core/wsgi.py b/yoo/core/wsgi.py new file mode 100644 index 0000000..d414bb0 --- /dev/null +++ b/yoo/core/wsgi.py @@ -0,0 +1,9 @@ +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() diff --git a/yoo/justfile b/yoo/justfile new file mode 100644 index 0000000..a3e7127 --- /dev/null +++ b/yoo/justfile @@ -0,0 +1,154 @@ +# 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 diff --git a/yoo/manage.py b/yoo/manage.py new file mode 100755 index 0000000..ff307fd --- /dev/null +++ b/yoo/manage.py @@ -0,0 +1,26 @@ +#!/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() diff --git a/yoo/pyproject.toml b/yoo/pyproject.toml new file mode 100644 index 0000000..d4031ea --- /dev/null +++ b/yoo/pyproject.toml @@ -0,0 +1,41 @@ +[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"] diff --git a/yoo/requirements.txt b/yoo/requirements.txt new file mode 100644 index 0000000..6bdaa52 --- /dev/null +++ b/yoo/requirements.txt @@ -0,0 +1,10 @@ +Django +python-dotenv +django-jazzmin> +djangorestframework +djangorestframework_simplejwt +drf-spectacular +django-cors-headers +whitenoise +psycopg2-binary +gunicorn diff --git a/yoo/runtime.txt b/yoo/runtime.txt new file mode 100644 index 0000000..e912aa6 --- /dev/null +++ b/yoo/runtime.txt @@ -0,0 +1 @@ +python-3.13