1 | from django.contrib.admin import widgets
|
---|
2 | from django.contrib.admin.options import get_ul_class
|
---|
3 | from django.contrib.admin.widgets import AutocompleteSelect
|
---|
4 | from django.forms import boundfield, models
|
---|
5 | from django.urls import reverse
|
---|
6 | from django.urls.exceptions import NoReverseMatch
|
---|
7 | from django.utils.text import Truncator
|
---|
8 |
|
---|
9 |
|
---|
10 | class ForeignKeyRawIdWidget(widgets.ForeignKeyRawIdWidget):
|
---|
11 | def format_value(self, value):
|
---|
12 | """Try to return the `pk` if value is an object, otherwise just return
|
---|
13 | the value as fallback."""
|
---|
14 |
|
---|
15 | if value == '' or value is None:
|
---|
16 | return None
|
---|
17 |
|
---|
18 | try:
|
---|
19 | return str(value.pk)
|
---|
20 | except AttributeError:
|
---|
21 | return str(value)
|
---|
22 |
|
---|
23 | def label_and_url_for_value(self, value):
|
---|
24 | """Instead of the original we do not have do a `get()` anymore instead
|
---|
25 | access the instance directly so when value is prefetched this will
|
---|
26 | prevent additional queries."""
|
---|
27 |
|
---|
28 | try:
|
---|
29 | pk = value.pk
|
---|
30 | meta = value._meta
|
---|
31 | except AttributeError:
|
---|
32 | # Fallback for compatibility with plain pk values
|
---|
33 | return super().label_and_url_for_value(value)
|
---|
34 |
|
---|
35 | try:
|
---|
36 | url = reverse(
|
---|
37 | '%s:%s_%s_change' % (
|
---|
38 | self.admin_site.name,
|
---|
39 | meta.app_label,
|
---|
40 | meta.object_name.lower(),
|
---|
41 | ),
|
---|
42 | args=(pk,)
|
---|
43 | )
|
---|
44 | except NoReverseMatch:
|
---|
45 | url = '' # Admin not registered for target model.
|
---|
46 |
|
---|
47 | return Truncator(value).words(14), url
|
---|
48 |
|
---|
49 |
|
---|
50 | class BoundField(boundfield.BoundField):
|
---|
51 | def value(self):
|
---|
52 | """Return the instance instead of plain value if possible.
|
---|
53 |
|
---|
54 | In order for `ForeignKeyRawIdWidget` to access the model instance directly
|
---|
55 | we grab if from the form if available."""
|
---|
56 |
|
---|
57 | if type(self.field.widget) == ForeignKeyRawIdWidget:
|
---|
58 | try:
|
---|
59 | return getattr(self.form.instance, self.name)
|
---|
60 | except AttributeError:
|
---|
61 | pass
|
---|
62 |
|
---|
63 | # Otherwise default behaviour
|
---|
64 | return super().value()
|
---|
65 |
|
---|
66 |
|
---|
67 | class ModelChoiceField(models.ModelChoiceField):
|
---|
68 | def get_bound_field(self, form, field_name):
|
---|
69 | """Return our custom `BoundField`."""
|
---|
70 |
|
---|
71 | return BoundField(form, self, field_name)
|
---|
72 |
|
---|
73 |
|
---|
74 | class RawIdWidgetAdminMixin:
|
---|
75 | def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
---|
76 | """ModelAdmin mixin that uses a custom `ForeignKeyRawIdWidget`.
|
---|
77 |
|
---|
78 | This prevents extra queries when the queryset has been prefetched using
|
---|
79 | `prefetch_related()`. Only works when `raw_id_fields` is filled."""
|
---|
80 |
|
---|
81 | if db_field.name not in self.raw_id_fields:
|
---|
82 | # If we are not using raw_id_fields then skip the whole thing
|
---|
83 | return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
---|
84 |
|
---|
85 | db = kwargs.get('using')
|
---|
86 |
|
---|
87 | if 'widget' not in kwargs:
|
---|
88 | if db_field.name in self.get_autocomplete_fields(request):
|
---|
89 | kwargs['widget'] = AutocompleteSelect(db_field.remote_field, self.admin_site, using=db)
|
---|
90 | elif db_field.name in self.raw_id_fields:
|
---|
91 | # Using our modified ForeignKeyRawIdWidget here instead
|
---|
92 | kwargs['widget'] = ForeignKeyRawIdWidget(db_field.remote_field, self.admin_site, using=db)
|
---|
93 | elif db_field.name in self.radio_fields:
|
---|
94 | kwargs['widget'] = widgets.AdminRadioSelect(attrs={
|
---|
95 | 'class': get_ul_class(self.radio_fields[db_field.name]),
|
---|
96 | })
|
---|
97 | kwargs['empty_label'] = _('None') if db_field.blank else None
|
---|
98 |
|
---|
99 | if 'queryset' not in kwargs:
|
---|
100 | queryset = self.get_field_queryset(db, db_field, request)
|
---|
101 | if queryset is not None:
|
---|
102 | kwargs['queryset'] = queryset
|
---|
103 |
|
---|
104 | if isinstance(db_field.remote_field.model, str):
|
---|
105 | raise ValueError("Cannot create form field for %r yet, because "
|
---|
106 | "its related model %r has not been loaded yet" %
|
---|
107 | (db_field.name, db_field.remote_field.model))
|
---|
108 | return super(type(db_field), db_field).formfield(**{
|
---|
109 | # Using our modified ModelChoiceField here instead
|
---|
110 | 'form_class': ModelChoiceField,
|
---|
111 | 'queryset': db_field.remote_field.model._default_manager.using(db),
|
---|
112 | 'to_field_name': db_field.remote_field.field_name,
|
---|
113 | **kwargs,
|
---|
114 | 'blank': db_field.blank,
|
---|
115 | })
|
---|