Skip to content

Instantly share code, notes, and snippets.

@jpic
Created January 29, 2016 14:42
Show Gist options
  • Save jpic/f4cd57d061a05ac34ade to your computer and use it in GitHub Desktop.
Save jpic/f4cd57d061a05ac34ade to your computer and use it in GitHub Desktop.
"""
ModelForm mixin that supports virtual fields, ie. GenericForeignKey.
By default, Django's ModelForm will not update the instance's virtual fields
even if a matching form field was given to the form. For example::
class YourForm(forms.ModelForm):
gfk_name = SomeField()
instance = YourModel.objects.get(pk=4)
# Django doesn't populate virtual fields by default, compensate manually:
initial = {'gfk_name': instance.gfk_name}
# Use the form as usual
form = YourForm(request.POST, instance=instance, initial=initial)
instance = form.save()
# instance.gfk_name will not be set, compensate manually:
instance.gfk_ctype = ContentType.objects.get_for_model(
instance.cleaned_data['gfk_name'])
instance.gfk_object_id = instance.cleaned_data['gfk_name'].pk
With YourForm(GenericForeignKeyModelFormMixin, forms.ModelForm) instead of
YourForm(forms.ModelForm), GenericForeignKeys are supported automatically.
"""
from django.contrib.contenttypes.models import ContentType
class GenericForeignKeyModelFormMixin(object):
"""
Enable virtual field (generic foreign key) handling in django's ModelForm.
- treat virtual fields like GenericForeignKey as normal fields,
- when setting a GenericForeignKey value, also set the object id and
content type id fields.
Probably, django doesn't do that for legacy reasons: virtual fields were
added after ModelForm and simply nobody asked django to add virtual field
support in ModelForm.
"""
def __init__(self, *args, **kwargs):
"""
The constructor adds virtual field values to
:py:attr:`django:django.forms.Form.initial`
"""
super(GenericForeignKeyModelFormMixin, self).__init__(*args, **kwargs)
# do what model_to_dict doesn't
for field in self._meta.model._meta.virtual_fields:
try:
self.initial[field.name] = getattr(
self.instance,
field.name,
None
)
except:
continue
def _post_clean(self):
"""
What ModelForm does, but also set virtual field values from
cleaned_data.
"""
super(GenericForeignKeyModelFormMixin, self)._post_clean()
# take care of virtual fields since django doesn't
for field in self._meta.model._meta.virtual_fields:
value = self.cleaned_data.get(field.name, None)
if value:
setattr(self.instance, field.name, value)
self.cleaned_data[field.ct_field] = \
ContentType.objects.get_for_model(value)
self.cleaned_data[field.fk_field] = value.pk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment