# AGENTS.md - AI Coding Agent Guidelines This document provides guidelines for AI coding agents working in the labhelper repository. ## Project Overview - **Type**: Django web application (lab inventory management system) - **Python**: 3.13.7 - **Django**: 5.2.9 - **Database**: SQLite (development) - **Virtual Environment**: `.venv/` ## Build/Run Commands ### Development Server ```bash python manage.py runserver # Start dev server on port 8000 python manage.py runserver 0.0.0.0:8000 # Bind to all interfaces ``` ### Database Operations ```bash python manage.py makemigrations # Create migration files python manage.py makemigrations boxes # Create migrations for specific app python manage.py migrate # Apply all migrations python manage.py showmigrations # List migration status ``` ### Testing ```bash # Run all tests python manage.py test # Run tests for a specific app python manage.py test boxes # Run a specific test class python manage.py test boxes.tests.TestClassName # Run a single test method python manage.py test boxes.tests.TestClassName.test_method_name # Run tests with verbosity python manage.py test -v 2 # Run tests with coverage coverage run manage.py test coverage report coverage html # Generate HTML report ``` ### Django Shell ```bash python manage.py shell # Interactive Django shell python manage.py createsuperuser # Create admin user python manage.py collectstatic # Collect static files ``` ### Production ```bash gunicorn labhelper.wsgi:application # Run with Gunicorn ``` ### Custom Management Commands ```bash # Create default users and groups python manage.py create_default_users # Clean up orphaned files from deleted things python manage.py clean_orphaned_files python manage.py clean_orphaned_files --dry-run # Clean up orphaned images and thumbnails python manage.py clean_orphaned_images python manage.py clean_orphaned_images --dry-run ``` ## Code Style Guidelines ### Python Style - Follow PEP 8 conventions - Use 4-space indentation (no tabs) - Maximum line length: 79 characters (PEP 8 standard) - Use single quotes for strings: `'string'` - Use double quotes for docstrings: `"""Docstring."""` ### Import Order Organize imports in this order, with blank lines between groups: ```python # 1. Standard library imports import os import sys from pathlib import Path # 2. Django imports from django.db import models from django.contrib import admin from django.shortcuts import render, redirect from django.http import HttpResponse, JsonResponse # 3. Third-party imports import requests from markdown import markdown # 4. Local application imports from .models import MyModel from .forms import MyForm ``` ### Naming Conventions | Type | Convention | Example | |------|------------|---------| | Modules | lowercase_with_underscores | `user_profile.py` | | Classes | PascalCase | `UserProfile` | | Functions | lowercase_with_underscores | `get_user_data()` | | Constants | UPPERCASE_WITH_UNDERSCORES | `MAX_CONNECTIONS` | | Variables | lowercase_with_underscores | `user_count` | | Django Models | PascalCase (singular) | `Box`, `UserProfile` | | Django Apps | lowercase (short) | `boxes`, `users` | ### Django-Specific Conventions **Models:** ```python from django.db import models class Box(models.Model): """A storage box in the lab.""" name = models.CharField(max_length=255) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name_plural = 'boxes' ordering = ['-created_at'] def __str__(self): return self.name ``` **Views:** ```python from django.shortcuts import render, get_object_or_404 from django.http import Http404 def box_detail(request, box_id): """Display details for a specific box.""" box = get_object_or_404(Box, pk=box_id) return render(request, 'boxes/detail.html', {'box': box}) ``` ### Error Handling ```python # Use specific exceptions try: result = some_operation() except SpecificError as exc: raise CustomError('Descriptive message') from exc # Django: Use get_object_or_404 for model lookups box = get_object_or_404(Box, pk=box_id) # Log errors appropriately import logging logger = logging.getLogger(__name__) logger.error('Error message: %s', error_detail) ``` ### Type Hints (Recommended) ```python from typing import Optional from django.http import HttpRequest, HttpResponse def get_box(request: HttpRequest, box_id: int) -> HttpResponse: """Retrieve a box by ID.""" ... ``` ## Project Structure ``` labhelper/ ├── .gitea/ │ └── workflows/ │ └── build-containers-on-demand.yml # CI/CD workflow ├── argocd/ # Kubernetes deployment manifests │ ├── 001_pvc.yaml # PersistentVolumeClaim │ ├── deployment.yaml # Deployment + Service │ ├── ingress.yaml # Traefik ingress │ ├── nfs-pv.yaml # NFS PersistentVolume │ ├── nfs-storageclass.yaml # NFS StorageClass │ └── secret.yaml # Django secret key template ├── boxes/ # Main Django app │ ├── management/ │ │ └── commands/ │ │ ├── clean_orphaned_files.py # Cleanup orphaned ThingFile attachments │ │ └── clean_orphaned_images.py # Cleanup orphaned Thing images │ ├── migrations/ # Database migrations │ ├── templates/ │ │ └── boxes/ │ │ ├── add_things.html # Form to add multiple things │ │ ├── box_detail.html # Box contents view │ │ ├── box_management.html # Box/BoxType CRUD management │ │ ├── edit_thing.html # Edit thing page (name, description, picture, tags, files, links) │ │ ├── index.html # Home page with boxes and tags │ │ ├── search.html # Search page with AJAX │ │ └── thing_detail.html # Read-only thing details view │ ├── templatetags/ │ │ └── dict_extras.py # Custom template filters: get_item, render_markdown, truncate_markdown │ ├── admin.py # Admin configuration │ ├── apps.py # App configuration │ ├── forms.py # All forms and formsets │ ├── models.py # Data models │ ├── tests.py # Test cases │ └── views.py # View functions ├── data-loader/ # Init container for database preload │ ├── Dockerfile # Alpine-based init container │ └── preload.sqlite3 # Preloaded database for deployment ├── labhelper/ # Project configuration │ ├── management/ │ │ └── commands/ │ │ └── create_default_users.py # Create default users/groups │ ├── templates/ │ │ ├── base.html # Base template with navigation │ │ └── login.html # Login page │ ├── asgi.py # ASGI configuration │ ├── settings.py # Django settings │ ├── urls.py # Root URL configuration │ └── wsgi.py # WSGI configuration ├── scripts/ │ ├── deploy_secret.sh # Generate and deploy Django secret │ ├── full_deploy.sh # Bump both container versions + copy DB │ └── partial_deploy.sh # Bump main container version only ├── .gitignore ├── AGENTS.md # This file ├── Dockerfile # Multi-stage build for main container ├── manage.py # Django CLI entry point └── requirements.txt # Python dependencies ``` ## Data Models ### boxes app | Model | Description | Key Fields | |-------|-------------|------------| | **BoxType** | Type of storage box with dimensions | `name`, `width`, `height`, `length` (in mm) | | **Box** | A storage box in the lab | `id` (CharField PK, max 10), `box_type` (FK) | | **Facet** | A category of tags (e.g., Priority, Category) | `name`, `slug`, `color`, `cardinality` (single/multiple) | | **Tag** | A tag value for a specific facet | `facet` (FK), `name` | | **Thing** | An item stored in a box | `name`, `box` (FK), `description` (Markdown), `picture`, `tags` (M2M) | | **ThingFile** | File attachment for a Thing | `thing` (FK), `file`, `title`, `uploaded_at` | | **ThingLink** | Hyperlink for a Thing | `thing` (FK), `url`, `title`, `uploaded_at` | **Model Relationships:** - BoxType -> Box (1:N via `boxes` related_name, PROTECT on delete) - Box -> Thing (1:N via `things` related_name, PROTECT on delete) - Facet -> Tag (1:N via `tags` related_name, CASCADE on delete) - Thing <-> Tag (M2M via `tags` related_name on Thing, `things` related_name on Tag) - Thing -> ThingFile (1:N via `files` related_name, CASCADE on delete) - Thing -> ThingLink (1:N via `links` related_name, CASCADE on delete) **Facet Cardinality:** - `single`: A thing can have at most one tag from this facet (e.g., Priority: High/Medium/Low) - `multiple`: A thing can have multiple tags from this facet (e.g., Category: Electronics, Tools) ## Available Django Extensions The project includes these pre-installed packages: - **django-mptt**: Tree structures (categories, hierarchies) - **django-mptt-admin**: Admin interface for MPTT models - **django-admin-sortable2**: Drag-and-drop ordering in admin - **django-nested-admin**: Nested inline forms in admin - **django-nested-inline**: Additional nested inline support - **django-revproxy**: Reverse proxy functionality - **sorl-thumbnail**: Image thumbnailing - **Pillow**: Image processing - **gunicorn**: Production WSGI server - **Markdown**: Markdown processing - **bleach**: HTML sanitization - **coverage**: Test coverage - **Font Awesome**: Icon library (loaded via CDN) - **jQuery**: JavaScript library (loaded via CDN) ## Frontend/CSS Guidelines ### Base Template The project uses a base template system at `labhelper/templates/base.html`. All page templates should extend this base: ```django {% extends "base.html" %} {% block title %}Page Title - LabHelper{% endblock %} {% block page_header %} {% endblock %} {% block content %} {% endblock %} {% block extra_css %}{% endblock %} {% block extra_js %}{% endblock %} ``` ### Design System **Color Scheme:** - Primary gradient: `#667eea` (purple) to `#764ba2` (purple-blue) - Success: Green gradient - Error: Red gradient - Background: Light gray `#f5f5f5` with gradient overlays - Cards: White with subtle shadows **Components:** - **Navigation**: Glassmorphism effect with blur backdrop - **Buttons**: Gradient backgrounds with hover lift effect - **Cards**: White with rounded corners and box shadows - **Tables**: Gradient headers with hover row effects - **Alerts**: Gradient backgrounds with icons - **Form Inputs**: Focused states with color transitions **Typography:** - System fonts: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif` - Headings: Bold, colored - Body: Regular, dark gray **Icons:** - Font Awesome 6.5.1 (CDN) - Use semantic icons for actions - Color: Match context or inherit from parent **Responsive Design:** - Mobile-first approach - Grid layouts with `repeat(auto-fill, minmax(250px, 1fr))` - Flexbox for component layouts - Breakpoints handled by grid and flex-wrap - **Navigation**: Responsive navbar with hamburger menu on mobile (≤768px) and full horizontal menu on desktop (≥769px) ### CSS Guidelines **Naming:** - Use descriptive class names - BEM pattern encouraged for complex components - Inline styles allowed for template-specific styling **Styles:** - Use base template styles when possible - Template-specific styles in `{% block extra_css %}` - JavaScript in `{% block extra_js %}` - Smooth transitions (0.2s - 0.3s) - Hover effects with transform and box-shadow **jQuery Usage:** - Loaded in base template - Use for interactive elements (toggles, hovers) - Event delegation for dynamically added elements - Focus/blur events for form inputs ### Available Pages/Views | View Function | URL Pattern | Name | Description | |---------------|-------------|------|-------------| | `index` | `/` | `index` | Home page with boxes grid and tags overview | | `box_management` | `/box-management/` | `box_management` | Manage boxes and box types | | `add_box_type` | `/box-type/add/` | `add_box_type` | Add new box type | | `edit_box_type` | `/box-type//edit/` | `edit_box_type` | Edit box type | | `delete_box_type` | `/box-type//delete/` | `delete_box_type` | Delete box type | | `add_box` | `/box/add/` | `add_box` | Add new box | | `edit_box` | `/box//edit/` | `edit_box` | Edit box | | `delete_box` | `/box//delete/` | `delete_box` | Delete box | | `box_detail` | `/box//` | `box_detail` | View box contents | | `thing_detail` | `/thing//` | `thing_detail` | Read-only view of thing details | | `edit_thing` | `/thing//edit/` | `edit_thing` | Edit thing (name, description, picture, tags, files, links, move) | | `add_things` | `/box//add/` | `add_things` | Add multiple things to a box | | `search` | `/search/` | `search` | Search page | | `search_api` | `/search/api/` | `search_api` | AJAX search endpoint | | `LoginView` | `/login/` | `login` | Django auth login | | `LogoutView` | `/logout/` | `logout` | Django auth logout | | `admin.site` | `/admin/` | - | Django admin | **All views except login require authentication via `@login_required`.** ### Template Best Practices 1. **Always extend base template** ```django {% extends "base.html" %} ``` 2. **Use block system for content injection** - `title`: Page title tag - `page_header`: Page header with breadcrumbs - `content`: Main page content - `extra_css`: Additional styles - `extra_js`: Additional JavaScript 3. **Load required template tags** ```django {% load static %} {% load mptt_tags %} {% load thumbnail %} {% load dict_extras %} ``` 4. **Use URL names for links** ```django ``` 5. **Use icons with Font Awesome** ```django ``` 6. **Add breadcrumbs for navigation** ```django ``` ### Markdown Support The `Thing.description` field supports Markdown formatting with HTML sanitization for security. **Available Template Filters:** - `render_markdown`: Converts Markdown text to sanitized HTML with automatic link handling - Converts Markdown syntax (headers, lists, bold, italic, links, code, tables, etc.) - Sanitizes HTML using `bleach` to prevent XSS attacks - Automatically adds `target="_blank"` and `rel="noopener noreferrer"` to external links - Use in `thing_detail.html` for full rendered Markdown - `truncate_markdown`: Converts Markdown to plain text and truncates - Strips HTML tags after Markdown conversion - Adds ellipsis (`...`) if text exceeds specified length (default: 100) - Use in `box_detail.html` or search API previews where space is limited **Usage Examples:** ```django
{{ thing.description|render_markdown }}
{{ thing.description|truncate_markdown:100 }} ``` **Supported Markdown Features:** - Bold: `**text**` or `__text__` - Italic: `*text*` or `_text_` - Headers: `# Header 1`, `## Header 2`, etc. - Lists: `- item` or `1. item` - Links: `[text](url)` - Code: `` `code` `` or ` ```code block``` - Blockquotes: `> quote` - Tables: `| A | B |\n|---|---|` **Security:** - All Markdown is sanitized before rendering - Dangerous HTML tags (`