-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
Expand file tree
/
Copy pathforms.py
More file actions
133 lines (124 loc) · 3.86 KB
/
forms.py
File metadata and controls
133 lines (124 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.forms import ModelForm, modelformset_factory
from django.forms.models import BaseModelFormSet
class BaseGenericInlineFormSet(BaseModelFormSet):
"""
A formset for generic inline objects to a parent.
"""
def __init__(
self,
data=None,
files=None,
instance=None,
save_as_new=False,
prefix=None,
queryset=None,
**kwargs,
):
opts = self.model._meta
self.instance = instance
self.rel_name = (
opts.app_label
+ "-"
+ opts.model_name
+ "-"
+ self.ct_field.name
+ "-"
+ self.ct_fk_field.name
)
self.save_as_new = save_as_new
if self.instance is None or not self.instance._is_pk_set():
qs = self.model._default_manager.none()
else:
if queryset is None:
queryset = self.model._default_manager
qs = queryset.filter(
**{
self.ct_field.name: ContentType.objects.get_for_model(
self.instance, for_concrete_model=self.for_concrete_model
),
self.ct_fk_field.name: self.instance.pk,
}
)
super().__init__(queryset=qs, data=data, files=files, prefix=prefix, **kwargs)
def initial_form_count(self):
if self.save_as_new:
return 0
return super().initial_form_count()
@classmethod
def get_default_prefix(cls):
opts = cls.model._meta
return (
opts.app_label
+ "-"
+ opts.model_name
+ "-"
+ cls.ct_field.name
+ "-"
+ cls.ct_fk_field.name
)
def save_new(self, form, commit=True):
setattr(
form.instance,
self.ct_field.attname,
ContentType.objects.get_for_model(self.instance).pk,
)
setattr(form.instance, self.ct_fk_field.attname, self.instance.pk)
return form.save(commit=commit)
def generic_inlineformset_factory(
model,
form=ModelForm,
formset=BaseGenericInlineFormSet,
ct_field="content_type",
fk_field="object_id",
fields=None,
exclude=None,
extra=3,
can_order=False,
can_delete=True,
max_num=None,
formfield_callback=None,
validate_max=False,
for_concrete_model=True,
min_num=None,
validate_min=False,
absolute_max=None,
can_delete_extra=True,
):
"""
Return a ``GenericInlineFormSet`` for the given kwargs.
You must provide ``ct_field`` and ``fk_field`` if they are different from
the defaults ``content_type`` and ``object_id`` respectively.
"""
opts = model._meta
# if there is no field called `ct_field` let the exception propagate
ct_field = opts.get_field(ct_field)
if (
not isinstance(ct_field, models.ForeignKey)
or ct_field.remote_field.model != ContentType
):
raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field)
fk_field = opts.get_field(fk_field) # let the exception propagate
exclude = [*(exclude or []), ct_field.name, fk_field.name]
FormSet = modelformset_factory(
model,
form=form,
formfield_callback=formfield_callback,
formset=formset,
extra=extra,
can_delete=can_delete,
can_order=can_order,
fields=fields,
exclude=exclude,
max_num=max_num,
validate_max=validate_max,
min_num=min_num,
validate_min=validate_min,
absolute_max=absolute_max,
can_delete_extra=can_delete_extra,
)
FormSet.ct_field = ct_field
FormSet.ct_fk_field = fk_field
FormSet.for_concrete_model = for_concrete_model
return FormSet