| | 284 | |
| | 285 | from django.newforms.models import BaseModelFormSet, modelformset_factory, save_instance |
| | 286 | from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets |
| | 287 | |
| | 288 | class GenericInlineFormset(BaseModelFormSet): |
| | 289 | """A formset for child objects related to a parent.""" |
| | 290 | def __init__(self, instance=None, data=None, files=None): |
| | 291 | self.instance = instance |
| | 292 | self.rel_name = '-'.join( ( self.model._meta.app_label, self.model._meta.object_name.lower(), self.ct.name, self.obj_id.name ) ) |
| | 293 | super(GenericInlineFormset, self).__init__(queryset=self.get_queryset(), data=data, files=files, prefix=self.rel_name) |
| | 294 | |
| | 295 | def get_queryset(self): |
| | 296 | # This import is done here to avoid circular import importing this module |
| | 297 | from django.contrib.contenttypes.models import ContentType |
| | 298 | if self.instance is None: |
| | 299 | return [] |
| | 300 | return self.model._default_manager.filter( **{ |
| | 301 | self.ct.name : ContentType.objects.get_for_model(self.instance), |
| | 302 | self.obj_id.name : self.instance.pk |
| | 303 | }) |
| | 304 | |
| | 305 | def save_new(self, form, commit=True): |
| | 306 | # This import is done here to avoid circular import importing this module |
| | 307 | from django.contrib.contenttypes.models import ContentType |
| | 308 | kwargs = { |
| | 309 | self.ct.get_attname(): ContentType.objects.get_for_model(self.instance).pk, |
| | 310 | self.obj_id.get_attname(): self.instance.pk, |
| | 311 | } |
| | 312 | new_obj = self.model(**kwargs) |
| | 313 | return save_instance(form, new_obj, commit=commit) |
| | 314 | |
| | 315 | class GenericInlineModelAdmin(InlineModelAdmin): |
| | 316 | ct_field_name = None |
| | 317 | id_field_name = None |
| | 318 | formset = GenericInlineFormset |
| | 319 | |
| | 320 | def get_formset( self, request, obj=None ): |
| | 321 | from django.db.models import ForeignKey |
| | 322 | from django.contrib.contenttypes.models import ContentType |
| | 323 | if self.declared_fieldsets: |
| | 324 | fields = flatten_fieldsets(self.declared_fieldsets) |
| | 325 | else: |
| | 326 | fields = None |
| | 327 | |
| | 328 | opts = self.model._meta |
| | 329 | # if there is no field called `ct_field_name` let the exception propagate |
| | 330 | ct = opts.get_field(self.ct_field_name) |
| | 331 | if not isinstance(ct, ForeignKey) or ct.rel.to != ContentType: |
| | 332 | raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % (self.ct_field_name)) |
| | 333 | obj_id = opts.get_field(self.id_field_name) # let the exception propagate |
| | 334 | |
| | 335 | FormSet = modelformset_factory(self.model, form=self.form, |
| | 336 | formfield_callback=self.formfield_for_dbfield, |
| | 337 | formset=self.formset, |
| | 338 | extra=self.extra, fields=fields, exclude=[ct.name, obj_id.name] ) |
| | 339 | FormSet.ct = ct |
| | 340 | FormSet.obj_id = obj_id |
| | 341 | return FormSet |
| | 342 | |
| | 343 | class GenericStackedInline(GenericInlineModelAdmin): |
| | 344 | template = 'admin/edit_inline/stacked.html' |
| | 345 | |
| | 346 | class GenericTabularInline(GenericInlineModelAdmin): |
| | 347 | template = 'admin/edit_inline/tabular.html' |
| | 348 | |