mirror of
https://github.com/daniviga/django-ram.git
synced 2026-02-03 17:40:39 +01:00
* Extend test coverage * Implement query optimization * More aggressing code reuse * Add more indexes and optimize usage * Fix tests * Further optimizations, improve counting to rely on backend DB * chore: add Makefile for frontend asset minification - Add comprehensive Makefile with targets for JS and CSS minification - Implements instructions from ram/portal/static/js/src/README.md - Provides targets: install, minify, minify-js, minify-css, clean, watch - Fix main.min.js to only include theme_selector.js and tabs_selector.js - Remove validators.js from minified output per README instructions * Add a Makefile to compile JS and CSS * docs: add blank line whitespace rule to AGENTS.md Specify that blank lines must not contain any whitespace (spaces or tabs) to maintain code cleanliness and PEP 8 compliance * Update for 0.20 release with optimizations * Improve Makefile
12 KiB
12 KiB
Django Railroad Assets Manager - Agent Guidelines
This document provides coding guidelines and command references for AI coding agents working on the Django-RAM project.
Project Overview
Django Railroad Assets Manager (django-ram) is a Django 6.0+ application for managing model railroad collections with DCC++ EX integration. The project manages rolling stock, consists, metadata, books/magazines, and provides an optional REST API for DCC control.
Environment Setup
Python Requirements
- Python 3.11+ (tested on 3.13, 3.14)
- Django >= 6.0
- Working directory:
ram/(Django project root) - Virtual environment recommended:
python3 -m venv venv && source venv/bin/activate
Installation
pip install -r requirements.txt # Core dependencies
pip install -r requirements-dev.txt # Development tools
cd ram && python manage.py migrate # Initialize database
python manage.py createsuperuser # Create admin user
Frontend Assets
npm install # Install clean-css-cli, terser
Project Structure
ram/ # Django project root
├── ram/ # Core settings, URLs, base models
├── portal/ # Public-facing frontend (Bootstrap 5)
├── roster/ # Rolling stock management (main app)
├── metadata/ # Manufacturers, companies, scales, decoders
├── bookshelf/ # Books and magazines
├── consist/ # Train consists (multiple locomotives)
├── repository/ # Document repository
├── driver/ # DCC++ EX API gateway (optional, disabled by default)
└── storage/ # Runtime data (SQLite DB, media, cache)
Build/Lint/Test Commands
Running the Development Server
cd ram
python manage.py runserver # Runs on http://localhost:8000
Database Management
python manage.py makemigrations # Create new migrations
python manage.py migrate # Apply migrations
python manage.py showmigrations # Show migration status
Testing
# Run all tests (comprehensive test suite with 75+ tests)
python manage.py test
# Run tests for a specific app
python manage.py test roster # Rolling stock tests
python manage.py test metadata # Metadata tests
python manage.py test bookshelf # Books/magazines tests
python manage.py test consist # Consist tests
# Run a specific test case class
python manage.py test roster.tests.RollingStockTestCase
python manage.py test metadata.tests.ScaleTestCase
# Run a single test method
python manage.py test roster.tests.RollingStockTestCase.test_road_number_int_extraction
python manage.py test bookshelf.tests.TocEntryTestCase.test_toc_entry_page_validation_exceeds_book
# Run with verbosity for detailed output
python manage.py test --verbosity=2
# Keep test database for inspection
python manage.py test --keepdb
# Run tests matching a pattern
python manage.py test --pattern="test_*.py"
Linting and Formatting
# Run flake8 (configured in requirements-dev.txt)
flake8 . # Lint entire project
flake8 roster/ # Lint specific app
flake8 roster/models.py # Lint specific file
# Note: No .flake8 config exists; uses PEP 8 defaults
# Long lines use # noqa: E501 comments in settings.py
# Run black formatter with 79 character line length
black -l 79 . # Format entire project
black -l 79 roster/ # Format specific app
black -l 79 roster/models.py # Format specific file
black -l 79 --check . # Check formatting without changes
black -l 79 --diff . # Show formatting changes
Admin Commands
python manage.py createsuperuser # Create admin user
python manage.py purge_cache # Custom: purge cache
python manage.py loaddata <fixture> # Load sample data
Debugging & Profiling
# Use pdbpp for debugging (installed via requirements-dev.txt)
import pdb; pdb.set_trace() # Set breakpoint in code
# Use pyinstrument for profiling
python manage.py runserver --noreload # With pyinstrument middleware
Code Style Guidelines
General Python Style
- PEP 8 compliant - Follow standard Python style guide
- Line length: 79 characters preferred; 119 acceptable for complex lines
- Long lines: Use
# noqa: E501comment when necessary (see settings.py) - Indentation: 4 spaces (no tabs)
- Encoding: UTF-8
- Blank lines: Must not contain any whitespace (spaces or tabs)
Import Organization
Follow Django's import style (as seen in models.py, views.py, admin.py):
# 1. Standard library imports
import os
import re
from itertools import chain
from functools import reduce
# 2. Related third-party imports
from django.db import models
from django.conf import settings
from django.contrib import admin
from tinymce import models as tinymce
# 3. Local application imports
from ram.models import BaseModel, Image
from ram.utils import DeduplicatedStorage, slugify
from metadata.models import Scale, Manufacturer
Key points:
- Group imports by category with blank lines between
- Use
from module import specificfor commonly used items - Avoid
import * - Use
asfor aliasing when needed (e.g.,tinymce.models as tinymce)
Naming Conventions
- Classes:
PascalCase(e.g.,RollingStock,BaseModel) - Functions/methods:
snake_case(e.g.,get_items_per_page(),image_thumbnail()) - Variables:
snake_case(e.g.,road_number,item_number_slug) - Constants:
UPPER_SNAKE_CASE(e.g.,BASE_DIR,ALLOWED_HOSTS) - Private methods: Prefix with
_(e.g.,_internal_method()) - Model Meta options: Use
verbose_name,verbose_name_plural,ordering
Django Model Patterns
class MyModel(BaseModel): # Inherit from BaseModel for common fields
# Field order: relationships first, then data fields, then metadata
foreign_key = models.ForeignKey(OtherModel, on_delete=models.CASCADE)
name = models.CharField(max_length=128)
slug = models.SlugField(max_length=128, unique=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["name"]
verbose_name = "My Model"
verbose_name_plural = "My Models"
def __str__(self):
return self.name
@property
def computed_field(self):
"""Document properties with docstrings."""
return self.calculate_something()
Model field conventions:
- Use
null=True, blank=Truefor optional fields - Use
help_textfor user-facing field descriptions - Use
limit_choices_tofor filtered ForeignKey choices - Use
related_namefor reverse relations - Set
on_delete=models.CASCADEexplicitly - Use
default=Nonewithnull=Truefor nullable fields
Admin Customization
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_display = ("name", "created", "custom_method")
list_filter = ("category", "created")
search_fields = ("name", "slug")
autocomplete_fields = ("foreign_key",)
readonly_fields = ("created", "updated")
save_as = True # Enable "Save as new" button
@admin.display(description="Custom Display")
def custom_method(self, obj):
return format_html('<strong>{}</strong>', obj.name)
Error Handling
# Use Django's exception classes
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.http import Http404
from django.db.utils import OperationalError, ProgrammingError
# Handle database errors gracefully
try:
config = get_site_conf()
except (OperationalError, ProgrammingError):
config = default_config # Provide fallback
Type Hints
- Not currently used in this project
- Follow existing patterns without type hints unless explicitly adding them
Django-Specific Patterns
Using BaseModel
All major models inherit from ram.models.BaseModel:
from ram.models import BaseModel
class MyModel(BaseModel):
# Automatically includes: uuid, description, notes, creation_time,
# updated_time, published, obj_type, obj_label properties
pass
Using PublicManager
Models use PublicManager for filtering published items:
from ram.managers import PublicManager
objects = PublicManager() # Only returns items where published=True
Image and Document Patterns
from ram.models import Image, Document, PrivateDocument
class MyImage(Image):
my_model = models.ForeignKey(MyModel, on_delete=models.CASCADE)
# Inherits: order, image, image_thumbnail()
class MyDocument(PrivateDocument):
my_model = models.ForeignKey(MyModel, on_delete=models.CASCADE)
# Inherits: description, file, private, creation_time, updated_time
Using DeduplicatedStorage
For media files that should be deduplicated:
from ram.utils import DeduplicatedStorage
image = models.ImageField(upload_to="images/", storage=DeduplicatedStorage)
Testing Practices
Test Coverage
The project has comprehensive test coverage:
- roster/tests.py: RollingStock, RollingClass models (~340 lines, 19+ tests)
- metadata/tests.py: Scale, Manufacturer, Company, etc. (~378 lines, 29+ tests)
- bookshelf/tests.py: Book, Magazine, Catalog, TocEntry (~436 lines, 25+ tests)
- consist/tests.py: Consist, ConsistItem (~315 lines, 15+ tests)
- ram/tests.py: BaseModel, utility functions (~140 lines, 11+ tests)
Writing Tests
from django.test import TestCase
from django.core.exceptions import ValidationError
from roster.models import RollingStock
class RollingStockTestCase(TestCase):
def setUp(self):
"""Set up test data."""
# Create necessary related objects
self.company = Company.objects.create(name="RGS", country="US")
self.scale = Scale.objects.create(scale="HO", ratio="1:87", tracks=16.5)
# ...
def test_road_number_int_extraction(self):
"""Test automatic extraction of integer from road number."""
stock = RollingStock.objects.create(
rolling_class=self.rolling_class,
road_number="RGS-42",
scale=self.scale,
)
self.assertEqual(stock.road_number_int, 42)
def test_validation_error(self):
"""Test that validation errors are raised correctly."""
with self.assertRaises(ValidationError):
# Test validation logic
pass
Testing best practices:
- Use descriptive test method names with
test_prefix - Include docstrings explaining what each test verifies
- Create necessary test data in
setUp()method - Test both success and failure cases
- Use
assertRaises()for exception testing - Test model properties, methods, and validation logic
Git & Version Control
- Branch:
master(main development branch) - CI runs on push and PR to master
- Follow conventional commit messages
- No pre-commit hooks configured (consider adding)
Additional Notes
- Settings override: Use
ram/local_settings.pyfor local configuration - Debug mode:
DEBUG = Truein settings.py (change for production) - Database: SQLite by default (in
storage/db.sqlite3) - Static files: Bootstrap 5.3.8, Bootstrap Icons 1.13.1
- Rich text: TinyMCE for HTMLField content
- REST API: Disabled by default (
REST_ENABLED = False) - Security: CSP middleware enabled, secure cookies in production