|
7 | 7 | from django.contrib.admin import StackedInline as BaseStackedInline |
8 | 8 | from django.contrib.admin import TabularInline as BaseTabularInline |
9 | 9 | from django.contrib.admin import display, helpers |
10 | | -from django.contrib.admin.utils import lookup_field, quote |
11 | 10 | from django.contrib.admin.widgets import RelatedFieldWidgetWrapper |
12 | | -from django.core.exceptions import ObjectDoesNotExist |
13 | 11 | from django.db import models |
14 | | -from django.db.models import ( |
15 | | - BLANK_CHOICE_DASH, |
16 | | - ForeignObjectRel, |
17 | | - JSONField, |
18 | | - ManyToManyRel, |
19 | | - Model, |
20 | | - OneToOneField, |
21 | | -) |
| 12 | +from django.db.models import BLANK_CHOICE_DASH, Model |
22 | 13 | from django.db.models.fields import Field |
23 | 14 | from django.db.models.fields.related import ForeignKey, ManyToManyField |
24 | 15 | from django.forms import Form |
25 | 16 | from django.forms.fields import TypedChoiceField |
26 | | -from django.forms.models import ( |
27 | | - ModelChoiceField, |
28 | | - ModelMultipleChoiceField, |
29 | | -) |
30 | | -from django.forms.utils import flatatt |
| 17 | +from django.forms.models import ModelChoiceField, ModelMultipleChoiceField |
31 | 18 | from django.forms.widgets import SelectMultiple |
32 | 19 | from django.http import HttpRequest, HttpResponse |
33 | 20 | from django.shortcuts import redirect |
34 | | -from django.template.defaultfilters import linebreaksbr |
35 | 21 | from django.template.response import TemplateResponse |
36 | | -from django.urls import NoReverseMatch, URLPattern, path, reverse |
37 | | -from django.utils.html import conditional_escape, format_html |
38 | | -from django.utils.module_loading import import_string |
39 | | -from django.utils.safestring import SafeText, mark_safe |
40 | | -from django.utils.text import capfirst |
| 22 | +from django.urls import URLPattern, path, reverse |
| 23 | +from django.utils.safestring import mark_safe |
41 | 24 | from django.utils.translation import gettext_lazy as _ |
42 | 25 | from django.views import View |
43 | 26 |
|
44 | 27 | from .checks import UnfoldModelAdminChecks |
45 | 28 | from .dataclasses import UnfoldAction |
46 | 29 | from .exceptions import UnfoldException |
| 30 | +from .fields import UnfoldAdminField, UnfoldAdminReadonlyField |
47 | 31 | from .forms import ActionForm |
48 | | -from .settings import get_config |
49 | 32 | from .typing import FieldsetsType |
50 | | -from .utils import display_for_field |
51 | 33 | from .widgets import ( |
52 | | - CHECKBOX_LABEL_CLASSES, |
53 | | - LABEL_CLASSES, |
54 | 34 | SELECT_CLASSES, |
55 | 35 | UnfoldAdminBigIntegerFieldWidget, |
56 | 36 | UnfoldAdminDecimalFieldWidget, |
|
90 | 70 | except ImportError: |
91 | 71 | HAS_MONEY = False |
92 | 72 |
|
93 | | -checkbox = UnfoldBooleanWidget({"class": "action-select"}, lambda value: False) |
94 | | - |
95 | 73 | FORMFIELD_OVERRIDES = { |
96 | 74 | models.DateTimeField: { |
97 | 75 | "form_class": forms.SplitDateTimeField, |
|
141 | 119 | } |
142 | 120 | ) |
143 | 121 |
|
144 | | - |
145 | | -class UnfoldAdminField(helpers.AdminField): |
146 | | - def label_tag(self) -> SafeText: |
147 | | - classes = [] |
148 | | - if not self.field.field.widget.__class__.__name__.startswith( |
149 | | - "Unfold" |
150 | | - ) and not self.field.field.widget.template_name.startswith("unfold"): |
151 | | - return super().label_tag() |
152 | | - |
153 | | - # TODO load config from current AdminSite (override Fieldline.__iter__ method) |
154 | | - for lang, flag in get_config()["EXTENSIONS"]["modeltranslation"][ |
155 | | - "flags" |
156 | | - ].items(): |
157 | | - if f"[{lang}]" in self.field.label: |
158 | | - self.field.label = self.field.label.replace(f"[{lang}]", flag) |
159 | | - break |
160 | | - |
161 | | - contents = conditional_escape(self.field.label) |
162 | | - |
163 | | - if self.is_checkbox: |
164 | | - classes.append(" ".join(CHECKBOX_LABEL_CLASSES)) |
165 | | - else: |
166 | | - classes.append(" ".join(LABEL_CLASSES)) |
167 | | - |
168 | | - if self.field.field.required: |
169 | | - classes.append("required") |
170 | | - |
171 | | - attrs = {"class": " ".join(classes)} if classes else {} |
172 | | - required = mark_safe(' <span class="text-red-600">*</span>') |
173 | | - |
174 | | - return self.field.label_tag( |
175 | | - contents=mark_safe(contents), |
176 | | - attrs=attrs, |
177 | | - label_suffix=required if self.field.field.required else "", |
178 | | - ) |
179 | | - |
| 122 | +checkbox = UnfoldBooleanWidget({"class": "action-select"}, lambda value: False) |
180 | 123 |
|
181 | 124 | helpers.AdminField = UnfoldAdminField |
182 | 125 |
|
183 | | - |
184 | | -class UnfoldAdminReadonlyField(helpers.AdminReadonlyField): |
185 | | - def label_tag(self) -> SafeText: |
186 | | - if not isinstance(self.model_admin, ModelAdmin) and not isinstance( |
187 | | - self.model_admin, ModelAdminMixin |
188 | | - ): |
189 | | - return super().label_tag() |
190 | | - |
191 | | - attrs = { |
192 | | - "class": " ".join(LABEL_CLASSES + ["mb-2"]), |
193 | | - } |
194 | | - |
195 | | - label = self.field["label"] |
196 | | - |
197 | | - return format_html( |
198 | | - "<label{}>{}{}</label>", |
199 | | - flatatt(attrs), |
200 | | - capfirst(label), |
201 | | - self.form.label_suffix, |
202 | | - ) |
203 | | - |
204 | | - def is_json(self) -> bool: |
205 | | - field, obj, model_admin = ( |
206 | | - self.field["field"], |
207 | | - self.form.instance, |
208 | | - self.model_admin, |
209 | | - ) |
210 | | - |
211 | | - try: |
212 | | - f, attr, value = lookup_field(field, obj, model_admin) |
213 | | - except (AttributeError, ValueError, ObjectDoesNotExist): |
214 | | - return False |
215 | | - |
216 | | - return isinstance(f, JSONField) |
217 | | - |
218 | | - def contents(self) -> str: |
219 | | - contents = self._get_contents() |
220 | | - contents = self._preprocess_field(contents) |
221 | | - return contents |
222 | | - |
223 | | - def get_admin_url(self, remote_field, remote_obj): |
224 | | - url_name = f"admin:{remote_field.model._meta.app_label}_{remote_field.model._meta.model_name}_change" |
225 | | - try: |
226 | | - url = reverse( |
227 | | - url_name, |
228 | | - args=[quote(remote_obj.pk)], |
229 | | - current_app=self.model_admin.admin_site.name, |
230 | | - ) |
231 | | - return format_html( |
232 | | - '<a href="{}" class="text-primary-600 underline">{}</a>', |
233 | | - url, |
234 | | - remote_obj, |
235 | | - ) |
236 | | - except NoReverseMatch: |
237 | | - return str(remote_obj) |
238 | | - |
239 | | - def _get_contents(self) -> str: |
240 | | - from django.contrib.admin.templatetags.admin_list import _boolean_icon |
241 | | - |
242 | | - field, obj, model_admin = ( |
243 | | - self.field["field"], |
244 | | - self.form.instance, |
245 | | - self.model_admin, |
246 | | - ) |
247 | | - try: |
248 | | - f, attr, value = lookup_field(field, obj, model_admin) |
249 | | - except (AttributeError, ValueError, ObjectDoesNotExist): |
250 | | - result_repr = self.empty_value_display |
251 | | - else: |
252 | | - if field in self.form.fields: |
253 | | - widget = self.form[field].field.widget |
254 | | - # This isn't elegant but suffices for contrib.auth's |
255 | | - # ReadOnlyPasswordHashWidget. |
256 | | - if getattr(widget, "read_only", False): |
257 | | - return widget.render(field, value) |
258 | | - |
259 | | - if f is None: |
260 | | - if getattr(attr, "boolean", False): |
261 | | - result_repr = _boolean_icon(value) |
262 | | - else: |
263 | | - if hasattr(value, "__html__"): |
264 | | - result_repr = value |
265 | | - else: |
266 | | - result_repr = linebreaksbr(value) |
267 | | - else: |
268 | | - if isinstance(f.remote_field, ManyToManyRel) and value is not None: |
269 | | - result_repr = ", ".join(map(str, value.all())) |
270 | | - elif ( |
271 | | - isinstance(f.remote_field, (ForeignObjectRel, OneToOneField)) |
272 | | - and value is not None |
273 | | - ): |
274 | | - result_repr = self.get_admin_url(f.remote_field, value) |
275 | | - elif isinstance(f, models.URLField): |
276 | | - return format_html( |
277 | | - '<a href="{}" class="text-primary-600 underline">{}</a>', |
278 | | - value, |
279 | | - value, |
280 | | - ) |
281 | | - else: |
282 | | - result_repr = display_for_field(value, f, self.empty_value_display) |
283 | | - return conditional_escape(result_repr) |
284 | | - result_repr = linebreaksbr(result_repr) |
285 | | - return conditional_escape(result_repr) |
286 | | - |
287 | | - def _preprocess_field(self, contents: str) -> str: |
288 | | - if ( |
289 | | - hasattr(self.model_admin, "readonly_preprocess_fields") |
290 | | - and self.field["field"] in self.model_admin.readonly_preprocess_fields |
291 | | - ): |
292 | | - func = self.model_admin.readonly_preprocess_fields[self.field["field"]] |
293 | | - if isinstance(func, str): |
294 | | - contents = import_string(func)(contents) |
295 | | - elif callable(func): |
296 | | - contents = func(contents) |
297 | | - |
298 | | - return contents |
299 | | - |
300 | | - |
301 | 126 | helpers.AdminReadonlyField = UnfoldAdminReadonlyField |
302 | 127 |
|
303 | 128 |
|
|
0 commit comments