- Implemented custom form widgets for date, time, and datetime fields with DaisyUI styling. - Created utility functions for formatting dates, times, and numbers according to user preferences. - Developed views for profile settings, API key management, and notifications, including health check endpoints. - Added URL configurations for Themis tests and main application routes. - Established test cases for custom widgets to ensure proper functionality and integration. - Defined project metadata and dependencies in pyproject.toml for package management.
10 KiB
🐾 Red Panda Approval™
This project follows Red Panda Approval standards — our gold standard for Django application quality. Code must be elegant, reliable, and maintainable to earn the approval of our adorable red panda judges.
The 5 Sacred Django Criteria
- Fresh Migration Test — Clean migrations from empty database
- Elegant Simplicity — No unnecessary complexity
- Observable & Debuggable — Proper logging and error handling
- Consistent Patterns — Follow Django conventions
- Actually Works — Passes all checks and serves real user needs
Environment Standards
- Virtual environment: ~/env/PROJECT/bin/activate
- Use pyproject.toml for project configuration (no setup.py, no requirements.txt)
- Python version: specified in pyproject.toml
- Dependencies: floor-pinned with ceiling (e.g.
Django>=5.2,<6.0)
Dependency Pinning
# Correct — floor pin with ceiling
dependencies = [
"Django>=5.2,<6.0",
"djangorestframework>=3.14,<4.0",
"cryptography>=41.0,<45.0",
]
# Wrong — exact pins in library packages
dependencies = [
"Django==5.2.7", # too strict, breaks downstream
]
Exact pins (==) are only appropriate in application-level lock files, not in reusable library packages.
Directory Structure
myproject/ # Git repository root ├── .gitignore ├── README.md ├── pyproject.toml # Project configuration (moved to repo root) ├── docker-compose.yml ├── .env # Docker Compose environment (DATABASE_URL=postgres://...) ├── .env.example │ ├── project/ # Django project root (manage.py lives here) │ ├── manage.py │ ├── Dockerfile │ ├── .env # Local development environment (DATABASE_URL=sqlite:///...) │ ├── .env.example │ │ │ ├── config/ # Django configuration module │ │ ├── init.py │ │ ├── settings.py │ │ ├── urls.py │ │ ├── wsgi.py │ │ └── asgi.py │ │ │ ├── accounts/ # Django app │ │ ├── init.py │ │ ├── models.py │ │ ├── views.py │ │ └── urls.py │ │ │ ├── blog/ # Django app │ │ ├── init.py │ │ ├── models.py │ │ ├── views.py │ │ └── urls.py │ │ │ ├── static/ │ │ ├── css/ │ │ └── js/ │ │ │ └── templates/ │ └── base.html │ ├── web/ # Nginx configuration │ └── nginx.conf │ ├── db/ # PostgreSQL configuration │ └── postgresql.conf │ └── docs/ # Project documentation └── index.md
Settings Structure
- Use a single settings.py file
- Use django-environ or python-dotenv for environment variables
- Never commit .env files to version control
- Provide .env.example with all required variables documented
- Create .gitignore file
- Create a .dockerignore file
Code Organization
- Imports: PEP 8 ordering (stdlib, third-party, local)
- Type hints on function parameters
- CSS: External .css files only (no inline styles, no embedded
<style>tags) - JS: External .js files only (no inline handlers, no embedded
<script>blocks) - Maximum file length: 1000 lines
- If a file exceeds 500 lines, consider splitting by domain concept
Database Conventions
- Migrations run cleanly from empty database
- Never edit deployed migrations
- Use meaningful migration names: --name add_email_to_profile
- One logical change per migration when possible
- Test migrations both forward and backward
Development vs Production
- Development: SQLite
- Production: PostgreSQL
Caching
- Expensive queries are cached
- Cache keys follow naming convention
- TTLs are appropriate (not infinite)
- Invalidation is documented
- Key Naming Pattern: {app}:{model}:{identifier}:{field}
Model Naming
- Model names: singular PascalCase (User, BlogPost, OrderItem)
- Correct English pluralization on related names
- All models have created_at and updated_at
- All models define str and get_absolute_url
- TextChoices used for status fields
- related_name defined on ForeignKey fields
- Related names: plural snake_case with proper English pluralization
Forms
- Use ModelForm with explicit fields list (never all)
Field Naming
- Foreign keys: singular without _id suffix (author, category, parent)
- Boolean fields: use prefixes (is_active, has_permission, can_edit)
- Date fields: use suffixes (created_at, updated_at, published_on)
- Avoid abbreviations (use description, not desc)
Required Model Fields
- All models should include:
- created_at = models.DateTimeField(auto_now_add=True)
- updated_at = models.DateTimeField(auto_now=True)
- Consider adding:
- id = models.UUIDField(primary_key=True) for public-facing models
- is_active = models.BooleanField(default=True) for soft deletes
Indexing
- Add db_index=True to frequently queried fields
- Use Meta.indexes for composite indexes
- Document why each index exists
Queries
- Use select_related() for foreign keys
- Use prefetch_related() for reverse relations and M2M
- Avoid queries in loops (N+1 problem)
- Use .only() and .defer() for large models
- Add comments explaining complex querysets
Docstrings
- Use Sphinx style docstrings
- Document all public functions, classes, and modules
- Skip docstrings for obvious one-liners and standard Django overrides
Views
- Use Function-Based Views (FBVs) exclusively
- Explicit logic is preferred over implicit inheritance
- Extract shared logic into utility functions
URLs & Identifiers
- Public URLs use short UUIDs (12 characters) via
shortuuid - Never expose sequential IDs in URLs (security/enumeration risk)
- Internal references may use standard UUIDs or PKs
URL Patterns
- Resource-based URLs (RESTful style)
- Namespaced URL names per app
- Trailing slashes (Django default)
- Flat structure preferred over deep nesting
Background Tasks
- All tasks are run synchronously unless the design specifies background tasks are needed for long operations
- Long operations use Celery tasks
- Use Memcached, task progress pattern: {app}:task:{task_id}:progress
- Tasks are idempotent
- Tasks include retry logic
- Tasks live in app/tasks.py
- RabbitMQ is the Message Broker
- Flower Monitoring: Use for debugging failed tasks
Testing
- Framework: Django TestCase (not pytest)
- Separate test files per module: test_models.py, test_views.py, test_forms.py
Frontend Standards
New Projects (DaisyUI + Tailwind)
- DaisyUI 4 via CDN for component classes
- Tailwind CSS via CDN for utility classes
- Theme management via Themis (DaisyUI
data-themeattribute) - All apps extend
themis/base.htmlfor consistent navigation - No inline styles or scripts
Existing Projects (Bootstrap 5)
- Bootstrap 5 via CDN
- Bootstrap Icons via CDN
- Bootswatch for theme variants (if applicable)
- django-bootstrap5 and crispy-bootstrap5 for form rendering
Preferred Packages
Core Django
- django>=5.2,<6.0
- django-environ — Environment variables
Authentication & Security
- django-allauth — User management
- django-allauth-2fa — Two-factor authentication
API Development
- djangorestframework>=3.14,<4.0 — REST APIs
- drf-spectacular — OpenAPI/Swagger documentation
Encryption
- cryptography — Fernet encryption for secrets/API keys
Background Tasks
- celery — Async task queue
- django-celery-progress — Progress bars
- flower — Celery monitoring
Caching
- pymemcache — Memcached backend
Database
- dj-database-url — Database URL configuration
- psycopg[binary] — PostgreSQL adapter
- shortuuid — Short UUIDs for public URLs
Production
- gunicorn — WSGI server
Shared Apps
- django-heluca-themis — User preferences, themes, key management, navigation
Deprecated / Removed
pytz— Use stdlibzoneinfo(Python 3.9+, Django 4+)Pillow— Only add if your app needs ImageFielddjango-heluca-core— Replaced by Themis
Anti-Patterns to Avoid
Models
- Don't use
Model.objects.get()without handlingDoesNotExist - Don't use
null=TrueonCharFieldorTextField(useblank=True, default="") - Don't use
related_name='+'unless you have a specific reason - Don't override
save()for business logic (use signals or service functions) - Don't use
auto_now=Trueon fields you might need to manually set - Don't use
ForeignKeywithout specifyingon_deleteexplicitly - Don't use
Meta.orderingon large tables (specify ordering in queries)
Queries
- Don't query inside loops (N+1 problem)
- Don't use
.all()when you need a subset - Don't use raw SQL unless absolutely necessary
- Don't forget
select_related()andprefetch_related()
Views
- Don't put business logic in views
- Don't use
request.POST.get()without validation (use forms) - Don't return sensitive data in error messages
- Don't forget
login_requireddecorator on protected views
Forms
- Don't use
fields = '__all__'in ModelForm - Don't trust client-side validation alone
- Don't use
excludein ModelForm (use explicitfields)
Templates
- Don't use
{{ variable }}for URLs (use{% url %}tag) - Don't put logic in templates
- Don't use inline CSS or JavaScript (external files only)
- Don't forget
{% csrf_token %}in forms
Security
- Don't store secrets in
settings.py(use environment variables) - Don't commit
.envfiles to version control - Don't use
DEBUG=Truein production - Don't expose sequential IDs in public URLs
- Don't use
mark_safe()on user-supplied content - Don't disable CSRF protection
Imports & Code Style
- Don't use
from module import * - Don't use mutable default arguments
- Don't use bare
except:clauses - Don't ignore linter warnings without documented reason
Migrations
- Don't edit migrations that have been deployed
- Don't use
RunPythonwithout a reverse function - Don't add non-nullable fields without a default value
Celery Tasks
- Don't pass model instances to tasks (pass IDs and re-fetch)
- Don't assume tasks run immediately
- Don't forget retry logic for external service calls