64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
|
class KeepUnselectableModelFormMixin:
|
||
|
"""
|
||
|
Keep unselectable items of queryset-based fields.
|
||
|
|
||
|
Mixin for 'ModelForm'.
|
||
|
|
||
|
Attribute
|
||
|
keep_unselectable_fields (list of field names)
|
||
|
|
||
|
This adding is performed in 'save' method. Specifically, if 'commit' arg
|
||
|
is False, it's done in 'save_m2m' method.
|
||
|
|
||
|
These fields must have a 'queryset' attribute (the selectable items), like
|
||
|
'ModelMultipleChoiceField' (default field for ManyToMany model fields).
|
||
|
"""
|
||
|
keep_unselectable_fields = []
|
||
|
|
||
|
def get_unselectable(self, field_name):
|
||
|
"""
|
||
|
Returns 'field_name' model field items of instance which can't be
|
||
|
selected with the corresponding form field.
|
||
|
|
||
|
Should be used before 'form.save' call, or 'form.save_m2m' call if
|
||
|
'commit=False' was passed as argument to 'form.save'.
|
||
|
"""
|
||
|
if self.instance.pk:
|
||
|
previous = getattr(self.instance, field_name).all()
|
||
|
selectable = self.fields[field_name].queryset
|
||
|
if callable(selectable):
|
||
|
selectable = selectable()
|
||
|
return previous.exclude(pk__in=[o.pk for o in selectable])
|
||
|
else:
|
||
|
# Instance is being created, there is no previous item.
|
||
|
return []
|
||
|
|
||
|
def save(self, commit=True):
|
||
|
# Use 'commit=False' to get the 'save_m2m' method.
|
||
|
instance = super().save(commit=False)
|
||
|
|
||
|
_save_m2m = self.save_m2m
|
||
|
|
||
|
def save_m2m():
|
||
|
# Get the unselectable items.
|
||
|
# Force evaluate because those items are going to change.
|
||
|
unselectable_f = {
|
||
|
f_name: list(self.get_unselectable(f_name))
|
||
|
for f_name in self.keep_unselectable_fields
|
||
|
}
|
||
|
# Default 'save_m2m' use 'set' method of m2m relationships with
|
||
|
# fields' cleaned data.
|
||
|
_save_m2m()
|
||
|
# Add the unselectable elements.
|
||
|
for f_name, unselectable in unselectable_f.items():
|
||
|
getattr(instance, f_name).add(*unselectable)
|
||
|
|
||
|
# Implement the default behavior of 'save' method, with our own
|
||
|
# 'save_m2m'.
|
||
|
if commit:
|
||
|
instance.save()
|
||
|
save_m2m()
|
||
|
else:
|
||
|
self.save_m2m = save_m2m
|
||
|
return instance
|