kpsul/utils/forms.py
2019-01-14 22:41:38 +01:00

63 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
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