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