django-unfold 0.12.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 (32) hide show
  1. {django_unfold-0.12.0.dist-info → django_unfold-0.13.0.dist-info}/METADATA +209 -102
  2. {django_unfold-0.12.0.dist-info → django_unfold-0.13.0.dist-info}/RECORD +32 -13
  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 +3 -7
  25. unfold/templates/admin/widgets/radio.html +20 -0
  26. unfold/templates/admin/widgets/radio_option.html +12 -0
  27. unfold/templates/unfold/helpers/field_readonly.html +9 -0
  28. unfold/templates/unfold/helpers/submit.html +3 -0
  29. unfold/templatetags/unfold.py +14 -0
  30. unfold/widgets.py +19 -0
  31. /django_unfold-0.12.0.dist-info/LICENSE → /django_unfold-0.13.0.dist-info/LICENSE.md +0 -0
  32. {django_unfold-0.12.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.12.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,73 +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
- """
366
- Third argument is short text which will appear as prefix in circle
367
- """
368
- return "First main heading", "Smaller additional description", "AB"
369
- ```
370
-
371
311
  ## Actions
372
312
 
373
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.
@@ -398,13 +338,13 @@ Unfold also uses custom `@action` decorator, supporting 2 more parameters in com
398
338
  This section provides explanation of how the action handler functions should be constructed for Unfold actions.
399
339
  For default actions, follow official Django admin documentation.
400
340
 
401
- #### For submit row action
341
+ #### For submit row action <!-- omit from toc -->
402
342
 
403
343
  Submit row actions work a bit differently when compared to other custom Unfold actions.
404
344
  These actions first invoke form save (same as if you hit `Save` button) and then lets you
405
345
  perform additional logic on already saved instance.
406
346
 
407
- #### For global, row and detail action
347
+ #### For global, row and detail action <!-- omit from toc -->
408
348
 
409
349
  All these actions are based on custom URLs generated for each of them. Handler function for these views is
410
350
  basically function based view.
@@ -502,6 +442,10 @@ class UserAdmin(ModelAdmin):
502
442
 
503
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.
504
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
+
505
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.
506
450
 
507
451
  ```python
@@ -516,8 +460,6 @@ from unfold.contrib.filters.admin import (
516
460
  RangeNumericFilter,
517
461
  SingleNumericFilter,
518
462
  SliderNumericFilter,
519
- RangeDateFilter,
520
- RangeDateTimeFilter,
521
463
  )
522
464
 
523
465
 
@@ -539,8 +481,6 @@ class YourModelAdmin(ModelAdmin):
539
481
  ("field_B", RangeNumericFilter), # Numeric range search, __gte and __lte lookup
540
482
  ("field_C", SliderNumericFilter), # Numeric range filter but with slider
541
483
  ("field_D", CustomSliderNumericFilter), # Numeric filter with custom attributes
542
- ("field_E", RangeDateFilter), # Date filter
543
- ("field_F", RangeDateTimeFilter), # Datetime filter
544
484
  CustomRangeNumericListFilter, # Numeric range search not restricted to a model field
545
485
  )
546
486
 
@@ -548,13 +488,150 @@ class YourModelAdmin(ModelAdmin):
548
488
  return super().get_queryset().annotate(items_count=Count("item", distinct=True))
549
489
  ```
550
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
+
551
580
  ## Third party packages
552
581
 
553
- ### 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
554
629
 
555
- 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.
556
631
 
557
- 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.
558
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.
559
636
 
560
637
  ```python
@@ -568,6 +645,36 @@ class ExampleAdmin(ModelAdmin, ImportExportModelAdmin):
568
645
  export_form_class = ExportForm
569
646
  ```
570
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
+
571
678
  ## User Admin Form
572
679
 
573
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.
@@ -590,7 +697,7 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
590
697
  change_password_form = AdminPasswordChangeForm
591
698
  ```
592
699
 
593
- ## Adding Custom Styles and Scripts
700
+ ## Adding custom styles and scripts
594
701
 
595
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.
596
703
 
@@ -609,7 +716,7 @@ UNFOLD = {
609
716
  }
610
717
  ```
611
718
 
612
- ## Project Level Tailwind Stylesheet
719
+ ## Project level Tailwind stylesheet
613
720
 
614
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.
615
722
 
@@ -642,7 +749,7 @@ Once the configuration file is set, it is possible to compile new styles which c
642
749
  npx tailwindcss -o your_project/static/css/styles.css --watch --minify
643
750
  ```
644
751
 
645
- ## Custom Admin Dashboard
752
+ ## Custom admin dashboard
646
753
 
647
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.
648
755
 
@@ -668,7 +775,7 @@ Create `templates/admin/index.html` in your project and paste the base template
668
775
 
669
776
  Note: In case that it is needed to pass custom variables into dashboard tamplate, check **DASHOARD_CALLBACK** in **UNFOLD** dict.
670
777
 
671
- ## Unfold Development
778
+ ## Unfold development
672
779
 
673
780
  ### Pre-commit
674
781
 
@@ -680,7 +787,7 @@ pre-commit install
680
787
  pre-commit install --hook-type commit-msg
681
788
  ```
682
789
 
683
- ### Poetry Configuration
790
+ ### Poetry configuration
684
791
 
685
792
  To add a new feature or fix the easiest approach is to use django-unfold in combination with Poetry. The process looks like:
686
793
 
@@ -703,9 +810,9 @@ npm run tailwind:build # run once
703
810
 
704
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**.
705
812
 
706
- 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.
707
814
 
708
- # Credits
815
+ ## Credits
709
816
 
710
817
  - [TailwindCSS](https://tailwindcss.com/) - CSS framework
711
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=eXgzvmodY-SjSR37YZz6q3XQ6_IZo7XQsZvH2U7QALY,73345
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=r5DX-xdGMW0dlib1D-4EGchCvjg04_Uq5lRQ5lIlbyc,3074
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
@@ -98,6 +115,7 @@ unfold/templates/unfold/helpers/breadcrumb_item.html,sha256=k_1j57UV0WtzFFlMKaew
98
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.12.0.dist-info/LICENSE,sha256=D2dAcLArwGySIf62-9y-8Q4pcJOiWj8lLpBxYPzDCBc,1069
136
- django_unfold-0.12.0.dist-info/METADATA,sha256=ZOBlpMrv4smf6R6hkVGzP61bnGewL-Ujij6taH5Kum4,28396
137
- django_unfold-0.12.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
138
- django_unfold-0.12.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
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class GuardianConfig(AppConfig):
5
+ name = "unfold.contrib.guardian"
6
+ label = "unfoldguardian"