Skip to content

Commit 5978dec

Browse files
authored
feat: parallel default admin support (#379)
1 parent 41ebb0b commit 5978dec

File tree

13 files changed

+58
-33
lines changed

13 files changed

+58
-33
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Did you decide to start using Unfold but you don't have time to make the switch
3535
- **Colors:** possibility to override default color scheme
3636
- **Third party packages:** default support for multiple popular applications
3737
- **Environment label**: distinguish between environments by displaying a label
38+
- **Parallel admin**: support for having default admin in parallel with Unfold
3839
- **VS Code**: project configuration and development container is included
3940

4041
## Table of contents <!-- omit from toc -->

src/unfold/admin.py

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.contrib.admin import TabularInline as BaseTabularInline
99
from django.contrib.admin import display, helpers
1010
from django.contrib.admin.utils import lookup_field
11+
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
1112
from django.core.exceptions import ObjectDoesNotExist
1213
from django.db import models
1314
from django.db.models import (
@@ -39,17 +40,16 @@
3940
from django.utils.text import capfirst
4041
from django.utils.translation import gettext_lazy as _
4142
from django.views import View
42-
from unfold.utils import display_for_field
4343

4444
from .checks import UnfoldModelAdminChecks
4545
from .dataclasses import UnfoldAction
4646
from .exceptions import UnfoldException
4747
from .forms import ActionForm
4848
from .settings import get_config
4949
from .typing import FieldsetsType
50+
from .utils import display_for_field
5051
from .widgets import (
5152
CHECKBOX_LABEL_CLASSES,
52-
INPUT_CLASSES,
5353
LABEL_CLASSES,
5454
SELECT_CLASSES,
5555
UnfoldAdminBigIntegerFieldWidget,
@@ -62,6 +62,7 @@
6262
UnfoldAdminMoneyWidget,
6363
UnfoldAdminNullBooleanSelectWidget,
6464
UnfoldAdminRadioSelectWidget,
65+
UnfoldAdminSelect,
6566
UnfoldAdminSingleDateWidget,
6667
UnfoldAdminSingleTimeWidget,
6768
UnfoldAdminSplitDateTimeWidget,
@@ -142,6 +143,11 @@ class UnfoldAdminField(helpers.AdminField):
142143
def label_tag(self) -> SafeText:
143144
classes = []
144145

146+
if not self.field.field.widget.__class__.__name__.startswith(
147+
"Unfold"
148+
) and not self.field.field.widget.template_name.startswith("unfold"):
149+
return super().label_tag()
150+
145151
# TODO load config from current AdminSite (override Fieldline.__iter__ method)
146152
for lang, flag in get_config()["EXTENSIONS"]["modeltranslation"][
147153
"flags"
@@ -175,6 +181,9 @@ def label_tag(self) -> SafeText:
175181

176182
class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
177183
def label_tag(self) -> SafeText:
184+
if not isinstance(self.model_admin, ModelAdmin):
185+
return super().label_tag()
186+
178187
attrs = {
179188
"class": " ".join(LABEL_CLASSES + ["mb-2"]),
180189
}
@@ -285,9 +294,7 @@ def formfield_for_choice_field(
285294
radio_style=self.radio_fields[db_field.name]
286295
)
287296
else:
288-
kwargs["widget"] = forms.Select(
289-
attrs={"class": " ".join(SELECT_CLASSES)}
290-
)
297+
kwargs["widget"] = UnfoldAdminSelect()
291298

292299
kwargs["choices"] = db_field.get_choices(
293300
include_blank=db_field.blank, blank_choice=[("", _("Select value"))]
@@ -301,16 +308,12 @@ def formfield_for_foreignkey(
301308
# Overrides widgets for all related fields
302309
if "widget" not in kwargs:
303310
if db_field.name in self.raw_id_fields:
304-
kwargs["widget"] = forms.TextInput(
305-
attrs={"class": " ".join(INPUT_CLASSES)}
306-
)
311+
kwargs["widget"] = UnfoldAdminTextInputWidget()
307312
elif (
308313
db_field.name not in self.get_autocomplete_fields(request)
309314
and db_field.name not in self.radio_fields
310315
):
311-
kwargs["widget"] = forms.Select(
312-
attrs={"class": " ".join(SELECT_CLASSES)}
313-
)
316+
kwargs["widget"] = UnfoldAdminSelect()
314317
kwargs["empty_label"] = _("Select value")
315318

316319
return super().formfield_for_foreignkey(db_field, request, **kwargs)
@@ -323,9 +326,7 @@ def formfield_for_manytomany(
323326
) -> ModelMultipleChoiceField:
324327
if "widget" not in kwargs:
325328
if db_field.name in self.raw_id_fields:
326-
kwargs["widget"] = forms.TextInput(
327-
attrs={"class": " ".join(INPUT_CLASSES)}
328-
)
329+
kwargs["widget"] = UnfoldAdminTextInputWidget()
329330

330331
form_field = super().formfield_for_manytomany(db_field, request, **kwargs)
331332

@@ -342,9 +343,7 @@ def formfield_for_nullboolean_field(
342343
self, db_field: Field, request: HttpRequest, **kwargs
343344
) -> Optional[Field]:
344345
if "widget" not in kwargs:
345-
kwargs["widget"] = forms.NullBooleanSelect(
346-
attrs={"class": " ".join(SELECT_CLASSES)}
347-
)
346+
kwargs["widget"] = UnfoldAdminNullBooleanSelectWidget()
348347

349348
return db_field.formfield(**kwargs)
350349

@@ -354,7 +353,14 @@ def formfield_for_dbfield(
354353
if isinstance(db_field, models.BooleanField) and db_field.null is True:
355354
return self.formfield_for_nullboolean_field(db_field, request, **kwargs)
356355

357-
return super().formfield_for_dbfield(db_field, request, **kwargs)
356+
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
357+
358+
if formfield and isinstance(formfield.widget, RelatedFieldWidgetWrapper):
359+
formfield.widget.template_name = (
360+
"unfold/widgets/related_widget_wrapper.html"
361+
)
362+
363+
return formfield
358364

359365

360366
class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
@@ -546,7 +552,8 @@ def changeform_view(
546552
"title": action.description,
547553
"attrs": action.method.attrs,
548554
"path": reverse(
549-
f"admin:{action.action_name}", args=(object_id,)
555+
f"{self.admin_site.name}:{action.action_name}",
556+
args=(object_id,),
550557
),
551558
}
552559
)
@@ -570,7 +577,7 @@ def changelist_view(
570577
{
571578
"title": action.description,
572579
"attrs": action.method.attrs,
573-
"path": reverse(f"admin:{action.action_name}"),
580+
"path": reverse(f"{self.admin_site.name}:{action.action_name}"),
574581
}
575582
for action in self.get_actions_list(request)
576583
]
@@ -579,7 +586,7 @@ def changelist_view(
579586
{
580587
"title": action.description,
581588
"attrs": action.method.attrs,
582-
"raw_path": f"admin:{action.action_name}",
589+
"raw_path": f"{self.admin_site.name}:{action.action_name}",
583590
}
584591
for action in self.get_actions_row(request)
585592
]

src/unfold/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77

88
class DefaultAppConfig(AppConfig):
99
name = "unfold"
10+
default = True
1011

1112
def ready(self):
1213
site = UnfoldAdminSite()
1314

1415
admin.site = site
1516
sites.site = site
17+
18+
19+
class BasicAppConfig(AppConfig):
20+
name = "unfold"

src/unfold/sites.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ def password_change(
205205

206206
from .forms import AdminOwnPasswordChangeForm
207207

208-
url = reverse("admin:password_change_done", current_app=self.name)
208+
url = reverse(f"{self.name}:password_change_done", current_app=self.name)
209209
defaults = {
210210
"form_class": AdminOwnPasswordChangeForm,
211211
"success_url": url,
@@ -224,9 +224,9 @@ def _get_is_active(link: str) -> bool:
224224
if not isinstance(link, str):
225225
link = str(link)
226226

227-
if link in request.path and link != reverse_lazy("admin:index"):
227+
if link in request.path and link != reverse_lazy(f"{self.name}:index"):
228228
return True
229-
elif link == request.path == reverse_lazy("admin:index"):
229+
elif link == request.path == reverse_lazy(f"{self.name}:index"):
230230
return True
231231

232232
return False

src/unfold/static/unfold/css/styles.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/unfold/templates/admin/change_form.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
{% csrf_token %}
6666

6767
{% if actions_detail %}
68-
<div class="bg-gray-50 flex justify-end -mt-6 mb-4 p-3 rounded-md dark:bg-gray-800">
68+
<div class="bg-gray-50 flex justify-end mb-4 p-3 rounded-md dark:bg-gray-800">
6969
{% for action in actions_detail %}
7070
<a href="{{ action.path }}" class="bg-white text-gray-500 border cursor-pointer flex font-medium items-center px-3 py-2 mr-3 rounded-md shadow-sm text-sm dark:bg-gray-900 dark:border dark:border-gray-700 dark:text-gray-400"
7171
{% for attr_name, attr_value in action.attrs.items %}
@@ -89,7 +89,6 @@
8989
<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">
9090
{% endif %}
9191

92-
9392
{% include "unfold/helpers/messages/errornote.html" with errors=errors %}
9493
{% include "unfold/helpers/messages/error.html" with errors=adminform.form.non_field_errors %}
9594

src/unfold/templates/admin/edit_inline/tabular.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ <h2 class="bg-gray-100 border border-transparent font-semibold mb-6 px-4 py-3 ro
109109
{% with is_last_col=forloop.last %}
110110
{% for field in line %}
111111
{% if field.is_readonly or not field.field.is_hidden %}
112-
<td{% if field.field.name %} class="field-{{ field.field.name }}{% if field.field.errors|length > 0 %} errors{% endif %}{% if inline_admin_form.original %} p-3 lg:py-3{% else %} py-3{% endif %}{% if field.is_checkbox %} align-middle{% else %} align-top{% endif %} {% if is_last_row and not inline_admin_formset.has_add_permission %}{% if is_last_col %}border-0 {% else %}border-b lg:border-0{% endif %}{% else %}border-b{% endif %} border-gray-200 flex items-center before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 before:w-72 lg:before:hidden font-normal px-3 text-left text-sm lg:table-cell dark:border-gray-800"{% endif %} data-label="{{ field.field.label }}">
112+
<td{% if field.field.name %} class="field-{{ field.field.name }}{% if field.field.errors|length > 0 %} errors{% endif %}{% if inline_admin_form.original %} p-3 lg:py-3{% else %} py-3{% endif %}{% if field.is_checkbox %} align-middle{% else %} align-top{% endif %} {% if is_last_row and not inline_admin_formset.has_add_permission %}{% if is_last_col %}border-0 {% else %}border-b lg:border-0{% endif %}{% else %}border-b{% endif %} border-gray-200 flex items-center before:capitalize before:content-[attr(data-label)] before:mr-auto before:text-gray-500 before:w-72 lg:before:hidden font-normal px-3 text-left text-sm lg:table-cell dark:border-gray-800 {% if field.field.is_hidden %} !hidden{% endif %}"{% endif %} data-label="{{ field.field.label }}">
113113
{% if field.is_readonly %}
114-
<p class="bg-gray-50 border font-medium max-w-lg px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm truncate whitespace-nowrap dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800">
114+
<div class="bg-gray-50 border font-medium max-w-lg px-3 py-2 rounded-md shadow-sm text-gray-500 text-sm truncate whitespace-nowrap dark:border-gray-700 dark:text-gray-400 dark:bg-gray-800">
115115
{{ field.contents }}
116-
</p>
116+
</div>
117117
{% else %}
118118
{{ field.field }}
119119

0 commit comments

Comments
 (0)