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