django-unfold 0.11.0__py3-none-any.whl → 0.13.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. {django_unfold-0.11.0.dist-info → django_unfold-0.13.0.dist-info}/METADATA +209 -99
  2. {django_unfold-0.11.0.dist-info → django_unfold-0.13.0.dist-info}/RECORD +33 -14
  3. unfold/admin.py +26 -6
  4. unfold/contrib/guardian/__init__.py +0 -0
  5. unfold/contrib/guardian/apps.py +6 -0
  6. unfold/contrib/guardian/templates/admin/guardian/model/change_form.html +13 -0
  7. unfold/contrib/guardian/templates/admin/guardian/model/field.html +11 -0
  8. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +35 -0
  9. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +55 -0
  10. unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +56 -0
  11. unfold/contrib/guardian/templates/unfold/guardian/group_form.html +72 -0
  12. unfold/contrib/guardian/templates/unfold/guardian/user_form.html +72 -0
  13. unfold/contrib/simple_history/__init__.py +0 -0
  14. unfold/contrib/simple_history/apps.py +6 -0
  15. unfold/contrib/simple_history/templates/simple_history/_object_history_list.html +80 -0
  16. unfold/contrib/simple_history/templates/simple_history/object_history.html +21 -0
  17. unfold/contrib/simple_history/templates/simple_history/object_history_form.html +55 -0
  18. unfold/contrib/simple_history/templates/simple_history/submit_line.html +25 -0
  19. unfold/settings.py +1 -0
  20. unfold/sites.py +4 -0
  21. unfold/static/unfold/css/styles.css +1 -1
  22. unfold/styles.css +134 -66
  23. unfold/templates/admin/edit_inline/tabular.html +1 -1
  24. unfold/templates/admin/includes/fieldset.html +4 -8
  25. unfold/templates/admin/widgets/radio.html +20 -0
  26. unfold/templates/admin/widgets/radio_option.html +12 -0
  27. unfold/templates/unfold/helpers/display_header.html +14 -10
  28. unfold/templates/unfold/helpers/field_readonly.html +9 -0
  29. unfold/templates/unfold/helpers/submit.html +3 -0
  30. unfold/templatetags/unfold.py +14 -0
  31. unfold/widgets.py +19 -0
  32. /django_unfold-0.11.0.dist-info/LICENSE → /django_unfold-0.13.0.dist-info/LICENSE.md +0 -0
  33. {django_unfold-0.11.0.dist-info → django_unfold-0.13.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-unfold
3
- Version: 0.11.0
3
+ Version: 0.13.0
4
4
  Summary: Clean & minimal Django admin theme based on Tailwind CSS
5
5
  Home-page: https://unfoldadmin.com
6
6
  License: MIT
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.8
17
17
  Classifier: Programming Language :: Python :: 3.9
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
20
21
  Requires-Dist: django (>=3.2)
21
22
  Requires-Dist: importlib-metadata (==6.7.0)
22
23
  Project-URL: Repository, https://github.com/unfoldadmin/django-unfold
@@ -24,7 +25,7 @@ Description-Content-Type: text/markdown
24
25
 
25
26
  ![screenshot](https://github.com/unfoldadmin/django-unfold/assets/10785882/daef6e7e-e8a1-4142-8e4c-fa2a287978d2)
26
27
 
27
- ## Unfold Django Admin Theme
28
+ ## Unfold Django Admin Theme <!-- omit from toc -->
28
29
 
29
30
  [![Build](https://img.shields.io/github/actions/workflow/status/unfoldadmin/django-unfold/release.yml?style=for-the-badge)](https://github.com/unfoldadmin/django-unfold/actions?query=workflow%3Arelease)
30
31
  [![PyPI - Version](https://img.shields.io/pypi/v/django-unfold.svg?style=for-the-badge)](https://pypi.org/project/django-unfold/)
@@ -33,11 +34,11 @@ Description-Content-Type: text/markdown
33
34
 
34
35
  Unfold is theme for Django admin incorporating most common practises for building full-fledged admin areas. It is designed to work at the top of default administration provided by Django.
35
36
 
36
- - demo site is available at [unfoldadmin.com](https://unfoldadmin.com)
37
- - repository with demo implementation at [github.com/unfoldadmin/formula](https://github.com/unfoldadmin/formula)
38
- - Django & Next.js boilerplate implementing Unfold at [github.com/unfoldadmin/turbo](https://github.com/unfoldadmin/turbo)
37
+ - **Unfold:** demo site is available at [unfoldadmin.com](https://unfoldadmin.com)
38
+ - **Formula:** repository with demo implementation at [github.com/unfoldadmin/formula](https://github.com/unfoldadmin/formula)
39
+ - **Turbo:** Django & Next.js boilerplate implementing Unfold at [github.com/unfoldadmin/turbo](https://github.com/unfoldadmin/turbo)
39
40
 
40
- ## Features
41
+ ## Features <!-- omit from toc -->
41
42
 
42
43
  - **Visual**: provides new user interface based on Tailwind CSS framework
43
44
  - **Sidebar:** simplifies definition of custom sidebar navigation with icons
@@ -46,38 +47,41 @@ Unfold is theme for Django admin incorporating most common practises for buildin
46
47
  - **Dependencies:** completely based only on `django.contrib.admin`
47
48
  - **Actions:** multiple ways how to define actions within different parts of admin
48
49
  - **WYSIWYG:** built-in support for WYSIWYG (Trix)
49
- - **Numeric filters:** widgets for filtering number values
50
- - **Datetime filters:** widgets for filtering datetime values
50
+ - **Custom filters:** widgets for filtering number & datetime values
51
51
  - **Dashboard:** helpers to bootstrap custom dashboard
52
52
  - **Tabs:** define custom tab navigations for models
53
53
  - **Colors:** possibility to override default color scheme
54
- - **Django import / export:** default support for this popular application
54
+ - **Third party packages:** default support for multiple popular applications
55
55
 
56
- ## Table of Contents
56
+ ## Table of contents <!-- omit from toc -->
57
57
 
58
58
  - [Installation](#installation)
59
59
  - [Configuration](#configuration)
60
60
  - [Available settings.py options](#available-settingspy-options)
61
61
  - [Available unfold.admin.ModelAdmin options](#available-unfoldadminmodeladmin-options)
62
- - [Decorators](#decorators)
63
- - [@display](#display)
64
62
  - [Actions](#actions)
65
63
  - [Actions overview](#actions-overview)
66
64
  - [Custom unfold @action decorator](#custom-unfold-action-decorator)
67
65
  - [Action handler functions](#action-handler-functions)
68
- - [For submit row action](#for-submit-row-action)
69
- - [For global, row and detail action](#for-global-row-and-detail-action)
70
66
  - [Action examples](#action-examples)
71
67
  - [Filters](#filters)
68
+ - [Numeric filters](#numeric-filters)
69
+ - [Date/time filters](#datetime-filters)
70
+ - [Display decorator](#display-decorator)
72
71
  - [Third party packages](#third-party-packages)
72
+ - [django-celery-beat](#django-celery-beat)
73
+ - [django-guardian](#django-guardian)
73
74
  - [django-import-export](#django-import-export)
75
+ - [django-modeltranslation](#django-modeltranslation)
76
+ - [django-money](#django-money)
77
+ - [django-simple-history](#django-simple-history)
74
78
  - [User Admin Form](#user-admin-form)
75
- - [Adding Custom Styles and Scripts](#adding-custom-styles-and-scripts)
76
- - [Project Level Tailwind Stylesheet](#project-level-tailwind-stylesheet)
77
- - [Custom Admin Dashboard](#custom-admin-dashboard)
78
- - [Unfold Development](#unfold-development)
79
+ - [Adding custom styles and scripts](#adding-custom-styles-and-scripts)
80
+ - [Project level Tailwind stylesheet](#project-level-tailwind-stylesheet)
81
+ - [Custom admin dashboard](#custom-admin-dashboard)
82
+ - [Unfold development](#unfold-development)
79
83
  - [Pre-commit](#pre-commit)
80
- - [Poetry Configuration](#poetry-configuration)
84
+ - [Poetry configuration](#poetry-configuration)
81
85
  - [Compiling Tailwind](#compiling-tailwind)
82
86
  - [Credits](#credits)
83
87
 
@@ -93,6 +97,8 @@ INSTALLED_APPS = [
93
97
  "unfold.contrib.filters", # optional, if special filters are needed
94
98
  "unfold.contrib.forms", # optional, if special form elements are needed
95
99
  "unfold.contrib.import_export", # optional, if django-import-export package is used
100
+ "unfold.contrib.guardian", # optional, if django-guardian package is used
101
+ "unfold.contrib.simple_history", # optional, if django-simple-history package is used
96
102
  "django.contrib.admin", # required
97
103
  ]
98
104
  ```
@@ -192,6 +198,7 @@ UNFOLD = {
192
198
  "700": "126 34 206",
193
199
  "800": "107 33 168",
194
200
  "900": "88 28 135",
201
+ "950": "59 7 100",
195
202
  },
196
203
  },
197
204
  "EXTENSIONS": {
@@ -301,70 +308,6 @@ class CustomAdminClass(ModelAdmin):
301
308
  }
302
309
  ```
303
310
 
304
- ## Decorators
305
-
306
- ### @display
307
-
308
- Unfold introduces it's own `unfold.decorators.display` decorator. By default it has exactly same behavior as native `django.contrib.admin.decorators.display` but it adds same customizations which helps to extends default logic.
309
-
310
- `@display(label=True)`, `@display(label={"value1": "success"})` displays a result as a label. This option fits for different types of statuses. Label can be either boolean indicating we want to use label with default color or dict where the dict is responsible for displaying labels in different colors. At the moment these color combinations are supported: success(green), info(blue), danger(red) and warning(orange).
311
-
312
- `@display(header=True)` displays in results list two information in one table cell. Good example is when we want to display customer information, first line is going to be customer's name and right below the name display corresponding email address. Method with such a decorator is supposed to return a list with two elements `return "Full name", "E-mail address"`.
313
-
314
- ```python
315
- # admin.py
316
-
317
- from django.db.models import TextChoices
318
- from django.utils.translation import gettext_lazy as _
319
-
320
- from unfold.admin import ModelAdmin
321
- from unfold.decorators import display
322
-
323
-
324
- class UserStatus(TextChoices):
325
- ACTIVE = "ACTIVE", _("Active")
326
- PENDING = "PENDING", _("Pending")
327
- INACTIVE = "INACTIVE", _("Inactive")
328
- CANCELLED = "CANCELLED", _("Cancelled")
329
-
330
-
331
- class UserAdmin(ModelAdmin):
332
- list_display = [
333
- "display_as_two_line_heading",
334
- "show_status",
335
- "show_status_with_custom_label",
336
- ]
337
-
338
- @display(
339
- description=_("Status"),
340
- ordering="status",
341
- label=True
342
- )
343
- def show_status_default_color(self, obj):
344
- return obj.status
345
-
346
- @display(
347
- description=_("Status"),
348
- ordering="status",
349
- label={
350
- UserStatus.ACTIVE: "success", # green
351
- UserStatus.PENDING: "info", # blue
352
- UserStatus.INACTIVE: "warning", # orange
353
- UserStatus.CANCELLED: "danger", # red
354
- },
355
- )
356
- def show_status_customized_color(self, obj)
357
- return obj.status
358
-
359
- @display(description=_("Status with label"), ordering="status", label=True)
360
- def show_status_with_custom_label(self, obj):
361
- return obj.status, obj.get_status_display()
362
-
363
- @display(header=True)
364
- def display_as_two_line_heading(self, obj):
365
- return "First main heading", "Smaller additional description"
366
- ```
367
-
368
311
  ## Actions
369
312
 
370
313
  It is highly recommended to read the base [Django actions documentation](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/actions/) before reading this section, since Unfold actions are derived from Django actions.
@@ -395,13 +338,13 @@ Unfold also uses custom `@action` decorator, supporting 2 more parameters in com
395
338
  This section provides explanation of how the action handler functions should be constructed for Unfold actions.
396
339
  For default actions, follow official Django admin documentation.
397
340
 
398
- #### For submit row action
341
+ #### For submit row action <!-- omit from toc -->
399
342
 
400
343
  Submit row actions work a bit differently when compared to other custom Unfold actions.
401
344
  These actions first invoke form save (same as if you hit `Save` button) and then lets you
402
345
  perform additional logic on already saved instance.
403
346
 
404
- #### For global, row and detail action
347
+ #### For global, row and detail action <!-- omit from toc -->
405
348
 
406
349
  All these actions are based on custom URLs generated for each of them. Handler function for these views is
407
350
  basically function based view.
@@ -499,6 +442,10 @@ class UserAdmin(ModelAdmin):
499
442
 
500
443
  By default, Django admin handles all filters as regular HTML links pointing at the same URL with different query parameters. This approach is for basic filtering more than enough. In the case of more advanced filtering by incorporating input fields, it is not going to work.
501
444
 
445
+ **Note:** when implementing a filter which contains input fields, there is a no way that user can submit the values, because default filters does not contain submit button. To implement submit button, `unfold.admin.ModelAdmin` contains boolean `list_filter_submit` flag which enables submit button in filter form.
446
+
447
+ ### Numeric filters
448
+
502
449
  Currently, Unfold implements numeric filters inside `unfold.contrib.filters` application. In order to use these filters, it is required to add this application into `INSTALLED_APPS` in `settings.py` right after `unfold` application.
503
450
 
504
451
  ```python
@@ -513,8 +460,6 @@ from unfold.contrib.filters.admin import (
513
460
  RangeNumericFilter,
514
461
  SingleNumericFilter,
515
462
  SliderNumericFilter,
516
- RangeDateFilter,
517
- RangeDateTimeFilter,
518
463
  )
519
464
 
520
465
 
@@ -536,8 +481,6 @@ class YourModelAdmin(ModelAdmin):
536
481
  ("field_B", RangeNumericFilter), # Numeric range search, __gte and __lte lookup
537
482
  ("field_C", SliderNumericFilter), # Numeric range filter but with slider
538
483
  ("field_D", CustomSliderNumericFilter), # Numeric filter with custom attributes
539
- ("field_E", RangeDateFilter), # Date filter
540
- ("field_F", RangeDateTimeFilter), # Datetime filter
541
484
  CustomRangeNumericListFilter, # Numeric range search not restricted to a model field
542
485
  )
543
486
 
@@ -545,13 +488,150 @@ class YourModelAdmin(ModelAdmin):
545
488
  return super().get_queryset().annotate(items_count=Count("item", distinct=True))
546
489
  ```
547
490
 
491
+ ### Date/time filters
492
+
493
+ ```python
494
+ # admin.py
495
+
496
+ from django.contrib import admin
497
+ from django.contrib.auth.models import User
498
+
499
+ from unfold.admin import ModelAdmin
500
+ from unfold.contrib.filters.admin import (
501
+ RangeDateFilter,
502
+ RangeDateTimeFilter,
503
+ )
504
+
505
+
506
+ @admin.register(User)
507
+ class YourModelAdmin(ModelAdmin):
508
+ list_filter_submit = True # Submit button at the bottom of the filter
509
+ list_filter = (
510
+ ("field_E", RangeDateFilter), # Date filter
511
+ ("field_F", RangeDateTimeFilter), # Datetime filter
512
+ )
513
+ ```
514
+
515
+ ## Display decorator
516
+
517
+ Unfold introduces it's own `unfold.decorators.display` decorator. By default it has exactly same behavior as native `django.contrib.admin.decorators.display` but it adds same customizations which helps to extends default logic.
518
+
519
+ `@display(label=True)`, `@display(label={"value1": "success"})` displays a result as a label. This option fits for different types of statuses. Label can be either boolean indicating we want to use label with default color or dict where the dict is responsible for displaying labels in different colors. At the moment these color combinations are supported: success(green), info(blue), danger(red) and warning(orange).
520
+
521
+ `@display(header=True)` displays in results list two information in one table cell. Good example is when we want to display customer information, first line is going to be customer's name and right below the name display corresponding email address. Method with such a decorator is supposed to return a list with two elements `return "Full name", "E-mail address"`. There is a third optional argument, which is type of the string and its value is displayed in a circle before first two values on the front end. Its optimal usage is for displaying initials.
522
+
523
+ ```python
524
+ # admin.py
525
+
526
+ from django.db.models import TextChoices
527
+ from django.utils.translation import gettext_lazy as _
528
+
529
+ from unfold.admin import ModelAdmin
530
+ from unfold.decorators import display
531
+
532
+
533
+ class UserStatus(TextChoices):
534
+ ACTIVE = "ACTIVE", _("Active")
535
+ PENDING = "PENDING", _("Pending")
536
+ INACTIVE = "INACTIVE", _("Inactive")
537
+ CANCELLED = "CANCELLED", _("Cancelled")
538
+
539
+
540
+ class UserAdmin(ModelAdmin):
541
+ list_display = [
542
+ "display_as_two_line_heading",
543
+ "show_status",
544
+ "show_status_with_custom_label",
545
+ ]
546
+
547
+ @display(
548
+ description=_("Status"),
549
+ ordering="status",
550
+ label=True
551
+ )
552
+ def show_status_default_color(self, obj):
553
+ return obj.status
554
+
555
+ @display(
556
+ description=_("Status"),
557
+ ordering="status",
558
+ label={
559
+ UserStatus.ACTIVE: "success", # green
560
+ UserStatus.PENDING: "info", # blue
561
+ UserStatus.INACTIVE: "warning", # orange
562
+ UserStatus.CANCELLED: "danger", # red
563
+ },
564
+ )
565
+ def show_status_customized_color(self, obj)
566
+ return obj.status
567
+
568
+ @display(description=_("Status with label"), ordering="status", label=True)
569
+ def show_status_with_custom_label(self, obj):
570
+ return obj.status, obj.get_status_display()
571
+
572
+ @display(header=True)
573
+ def display_as_two_line_heading(self, obj):
574
+ """
575
+ Third argument is short text which will appear as prefix in circle
576
+ """
577
+ return "First main heading", "Smaller additional description", "AB"
578
+ ```
579
+
548
580
  ## Third party packages
549
581
 
550
- ### django-import-export
582
+ ### django-celery-beat
583
+
584
+ In general, django-celery-beat does not have any components that require special styling. The default changelist templates are not inheriting from Unfold's `ModelAdmin` but they are using default `ModelAdmin` coming from `django.contrib.admin` which is causing some design discrepancies in the changelist.
585
+
586
+ In the source code below you can find a short code snippet to unregister all `django-celery-beat` admin classes and register them with the proper parent `ModelAdmin` class.
587
+
588
+ ```python
589
+ # admin.py
590
+ from django.contrib import admin
591
+ from unfold.admin import ModelAdmin
592
+
593
+ from django_celery_beat.models import (
594
+ ClockedSchedule,
595
+ CrontabSchedule,
596
+ IntervalSchedule,
597
+ PeriodicTask,
598
+ SolarSchedule,
599
+ )
600
+
601
+
602
+ admin.site.unregister(PeriodicTask)
603
+ admin.site.unregister(IntervalSchedule)
604
+ admin.site.unregister(CrontabSchedule)
605
+ admin.site.unregister(SolarSchedule)
606
+ admin.site.unregister(ClockedSchedule)
607
+
608
+ @admin.register(PeriodicTask)
609
+ class PeriodicTaskAdmin(ModelAdmin):
610
+ pass
611
+
612
+
613
+ @admin.register(IntervalSchedule)
614
+ class IntervalScheduleAdmin(ModelAdmin):
615
+ pass
616
+
617
+
618
+ @admin.register(CrontabSchedule)
619
+ class CrontabScheduleAdmin(ModelAdmin):
620
+ pass
621
+
622
+
623
+ @admin.register(SolarSchedule)
624
+ class SolarScheduleAdmin(ModelAdmin):
625
+ pass
626
+ ```
627
+
628
+ ### django-guardian
551
629
 
552
- To get proper visual appearance for django-import-export, two things are needed
630
+ Adding support for django-guardian is quote straightforward in Unfold, just add `unfold.contrib.guardian` to `INSTALLED_APPS` at the beggining of the file. This action will override all templates coming from the django-guardian. Please note that **Object permissions** link is available in top right dropdown navigation.
553
631
 
554
- 1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the begging of the file. This action will override all templates coming from the plugin.
632
+ ### django-import-export
633
+
634
+ 1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the beggining of the file. This action will override all templates coming from the application.
555
635
  2. Change `import_form_class` and `export_form_class` in ModelAdmin which is inheriting from `ImportExportModelAdmin`. This chunk of code is responsible for adding proper styling to form elements.
556
636
 
557
637
  ```python
@@ -565,6 +645,36 @@ class ExampleAdmin(ModelAdmin, ImportExportModelAdmin):
565
645
  export_form_class = ExportForm
566
646
  ```
567
647
 
648
+ ### django-modeltranslation
649
+
650
+ By default Unfold does not contain any specific implementation for django-modeltranslation and the application is partially supported. Basic behavior is supported except of tab navigation provided by django-modeltranslation. At the moment there are no plans in supporting this behavior.
651
+
652
+ For django-modeltranslation fields for spefic languages, it is possible to define custom flags which will appear as a suffix in field's label. It is recommended to use emojis as suffix.
653
+
654
+ ```python
655
+ # settings.py
656
+
657
+ UNFOLD = {
658
+ "EXTENSIONS": {
659
+ "modeltranslation": {
660
+ "flags": {
661
+ "en": "🇬🇧",
662
+ "fr": "🇫🇷",
663
+ "nl": "🇧🇪",
664
+ },
665
+ },
666
+ },
667
+ }
668
+ ```
669
+
670
+ ### django-money
671
+
672
+ This application is supported in Unfold by default. It is not needed to add any other applications into `INSTALLED_APPS`. Unfold is recognizing special form widget coming from django-money and applying specific styling.
673
+
674
+ ### django-simple-history
675
+
676
+ To make this application work, add `unfold.contrib.simple_history` into `settings.py` in `INSTALLED_APPS` variable before right after `unfold`. This app should ensure that templates coming from django-simple-history are overriden by Unfold.
677
+
568
678
  ## User Admin Form
569
679
 
570
680
  User's admin in Django is specific as it contains several forms which are requiring custom styling. All of these forms has been inherited and accordingly adjusted. In user admin class it is needed to use these inherited form classes to enable custom styling matching rest of the website.
@@ -587,7 +697,7 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
587
697
  change_password_form = AdminPasswordChangeForm
588
698
  ```
589
699
 
590
- ## Adding Custom Styles and Scripts
700
+ ## Adding custom styles and scripts
591
701
 
592
702
  To add new custom styles, for example for custom dashboard, it is possible to load them via **STYLES** key in **UNFOLD** dict. This key accepts a list of strings or lambda functions which will be loaded on all pages. JavaScript files can be loaded by using similar apprach, but **SCRIPTS** is used.
593
703
 
@@ -606,7 +716,7 @@ UNFOLD = {
606
716
  }
607
717
  ```
608
718
 
609
- ## Project Level Tailwind Stylesheet
719
+ ## Project level Tailwind stylesheet
610
720
 
611
721
  When creating custom dashboard or adding custom components, it is needed to add own styles. Adding custom styles is described above. Most of the time, it is supposed that new elements are going to match with the rest of the administration panel. First of all, create tailwind.config.js in your application. Below is located minimal configuration for this file.
612
722
 
@@ -639,7 +749,7 @@ Once the configuration file is set, it is possible to compile new styles which c
639
749
  npx tailwindcss -o your_project/static/css/styles.css --watch --minify
640
750
  ```
641
751
 
642
- ## Custom Admin Dashboard
752
+ ## Custom admin dashboard
643
753
 
644
754
  The most common thing which needs to be adjusted for each project in admin is the dashboard. By default Unfold does not provide any dashboard components. The default dashboard experience with list of all applications and models is kept with proper styling matching rest of the components but thats it. Anyway, Unfold was created that creation of custom dashboard will be streamlined.
645
755
 
@@ -665,7 +775,7 @@ Create `templates/admin/index.html` in your project and paste the base template
665
775
 
666
776
  Note: In case that it is needed to pass custom variables into dashboard tamplate, check **DASHOARD_CALLBACK** in **UNFOLD** dict.
667
777
 
668
- ## Unfold Development
778
+ ## Unfold development
669
779
 
670
780
  ### Pre-commit
671
781
 
@@ -677,7 +787,7 @@ pre-commit install
677
787
  pre-commit install --hook-type commit-msg
678
788
  ```
679
789
 
680
- ### Poetry Configuration
790
+ ### Poetry configuration
681
791
 
682
792
  To add a new feature or fix the easiest approach is to use django-unfold in combination with Poetry. The process looks like:
683
793
 
@@ -700,9 +810,9 @@ npm run tailwind:build # run once
700
810
 
701
811
  Some components like datepickers, calendars or selectors in admin was not possible to style by overriding html templates so their default styles are overriden in **styles.css**.
702
812
 
703
- None: most of the custom styles localted in style.css are created via `@apply some-tailwind-class;`.
813
+ **Note:** most of the custom styles located in style.css are created via `@apply some-tailwind-class;` as is not possible to manually add CSS class to element which are for example created via jQuery.
704
814
 
705
- # Credits
815
+ ## Credits
706
816
 
707
817
  - [TailwindCSS](https://tailwindcss.com/) - CSS framework
708
818
  - [HTMX](https://htmx.org/) - AJAX communication with backend
@@ -1,5 +1,5 @@
1
1
  unfold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- unfold/admin.py,sha256=tBbpl7OaPtfiWGk0TPUG2CoANmiAd8pn0nJ_RyOUWas,22454
2
+ unfold/admin.py,sha256=vRFfTtKTaGCu-lOqPMc62EK1aVirjapIP2BKRrZUvXw,23085
3
3
  unfold/apps.py,sha256=LXJVMj1WIoQXjRmiz2bFtsVc9gKhdby7UQczdtjieY0,307
4
4
  unfold/checks.py,sha256=hCOJKAaumb-Rzj3IYyMpLi5oBTThciYfjGLMPVB3jak,1835
5
5
  unfold/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -25,6 +25,15 @@ unfold/contrib/forms/static/unfold/forms/js/trix.js,sha256=0tRM-QGKIGPA83Z5zCyCo
25
25
  unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html,sha256=yS8Zy-UrzvZ5RUYwdprQzREffnYq0NlIbXBfZM2UB04,9700
26
26
  unfold/contrib/forms/templates/unfold/forms/wysiwyg.html,sha256=4ZefV6XrjJlUczcuSw8BhvMJUFSZPSXo1IkgkBivh5g,351
27
27
  unfold/contrib/forms/widgets.py,sha256=_81_fsvK-yEsFIqLU59BTIIs2KAJk61pLs7J9sNi1G0,962
28
+ unfold/contrib/guardian/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ unfold/contrib/guardian/apps.py,sha256=m8z-GJ6NmPv8wqQ40qY_L-FhooshSytzkMLf_fktm2U,135
30
+ unfold/contrib/guardian/templates/admin/guardian/model/change_form.html,sha256=hbobVP04sufV8cECaFjC04DSwCK3K2y_BL_8WDBhYD0,483
31
+ unfold/contrib/guardian/templates/admin/guardian/model/field.html,sha256=V9ZgmYiIQAFy3GC464y0iBOHm3SDvEEymbuhT3S0qKU,296
32
+ unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html,sha256=p7GmZOyDqj1urDAh9pyZhatnSpe-G6gXtQ_DpRK-J8k,1426
33
+ unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html,sha256=TOXgGhNqujwl4XUYMXOMfPmTPszNkY4Sb3-7oRAy158,2318
34
+ unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html,sha256=drAeEnr6lh6SgDPC-bkaGtLkd_DxOtcHBSbgb_zaV00,2316
35
+ unfold/contrib/guardian/templates/unfold/guardian/group_form.html,sha256=P8WMC5EejUHV5AxEiIQ2LOGzefLHk5J5UHiNq9wnBgY,4145
36
+ unfold/contrib/guardian/templates/unfold/guardian/user_form.html,sha256=ci7FRrhTEKbFKKxsJ-07_dWXBYz4mqXPoqu5HfqYLaM,4132
28
37
  unfold/contrib/import_export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
38
  unfold/contrib/import_export/apps.py,sha256=TfdmxCH5BxFo0kk-kV6f6xZbUxL3_FXUVlBYp74Eb9o,142
30
39
  unfold/contrib/import_export/forms.py,sha256=dLqLv7YP0i8_CrxupEfh3VncJ8CJHf7O1eQoWFOcydU,672
@@ -39,20 +48,26 @@ unfold/contrib/import_export/templates/admin/import_export/import_errors.html,sh
39
48
  unfold/contrib/import_export/templates/admin/import_export/import_form.html,sha256=wyV-j0EDIxLzFaJaS-OU1YuTYn6536bg5ohILsDcXoc,1312
40
49
  unfold/contrib/import_export/templates/admin/import_export/import_preview.html,sha256=pNuLDW6zc5yOF1jurL2EgR0j05RL9ZVJLZiV4R21GJc,2413
41
50
  unfold/contrib/import_export/templates/admin/import_export/import_validation.html,sha256=1wQOiXN_Ga9VO6GGyl__KEiuJlCh4gTqzZdzIbmKxG0,4880
51
+ unfold/contrib/simple_history/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
+ unfold/contrib/simple_history/apps.py,sha256=eF_KVYb60CAnGgWk2Z1YKYGfgA3TJBMr229qI7e2pgU,153
53
+ unfold/contrib/simple_history/templates/simple_history/_object_history_list.html,sha256=aXOQ1zwsRBlFmzODsZApvMtb8t1IPXim6i4plXUR5XE,5112
54
+ unfold/contrib/simple_history/templates/simple_history/object_history.html,sha256=AZ6uQRr7wKxV_rys5hGTVGYtVS-Fp5eHIqiXYW8FB1c,847
55
+ unfold/contrib/simple_history/templates/simple_history/object_history_form.html,sha256=ii_1lM4wC-GOJhyKkmue4ymxek0gsvRSUK4XecRDezM,2289
56
+ unfold/contrib/simple_history/templates/simple_history/submit_line.html,sha256=Qx3aZcVf93AEUB58QP2GLDfWB1TUQ0GUb_SaJ9C7s6E,1637
42
57
  unfold/dataclasses.py,sha256=JJdGYzQ8MpeOe2FQPJqrMn_UJcxUz1VJgHCuCtkZCA8,199
43
58
  unfold/decorators.py,sha256=BVDlxhZxB4ND3f5-5oiENRTv_W_Q_Eu-gZlsrYKOxiU,3272
44
59
  unfold/exceptions.py,sha256=gcCj1ox61E137bk_0Cqy4YC3SttdPgB-fiJUqpmyHSE,43
45
60
  unfold/forms.py,sha256=pLdtUttPmPxanLXKRqAgTLxLSifKJy43JfGcEyL4m2Y,3303
46
- unfold/settings.py,sha256=T0wmJX0orFslebIWyDUvb3m_e7uUHSE1ves881151u4,1118
47
- unfold/sites.py,sha256=RikhPBpxEqaWsBuTbyTV_uWARTnRVz3k0oi2zlv7-OU,10212
61
+ unfold/settings.py,sha256=NdKo2mxxh7hoD6YFkAdUlmG2Pk4xcKG9jdtqj3U73NI,1149
62
+ unfold/sites.py,sha256=VyrgHO_iwGmH4jGr7iTVEeohNhXWvdRAo7uVGtoqqNM,10349
48
63
  unfold/static/unfold/css/simplebar.css,sha256=yedjIaMP55VAxaX-dEENPKgT4S4TBlsut5zk6WlEPRE,3890
49
- unfold/static/unfold/css/styles.css,sha256=UyUlQpCPPsmxhL9MPC7zTKdLRQZKdcGI0bMQsyhW2vM,73456
64
+ unfold/static/unfold/css/styles.css,sha256=4W8XZiqf8LwTut92GWi8KnvrcrsLkrcqodVjTeCfQQ0,75926
50
65
  unfold/static/unfold/js/alpine.js,sha256=XQltNouEUe1VZDk-nixNjSZDFH9lyKSBiK7BjpTB-0s,41071
51
66
  unfold/static/unfold/js/alpine.persist.js,sha256=84PZYnPi25AFm7wIWRe1gzA74c5Rv2VYASYEfaqKLHI,626
52
67
  unfold/static/unfold/js/app.js,sha256=utNVPAkw4ApD27c_wGNcjv_HPIa7hI_gKiK-Z92YVpA,2519
53
68
  unfold/static/unfold/js/htmx.js,sha256=XOLqvnZiyEx46EW9vaJTBUaaWg8CGVVfXJkVsUmJbpI,42820
54
69
  unfold/static/unfold/js/simplebar.js,sha256=eUk3RAE1plWQQSNaxZzi66kbAj3YdIkgg-Bj1iRO0Y4,65855
55
- unfold/styles.css,sha256=dzrkEUYH894PSUeIlRzX-U1m7NMPUkfuKb1xvfWGa9g,17121
70
+ unfold/styles.css,sha256=tXCBwnSJ3ZAZk390tD6KZdR_ebsLwp_t1XnatQcL4kI,18273
56
71
  unfold/templates/admin/actions.html,sha256=W8mGF_9VjqMsO1RSRdMo5DJvVmhSwEj5xNjamDWbfis,2519
57
72
  unfold/templates/admin/app_index.html,sha256=lVjMIFsspHQ09LGHKfdfg7TlqlL39AX5LbwoeoZjFhk,1335
58
73
  unfold/templates/admin/app_list.html,sha256=lNdqEIYIj3WuuF8ECN7UpRp6E3Qt-1HXt2lrwXrmdv4,3594
@@ -69,9 +84,9 @@ unfold/templates/admin/date_hierarchy.html,sha256=BfUPbsLpHZVa40BHBahz1H9RSVuz36
69
84
  unfold/templates/admin/delete_confirmation.html,sha256=hpa2E14oZEXBBs6W1qdNQuF650TIO2Rhr52Q6UfwVeQ,5166
70
85
  unfold/templates/admin/delete_selected_confirmation.html,sha256=Foka2yvwAMEZre-Kh1KNadRzrCotdKM2U4e6AJQYZu8,4941
71
86
  unfold/templates/admin/edit_inline/stacked.html,sha256=YRgW-bMVIolWeOawpOnWHofdkwLUQIK12AtlEzIczSk,4372
72
- unfold/templates/admin/edit_inline/tabular.html,sha256=1xmfczFMSKXMURr2lHtqRRu_E5mj41rS_EhxgavMZUI,12199
87
+ unfold/templates/admin/edit_inline/tabular.html,sha256=RAioUpBLzoKeZo4oPZ8FiRLu4y3yiLqcC_Hxki4DHKE,12218
73
88
  unfold/templates/admin/filter.html,sha256=UnDlHT2_26DXRKYw3wVgq-i8_FMVov66M-YiJabnyEg,1479
74
- unfold/templates/admin/includes/fieldset.html,sha256=8AVEh0DKHzCChftMJBLUBPWT_uLwO1FfzbUtsZLtdrY,3047
89
+ unfold/templates/admin/includes/fieldset.html,sha256=VNt412BhMzPCAxIv1PZfjKEJ6ApPOD4B9M-i5LrhE-4,2915
75
90
  unfold/templates/admin/includes/object_delete_summary.html,sha256=Nv69SCzyJHFX14iJFfodxKM0IIpQegKZH0fvKB15QJI,468
76
91
  unfold/templates/admin/index.html,sha256=9-mW2yW1ms6DSAKnpMGK--vjfRSeIc-s9xcGU4lkGpM,674
77
92
  unfold/templates/admin/login.html,sha256=zZ1mppzvv9pNxW3O5XgU7rGsa8Oq6Kab5bofqv_FN9M,3494
@@ -81,6 +96,8 @@ unfold/templates/admin/pagination.html,sha256=KWTPV7_hVSZ1374a-pqHXhnOueNQKu1UnS
81
96
  unfold/templates/admin/search_form.html,sha256=BfAJPBzHo6DC6BO53ypZJpnQZBsPjRFp6GeFGOY5LXI,1190
82
97
  unfold/templates/admin/submit_line.html,sha256=_J52WNMzXUC-ONZGLiTZiNWL_Ow5ZIy4prD1_4_-eRM,4186
83
98
  unfold/templates/admin/widgets/clearable_file_input.html,sha256=01z48YtzG_uBIYENMcEJxQaD1xJqaNFXs3nBM0Q28nI,1851
99
+ unfold/templates/admin/widgets/radio.html,sha256=3WcmclQNg7R_pRjEHL1dHkGjAzWlWNYnhHkAirC4nuA,646
100
+ unfold/templates/admin/widgets/radio_option.html,sha256=IZgPx-aWKJuxrSalJ3K50RFd1vwSpb9Qk0yZwfV78_A,368
84
101
  unfold/templates/admin/widgets/related_widget_wrapper.html,sha256=U6RaeR86xbi1AWUrMm1SbjlXGwpC3PZdNLTbk3alxXI,3799
85
102
  unfold/templates/admin/widgets/split_datetime.html,sha256=eXLFZyCv84LCTFWAUhNO3xAIzWvGBvI1ZpYbB38_HOI,862
86
103
  unfold/templates/auth/widgets/read_only_password_hash.html,sha256=Li9efo-3cFC5zj9im0SPfc62R4ZNVPQhs24H1U7xmD8,785
@@ -95,9 +112,10 @@ unfold/templates/unfold/helpers/app_list.html,sha256=vbWGaJaKqtxoZo7jq2zeEZ6UAz5
95
112
  unfold/templates/unfold/helpers/app_list_default.html,sha256=dmg01K-xrlZaA8z0X4ZJNefpiU0VwarJ7Pm9YplUGAk,3280
96
113
  unfold/templates/unfold/helpers/boolean.html,sha256=p_WOlytoXvDwta76WgcV4JSWKpBgKf4amhqmHF798F8,564
97
114
  unfold/templates/unfold/helpers/breadcrumb_item.html,sha256=k_1j57UV0WtzFFlMKaewj4NLbR_DhXI6RzCHThblZLw,234
98
- unfold/templates/unfold/helpers/display_header.html,sha256=FQlQt78fzgZzaVW2E-wJ1vWcpujDx8RgElYj3HKT4dM,258
115
+ unfold/templates/unfold/helpers/display_header.html,sha256=E-yG9ydyb6rRIR5TT4FxekD3qokilfoOwaEaB7np8WI,433
99
116
  unfold/templates/unfold/helpers/display_label.html,sha256=_hAHylpRgUGj2IiwJv3Jhs7vXbgY5fhcCxT-CIcBc3c,2072
100
117
  unfold/templates/unfold/helpers/field.html,sha256=aQnkejALvuQkrIUUB9VuRDdM033fIxjWtT3K8olBb0U,335
118
+ unfold/templates/unfold/helpers/field_readonly.html,sha256=v7-2oSSDgOsuYpP70y8DqdBqbRybubAfSDzstveoBuw,382
101
119
  unfold/templates/unfold/helpers/form_errors.html,sha256=EwerIJptSCWXvtAJ1IZKfEn98qlShBIGavsTThbklAs,266
102
120
  unfold/templates/unfold/helpers/help_text.html,sha256=9WjUjgUOYIOJAnkBaU12dN6CIt80_-MqQxsI-YyqFn8,151
103
121
  unfold/templates/unfold/helpers/history.html,sha256=EFNNFG7mB064br8uWAvm8xB9GhNAiT01cVe9aq6KTmc,1995
@@ -112,6 +130,7 @@ unfold/templates/unfold/helpers/pagination_ellipsis.html,sha256=Ar9Ntf2I_79mIVW5
112
130
  unfold/templates/unfold/helpers/search.html,sha256=T3JLlzEeHTEpX6qfjNQ0cQPW2rtVIOyE9quEyVHVXsA,1382
113
131
  unfold/templates/unfold/helpers/search_results.html,sha256=nv2HDqGvP9aAmQ35Fdq0oiZcDFTXgkLic1AVMLumyU8,725
114
132
  unfold/templates/unfold/helpers/site_icon.html,sha256=O7syHpahHnsiWfmNQOMkYrOIgkJjYViBeeQw3s6PAms,442
133
+ unfold/templates/unfold/helpers/submit.html,sha256=oSzq85LRLhdOlbFtFZFhYm6ucT95u6LunTeSTDClszQ,206
115
134
  unfold/templates/unfold/helpers/tab_list.html,sha256=9RsSXB2K8VACmojn-FxslwWqGhVqLQuNjDGhHHBopRs,2038
116
135
  unfold/templates/unfold/helpers/theme_switch.html,sha256=skkl6fYUnYLM7fAPivHxWjnOnuQSKa-7aDxh7I8dGIg,2266
117
136
  unfold/templates/unfold/helpers/userlinks.html,sha256=tl4jijMso7xEBBCuH_hFJaBvfeexVk5yIKIgTXGs7EY,403
@@ -126,13 +145,13 @@ unfold/templates/unfold/widgets/split_money.html,sha256=AFLUBmzGbY-RXgsfz7gaDxVR
126
145
  unfold/templates/unfold/widgets/textarea.html,sha256=4xIGWb20DuwiwumndByrAyh7xF-ReXKLulSpDImX_cs,459
127
146
  unfold/templates/unfold/widgets/time.html,sha256=Dq9s-Kyfkw7p4TfchR-XFVOX6pOS7GkXPOWuP9CLRVw,106
128
147
  unfold/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
- unfold/templatetags/unfold.py,sha256=YAAkiuq3p8Xce_h3H_oTC829ukSI-SDUgUBS4tY3Lsc,3563
148
+ unfold/templatetags/unfold.py,sha256=ii7yWAZb8g8kcdGD45MScdIHu22GWmoYqul4v2ktmqM,3943
130
149
  unfold/templatetags/unfold_list.py,sha256=pkmVBqsh47T6b86vOjO-I8MgZlE1KeY-ZhIH5Qfksuo,10117
131
150
  unfold/typing.py,sha256=MDuh0ZrS3UQdpmuunTV4_6Z0iwo1f3b2Ixz5ZbG0t_o,577
132
151
  unfold/utils.py,sha256=XU08gcFEP4hY4AOrgiTxXCWJNK9a0_RK9Mwy1SS17Vc,3878
133
152
  unfold/views.py,sha256=Ml3XlEoHLcbEWof59Dw8ihKBMcmp-gBAibThtBFj55A,708
134
- unfold/widgets.py,sha256=oEbIsdojJR3LaPvShl1UEX3TSt2BIwRDbTigDDiGnwc,9571
135
- django_unfold-0.11.0.dist-info/LICENSE,sha256=D2dAcLArwGySIf62-9y-8Q4pcJOiWj8lLpBxYPzDCBc,1069
136
- django_unfold-0.11.0.dist-info/METADATA,sha256=ZtHXkK_5wkxlpebJXcso-W1XgphLovuft2bnFJClefI,28291
137
- django_unfold-0.11.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
138
- django_unfold-0.11.0.dist-info/RECORD,,
153
+ unfold/widgets.py,sha256=fpvOye1j-SRyZVrI19XF2GCNXb8ZJRxXZDxgLqxgoeQ,10186
154
+ django_unfold-0.13.0.dist-info/LICENSE.md,sha256=D2dAcLArwGySIf62-9y-8Q4pcJOiWj8lLpBxYPzDCBc,1069
155
+ django_unfold-0.13.0.dist-info/METADATA,sha256=VpXIv8DfR5ZPVH-_yvNVB5mMpY1PuoCTyzn9VsXS5AI,32704
156
+ django_unfold-0.13.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
157
+ django_unfold-0.13.0.dist-info/RECORD,,
unfold/admin.py CHANGED
@@ -13,6 +13,7 @@ from django.db import models
13
13
  from django.db.models import (
14
14
  BLANK_CHOICE_DASH,
15
15
  ForeignObjectRel,
16
+ JSONField,
16
17
  ManyToManyRel,
17
18
  Model,
18
19
  OneToOneField,
@@ -60,6 +61,7 @@ from .widgets import (
60
61
  UnfoldAdminIntegerRangeWidget,
61
62
  UnfoldAdminMoneyWidget,
62
63
  UnfoldAdminNullBooleanSelectWidget,
64
+ UnfoldAdminRadioSelectWidget,
63
65
  UnfoldAdminSingleDateWidget,
64
66
  UnfoldAdminSingleTimeWidget,
65
67
  UnfoldAdminSplitDateTimeWidget,
@@ -185,11 +187,23 @@ class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
185
187
  self.form.label_suffix,
186
188
  )
187
189
 
188
- def contents(self) -> str:
189
- contents = self._get_contents()
190
+ def is_json(self) -> bool:
191
+ field, obj, model_admin = (
192
+ self.field["field"],
193
+ self.form.instance,
194
+ self.model_admin,
195
+ )
196
+
197
+ try:
198
+ f, attr, value = lookup_field(field, obj, model_admin)
199
+ except (AttributeError, ValueError, ObjectDoesNotExist):
200
+ return False
190
201
 
191
- self._preprocess_field(contents)
202
+ return isinstance(f, JSONField)
192
203
 
204
+ def contents(self) -> str:
205
+ contents = self._get_contents()
206
+ contents = self._preprocess_field(contents)
193
207
  return contents
194
208
 
195
209
  def _get_contents(self) -> str:
@@ -239,7 +253,6 @@ class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
239
253
  and self.field["field"] in self.model_admin.readonly_preprocess_fields
240
254
  ):
241
255
  func = self.model_admin.readonly_preprocess_fields[self.field["field"]]
242
-
243
256
  if isinstance(func, str):
244
257
  contents = import_string(func)(contents)
245
258
  elif callable(func):
@@ -265,9 +278,16 @@ class ModelAdminMixin:
265
278
  def formfield_for_choice_field(
266
279
  self, db_field: Field, request: HttpRequest, **kwargs
267
280
  ) -> TypedChoiceField:
268
- # Overrides widget for CharFields which have choices attribute
269
281
  if "widget" not in kwargs:
270
- kwargs["widget"] = forms.Select(attrs={"class": " ".join(SELECT_CLASSES)})
282
+ if db_field.name in self.radio_fields:
283
+ kwargs["widget"] = UnfoldAdminRadioSelectWidget(
284
+ radio_style=self.radio_fields[db_field.name]
285
+ )
286
+ else:
287
+ kwargs["widget"] = forms.Select(
288
+ attrs={"class": " ".join(SELECT_CLASSES)}
289
+ )
290
+
271
291
  kwargs["choices"] = db_field.get_choices(
272
292
  include_blank=db_field.blank, blank_choice=[("", _("Select value"))]
273
293
  )
File without changes