diff --git a/djinit/cli.py b/djinit/cli.py index 53dc429..3644db7 100644 --- a/djinit/cli.py +++ b/djinit/cli.py @@ -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) diff --git a/djinit/scripts/app_generator.py b/djinit/scripts/app_generator.py index b594e8f..5b46633 100644 --- a/djinit/scripts/app_generator.py +++ b/djinit/scripts/app_generator.py @@ -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) diff --git a/djinit/scripts/input_handler.py b/djinit/scripts/input_handler.py index 65f4cc7..01ecd6b 100644 --- a/djinit/scripts/input_handler.py +++ b/djinit/scripts/input_handler.py @@ -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: diff --git a/djinit/scripts/project.py b/djinit/scripts/project.py index 2105f02..da7a9a2 100644 --- a/djinit/scripts/project.py +++ b/djinit/scripts/project.py @@ -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 diff --git a/djinit/utils.py b/djinit/utils.py index 05cfd07..a41e9a0 100644 --- a/djinit/utils.py +++ b/djinit/utils.py @@ -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) diff --git a/yoo/.env.sample b/yoo/.env.sample deleted file mode 100644 index 8130d94..0000000 --- a/yoo/.env.sample +++ /dev/null @@ -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= diff --git a/yoo/.gitignore b/yoo/.gitignore deleted file mode 100644 index 2b5243c..0000000 --- a/yoo/.gitignore +++ /dev/null @@ -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 diff --git a/yoo/Procfile b/yoo/Procfile deleted file mode 100644 index bffaa10..0000000 --- a/yoo/Procfile +++ /dev/null @@ -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 diff --git a/yoo/README.md b/yoo/README.md deleted file mode 100644 index 6d4a03f..0000000 --- a/yoo/README.md +++ /dev/null @@ -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/ diff --git a/yoo/apps/__init__.py b/yoo/apps/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/__init__.py b/yoo/apps/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/admin/__init__.py b/yoo/apps/api/admin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/apps.py b/yoo/apps/api/apps.py deleted file mode 100644 index 3ed621b..0000000 --- a/yoo/apps/api/apps.py +++ /dev/null @@ -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' diff --git a/yoo/apps/api/models/__init__.py b/yoo/apps/api/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/serializers/__init__.py b/yoo/apps/api/serializers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/services/__init__.py b/yoo/apps/api/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/tests/__init__.py b/yoo/apps/api/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/urls/__init__.py b/yoo/apps/api/urls/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/api/views/__init__.py b/yoo/apps/api/views/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/core/__init__.py b/yoo/apps/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/core/apps.py b/yoo/apps/core/apps.py deleted file mode 100644 index 00c1458..0000000 --- a/yoo/apps/core/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class Config(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "" diff --git a/yoo/apps/core/middleware/__init__.py b/yoo/apps/core/middleware/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/core/models/__init__.py b/yoo/apps/core/models/__init__.py deleted file mode 100644 index 980c8e5..0000000 --- a/yoo/apps/core/models/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.db import models - -# Create your models here. diff --git a/yoo/apps/core/models/base.py b/yoo/apps/core/models/base.py deleted file mode 100644 index a3970f9..0000000 --- a/yoo/apps/core/models/base.py +++ /dev/null @@ -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 diff --git a/yoo/apps/core/models/mixins.py b/yoo/apps/core/models/mixins.py deleted file mode 100644 index fe40544..0000000 --- a/yoo/apps/core/models/mixins.py +++ /dev/null @@ -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 diff --git a/yoo/apps/core/permissions/__init__.py b/yoo/apps/core/permissions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/apps/core/utils/__init__.py b/yoo/apps/core/utils/__init__.py deleted file mode 100644 index 155f5b4..0000000 --- a/yoo/apps/core/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .responses import success, error - -__all__ = ["success", "error"] diff --git a/yoo/apps/core/utils/responses.py b/yoo/apps/core/utils/responses.py deleted file mode 100644 index 929c2f6..0000000 --- a/yoo/apps/core/utils/responses.py +++ /dev/null @@ -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) diff --git a/yoo/core/__init__.py b/yoo/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/core/asgi.py b/yoo/core/asgi.py deleted file mode 100644 index 03b888c..0000000 --- a/yoo/core/asgi.py +++ /dev/null @@ -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() diff --git a/yoo/core/settings/__init__.py b/yoo/core/settings/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/yoo/core/settings/base.py b/yoo/core/settings/base.py deleted file mode 100644 index 9c92304..0000000 --- a/yoo/core/settings/base.py +++ /dev/null @@ -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" diff --git a/yoo/core/settings/development.py b/yoo/core/settings/development.py deleted file mode 100644 index f78a937..0000000 --- a/yoo/core/settings/development.py +++ /dev/null @@ -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 diff --git a/yoo/core/settings/production.py b/yoo/core/settings/production.py deleted file mode 100644 index e0cd36c..0000000 --- a/yoo/core/settings/production.py +++ /dev/null @@ -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" diff --git a/yoo/core/urls.py b/yoo/core/urls.py deleted file mode 100644 index 662a94c..0000000 --- a/yoo/core/urls.py +++ /dev/null @@ -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")), - ] diff --git a/yoo/core/wsgi.py b/yoo/core/wsgi.py deleted file mode 100644 index fa32c6a..0000000 --- a/yoo/core/wsgi.py +++ /dev/null @@ -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() diff --git a/yoo/justfile b/yoo/justfile deleted file mode 100644 index 2299604..0000000 --- a/yoo/justfile +++ /dev/null @@ -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}} diff --git a/yoo/manage.py b/yoo/manage.py deleted file mode 100755 index 422a555..0000000 --- a/yoo/manage.py +++ /dev/null @@ -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() diff --git a/yoo/pyproject.toml b/yoo/pyproject.toml deleted file mode 100644 index 4e6bf5e..0000000 --- a/yoo/pyproject.toml +++ /dev/null @@ -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"] diff --git a/yoo/requirements.txt b/yoo/requirements.txt deleted file mode 100644 index 5d8a32d..0000000 --- a/yoo/requirements.txt +++ /dev/null @@ -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 diff --git a/yoo/runtime.txt b/yoo/runtime.txt deleted file mode 100644 index e912aa6..0000000 --- a/yoo/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-3.13