Autodiscovery annotations added to Ingress
This commit is contained in:
606
AGENTS.md
606
AGENTS.md
@@ -1,606 +0,0 @@
|
||||
# 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
|
||||
│ ├── static/
|
||||
│ │ └── css/
|
||||
│ │ ├── base.css # Base styles (layout, navbar, buttons, alerts, etc.)
|
||||
│ │ ├── edit_thing.css # Edit thing form styles
|
||||
│ │ └── thing_detail.css # Markdown content + lightbox styles
|
||||
│ ├── templates/
|
||||
│ │ └── boxes/
|
||||
│ │ ├── add_things.html # Form to add multiple things
|
||||
│ │ ├── box_detail.html # Box contents view
|
||||
│ │ ├── box_management.html # Box/BoxType CRUD management
|
||||
│ │ ├── boxes_list.html # Boxes list page with tabular view
|
||||
│ │ ├── edit_thing.html # Edit thing page (name, description, picture, tags, files, links)
|
||||
│ │ ├── index.html # Home page with search and tags
|
||||
│ │ ├── resources_list.html # List all links and files from things
|
||||
│ │ └── 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 %}
|
||||
<!-- 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. Desktop (≥769px) shows horizontal menu with dropdown for authenticated user (contains Box Management, Resources, Admin, Logout)
|
||||
- **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 horizontal menu with user dropdown on desktop (≥769px). Mobile keeps all items in the dropdown list
|
||||
|
||||
### CSS Guidelines
|
||||
|
||||
**Static CSS Files:**
|
||||
- Base styles live in `boxes/static/css/base.css` (loaded by `base.html` via `{% static %}`)
|
||||
- Page-specific styles live in separate static CSS files (e.g., `thing_detail.css`, `edit_thing.css`)
|
||||
- Child templates load their CSS via `{% block extra_css %}` with `<link>` tags
|
||||
- WhiteNoise serves and cache-busts static files via `CompressedManifestStaticFilesStorage`
|
||||
- Run `python manage.py collectstatic` after adding or modifying static CSS files
|
||||
|
||||
**Naming:**
|
||||
- Use descriptive class names
|
||||
- BEM pattern encouraged for complex components
|
||||
- Inline styles allowed for template-specific one-off styling
|
||||
|
||||
**Styles:**
|
||||
- Use base CSS classes when possible
|
||||
- Page-specific styles in dedicated static CSS files loaded via `{% 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 search and tags overview |
|
||||
| `boxes_list` | `/search/` | `search`, `boxes_list` | Boxes list page with tabular view |
|
||||
| `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` | Read-only view of thing details |
|
||||
| `edit_thing` | `/thing/<int:thing_id>/edit/` | `edit_thing` | Edit thing (name, description, picture, tags, files, links, move) |
|
||||
| `add_things` | `/box/<str:box_id>/add/` | `add_things` | Add multiple things to a box |
|
||||
| `search_api` | `/search/api/` | `search_api` | AJAX search endpoint |
|
||||
| `resources_list` | `/resources/` | `resources_list` | List all links and files from things (sorted by thing name) |
|
||||
| `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 CSS via `<link>` tags to static files
|
||||
- `extra_head`: Additional head elements
|
||||
- `extra_js`: Additional JavaScript
|
||||
|
||||
3. **Load required template tags**
|
||||
```django
|
||||
{% load static %} {# Required when using {% static %} for CSS/asset links #}
|
||||
{% load mptt_tags %}
|
||||
{% load thumbnail %}
|
||||
{% load dict_extras %}
|
||||
```
|
||||
|
||||
4. **Link page-specific CSS from static files**
|
||||
```django
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/thing_detail.css' %}">
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
5. **Use URL names for links**
|
||||
```django
|
||||
<a href="{% url 'box_detail' box.id %}">
|
||||
```
|
||||
|
||||
6. **Use icons with Font Awesome**
|
||||
```django
|
||||
<i class="fas fa-box"></i>
|
||||
```
|
||||
|
||||
7. **Add breadcrumbs for navigation**
|
||||
```django
|
||||
<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>
|
||||
```
|
||||
|
||||
8. **Icon alignment in lists**: When using icons in list items, use fixed width containers to ensure proper alignment
|
||||
```django
|
||||
<a href="{% url 'thing_detail' thing.id %}" style="display: inline-block; width: 20px; text-align: center;">
|
||||
<i class="fas fa-link"></i>
|
||||
</a>
|
||||
```
|
||||
|
||||
### 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
|
||||
<!-- 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 managed separately |
|
||||
| `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
|
||||
|
||||
```python
|
||||
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
|
||||
8. **Be careful with process management**: Avoid blanket kills on ports (e.g., `lsof -ti:8000 | xargs kill -9`) as they can kill unintended processes like web browsers. Use specific process kills instead: `pkill -f "process_name"`
|
||||
|
||||
## 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:
|
||||
```bash
|
||||
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.
|
||||
272
AGENTS.md.backup
272
AGENTS.md.backup
@@ -1,272 +0,0 @@
|
||||
# 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
|
||||
- **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
|
||||
```
|
||||
|
||||
## 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/
|
||||
├── manage.py # Django CLI entry point
|
||||
├── requirements.txt # Python dependencies
|
||||
├── labhelper/ # Project configuration
|
||||
│ ├── settings.py # Django settings
|
||||
│ ├── urls.py # Root URL routing
|
||||
│ ├── wsgi.py # WSGI application
|
||||
│ └── asgi.py # ASGI application
|
||||
└── boxes/ # Django app
|
||||
├── admin.py # Admin configuration
|
||||
├── apps.py # App configuration
|
||||
├── models.py # Data models
|
||||
├── views.py # View functions
|
||||
├── tests.py # Test cases
|
||||
├── migrations/ # Database migrations
|
||||
└── templates/ # HTML templates
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
```python
|
||||
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. **Always activate venv**: `source .venv/bin/activate`
|
||||
2. **Run migrations after model changes**: `makemigrations` then `migrate`
|
||||
3. **Add new apps to INSTALLED_APPS** in `settings.py`
|
||||
4. **Use get_object_or_404** instead of bare `.get()` calls
|
||||
5. **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:
|
||||
```bash
|
||||
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.
|
||||
@@ -5,6 +5,13 @@ metadata:
|
||||
namespace: labhelper
|
||||
annotations:
|
||||
argocd.argoproj.io/ignore-healthcheck: "true"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "Labhelper"
|
||||
gethomepage.dev/description: "Laboratory inventory system"
|
||||
gethomepage.dev/group: "Kubernetes"
|
||||
gethomepage.dev/icon: "shield.png"
|
||||
gethomepage.dev/href: "https://labhelper.adebaumann.com"
|
||||
gethomepage.dev/ping: "https://labhelper.adebaumann.com"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
|
||||
Reference in New Issue
Block a user