Files
labhelper/AGENTS.md
Adrian A. Baumann ca50832b54
All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 1m44s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 6s
Markdown support for description fields added; Tests updated
2026-01-05 11:00:16 +01:00

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)
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 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:

{% 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 #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

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

  1. Always extend base template

    {% 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

    {% load static %}
    {% load mptt_tags %}
    {% load thumbnail %}
    {% load dict_extras %}
    
  4. Use URL names for links

    <a href="{% url 'box_detail' box.id %}">
    
  5. Use icons with Font Awesome

    <i class="fas fa-box"></i>
    
  6. 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 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:

<!-- 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: - 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 (<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.TestCase for database tests
  • Use django.test.SimpleTestCase for tests without database
  • Name test files test_*.py or *_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 variables
  • data/db.sqlite3 - Database file
  • keys/ - Secret keys

Common Pitfalls

  1. NEVER commit or push without explicit permission: Always ask the user before running git commit or git 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.
  2. Always activate venv: source .venv/bin/activate
  3. Run migrations after model changes: makemigrations then migrate
  4. Add new apps to INSTALLED_APPS in settings.py
  5. Templates in labhelper/templates/: The base template and shared templates are in labhelper/templates/. App-specific templates remain in app_name/templates/.
  6. Use get_object_or_404 instead of bare .get() calls
  7. 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:

  1. Bump container versions: In argocd/deployment.yaml, increment the version numbers by 0.001 for both containers:

    • labhelper-data-loader (initContainer)
    • labhelper (main container)
  2. 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:

  1. 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.