django-smartbase-admin 1.0.38__py3-none-any.whl → 1.0.40__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -427,6 +427,11 @@ class SBAdminListAction(SBAdminAction):
427
427
  data: row.get(data, None)
428
428
  for data in self.view.sbadmin_list_display_data
429
429
  }
430
+ # Include supporting_annotates values in additional_data
431
+ for field in visible_columns:
432
+ if field.supporting_annotates:
433
+ for key in field.supporting_annotates.keys():
434
+ additional_data[key] = row.get(key, None)
430
435
  for field_key, value in row.items():
431
436
  if field_key in field_key_field_map:
432
437
  field = field_key_field_map[field_key]
@@ -11,7 +11,12 @@ from django.contrib.admin.widgets import (
11
11
  ForeignKeyRawIdWidget,
12
12
  )
13
13
  from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
14
- from django.core.exceptions import ValidationError, ImproperlyConfigured
14
+ from django.core.exceptions import (
15
+ FieldDoesNotExist,
16
+ ImproperlyConfigured,
17
+ ValidationError,
18
+ )
19
+ from django.db.models import ForeignKey, OneToOneField
15
20
  from django.template.loader import render_to_string
16
21
  from django.urls import reverse
17
22
  from django.utils.formats import get_format
@@ -359,13 +364,19 @@ class SBAdminAutocompleteWidget(
359
364
  form = None
360
365
  field_name = None
361
366
  initialised = None
367
+ allow_add = None
368
+ create_value_field = None
362
369
  default_create_data = None
370
+ forward_to_create = None
363
371
  reload_on_save = None
364
372
  REQUEST_CREATED_DATA_KEY = "autocomplete_created_data"
365
373
 
366
374
  def __init__(self, form_field=None, *args, **kwargs):
367
375
  attrs = kwargs.pop("attrs", None)
368
376
  self.reload_on_save = kwargs.pop("reload_on_save", False)
377
+ self.allow_add = kwargs.pop("allow_add", None)
378
+ self.create_value_field = kwargs.pop("create_value_field", None)
379
+ self.forward_to_create = kwargs.pop("forward_to_create", [])
369
380
  super().__init__(form_field, *args, **kwargs)
370
381
  self.attrs = {} if attrs is None else attrs.copy()
371
382
  if self.multiselect and self.allow_add:
@@ -595,6 +606,34 @@ class SBAdminAutocompleteWidget(
595
606
 
596
607
  return forward_data
597
608
 
609
+ def get_forward_data_to_create(self, request, forward_data):
610
+ forward_data_to_create = {}
611
+ for field_name in self.forward_to_create:
612
+ value = forward_data.get(field_name)
613
+ if value is None:
614
+ continue
615
+ # If forwarding a FK value from the parent form (e.g. for dependent dropdowns),
616
+ # store it under `<field>_id` so `Model(**kwargs)` accepts the raw PK.
617
+ store_key = field_name
618
+ form_model = getattr(getattr(self, "form", None), "model", None)
619
+ if form_model is not None:
620
+ try:
621
+ form_model_field = form_model._meta.get_field(field_name)
622
+ except FieldDoesNotExist:
623
+ form_model_field = None
624
+ if isinstance(form_model_field, (ForeignKey, OneToOneField)):
625
+ store_key = form_model_field.attname
626
+
627
+ forward_data_to_create[store_key] = self.parse_value_from_input(
628
+ request, value
629
+ )
630
+ if not self.is_multiselect():
631
+ forward_data_to_create[store_key] = next(
632
+ iter(forward_data_to_create[store_key]), None
633
+ )
634
+
635
+ return forward_data_to_create
636
+
598
637
  def value_from_datadict(self, data, files, name):
599
638
  input_value = super().value_from_datadict(data, files, name)
600
639
  threadsafe_request = SBAdminThreadLocalService.get_request()
@@ -630,7 +669,11 @@ class SBAdminAutocompleteWidget(
630
669
  )
631
670
  self.form_field.queryset = qs
632
671
  parsed_value = self.validate(
633
- parsed_value, qs, threadsafe_request, parsed_is_create
672
+ parsed_value,
673
+ qs,
674
+ threadsafe_request,
675
+ forward_data,
676
+ parsed_is_create,
634
677
  )
635
678
 
636
679
  return parsed_value
@@ -638,14 +681,18 @@ class SBAdminAutocompleteWidget(
638
681
  def should_create_new_obj(self):
639
682
  return self.allow_add and self.create_value_field
640
683
 
641
- def create_new_obj(self, value, queryset, is_create):
684
+ def create_new_obj(self, value, queryset, request, forward_data):
642
685
  if isinstance(value, list):
643
686
  # TODO: multiselect creation
644
687
  return self.form_field.to_python(value)
645
688
  else:
689
+ forward_data_to_create = self.get_forward_data_to_create(
690
+ request, forward_data
691
+ )
646
692
  data_to_create = {
647
693
  self.create_value_field: value,
648
694
  **self.default_create_data,
695
+ **forward_data_to_create,
649
696
  }
650
697
  new_obj = queryset.model.objects.create(**data_to_create)
651
698
  try:
@@ -658,12 +705,12 @@ class SBAdminAutocompleteWidget(
658
705
  params={"value": value},
659
706
  )
660
707
 
661
- def validate(self, value, queryset, request, is_create=False):
708
+ def validate(self, value, queryset, request, forward_data, is_create=False):
662
709
  is_create_value = (
663
710
  True in is_create if isinstance(is_create, list) else is_create
664
711
  )
665
712
  if is_create_value and self.should_create_new_obj():
666
- new_object = self.create_new_obj(value, queryset, is_create)
713
+ new_object = self.create_new_obj(value, queryset, request, forward_data)
667
714
  request.request_data.additional_data[self.REQUEST_CREATED_DATA_KEY] = (
668
715
  request.request_data.additional_data.get(
669
716
  self.REQUEST_CREATED_DATA_KEY, {}
@@ -1,9 +1,10 @@
1
1
  from enum import Enum
2
2
 
3
3
  from django.template.defaultfilters import date, time
4
+ from django.utils import timezone
5
+ from django.utils.html import format_html, format_html_join
4
6
  from django.utils.safestring import mark_safe
5
7
  from django.utils.translation import gettext_lazy as _
6
- from django.utils import timezone
7
8
 
8
9
 
9
10
  class BadgeType(Enum):
@@ -39,21 +40,26 @@ def datetime_formatter_with_format(date_format=None, time_format=None):
39
40
 
40
41
  def boolean_formatter(object_id, value):
41
42
  if value:
42
- return mark_safe(
43
- f'<span class="badge badge-simple badge-positive">{_("Yes")}</span>'
43
+ return format_html(
44
+ '<span class="badge badge-simple badge-positive">{}</span>', _("Yes")
44
45
  )
45
- return mark_safe(f'<span class="badge badge-simple badge-neutral">{_("No")}</span>')
46
+ return format_html(
47
+ '<span class="badge badge-simple badge-neutral">{}</span>', _("No")
48
+ )
46
49
 
47
50
 
48
51
  def format_array(value_list, separator="", badge_type: BadgeType = BadgeType.NOTICE):
49
- result = ""
50
52
  if not value_list:
51
- return result
52
- for value in value_list:
53
- if not value:
54
- continue
55
- result += f'<span class="badge badge-simple badge-{badge_type.value} mr-4">{value}</span>{separator}'
56
- return mark_safe(result)
53
+ return ""
54
+
55
+ # `separator` is intended to be an internal constant (e.g. "" or "<br>").
56
+ # We mark it safe so HTML separators render as HTML rather than being escaped.
57
+ sep = mark_safe(separator) if separator else ""
58
+ return format_html_join(
59
+ sep,
60
+ '<span class="badge badge-simple badge-{} mr-4">{}</span>',
61
+ ((badge_type.value, value) for value in value_list if value),
62
+ )
57
63
 
58
64
 
59
65
  def array_badge_formatter(object_id, value_list):
@@ -61,14 +67,18 @@ def array_badge_formatter(object_id, value_list):
61
67
 
62
68
 
63
69
  def newline_separated_array_badge_formatter(object_id, value_list):
64
- return mark_safe(f'<div>{format_array(value_list, separator="<br>")}</div>')
70
+ return format_html("<div>{}</div>", format_array(value_list, separator="<br>"))
65
71
 
66
72
 
67
73
  def rich_text_formatter(object_id, value):
68
- return mark_safe(
69
- f'<div style="max-width: 500px; white-space: normal;">{value}</div>'
74
+ # Intentionally renders HTML (e.g. from a rich text editor field).
75
+ return format_html(
76
+ '<div style="max-width: 500px; white-space: normal;">{}</div>',
77
+ mark_safe(value) if value else "",
70
78
  )
71
79
 
72
80
 
73
81
  def link_formatter(object_id, value):
74
- return mark_safe(f'<a href="{value}">{value}</a>')
82
+ if not value:
83
+ return ""
84
+ return format_html('<a href="{0}">{0}</a>', value)
@@ -492,7 +492,6 @@ class AutocompleteFilterWidget(
492
492
  forward = None
493
493
  label_lambda = None
494
494
  value_lambda = None
495
- allow_add = False
496
495
  hide_clear_button = False
497
496
  search_query_lambda = None
498
497
  create_value_field = None
@@ -515,10 +514,8 @@ class AutocompleteFilterWidget(
515
514
  value_lambda=None,
516
515
  multiselect=None,
517
516
  forward=None,
518
- allow_add=None,
519
517
  hide_clear_button=None,
520
518
  search_query_lambda=None,
521
- create_value_field=None,
522
519
  **kwargs,
523
520
  ) -> None:
524
521
  super().__init__(template_name, default_value, **kwargs)
@@ -534,8 +531,6 @@ class AutocompleteFilterWidget(
534
531
  self.multiselect = multiselect if multiselect is not None else self.multiselect
535
532
  self.multiselect = self.multiselect if self.multiselect is not None else True
536
533
  self.forward = forward or self.forward
537
- self.allow_add = allow_add or self.allow_add
538
- self.create_value_field = create_value_field or self.create_value_field
539
534
  self.hide_clear_button = (
540
535
  hide_clear_button
541
536
  if hide_clear_button is not None