django-admin-deux 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. django_admin_deux-0.1.0/PKG-INFO +584 -0
  2. django_admin_deux-0.1.0/README.md +550 -0
  3. django_admin_deux-0.1.0/djadmin/__init__.py +43 -0
  4. django_admin_deux-0.1.0/djadmin/actions/__init__.py +46 -0
  5. django_admin_deux-0.1.0/djadmin/actions/base.py +634 -0
  6. django_admin_deux-0.1.0/djadmin/actions/dashboard.py +234 -0
  7. django_admin_deux-0.1.0/djadmin/actions/list_view.py +130 -0
  8. django_admin_deux-0.1.0/djadmin/actions/view_mixins.py +350 -0
  9. django_admin_deux-0.1.0/djadmin/apps.py +219 -0
  10. django_admin_deux-0.1.0/djadmin/conf.py +2 -0
  11. django_admin_deux-0.1.0/djadmin/dataclasses.py +541 -0
  12. django_admin_deux-0.1.0/djadmin/decorators.py +35 -0
  13. django_admin_deux-0.1.0/djadmin/factories/__init__.py +5 -0
  14. django_admin_deux-0.1.0/djadmin/factories/base.py +310 -0
  15. django_admin_deux-0.1.0/djadmin/forms.py +246 -0
  16. django_admin_deux-0.1.0/djadmin/layout.py +651 -0
  17. django_admin_deux-0.1.0/djadmin/management/__init__.py +1 -0
  18. django_admin_deux-0.1.0/djadmin/management/commands/__init__.py +1 -0
  19. django_admin_deux-0.1.0/djadmin/management/commands/djadmin_inspect.py +483 -0
  20. django_admin_deux-0.1.0/djadmin/management/commands/djadmin_list_apps.py +65 -0
  21. django_admin_deux-0.1.0/djadmin/options.py +440 -0
  22. django_admin_deux-0.1.0/djadmin/plugins/__init__.py +617 -0
  23. django_admin_deux-0.1.0/djadmin/plugins/core/__init__.py +1 -0
  24. django_admin_deux-0.1.0/djadmin/plugins/core/actions.py +309 -0
  25. django_admin_deux-0.1.0/djadmin/plugins/core/djadmin_hooks.py +382 -0
  26. django_admin_deux-0.1.0/djadmin/plugins/core/mixins.py +469 -0
  27. django_admin_deux-0.1.0/djadmin/plugins/core/utils.py +15 -0
  28. django_admin_deux-0.1.0/djadmin/plugins/modifiers.py +372 -0
  29. django_admin_deux-0.1.0/djadmin/plugins/permissions/__init__.py +25 -0
  30. django_admin_deux-0.1.0/djadmin/plugins/permissions/apps.py +12 -0
  31. django_admin_deux-0.1.0/djadmin/plugins/permissions/djadmin_hooks.py +50 -0
  32. django_admin_deux-0.1.0/djadmin/plugins/permissions/mixins.py +65 -0
  33. django_admin_deux-0.1.0/djadmin/plugins/permissions/operators.py +165 -0
  34. django_admin_deux-0.1.0/djadmin/plugins/permissions/permissions.py +233 -0
  35. django_admin_deux-0.1.0/djadmin/plugins/theme/__init__.py +1 -0
  36. django_admin_deux-0.1.0/djadmin/plugins/theme/apps.py +7 -0
  37. django_admin_deux-0.1.0/djadmin/plugins/theme/djadmin_hooks.py +44 -0
  38. django_admin_deux-0.1.0/djadmin/plugins/theme/static/djadmin/theme/css/theme.css +1 -0
  39. django_admin_deux-0.1.0/djadmin/plugins/theme/static/djadmin/theme/js/admin.js +214 -0
  40. django_admin_deux-0.1.0/djadmin/plugins/theme/templates/djadmin/_navigation.html +50 -0
  41. django_admin_deux-0.1.0/djadmin/plugins/theme/templates/djadmin/app_dashboard.html +48 -0
  42. django_admin_deux-0.1.0/djadmin/plugins/theme/templates/djadmin/project_dashboard.html +57 -0
  43. django_admin_deux-0.1.0/djadmin/plugins/theme/templates/registration/logged_out.html +29 -0
  44. django_admin_deux-0.1.0/djadmin/plugins/theme/templates/registration/login.html +64 -0
  45. django_admin_deux-0.1.0/djadmin/sites.py +281 -0
  46. django_admin_deux-0.1.0/djadmin/templates/djadmin/_navigation.html +37 -0
  47. django_admin_deux-0.1.0/djadmin/templates/djadmin/actions/_detail_item.html +14 -0
  48. django_admin_deux-0.1.0/djadmin/templates/djadmin/actions/add.html +14 -0
  49. django_admin_deux-0.1.0/djadmin/templates/djadmin/actions/confirm_bulk_delete.html +25 -0
  50. django_admin_deux-0.1.0/djadmin/templates/djadmin/actions/confirm_delete.html +20 -0
  51. django_admin_deux-0.1.0/djadmin/templates/djadmin/actions/detail.html +72 -0
  52. django_admin_deux-0.1.0/djadmin/templates/djadmin/actions/edit.html +29 -0
  53. django_admin_deux-0.1.0/djadmin/templates/djadmin/actions/form.html +22 -0
  54. django_admin_deux-0.1.0/djadmin/templates/djadmin/admin_base.html +120 -0
  55. django_admin_deux-0.1.0/djadmin/templates/djadmin/app_dashboard.html +54 -0
  56. django_admin_deux-0.1.0/djadmin/templates/djadmin/base_action.html +15 -0
  57. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/eye.html +4 -0
  58. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/funnel.html +3 -0
  59. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/magnifying-glass.html +3 -0
  60. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/menu.html +3 -0
  61. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/moon.html +4 -0
  62. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/pencil.html +3 -0
  63. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/plus.html +3 -0
  64. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/sort-down.html +3 -0
  65. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/sort-up.html +3 -0
  66. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/sort.html +3 -0
  67. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/sun.html +4 -0
  68. django_admin_deux-0.1.0/djadmin/templates/djadmin/icons/trash.html +3 -0
  69. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/_column_header_icons.html +1 -0
  70. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/_form.html +27 -0
  71. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/_icon.html +2 -0
  72. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/_sidebar.html +10 -0
  73. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/form_collection_warning.html +8 -0
  74. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/form_field.html +20 -0
  75. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/form_fieldset.html +16 -0
  76. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/form_layout.html +8 -0
  77. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/form_row.html +8 -0
  78. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/form_unknown_component.html +8 -0
  79. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/layout_item.html +3 -0
  80. django_admin_deux-0.1.0/djadmin/templates/djadmin/includes/search_widget.html +33 -0
  81. django_admin_deux-0.1.0/djadmin/templates/djadmin/model_list.html +277 -0
  82. django_admin_deux-0.1.0/djadmin/templates/djadmin/project_dashboard.html +56 -0
  83. django_admin_deux-0.1.0/djadmin/templatetags/__init__.py +1 -0
  84. django_admin_deux-0.1.0/djadmin/templatetags/djadmin_layout.py +150 -0
  85. django_admin_deux-0.1.0/djadmin/templatetags/djadmin_tags.py +254 -0
  86. django_admin_deux-0.1.0/djadmin/testing.py +453 -0
  87. django_admin_deux-0.1.0/djadmin/views.py +103 -0
  88. django_admin_deux-0.1.0/django_admin_deux.egg-info/PKG-INFO +584 -0
  89. django_admin_deux-0.1.0/django_admin_deux.egg-info/SOURCES.txt +129 -0
  90. django_admin_deux-0.1.0/django_admin_deux.egg-info/dependency_links.txt +1 -0
  91. django_admin_deux-0.1.0/django_admin_deux.egg-info/requires.txt +17 -0
  92. django_admin_deux-0.1.0/django_admin_deux.egg-info/top_level.txt +1 -0
  93. django_admin_deux-0.1.0/pyproject.toml +139 -0
  94. django_admin_deux-0.1.0/setup.cfg +4 -0
  95. django_admin_deux-0.1.0/tests/test_action_edge_cases.py +188 -0
  96. django_admin_deux-0.1.0/tests/test_action_errors.py +226 -0
  97. django_admin_deux-0.1.0/tests/test_action_filtering.py +463 -0
  98. django_admin_deux-0.1.0/tests/test_action_instantiation.py +169 -0
  99. django_admin_deux-0.1.0/tests/test_action_integration.py +258 -0
  100. django_admin_deux-0.1.0/tests/test_action_permissions.py +101 -0
  101. django_admin_deux-0.1.0/tests/test_action_urls.py +213 -0
  102. django_admin_deux-0.1.0/tests/test_actions_base.py +359 -0
  103. django_admin_deux-0.1.0/tests/test_adminsite_errors.py +125 -0
  104. django_admin_deux-0.1.0/tests/test_breadcrumbs.py +168 -0
  105. django_admin_deux-0.1.0/tests/test_column.py +316 -0
  106. django_admin_deux-0.1.0/tests/test_crud_testing.py +480 -0
  107. django_admin_deux-0.1.0/tests/test_dashboard_edge_cases.py +171 -0
  108. django_admin_deux-0.1.0/tests/test_dashboard_filtering.py +388 -0
  109. django_admin_deux-0.1.0/tests/test_feature_advertising.py +297 -0
  110. django_admin_deux-0.1.0/tests/test_feature_validation.py +171 -0
  111. django_admin_deux-0.1.0/tests/test_form_builder_core.py +531 -0
  112. django_admin_deux-0.1.0/tests/test_integration.py +279 -0
  113. django_admin_deux-0.1.0/tests/test_layout_components.py +675 -0
  114. django_admin_deux-0.1.0/tests/test_layout_display_rendering.py +224 -0
  115. django_admin_deux-0.1.0/tests/test_layout_template_tags.py +225 -0
  116. django_admin_deux-0.1.0/tests/test_layout_validation.py +379 -0
  117. django_admin_deux-0.1.0/tests/test_management_commands.py +180 -0
  118. django_admin_deux-0.1.0/tests/test_metaclass.py +420 -0
  119. django_admin_deux-0.1.0/tests/test_model_admin_permissions.py +122 -0
  120. django_admin_deux-0.1.0/tests/test_modeladmin_edge_cases.py +239 -0
  121. django_admin_deux-0.1.0/tests/test_pagination.py +214 -0
  122. django_admin_deux-0.1.0/tests/test_plugin_apps.py +86 -0
  123. django_admin_deux-0.1.0/tests/test_plugins.py +55 -0
  124. django_admin_deux-0.1.0/tests/test_query_parameter_preservation.py +282 -0
  125. django_admin_deux-0.1.0/tests/test_search.py +334 -0
  126. django_admin_deux-0.1.0/tests/test_sidebar_widgets.py +298 -0
  127. django_admin_deux-0.1.0/tests/test_template_tags.py +88 -0
  128. django_admin_deux-0.1.0/tests/test_urls.py +429 -0
  129. django_admin_deux-0.1.0/tests/test_view_factory.py +356 -0
  130. django_admin_deux-0.1.0/tests/test_viewfactory_errors.py +151 -0
  131. django_admin_deux-0.1.0/tests/test_webshop_plugin.py +178 -0
@@ -0,0 +1,584 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-admin-deux
3
+ Version: 0.1.0
4
+ Summary: A modern, extensible replacement for Django admin
5
+ Author-email: Emma Delescolle <dev@levit.be>
6
+ Project-URL: Repository, https://gitlab.levitnet.be/levit/django-admin-deux/-/tree/main
7
+ Project-URL: Documentation, https://gitlab.levitnet.be/levit/django-admin-deux/-/tree/main/docs
8
+ Project-URL: Homepage, https://gitlab.levitnet.be/levit/django-admin-deux/-/tree/main/docs
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 5.2
12
+ Classifier: Framework :: Django :: 6.0
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: Django>=5.2
20
+ Requires-Dist: djp>=0.1.0
21
+ Provides-Extra: full
22
+ Requires-Dist: djadmin-formset>=0.1.0; extra == "full"
23
+ Requires-Dist: djadmin-filters>=0.1.0; extra == "full"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0; extra == "dev"
26
+ Requires-Dist: pytest-django>=4.0; extra == "dev"
27
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
28
+ Requires-Dist: pytest-factoryboy>=2.6.0; extra == "dev"
29
+ Requires-Dist: factory-boy>=3.3.0; extra == "dev"
30
+ Requires-Dist: faker>=22.0.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.8.0; extra == "dev"
32
+ Requires-Dist: djlint>=1.34.0; extra == "dev"
33
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
34
+
35
+ # django-admin-deux
36
+
37
+ A modern, extensible replacement for Django's admin interface, built on factory patterns and a robust plugin system.
38
+
39
+ [![Python Version](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
40
+ [![Django Version](https://img.shields.io/badge/django-5.2+-green.svg)](https://www.djangoproject.com/)
41
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
42
+
43
+ ## Overview
44
+
45
+ **django-admin-deux** (pronounced "django admin two") is a complete reimagining of Django's admin interface. While maintaining familiar concepts and naming conventions, it provides superior extensibility, reusability, and modern UI/UX through a plugin-first architecture.
46
+
47
+ ### Key Features
48
+
49
+ - **Plugin Architecture**: Built on [djp](https://github.com/simonw/djp), allowing easy extension and customization
50
+ - **Factory Pattern**: Views are generated dynamically, enabling composition over inheritance
51
+ - **Feature Advertising**: Fail-fast validation ensures plugins provide requested features
52
+ - **Modern UI**: Tailwind-based default theme (coming in Milestone 4)
53
+ - **Familiar API**: If you know Django admin, you'll feel right at home
54
+ - **Incremental Adoption**: Can coexist with Django's built-in admin
55
+
56
+ ### Current Status
57
+
58
+ 🎉 **Milestone 5 Phase 2.7 Complete** - Permissions & Authorization System
59
+
60
+ Major milestones completed:
61
+ - ✅ **Milestone 1**: Foundation (Plugin system, AdminSite, URL routing, Feature validation)
62
+ - ✅ **Milestone 2**: Django-Filter Plugin (Filtering, ordering, search via djadmin-filters)
63
+ - ✅ **Milestone 3**: Layout API & Django-Formset Integration (Forms, inline editing via djadmin-formset)
64
+ - ✅ **Milestone 4**: Developer Experience (djadmin_inspect, BaseCRUDTestCase, djadmin_apps)
65
+ - ✅ **Milestone 5 Phase 2.7**: Permissions System (Core permissions, action filtering, ViewRecordAction)
66
+
67
+ **Test Results**: 720 passing tests, 82% coverage
68
+ **Plugins Available**: djadmin-formset, djadmin-filters
69
+ **Django Support**: 5.2, 6.0
70
+ **Python Support**: 3.11, 3.12, 3.13, 3.14
71
+
72
+ ## Quick Start
73
+
74
+ ### Installation
75
+
76
+ ```bash
77
+ # Basic installation
78
+ pip install django-admin-deux
79
+
80
+ # With all plugins (djadmin-formset + djadmin-filters)
81
+ pip install django-admin-deux[full]
82
+
83
+ # With specific plugins
84
+ pip install django-admin-deux[formset] # Just djadmin-formset
85
+ pip install django-admin-deux[filters] # Just djadmin-filters
86
+ ```
87
+
88
+ ### Basic Usage
89
+
90
+ **1. Add to your `INSTALLED_APPS`:**
91
+
92
+ ```python
93
+ # settings.py
94
+ INSTALLED_APPS = [
95
+ # ...
96
+ 'djadmin',
97
+ # ...
98
+ ]
99
+ ```
100
+
101
+ **2. Create a `djadmin.py` file in your app:**
102
+
103
+ ```python
104
+ # myapp/djadmin.py
105
+ from djadmin import ModelAdmin, register, Layout, Field, Fieldset, Row
106
+ from .models import Book
107
+
108
+ @register(Book)
109
+ class BookAdmin(ModelAdmin):
110
+ list_display = ['title', 'author', 'published_date']
111
+ search_fields = ['title', 'author']
112
+ list_filter = ['published_date']
113
+
114
+ # Optional: Customize form layout
115
+ layout = Layout(
116
+ Fieldset('Book Information',
117
+ Field('title'),
118
+ Row(
119
+ Field('author', css_classes=['flex-1', 'pr-2']),
120
+ Field('published_date', css_classes=['flex-1', 'pl-2']),
121
+ ),
122
+ ),
123
+ Fieldset('Content',
124
+ Field('description', widget='textarea', attrs={'rows': 6}),
125
+ ),
126
+ )
127
+ ```
128
+
129
+ **3. Add to your URLs:**
130
+
131
+ ```python
132
+ # urls.py
133
+ from django.urls import path, include
134
+ from djadmin import site
135
+
136
+ urlpatterns = [
137
+ path('admin/', admin.site.urls), # Django's admin (optional)
138
+ path('djadmin/', include((site.urls, 'djadmin'), namespace='djadmin')),
139
+ ]
140
+ ```
141
+
142
+ **4. Visit your admin:**
143
+
144
+ ```
145
+ http://localhost:8000/djadmin/
146
+ ```
147
+
148
+ ## Development Setup
149
+
150
+ ### Prerequisites
151
+
152
+ - Python 3.11 or higher
153
+ - [just](https://github.com/casey/just) command runner (optional but recommended)
154
+ - Git
155
+
156
+ ### Clone and Setup
157
+
158
+ ```bash
159
+ # Clone the repository
160
+ git clone https://github.com/yourusername/django-admin-deux.git
161
+ cd django-admin-deux
162
+
163
+ # Run the setup script
164
+ ./setup_dev.sh
165
+
166
+ # Or manually:
167
+ python -m venv .venv
168
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
169
+ pip install -e ".[dev]"
170
+ pre-commit install
171
+ ```
172
+
173
+ ### Development Commands
174
+
175
+ With `just` installed:
176
+
177
+ ```bash
178
+ # Run tests
179
+ just test
180
+
181
+ # Run tests with coverage
182
+ just test-coverage
183
+
184
+ # Format code
185
+ just format
186
+
187
+ # Run linters
188
+ just lint
189
+
190
+ # Run development server
191
+ just runserver
192
+
193
+ # See all commands
194
+ just --list
195
+ ```
196
+
197
+ Without `just`:
198
+
199
+ ```bash
200
+ # Run tests
201
+ pytest
202
+
203
+ # Run tests with coverage
204
+ pytest --cov=djadmin --cov-report=html
205
+
206
+ # Format code
207
+ ruff format .
208
+ ruff check . --fix
209
+ djlint djadmin/ --reformat
210
+
211
+ # Run linters
212
+ ruff check .
213
+ djlint djadmin/ --lint
214
+
215
+ # Run development server
216
+ cd tests && python manage.py runserver
217
+ ```
218
+
219
+ ### Project Structure
220
+
221
+ ```
222
+ django-admin-deux/
223
+ ├── djadmin/ # Main package
224
+ │ ├── plugins/ # Plugin system
225
+ │ ├── sites.py # AdminSite class
226
+ │ ├── options.py # ModelAdmin class
227
+ │ └── decorators.py # @register decorator
228
+ ├── examples/
229
+ │ └── webshop/ # Example e-commerce app
230
+ ├── tests/ # Test infrastructure
231
+ ├── pyproject.toml # Package configuration
232
+ └── tox.ini # CI test matrix
233
+ ```
234
+
235
+ ## Key Features
236
+
237
+ ## Architecture
238
+
239
+ ### Plugin System
240
+
241
+ django-admin-deux uses [djp](https://github.com/simonw/djp) for its plugin architecture. Plugins can:
242
+
243
+ - Add mixins to views
244
+ - Provide default actions
245
+ - Modify querysets
246
+ - Add context data
247
+ - Provide CSS/JS assets
248
+
249
+ **Example plugin:**
250
+
251
+ ```python
252
+ # myapp/djadmin_hooks.py
253
+ from djadmin.plugins import hookimpl
254
+
255
+ @hookimpl
256
+ def djadmin_provides_features():
257
+ """Advertise features this plugin provides"""
258
+ return ['search', 'filter']
259
+
260
+ @hookimpl
261
+ def djadmin_get_list_view_mixins():
262
+ """Add search functionality to ListView"""
263
+ from .mixins import SearchMixin
264
+ return [SearchMixin]
265
+ ```
266
+
267
+ ### Feature Advertising
268
+
269
+ ModelAdmin configurations are validated at startup. If you request a feature (like search or filtering) but no plugin provides it, you'll get a clear error:
270
+
271
+ ```python
272
+ class BookAdmin(ModelAdmin):
273
+ search_fields = ['title'] # Requires 'search' feature
274
+
275
+ # If no plugin provides 'search':
276
+ # ImproperlyConfigured: ModelAdmin BookAdmin requests features {'search'}
277
+ # but no registered plugin provides them.
278
+ ```
279
+
280
+ ### View Factory Pattern
281
+
282
+ Views are generated dynamically using class-based factories, allowing plugins to inject mixins and customize behavior without complex inheritance chains.
283
+
284
+ ```python
285
+ # Conceptual example (Milestone 2)
286
+ class ListViewFactory:
287
+ def create_view(self, model, admin_class, plugins):
288
+ # Collect mixins from plugins
289
+ mixins = []
290
+ for plugin in plugins:
291
+ mixins.extend(plugin.get_list_view_mixins())
292
+
293
+ # Generate view class dynamically
294
+ view_class = type(
295
+ f'{model.__name__}ListView',
296
+ tuple(mixins + [BaseListView]),
297
+ {'model': model, 'admin': admin_class}
298
+ )
299
+ return view_class
300
+ ```
301
+
302
+ ### Layout API
303
+
304
+ django-admin-deux provides a powerful Layout API for customizing form layouts with progressive enhancement:
305
+
306
+ ```python
307
+ from djadmin import ModelAdmin, register, Layout, Field, Fieldset, Row
308
+
309
+ @register(Author)
310
+ class AuthorAdmin(ModelAdmin):
311
+ layout = Layout(
312
+ Fieldset('Personal Information',
313
+ Row(
314
+ Field('first_name', css_classes=['flex-1', 'pr-2']),
315
+ Field('last_name', css_classes=['flex-1', 'pl-2']),
316
+ ),
317
+ Field('birth_date', label='Date of Birth'),
318
+ ),
319
+ Fieldset('Biography',
320
+ Field('bio', widget='textarea', attrs={'rows': 8}),
321
+ ),
322
+ )
323
+ ```
324
+
325
+ **Action-Specific Layouts**:
326
+
327
+ Use different layouts for create vs update actions (follows the same pattern as `create_form_class`/`update_form_class`):
328
+
329
+ ```python
330
+ @register(Product)
331
+ class ProductAdmin(ModelAdmin):
332
+ # Create-specific layout (simpler, focused on essentials)
333
+ create_layout = Layout(
334
+ Fieldset('New Product',
335
+ Field('name', required=True),
336
+ Row(
337
+ Field('price', css_classes=['flex-1']),
338
+ Field('cost', css_classes=['flex-1']),
339
+ ),
340
+ ),
341
+ )
342
+
343
+ # Update-specific layout (includes metadata fields)
344
+ update_layout = Layout(
345
+ Fieldset('Product Information',
346
+ Field('name'),
347
+ Field('description', widget='textarea'),
348
+ ),
349
+ Fieldset('Metadata',
350
+ Field('created_at', widget=DateTimeInput(attrs={'readonly': True})),
351
+ Field('updated_at', widget=DateTimeInput(attrs={'readonly': True})),
352
+ ),
353
+ )
354
+ ```
355
+
356
+ **Core Features** (no plugin required):
357
+ - ✅ **Fieldsets** - Group fields with legends and descriptions
358
+ - ✅ **Rows** - Horizontal layouts using flexbox
359
+ - ✅ **Field Customization** - Labels, widgets, help text, CSS classes
360
+ - ✅ **Widget Shortcuts** - Use strings like `'textarea'`, `'email'`
361
+ - ✅ **Django Admin Migration** - Automatic conversion of `fieldsets`
362
+ - ✅ **Action-Specific Layouts** - Different layouts for create/update (`create_layout`, `update_layout`)
363
+
364
+ **Plugin Features** (with djadmin-formset):
365
+ - ✅ **Collections** - Inline editing of related objects
366
+ - ✅ **Conditional Fields** - Show/hide fields based on values
367
+ - ✅ **Computed Fields** - Auto-calculate values
368
+ - ✅ **Client-side Validation** - Instant feedback
369
+ - ✅ **Drag-and-drop** - Reorder inline items
370
+
371
+ **Learn more**: [Layout API Documentation](docs/layout-api/index.md)
372
+
373
+ ## Testing
374
+
375
+ The project uses pytest with extensive test coverage:
376
+
377
+ ```bash
378
+ # Run all tests
379
+ just test
380
+
381
+ # Run specific test
382
+ just test-file tests/test_plugins.py
383
+
384
+ # Run tests matching pattern
385
+ just test-match test_register
386
+
387
+ # Run with coverage report
388
+ just test-coverage
389
+ ```
390
+
391
+ ### Test Organization
392
+
393
+ - **`tests/`** - Infrastructure tests (plugins, URLs, validation)
394
+ - **`examples/webshop/tests/`** - Integration tests using example models
395
+
396
+ ### Test Best Practices: Avoiding Test Pollution
397
+
398
+ When writing tests that register/unregister ModelAdmin classes, follow the **DynamicURLConf pattern** to avoid test pollution (tests that pass individually but fail in the suite):
399
+
400
+ ```python
401
+ from django.test import TestCase, override_settings
402
+ from django.urls import clear_url_caches
403
+ from djadmin import ModelAdmin, site
404
+
405
+ # Dynamic URLconf regenerates on each access
406
+ class DynamicURLConf:
407
+ @property
408
+ def urlpatterns(self):
409
+ from django.urls import path, include
410
+ return [path('djadmin/', include(site.urls))]
411
+
412
+ @override_settings(ROOT_URLCONF=DynamicURLConf())
413
+ class TestMyFeature(TestCase):
414
+ def setUp(self):
415
+ # Clean registry before test
416
+ if MyModel in site._registry:
417
+ site.unregister(MyModel)
418
+ self.my_objects = MyModelFactory.create_batch(5)
419
+
420
+ def tearDown(self):
421
+ # Clean registry and caches after test
422
+ if MyModel in site._registry:
423
+ site.unregister(MyModel)
424
+ clear_url_caches()
425
+ if hasattr(self.client, '_cached_urlconf'):
426
+ delattr(self.client, '_cached_urlconf')
427
+
428
+ def test_my_feature(self):
429
+ # Register with override=True
430
+ class MyModelAdmin(ModelAdmin):
431
+ list_display = ['field1', 'field2']
432
+ site.register(MyModel, MyModelAdmin, override=True)
433
+ # ... test code
434
+ ```
435
+
436
+ **Why this is needed**: Django caches URL patterns and view closures, so without this pattern, tests will use stale admin configurations from previous tests.
437
+
438
+ **Reference**: This pattern is based on https://ጮ.cc/2019/11/09/django-testing-dynamic-urlconf.html
439
+
440
+ See `tests/test_search.py` for a complete example.
441
+
442
+ ### Example Models
443
+
444
+ The webshop example app provides realistic test data:
445
+
446
+ - `Category` - Hierarchical product categories
447
+ - `Product` - Products with SKU, pricing, stock
448
+ - `Customer` - Customer accounts with addresses
449
+ - `Order` - Orders with status tracking
450
+ - `OrderItem` - Line items in orders
451
+ - `Review` - Product reviews with ratings
452
+ - `Tag` - Tags (many-to-many relationship example)
453
+
454
+ All models have corresponding factory_boy factories for easy test data creation.
455
+
456
+ ## Continuous Integration
457
+
458
+ The project uses GitLab CI with tox to test all Python/Django combinations:
459
+
460
+ - Python: 3.11, 3.12, 3.13, 3.14
461
+ - Django: 5.2, 6.0
462
+
463
+ Pre-commit hooks run on every commit to catch issues early:
464
+ - Ruff (linting and formatting)
465
+ - djLint (Django template linting)
466
+ - pytest (test suite)
467
+
468
+ ## Roadmap
469
+
470
+ ### Milestone 1: Foundation ✅ Complete
471
+ - ✅ Plugin system with djp
472
+ - ✅ AdminSite and ModelAdmin classes
473
+ - ✅ URL routing
474
+ - ✅ Feature validation
475
+ - ✅ Basic templates
476
+
477
+ ### Milestone 2: View Factories & Actions ✅ Complete
478
+ - ✅ Factory pattern implementation
479
+ - ✅ ListView, CreateView, DetailView
480
+ - ✅ Form handling
481
+ - ✅ List actions (e.g., "Add New")
482
+ - ✅ Bulk actions (e.g., "Delete Selected")
483
+ - ✅ Record actions (e.g., "Edit", "Delete")
484
+
485
+ ### Milestone 3: Layout API & Django-Formset Integration ✅ Complete
486
+ - ✅ Core Layout API (Field, Fieldset, Row, Collection)
487
+ - ✅ Automatic Django admin fieldsets conversion
488
+ - ✅ Feature advertising system
489
+ - ✅ Basic flexbox rendering
490
+ - ✅ FormFactory for django-formset integration
491
+ - ✅ Inline editing (Collections)
492
+ - ✅ Conditional fields (show_if/hide_if)
493
+ - ✅ Computed fields (calculate)
494
+ - ✅ 565 tests passing, 87% coverage
495
+
496
+ ### Milestone 4: Developer Experience ✅ Complete
497
+ - ✅ djadmin_inspect management command
498
+ - ✅ BaseCRUDTestCase for automated testing
499
+ - ✅ Plugin-driven INSTALLED_APPS
500
+ - ✅ Comprehensive documentation
501
+
502
+ ### Milestone 5: Permissions System (Current - Phase 2.7 Complete)
503
+ - ✅ Core permission classes (IsAuthenticated, IsStaff, HasDjangoPermission)
504
+ - ✅ Composition operators (AND/OR/NOT)
505
+ - ✅ ModelAdmin permission integration
506
+ - ✅ Action-level permissions
507
+ - ✅ Object-level permissions support
508
+ - ✅ Action filtering based on permissions
509
+ - ✅ ViewRecordAction for view-only users
510
+ - ✅ Dashboard filtering
511
+ - ✅ 720 tests passing, 82% coverage
512
+ - 📋 Next: Guardian plugin (optional), UI integration & polish
513
+
514
+ ### Milestone 6: Quality & Polish (Planned)
515
+ - Coverage improvements (>90%)
516
+ - Accessibility audit (WCAG 2.1 AA)
517
+ - Performance benchmarking
518
+ - CI/CD infrastructure
519
+ - Production-ready release
520
+
521
+ ## Contributing
522
+
523
+ We welcome contributions! The project is in early development, so there are many opportunities to help shape the future of Django admin interfaces.
524
+
525
+ ### Development Workflow
526
+
527
+ 1. **Fork and clone** the repository
528
+ 2. **Create a feature branch**: `git checkout -b feature/my-feature`
529
+ 3. **Make your changes** and add tests
530
+ 4. **Run tests and linters**: `just test && just lint`
531
+ 5. **Commit** (pre-commit hooks will run automatically)
532
+ 6. **Push** and create a merge request
533
+
534
+ ### Code Style
535
+
536
+ - **Python**: Ruff formatter (120 character line length)
537
+ - **Templates**: djLint formatter
538
+ - **Commits**: Use conventional commit format (e.g., `feat:`, `fix:`, `docs:`)
539
+ - **Tests**: Pytest with >80% coverage required
540
+
541
+ ### Running Tests Locally
542
+
543
+ ```bash
544
+ # Run full test suite
545
+ just test
546
+
547
+ # Test specific Python/Django combination
548
+ tox -e py311-django52
549
+
550
+ # Test all combinations (like CI)
551
+ tox
552
+ ```
553
+
554
+ ## Documentation
555
+
556
+ ### User Documentation
557
+ - **[Layout API Overview](docs/layout-api/index.md)** - Introduction to the Layout API
558
+ - **[Component Reference](docs/layout-api/components.md)** - Detailed API for each component
559
+ - **[Django Admin Migration Guide](docs/layout-api/django-admin-migration.md)** - Migrate from Django admin
560
+ - **[Layout Examples](docs/layout-api/examples.md)** - Real-world usage patterns
561
+
562
+ ### Developer Documentation
563
+ - **[CLAUDE.md](CLAUDE.md)** - Technical documentation for AI assistants
564
+ - **[PRD](claude_docs/django-admin-deux-PRD.md)** - Complete product requirements (v2.7)
565
+ - **[Milestone 5 Implementation Plan](claude_docs/milestone-5-implementation-plan.md)** - Current milestone plan
566
+ - **[CHANGELOG](CHANGELOG.md)** - Version history and release notes
567
+
568
+ ## License
569
+
570
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
571
+
572
+ ## Acknowledgments
573
+
574
+ - Inspired by [django-admin2](https://github.com/jazzband/django-admin2)
575
+ - Built with [djp](https://github.com/simonw/djp) by Simon Willison
576
+ - Uses [factory_boy](https://github.com/FactoryBoy/factory_boy) for test data
577
+ - Styled with [Tailwind CSS](https://tailwindcss.com/) (coming in Milestone 4)
578
+
579
+ ---
580
+
581
+ **Status**: 🎉 Milestone 5 Phase 2.7 Complete
582
+ **Python**: 3.11+
583
+ **Django**: 5.2+
584
+ **License**: MIT