Ticket #9976: generic-foreign-key-widget.diff
File generic-foreign-key-widget.diff, 11.2 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/media/js/admin/RelatedObjectLookups.js
diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js index d201f39..4bcff7f 100644
a b function showRelatedObjectLookupPopup(triggeringLink) { 41 41 return false; 42 42 } 43 43 44 function showGenericRelatedObjectLookupPopup(ct_select, triggering_link, url_base) { 45 var url = content_types[ct_select.options[ct_select.selectedIndex].value]; 46 if (url != undefined) { 47 triggering_link.href = url_base + url; 48 return showRelatedObjectLookupPopup(triggering_link); 49 } 50 return false; 51 } 52 44 53 function dismissRelatedLookupPopup(win, chosenId) { 45 54 var name = windowname_to_id(win.name); 46 55 var elem = document.getElementById(name); -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3d60b9d..4ba49bc 100644
a b class BaseModelAdmin(object): 38 38 filter_horizontal = () 39 39 radio_fields = {} 40 40 prepopulated_fields = {} 41 generic_fields = () 41 42 42 43 def formfield_for_dbfield(self, db_field, **kwargs): 43 44 """ … … class BaseModelAdmin(object): 63 64 else: 64 65 # Otherwise, use the default select widget. 65 66 return db_field.formfield(**kwargs) 67 68 # For generic foreign keys marked as generic_fields we use a special widget 69 if db_field.name in [f.fk_field for f in self.model._meta.virtual_fields if f.name in self.generic_fields]: 70 for gfk in self.model._meta.virtual_fields: 71 if gfk.fk_field == db_field.name: 72 break 73 return db_field.formfield(label=capfirst(gfk.name.replace('_', ' ')), widget=widgets.GenericForeignKeyRawIdWidget(gfk.ct_field, self.admin_site._registry.keys())) 66 74 67 75 # For DateTimeFields, use a special field and widget. 68 76 if isinstance(db_field, models.DateTimeField): -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index ccade8a..779c91d 100644
a b def validate(cls, model): 102 102 if not isinstance(getattr(cls, attr), bool): 103 103 raise ImproperlyConfigured("'%s.%s' should be a boolean." 104 104 % (cls.__name__, attr)) 105 106 if hasattr(cls, 'generic_fields'): 107 check_isseq(cls, 'generic_fields', cls.generic_fields) 108 for i, field in enumerate(cls.generic_fields): 109 if field not in [f.name for f in model._meta.virtual_fields]: 110 raise ImproperlyConfigured("Item number %d in %s.generic_fields" \ 111 "is not a GenericForeignKey on %s" % (i, cls.__name__, 112 model.__name__)) 105 113 106 114 # inlines = [] 107 115 if hasattr(cls, 'inlines'): -
django/contrib/admin/widgets.py
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 291bee0..aa69d70 100644
a b class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput): 278 278 if attrs is not None: 279 279 final_attrs.update(attrs) 280 280 super(AdminCommaSeparatedIntegerFieldWidget, self).__init__(attrs=final_attrs) 281 282 class GenericForeignKeyRawIdWidget(ForeignKeyRawIdWidget): 283 def __init__(self, ct_field, cts = [], attrs=None): 284 self.ct_field = ct_field 285 self.cts = cts 286 forms.TextInput.__init__(self, attrs) 287 288 def render(self, name, value, attrs=None): 289 if attrs is None: 290 attrs = {} 291 related_url = '../../../' 292 params = self.url_parameters() 293 if params: 294 url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.iteritems()]) 295 else: 296 url = '' 297 if 'class' not in attrs: 298 attrs['class'] = 'vForeignKeyRawIdAdminField' 299 output = [forms.TextInput.render(self, name, value, attrs)] 300 output.append(""" 301 <a href="%(related)s%(url)s" class="related-lookup" id="lookup_id_%(name)s" onclick="return showGenericRelatedObjectLookupPopup(document.getElementById('id_%(ct_field)s'), this, '%(related)s%(url)s');"> """ 302 % {'related': related_url, 'url': url, 'name': name, 'ct_field': self.ct_field}) 303 output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup'))) 304 if value: 305 output.append(self.label_for_value(value)) 306 307 from django.contrib.contenttypes.models import ContentType 308 content_types = """ 309 <script type="text/javascript"> 310 var content_types = new Array(); 311 %s 312 </script> 313 """ % ('\n'.join(["content_types[%s] = '%s/%s/';" % (ContentType.objects.get_for_model(ct).id, ct._meta.app_label, ct._meta.object_name.lower()) for ct in self.cts])) 314 return mark_safe(u''.join(output) + content_types) 315 316 def url_parameters(self): 317 return {} -
docs/ref/contrib/admin.txt
diff --git a/docs/ref/contrib/admin.txt b/docs/ref/contrib/admin.txt index f24dc46..7fb1ae4 100644
a b list of fields that should be displayed as a horizontal filter interface. See 238 238 Same as ``filter_horizontal``, but is a vertical display of the filter 239 239 interface. 240 240 241 ``generic_fields`` 242 ~~~~~~~~~~~~~~~~~~ 243 244 This is a list of ``GenericForeignKeys`` that will have an interface similar to 245 that of ``raw_id_fields``. 246 241 247 ``list_display`` 242 248 ~~~~~~~~~~~~~~~~ 243 249 -
tests/regressiontests/admin_widgets/models.py
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index ddfc6c2..bf478f0 100644
a b 2 2 from django.conf import settings 3 3 from django.db import models 4 4 from django.core.files.storage import default_storage 5 from django.contrib.contenttypes import generic 6 from django.contrib.contenttypes.models import ContentType 5 7 6 8 class Member(models.Model): 7 9 name = models.CharField(max_length=100) … … class Inventory(models.Model): 41 43 def __unicode__(self): 42 44 return self.name 43 45 46 class Image(models.Model): 47 image = models.ImageField(upload_to='images') 48 description = models.CharField(max_length=200) 49 50 content_type = models.ForeignKey(ContentType) 51 object_id = models.PositiveIntegerField() 52 object = generic.GenericForeignKey('content_type', 'object_id') 53 54 def __unicode__(self): 55 return u"%s - %s" % (self.image.name, self.description[:100]) 56 44 57 __test__ = {'WIDGETS_TESTS': """ 45 58 >>> from datetime import datetime 46 59 >>> from django.utils.html import escape, conditional_escape … … __test__ = {'WIDGETS_TESTS': """ 48 61 >>> from django.contrib.admin.widgets import FilteredSelectMultiple, AdminSplitDateTime 49 62 >>> from django.contrib.admin.widgets import AdminFileWidget, ForeignKeyRawIdWidget, ManyToManyRawIdWidget 50 63 >>> from django.contrib.admin.widgets import RelatedFieldWidgetWrapper 64 >>> from django.contrib.admin.widgets import GenericForeignKeyRawIdWidget 51 65 52 66 Calling conditional_escape on the output of widget.render will simulate what 53 67 happens in the template. This is easier than setting up a template and context … … True 116 130 >>> child_of_hidden = Inventory.objects.create(barcode=94, name='Child of hidden', parent=hidden) 117 131 >>> print w.render('test', child_of_hidden.parent_id, attrs={}) 118 132 <input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <strong>Hidden</strong> 133 134 >>> print GenericForeignKeyRawIdWidget(Image._meta.virtual_fields[0].ct_field).render('test', '', {}) 135 <input type="text" name="test" class="vForeignKeyRawIdAdminField" /> 136 <a href="../../../" class="related-lookup" id="lookup_id_test" onclick="return showGenericRelatedObjectLookupPopup(document.getElementById('id_content_type'), this, '../../../');"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> 137 <script type="text/javascript"> 138 var content_types = new Array(); 139 </script> 140 141 >>> print GenericForeignKeyRawIdWidget(Image._meta.virtual_fields[0].ct_field, [x.model_class() for x in ContentType.objects.filter(pk__lt=3)]).render('test', '', {}) 142 <input type="text" name="test" class="vForeignKeyRawIdAdminField" /> 143 <a href="../../../" class="related-lookup" id="lookup_id_test" onclick="return showGenericRelatedObjectLookupPopup(document.getElementById('id_content_type'), this, '../../../');"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a> 144 <script type="text/javascript"> 145 var content_types = new Array(); 146 content_types[1] = 'contenttypes/contenttype/'; 147 content_types[2] = 'auth/permission/'; 148 </script> 149 119 150 """ % { 120 151 'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX, 121 152 'STORAGE_URL': default_storage.url(''), -
tests/regressiontests/modeladmin/models.py
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py index 3a7d3f0..eba5061 100644
a b from datetime import date 3 3 4 4 from django.db import models 5 5 from django.contrib.auth.models import User 6 from django.contrib.contenttypes import generic 7 from django.contrib.contenttypes.models import ContentType 6 8 7 9 class Band(models.Model): 8 10 name = models.CharField(max_length=100) … … class ValidationTestModel(models.Model): 35 37 class ValidationTestInlineModel(models.Model): 36 38 parent = models.ForeignKey(ValidationTestModel) 37 39 40 class Image(models.Model): 41 image = models.ImageField(upload_to='pictures') 42 description = models.TextField() 43 44 content_type = models.ForeignKey(ContentType) 45 object_id = models.PositiveIntegerField() 46 object = generic.GenericForeignKey('content_type', 'object_id') 47 48 38 49 __test__ = {'API_TESTS': """ 39 50 40 51 >>> from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL … … ImproperlyConfigured: 'ValidationTestInline.formset' does not inherit from BaseM 912 923 ... inlines = [ValidationTestInline] 913 924 >>> validate(ValidationTestModelAdmin, ValidationTestModel) 914 925 926 >>> class GenericModelAdmin(ModelAdmin): 927 ... generic_fields = None 928 >>> validate(GenericModelAdmin, Image) 929 Traceback (most recent call last): 930 ... 931 ImproperlyConfigured: 'GenericModelAdmin.generic_fields' must be a list or tuple. 932 >>> class GenericModelAdmin(ModelAdmin): 933 ... generic_fields = ["abc"] 934 >>> validate(GenericModelAdmin, Image) 935 Traceback (most recent call last): 936 ... 937 ImproperlyConfigured: Item number 0 in GenericModelAdmin.generic_fieldsis not a GenericForeignKey on Image 938 939 >>> class GenericModelAdmin(ModelAdmin): 940 ... generic_fields = ["object"] 941 >>> validate(GenericModelAdmin, Image) 942 915 943 """ 916 944 }