Changeset 8279
- Timestamp:
- 08/09/08 23:03:01 (1 year ago)
- Files:
-
- django/trunk/django/contrib/admin/options.py (modified) (1 diff)
- django/trunk/django/contrib/contenttypes/generic.py (modified) (2 diffs)
- django/trunk/docs/admin.txt (modified) (1 diff)
- django/trunk/docs/contenttypes.txt (modified) (2 diffs)
- django/trunk/tests/modeltests/generic_relations/models.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/contrib/admin/options.py
r8274 r8279 133 133 If kwargs are given, they're passed to the form Field's constructor. 134 134 """ 135 135 136 136 # If the field specifies choices, we don't need to look for special 137 137 # admin widgets - we just need to use a select widget of some kind. django/trunk/django/contrib/contenttypes/generic.py
r8223 r8279 7 7 from django.db import connection 8 8 from django.db.models import signals 9 from django.db import models 9 10 from django.db.models.fields.related import RelatedField, Field, ManyToManyRel 10 11 from django.db.models.loading import get_model 11 12 from django.utils.functional import curry 13 14 from django.forms import ModelForm 15 from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance 16 from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets 12 17 13 18 class GenericForeignKey(object): … … 274 279 def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True): 275 280 self.to = to 276 self.num_in_admin = 0277 281 self.related_name = related_name 278 self.filter_interface = None279 282 self.limit_choices_to = limit_choices_to or {} 280 283 self.edit_inline = False 281 self.raw_id_admin = False282 284 self.symmetrical = symmetrical 283 285 self.multiple = True 284 assert not (self.raw_id_admin and self.filter_interface), \ 285 "Generic relations may not use both raw_id_admin and filter_interface" 286 287 class BaseGenericInlineFormSet(BaseModelFormSet): 288 """ 289 A formset for generic inline objects to a parent. 290 """ 291 ct_field_name = "content_type" 292 ct_fk_field_name = "object_id" 293 294 def __init__(self, data=None, files=None, instance=None, save_as_new=None): 295 opts = self.model._meta 296 self.instance = instance 297 self.rel_name = '-'.join(( 298 opts.app_label, opts.object_name.lower(), 299 self.ct_field.name, self.ct_fk_field.name, 300 )) 301 super(BaseGenericInlineFormSet, self).__init__( 302 queryset=self.get_queryset(), data=data, files=files, 303 prefix=self.rel_name 304 ) 305 306 def get_queryset(self): 307 # Avoid a circular import. 308 from django.contrib.contenttypes.models import ContentType 309 if self.instance is None: 310 return self.model._default_manager.empty() 311 return self.model._default_manager.filter(**{ 312 self.ct_field.name: ContentType.objects.get_for_model(self.instance), 313 self.ct_fk_field.name: self.instance.pk, 314 }) 315 316 def save_new(self, form, commit=True): 317 # Avoid a circular import. 318 from django.contrib.contenttypes.models import ContentType 319 kwargs = { 320 self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk, 321 self.ct_fk_field.get_attname(): self.instance.pk, 322 } 323 new_obj = self.model(**kwargs) 324 return save_instance(form, new_obj, commit=commit) 325 326 def generic_inlineformset_factory(model, form=ModelForm, 327 formset=BaseGenericInlineFormSet, 328 ct_field="content_type", fk_field="object_id", 329 fields=None, exclude=None, 330 extra=3, can_order=False, can_delete=True, 331 max_num=0, 332 formfield_callback=lambda f: f.formfield()): 333 """ 334 Returns an ``GenericInlineFormSet`` for the given kwargs. 335 336 You must provide ``ct_field`` and ``object_id`` if they different from the 337 defaults ``content_type`` and ``object_id`` respectively. 338 """ 339 opts = model._meta 340 # Avoid a circular import. 341 from django.contrib.contenttypes.models import ContentType 342 # if there is no field called `ct_field` let the exception propagate 343 ct_field = opts.get_field(ct_field) 344 if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType: 345 raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field) 346 fk_field = opts.get_field(fk_field) # let the exception propagate 347 if exclude is not None: 348 exclude.extend([ct_field.name, fk_field.name]) 349 else: 350 exclude = [ct_field.name, fk_field.name] 351 FormSet = modelformset_factory(model, form=form, 352 formfield_callback=formfield_callback, 353 formset=formset, 354 extra=extra, can_delete=can_delete, can_order=can_order, 355 fields=fields, exclude=exclude, max_num=max_num) 356 FormSet.ct_field = ct_field 357 FormSet.ct_fk_field = fk_field 358 return FormSet 359 360 class GenericInlineModelAdmin(InlineModelAdmin): 361 ct_field = "content_type" 362 ct_fk_field = "object_id" 363 formset = BaseGenericInlineFormSet 364 365 def get_formset(self, request, obj=None): 366 if self.declared_fieldsets: 367 fields = flatten_fieldsets(self.declared_fieldsets) 368 else: 369 fields = None 370 defaults = { 371 "ct_field": self.ct_field, 372 "fk_field": self.ct_fk_field, 373 "form": self.form, 374 "formfield_callback": self.formfield_for_dbfield, 375 "formset": self.formset, 376 "extra": self.extra, 377 "can_delete": True, 378 "can_order": False, 379 "fields": fields, 380 } 381 return generic_inlineformset_factory(self.model, **defaults) 382 383 class GenericStackedInline(GenericInlineModelAdmin): 384 template = 'admin/edit_inline/stacked.html' 385 386 class GenericTabularInline(GenericInlineModelAdmin): 387 template = 'admin/edit_inline/tabular.html' 388 django/trunk/docs/admin.txt
r8273 r8279 786 786 either the ``Person`` or the ``Group`` detail pages. 787 787 788 Using generic relations as an inline 789 ------------------------------------ 790 791 It is possible to use an inline with generically related objects. Let's say 792 you have the following models:: 793 794 class Image(models.Model): 795 image = models.ImageField(upload_to="images") 796 content_type = models.ForeignKey(ContentType) 797 object_id = models.PositiveIntegerField() 798 content_object = generic.GenericForeignKey("content_type", "object_id") 799 800 class Product(models.Model): 801 name = models.CharField(max_length=100) 802 803 If you want to allow editing and creating ``Image`` instance on the ``Product`` 804 add/change views you can simply use ``GenericInlineModelAdmin`` provided by 805 ``django.contrib.contenttypes.generic``. In your ``admin.py`` for this 806 example app:: 807 808 from django.contrib import admin 809 from django.contrib.contenttypes import generic 810 811 from myproject.myapp.models import Image, Product 812 813 class ImageInline(generic.GenericTabularInline): 814 model = Image 815 816 class ProductAdmin(admin.ModelAdmin): 817 inlines = [ 818 ImageInline, 819 ] 820 821 admin.site.register(Product, ProductAdmin) 822 823 ``django.contrib.contenttypes.generic`` provides both a ``GenericTabularInline`` 824 and ``GenericStackedInline`` and behave just like any other inline. See the 825 `contenttypes documentation`_ for more specific information. 826 827 .. _contenttypes documentation: ../contenttypes/ 828 788 829 ``AdminSite`` objects 789 830 ===================== django/trunk/docs/contenttypes.txt
r8042 r8279 73 73 74 74 Let's look at an example to see how this works. If you already have 75 the contenttypes application installed, and then add `the sites 76 application`_ to your ``INSTALLED_APPS`` setting and run ``manage.py 77 syncdb`` to install it, the model ``django.contrib.sites.models.Site`` 78 will be installed into your database. Along with it a new instance 79 of ``ContentType`` will be created withthe following values:75 the contenttypes application installed, and then add `the sites application`_ 76 to your ``INSTALLED_APPS`` setting and run ``manage.py syncdb`` to install it, 77 the model ``django.contrib.sites.models.Site`` will be installed into your 78 database. Along with it a new instance of ``ContentType`` will be created with 79 the following values: 80 80 81 81 * ``app_label`` will be set to ``'sites'`` (the last part of the Python … … 262 262 the example above, this means that if a ``Bookmark`` object were deleted, any 263 263 ``TaggedItem`` objects pointing at it would be deleted at the same time. 264 265 Generic relations in forms and admin 266 ------------------------------------ 267 268 ``django.contrib.contenttypes.genric`` provides both a ``GenericInlineFormSet`` 269 and ``GenericInlineModelAdmin``. This enables the use of generic relations in 270 forms and the admin. See the `model formset`_ and `admin`_ documentation for 271 more information. 272 273 ``GenericInlineModelAdmin`` options 274 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 275 276 The ``GenericInlineModelAdmin`` class inherits all properties from an 277 ``InlineModelAdmin`` class. However, it adds a couple of its own for working 278 with the generic relation: 279 280 * ``ct_field`` - The name of the ``ContentType`` foreign key field on the 281 model. Defaults to ``content_type``. 282 283 * ``ct_fk_field`` - The name of the integer field that represents the ID 284 of the related object. Defaults to ``object_id``. 285 286 .. _model formset: ../modelforms/ 287 .. _admin: ../admin/ django/trunk/tests/modeltests/generic_relations/models.py
r8170 r8279 192 192 >>> Comparison.objects.all() 193 193 [<Comparison: tiger is stronger than None>] 194 195 # GenericInlineFormSet tests ################################################## 196 197 >>> from django.contrib.contenttypes.generic import generic_inlineformset_factory 198 199 >>> GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1) 200 >>> formset = GenericFormSet(instance=Animal()) 201 >>> for form in formset.forms: 202 ... print form.as_p() 203 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p> 204 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p> 205 206 >>> formset = GenericFormSet(instance=platypus) 207 >>> for form in formset.forms: 208 ... print form.as_p() 209 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50" /></p> 210 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="5" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p> 211 <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50" /></p> 212 <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p> 213 194 214 """}
