20 KiB
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
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
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
# 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
python manage.py shell # Interactive Django shell
python manage.py createsuperuser # Create admin user
python manage.py collectstatic # Collect static files
Production
gunicorn labhelper.wsgi:application # Run with Gunicorn
Custom Management Commands
# 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:
# 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:
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:
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
# 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)
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
│ │ ├── index.html # Home page with boxes and tags
│ │ ├── search.html # Search page with AJAX
│ │ └── thing_detail.html # Thing details view with tags
│ ├── 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
boxesrelated_name, PROTECT on delete) - Box -> Thing (1:N via
thingsrelated_name, PROTECT on delete) - Facet -> Tag (1:N via
tagsrelated_name, CASCADE on delete) - Thing <-> Tag (M2M via
tagsrelated_name on Thing,thingsrelated_name on Tag) - Thing -> ThingFile (1:N via
filesrelated_name, CASCADE on delete) - Thing -> ThingLink (1:N via
linksrelated_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:
{% extends "base.html" %}
{% block title %}Page Title - LabHelper{% endblock %}
{% block page_header %}
<!-- Optional page header with breadcrumbs -->
{% endblock %}
{% block content %}
<!-- Main page 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
#f5f5f5with 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
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/<int:type_id>/edit/ |
edit_box_type |
Edit box type |
delete_box_type |
/box-type/<int:type_id>/delete/ |
delete_box_type |
Delete box type |
add_box |
/box/add/ |
add_box |
Add new box |
edit_box |
/box/<str:box_id>/edit/ |
edit_box |
Edit box |
delete_box |
/box/<str:box_id>/delete/ |
delete_box |
Delete box |
box_detail |
/box/<str:box_id>/ |
box_detail |
View box contents |
thing_detail |
/thing/<int:thing_id>/ |
thing_detail |
View/edit thing (move, picture, files, links, tags) |
add_things |
/box/<str:box_id>/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
-
Always extend base template
{% extends "base.html" %} -
Use block system for content injection
title: Page title tagpage_header: Page header with breadcrumbscontent: Main page contentextra_css: Additional stylesextra_js: Additional JavaScript
-
Load required template tags
{% load static %} {% load mptt_tags %} {% load thumbnail %} {% load dict_extras %} -
Use URL names for links
<a href="{% url 'box_detail' box.id %}"> -
Use icons with Font Awesome
<i class="fas fa-box"></i> -
Add breadcrumbs for navigation
<p class="breadcrumb"> <a href="/"><i class="fas fa-home"></i> Home</a> / <a href="/box/{{ box.id }}/"><i class="fas fa-box"></i> Box {{ box.id }}</a> </p>
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
bleachto prevent XSS attacks - Automatically adds
target="_blank"andrel="noopener noreferrer"to external links - Use in
thing_detail.htmlfor 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.htmlor search API previews where space is limited
Usage Examples:
<!-- Full Markdown rendering -->
<div class="markdown-content">
{{ thing.description|render_markdown }}
</div>
<!-- Truncated plain text preview -->
{{ thing.description|truncate_markdown:100 }}
Supported Markdown Features:
- Bold:
**text**or__text__ - Italic:
*text*or_text_ - Headers:
# Header 1,## Header 2, etc. - Lists:
- itemor1. 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 (
<script>,<iframe>, etc.) are stripped - Only safe HTML tags and attributes are allowed
- External links automatically get
target="_blank"and security attributes
Forms
| Form | Model | Purpose |
|---|---|---|
ThingForm |
Thing | Add/edit a thing (name, description, picture, tags) |
ThingPictureForm |
Thing | Upload/change thing picture only |
ThingFileForm |
ThingFile | Add file attachment |
ThingLinkForm |
ThingLink | Add link |
BoxTypeForm |
BoxType | Add/edit box type |
BoxForm |
Box | Add/edit box |
ThingFormSet |
Thing | Formset for adding multiple things |
Management Commands
boxes app
| Command | Description | Options |
|---|---|---|
clean_orphaned_files |
Clean up orphaned files from deleted things | --dry-run |
clean_orphaned_images |
Clean up orphaned images and thumbnails | --dry-run |
labhelper project
| Command | Description |
|---|---|
create_default_users |
Create default users and groups (admin/admin123, staff/staff123, viewer/viewer123) |
Testing Guidelines
- Use
django.test.TestCasefor database tests - Use
django.test.SimpleTestCasefor tests without database - Name test files
test_*.pyor*_tests.py - Name test methods
test_* - Use descriptive test method names
from django.test import TestCase
from .models import Box
class BoxModelTests(TestCase):
"""Tests for the Box model."""
def setUp(self):
"""Set up test fixtures."""
self.box = Box.objects.create(name='Test Box')
def test_box_str_returns_name(self):
"""Box __str__ should return the box name."""
self.assertEqual(str(self.box), 'Test Box')
Files to Never Commit
Per .gitignore:
__pycache__/,*.pyc- Python bytecode.venv/- Virtual environment.env- Environment variablesdata/db.sqlite3- Database filekeys/- Secret keys
Common Pitfalls
- NEVER commit or push without explicit permission: Always ask the user before running
git commitorgit push. The user will explicitly say "commit and push" when they want you to do this. Do NOT automatically commit/push after making changes unless instructed to do so. - Always activate venv:
source .venv/bin/activate - Run migrations after model changes:
makemigrationsthenmigrate - Add new apps to INSTALLED_APPS in
settings.py - Templates in labhelper/templates/: The base template and shared templates are in
labhelper/templates/. App-specific templates remain inapp_name/templates/. - Use get_object_or_404 instead of bare
.get()calls - Never commit SECRET_KEY - use environment variables in production
Deployment Commands
Prepare a Full Deployment
When instructed to "Prepare a full deployment", perform the following steps:
-
Bump container versions: In
argocd/deployment.yaml, increment the version numbers by 0.001 for both containers:labhelper-data-loader(initContainer)labhelper(main container)
-
Copy database: Copy the current development database to the data-loader preload location:
cp data/db.sqlite3 data-loader/preload.sqlite3
Prepare a Partial Deployment
When instructed to "Prepare a partial deployment", perform the following step:
- Bump main container version only: In
argocd/deployment.yaml, increment the version number by 0.001 for the main container only:labhelper(main container)
Do NOT bump the data-loader version or copy the database.