| 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, data=None, files=None, instance=None, save_as_new=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, can_delete=True, can_order=False, |
| 339 | fields=fields, exclude=[ct.name, obj_id.name] ) |
| 340 | FormSet.ct = ct |
| 341 | FormSet.obj_id = obj_id |
| 342 | return FormSet |
| 343 | |
| 344 | class GenericStackedInline(GenericInlineModelAdmin): |
| 345 | template = 'admin/edit_inline/stacked.html' |
| 346 | |
| 347 | class GenericTabularInline(GenericInlineModelAdmin): |
| 348 | template = 'admin/edit_inline/tabular.html' |
| 349 | |