|
| 1 | +# Agent Instructions for django-content-editor |
| 2 | + |
| 3 | +This document provides guidance for AI coding agents working on the django-content-editor codebase. |
| 4 | + |
| 5 | +## Repository Overview |
| 6 | + |
| 7 | +django-content-editor is a Django library for editing structured content in the admin interface. It extends Django's inlines mechanism to manage heterogeneous collections of content blocks (plugins) organized into regions, commonly used for CMS-style content editing. |
| 8 | + |
| 9 | +**Key directories:** |
| 10 | +- `content_editor/` - Main package code |
| 11 | +- `tests/testapp/` - Test application and test suite |
| 12 | +- `docs/` - Sphinx documentation (reStructuredText) |
| 13 | + |
| 14 | +**Core modules:** |
| 15 | +- `admin.py` - ContentEditor, ContentEditorInline, and admin check classes |
| 16 | +- `models.py` - PluginBase model and Region class |
| 17 | +- `contents.py` - Contents class and helpers for fetching/organizing content |
| 18 | +- `checks.py` - Django system checks |
| 19 | + |
| 20 | +## Development Workflow |
| 21 | + |
| 22 | +### Before Making Changes |
| 23 | + |
| 24 | +1. **Always read files before editing them** - Use the Read tool to understand existing code structure |
| 25 | +2. **Check for existing tests** - Look in `tests/testapp/test_*.py` for related test coverage |
| 26 | +3. **Review system checks** - Changes to admin classes should consider the checks in `checks.py` |
| 27 | +4. **Check documentation** - Related docs may need updates in `docs/` |
| 28 | + |
| 29 | +### Testing Requirements |
| 30 | + |
| 31 | +**Run tests through tox:** |
| 32 | +```bash |
| 33 | +tox -e py312-dj52 # Run specific Python/Django version |
| 34 | +tox -l # List available test environments |
| 35 | +``` |
| 36 | + |
| 37 | +**Test suite structure:** |
| 38 | +- `test_content_editor.py` - Basic ContentEditor functionality |
| 39 | +- `test_contents.py` - Contents class and helpers |
| 40 | +- `test_checks.py` - Django system checks |
| 41 | +- `test_playwright.py` - Browser-based integration tests |
| 42 | +- `test_playwright_helpers.py` - Playwright utilities |
| 43 | + |
| 44 | +**Important testing practices:** |
| 45 | +- Write tests for new functionality in appropriate test files |
| 46 | +- Place imports at the top of test files, not inside test methods |
| 47 | +- Playwright tests require Chromium installation (handled by tox) |
| 48 | +- Tests must pass before changes are considered complete |
| 49 | +- Integration tests use pytest-playwright and test real browser interactions |
| 50 | + |
| 51 | +### Code Style |
| 52 | + |
| 53 | +- Follow existing code style (project uses pre-commit hooks with ruff and biome) |
| 54 | +- Run `prek` to execute pre-commit hooks before committing |
| 55 | +- Keep code minimal and focused - avoid over-engineering |
| 56 | +- Prefer editing existing files over creating new ones |
| 57 | +- Don't add comments, docstrings, or type annotations to unchanged code |
| 58 | +- Only add error handling where truly necessary (at boundaries) |
| 59 | + |
| 60 | +## Architecture Considerations |
| 61 | + |
| 62 | +### Plugin System |
| 63 | + |
| 64 | +**PluginBase model:** |
| 65 | +- Abstract base class for all content plugins |
| 66 | +- Provides `parent`, `region`, and `ordering` fields |
| 67 | +- Plugins are typically defined per-project (not in this library) |
| 68 | + |
| 69 | +**ContentEditorInline:** |
| 70 | +- Specialized StackedInline for plugins |
| 71 | +- Used as a marker to differentiate plugins from regular inlines |
| 72 | +- Can restrict plugins to specific regions using `regions` attribute |
| 73 | +- Supports customization via `icon`, `color`, `button` attributes |
| 74 | + |
| 75 | +**Important patterns:** |
| 76 | +- ContentEditor identifies plugins by checking `isinstance(inline, ContentEditorInline)` |
| 77 | +- The `.create()` classmethod dynamically creates inline classes |
| 78 | +- Plugins are rendered in regions using drag-and-drop interface |
| 79 | + |
| 80 | +### Region System |
| 81 | + |
| 82 | +**Regions organize content:** |
| 83 | +- Defined as a `regions` attribute/property on the model |
| 84 | +- Must return a list of `Region` instances |
| 85 | +- Each region has `key`, `title`, and optional `inherited` attributes |
| 86 | +- Region keys must be valid Python identifiers |
| 87 | + |
| 88 | +**Contents class:** |
| 89 | +- Groups content blocks by region |
| 90 | +- Supports inheritance (empty regions can inherit from parent instances) |
| 91 | +- Access content via attribute (`contents.main`) or subscription (`contents["main"]`) |
| 92 | +- Unknown regions go to `_unknown_region_contents` |
| 93 | + |
| 94 | +### System Checks |
| 95 | + |
| 96 | +The library provides four system checks: |
| 97 | +- `content_editor.E001` - Missing region/ordering in fieldsets |
| 98 | +- `content_editor.E002` - Missing regions attribute on model |
| 99 | +- `content_editor.E003` - Regions not iterable |
| 100 | +- `content_editor.I001` - Non-abstract base classes warning |
| 101 | + |
| 102 | +**Check implementation:** |
| 103 | +- Admin checks use custom `checks_class` on admin classes |
| 104 | +- Model checks use `@register()` decorator |
| 105 | +- All checks documented in `docs/checks.rst` |
| 106 | + |
| 107 | +## Documentation Practices |
| 108 | + |
| 109 | +### Sphinx Documentation Structure |
| 110 | + |
| 111 | +Documentation is in `docs/` using reStructuredText: |
| 112 | +- `index.rst` - Main entry with toctree |
| 113 | +- `installation.rst` - Installation instructions |
| 114 | +- `quickstart.rst` - Getting started guide |
| 115 | +- `admin-classes.rst` - ContentEditor, ContentEditorInline, RefinedModelAdmin |
| 116 | +- `contents.rst` - Contents class and regions |
| 117 | +- `checks.rst` - System checks reference |
| 118 | +- `design-decisions.rst` - Architecture explanations |
| 119 | +- `changelog.rst` - Links to CHANGELOG.rst |
| 120 | + |
| 121 | +### When Adding Documentation |
| 122 | + |
| 123 | +1. Determine appropriate .rst file (or create new one if needed) |
| 124 | +2. Keep documentation concise and practical |
| 125 | +3. Use code-block directives with Python syntax highlighting |
| 126 | +4. Add new files to index.rst toctree |
| 127 | +5. Follow existing RST formatting conventions |
| 128 | +6. Assume readers have Django knowledge |
| 129 | + |
| 130 | +### README.rst |
| 131 | + |
| 132 | +Keep the README minimal - it just points to Read the Docs. Full documentation lives in `docs/`. |
| 133 | + |
| 134 | +## Common Patterns |
| 135 | + |
| 136 | +### Creating Plugin Inlines |
| 137 | + |
| 138 | +Use the `.create()` classmethod for convenience: |
| 139 | +```python |
| 140 | +ContentEditorInline.create( |
| 141 | + model=MyPlugin, |
| 142 | + icon="description", # Material icon |
| 143 | + color="oklch(0.5 0.2 330)", # Custom color |
| 144 | + regions={"main"}, # Restrict to regions |
| 145 | +) |
| 146 | +``` |
| 147 | + |
| 148 | +### Region Restrictions |
| 149 | + |
| 150 | +Two helper functions for restricting plugins to regions: |
| 151 | +- `allow_regions({"main", "sidebar"})` - Only allow these regions |
| 152 | +- `deny_regions({"footer"})` - Allow all except these regions |
| 153 | + |
| 154 | +The `deny_regions` helper returns a callable that computes allowed regions dynamically. |
| 155 | + |
| 156 | +### Contents Helpers |
| 157 | + |
| 158 | +Prefer using helper functions over instantiating `Contents` directly: |
| 159 | +```python |
| 160 | +# Single item |
| 161 | +contents = contents_for_item(article, plugins=[RichText, Download]) |
| 162 | + |
| 163 | +# Multiple items (with batching) |
| 164 | +contents = contents_for_items(articles, plugins=[RichText, Download]) |
| 165 | + |
| 166 | +# With inheritance |
| 167 | +contents = contents_for_item( |
| 168 | + page, |
| 169 | + plugins=[RichText], |
| 170 | + inherit_from=page.ancestors().reverse(), |
| 171 | +) |
| 172 | +``` |
| 173 | + |
| 174 | +### Field Visibility |
| 175 | + |
| 176 | +ContentEditorInline automatically hides `region` and `ordering` fields using `HiddenInput` widget. These fields must exist in fieldsets but won't be visible to users. |
| 177 | + |
| 178 | +## Git and Version Control |
| 179 | + |
| 180 | +- Repository is at `github.com/matthiask/django-content-editor` |
| 181 | +- Follow conventional commit messages |
| 182 | +- Don't commit unless explicitly requested |
| 183 | +- Never use `--no-verify` or skip hooks |
| 184 | +- Stage specific files by name (avoid `git add -A`) |
| 185 | +- Watch for sensitive files (.env, credentials) before staging |
| 186 | + |
| 187 | +## File Organization |
| 188 | + |
| 189 | +**Don't create unnecessary files:** |
| 190 | +- No new markdown/documentation files without explicit request |
| 191 | +- Don't create helper utilities for one-time operations |
| 192 | +- Don't add configuration for hypothetical future needs |
| 193 | + |
| 194 | +**Static files:** |
| 195 | +- Admin CSS in `content_editor/static/content_editor/` |
| 196 | +- Admin JavaScript in same directory |
| 197 | +- Material icons CSS included |
| 198 | + |
| 199 | +**Translations:** |
| 200 | +- Locale files in `content_editor/locale/` |
| 201 | +- Use Django's translation functions (`gettext`, `gettext_lazy`) |
| 202 | + |
| 203 | +## When Stuck |
| 204 | + |
| 205 | +If you need to understand complex behavior: |
| 206 | +1. Read the test files - they demonstrate real usage patterns |
| 207 | +2. Check `admin.py` for the ContentEditor implementation |
| 208 | +3. Look at `contents.py` for the Contents class and helpers |
| 209 | +4. Review `models.py` for PluginBase and Region |
| 210 | +5. Check Playwright tests for browser interaction behavior |
| 211 | +6. Look at `docs/` for architectural explanations |
| 212 | + |
| 213 | +## JavaScript and Frontend |
| 214 | + |
| 215 | +The content editor includes significant JavaScript: |
| 216 | +- `content_editor.js` - Main drag-and-drop functionality |
| 217 | +- `save_shortcut.js` - Ctrl+S / Cmd+S shortcuts |
| 218 | +- `tabbed_fieldsets.js` - Tabbed fieldset support |
| 219 | + |
| 220 | +**Context object:** |
| 221 | +- JavaScript receives configuration via JSON script tag |
| 222 | +- Context includes plugins, regions, messages, permissions |
| 223 | +- Generated by `_content_editor_context()` method in admin |
| 224 | + |
| 225 | +## Common Issues |
| 226 | + |
| 227 | +**Plugin not appearing:** |
| 228 | +- Ensure inline uses `ContentEditorInline` (not just `StackedInline`) |
| 229 | +- Check if plugin is restricted to regions the model doesn't have |
| 230 | +- Verify model inherits from `PluginBase` |
| 231 | + |
| 232 | +**Region errors:** |
| 233 | +- Model must have `regions` attribute (not method) |
| 234 | +- Regions must be iterable (list, tuple, set) not string |
| 235 | +- Region keys must be valid Python identifiers |
| 236 | + |
| 237 | +**Fieldset validation:** |
| 238 | +- ContentEditorInline fieldsets must include `region` and `ordering` |
| 239 | +- These fields are automatically hidden (don't add HiddenInput manually) |
| 240 | + |
| 241 | +## References |
| 242 | + |
| 243 | +- Django documentation: https://docs.djangoproject.com/ |
| 244 | +- Read the Docs: https://django-content-editor.readthedocs.io/ |
| 245 | +- Related project FeinCMS: https://github.com/feincms/feincms/ |
| 246 | +- Related project feincms3: https://feincms3.readthedocs.io/ |
0 commit comments