Ticket #9976: 9976.generic-foreign-key-widget.diff

File 9976.generic-foreign-key-widget.diff, 11.2 KB (added by Julien Phalip, 13 years ago)

Updated patch to current trunk

  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index f05b5cb..1ac9d11 100644
    a b class BaseModelAdmin(object):  
    7272    formfield_overrides = {}
    7373    readonly_fields = ()
    7474    ordering = None
     75    generic_fields = ()
    7576
    7677    def __init__(self):
    7778        overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
    class BaseModelAdmin(object):  
    122123
    123124            return formfield
    124125
     126        # For generic foreign keys marked as generic_fields we use a special widget
     127        elif db_field.name in [f.fk_field for f
     128                             in self.model._meta.virtual_fields
     129                             if f.name in self.generic_fields]:
     130            for gfk in self.model._meta.virtual_fields:
     131                if gfk.fk_field == db_field.name:
     132                    break
     133            return db_field.formfield(
     134                label=capfirst(gfk.name.replace('_', ' ')),
     135                widget=widgets.GenericForeignKeyRawIdWidget(
     136                    gfk.ct_field,
     137                    self.admin_site._registry.keys()))
     138
    125139        # If we've got overrides for the formfield defined, use 'em. **kwargs
    126140        # passed to formfield_for_dbfield override the defaults.
    127141        for klass in db_field.__class__.mro():
  • django/contrib/admin/validation.py

    diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
    index 733f89d..0812407 100644
    a b def validate(cls, model):  
    171171            raise ImproperlyConfigured("'%s.%s' should be a boolean."
    172172                    % (cls.__name__, attr))
    173173
     174    if hasattr(cls, 'generic_fields'):
     175        check_isseq(cls, 'generic_fields', cls.generic_fields)
     176        for i, field in enumerate(cls.generic_fields):
     177            if field not in [f.name for f in model._meta.virtual_fields]:
     178                raise ImproperlyConfigured(
     179                    "Item number %d in %s.generic_fields"
     180                    "is not a GenericForeignKey on %s"
     181                    % (i, cls.__name__, model.__name__))
    174182
    175183    # inlines = []
    176184    if hasattr(cls, 'inlines'):
  • django/contrib/admin/widgets.py

    diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
    index 0d1f2a9..b87c366 100644
    a b Form Widget classes specific to the Django admin site.  
    55import copy
    66from django import forms
    77from django.contrib.admin.templatetags.admin_static import static
     8from django.contrib.contenttypes.models import ContentType
    89from django.core.urlresolvers import reverse
    910from django.forms.widgets import RadioFieldRenderer
    1011from django.forms.util import flatatt
    class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput):  
    309310        if attrs is not None:
    310311            final_attrs.update(attrs)
    311312        super(AdminCommaSeparatedIntegerFieldWidget, self).__init__(attrs=final_attrs)
     313
     314class GenericForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
     315    def __init__(self, ct_field, cts=[], attrs=None):
     316        self.ct_field = ct_field
     317        self.cts = cts
     318        forms.TextInput.__init__(self, attrs)
     319
     320    def render(self, name, value, attrs=None):
     321        if attrs is None:
     322            attrs = {}
     323        related_url = '../../../'
     324        params = self.url_parameters()
     325        if params:
     326            url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.iteritems()])
     327        else:
     328            url = ''
     329        if 'class' not in attrs:
     330            attrs['class'] = 'vForeignKeyRawIdAdminField'
     331        output = [
     332            forms.TextInput.render(self, name, value, attrs),
     333            "<a href=\"%(related)s%(url)s\" class=\"related-lookup\" "
     334            "id=\"lookup_id_%(name)s\" onclick=\"return "
     335            "showGenericRelatedObjectLookupPopup(document.getElementById("
     336            "'id_%(ct_field)s'), this, '%(related)s%(url)s');\"> "
     337                % {'related': related_url, 'url': url, 'name': name,
     338                   'ct_field': self.ct_field},
     339            "<img src=\"%s\" width=\"16\" height=\"16\" alt=\"%s\" /></a>"
     340                % (static('admin/img/selector-search.gif'), _('Lookup'))]
     341        if value:
     342            output.append(self.label_for_value(value))
     343
     344        content_types = """
     345<script type="text/javascript">
     346var content_types = new Array();
     347%s
     348</script>""" % ('\n'.join(["content_types[%s] = '%s/%s/';"
     349                           % (ContentType.objects.get_for_model(ct).id,
     350                              ct._meta.app_label,
     351                              ct._meta.object_name.lower())
     352                           for ct in self.cts]))
     353        return mark_safe(u''.join(output) + content_types)
     354
     355    def url_parameters(self):
     356        return {}
  • tests/regressiontests/admin_widgets/models.py

    diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
    index c4489e0..8e16641 100644
    a b  
    11from django.db import models
    22from django.contrib.auth.models import User
    3 
     3from django.contrib.contenttypes import generic
     4from django.contrib.contenttypes.models import ContentType
    45
    56class MyFileField(models.FileField):
    67    pass
    class Advisor(models.Model):  
    99100    """
    100101    name = models.CharField(max_length=20)
    101102    companies = models.ManyToManyField(Company)
     103
     104class Image(models.Model):
     105    image = models.ImageField(upload_to='images')
     106    description = models.CharField(max_length=200)
     107
     108    content_type = models.ForeignKey(ContentType, related_name='admin_widgets_image_set')
     109    object_id = models.PositiveIntegerField()
     110    object = generic.GenericForeignKey('content_type', 'object_id')
     111
     112    def __unicode__(self):
     113        return u"%s - %s" % (self.image.name, self.description[:100])
     114 No newline at end of file
  • tests/regressiontests/admin_widgets/tests.py

    diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
    index a7bfe55..0c03916 100644
    a b from django import forms  
    77from django.conf import settings
    88from django.contrib import admin
    99from django.contrib.admin import widgets
     10from django.contrib.contenttypes.models import ContentType
    1011from django.core.files.storage import default_storage
    1112from django.core.files.uploadedfile import SimpleUploadedFile
    1213from django.db.models import DateField
    from django.test import TestCase as DjangoTestCase  
    1415from django.utils import translation
    1516from django.utils.html import conditional_escape
    1617from django.utils.unittest import TestCase
     18from django.contrib.admin.widgets import GenericForeignKeyRawIdWidget
    1719
    1820import models
    1921from widgetadmin import site as widget_admin_site
    class AdminForeignKeyWidgetChangeList(DjangoTestCase):  
    148150        self.assertTrue('%s/auth/user/add/' % self.admin_root in response.content)
    149151
    150152
     153class AdminGenericForeignKeyRawIdWidget(DjangoTestCase):
     154    fixtures = ["admin-widgets-users.xml"]
     155    admin_root = '/widget_admin'
     156
     157    def test_gfk_raw_id_field(self):
     158        self.assertEqual(
     159            GenericForeignKeyRawIdWidget(models.Image._meta.virtual_fields[0].ct_field).render('test', '', {}),
     160            """<input type="text" name="test" class="vForeignKeyRawIdAdminField" /><a href="../../../" class="related-lookup" id="lookup_id_test" onclick="return showGenericRelatedObjectLookupPopup(document.getElementById('id_content_type'), this, '../../../');"> <img src="/static/admin/img/selector-search.gif" width="16" height="16" alt="Lookup" /></a>
     161<script type="text/javascript">
     162var content_types = new Array();
     163
     164</script>"""
     165        )
     166
     167        cts = [ct.model_class() for ct in ContentType.objects.filter(pk__lt=3)]
     168        self.assertEqual(
     169            GenericForeignKeyRawIdWidget(models.Image._meta.virtual_fields[0].ct_field, cts).render('test', '', {}),
     170            """<input type="text" name="test" class="vForeignKeyRawIdAdminField" /><a href="../../../" class="related-lookup" id="lookup_id_test" onclick="return showGenericRelatedObjectLookupPopup(document.getElementById('id_content_type'), this, '../../../');"> <img src="/static/admin/img/selector-search.gif" width="16" height="16" alt="Lookup" /></a>
     171<script type="text/javascript">
     172var content_types = new Array();
     173content_types[1] = '%s/%s/';
     174content_types[2] = '%s/%s/';
     175</script>""" % (cts[0]._meta.app_label, cts[0]._meta.object_name.lower(),
     176                cts[1]._meta.app_label, cts[1]._meta.object_name.lower(),)
     177        )
     178
    151179class AdminForeignKeyRawIdWidget(DjangoTestCase):
    152180    fixtures = ["admin-widgets-users.xml"]
    153181    admin_root = '/widget_admin'
  • tests/regressiontests/modeladmin/models.py

    diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
    index ae180a4..e296169 100644
    a b  
    11# coding: utf-8
    22from django.db import models
    33from django.contrib.auth.models import User
     4from django.contrib.contenttypes import generic
     5from django.contrib.contenttypes.models import ContentType
    46
    57class Band(models.Model):
    68    name = models.CharField(max_length=100)
    class ValidationTestModel(models.Model):  
    3941
    4042class ValidationTestInlineModel(models.Model):
    4143    parent = models.ForeignKey(ValidationTestModel)
     44
     45class Image(models.Model):
     46    image = models.ImageField(upload_to='pictures')
     47    description = models.TextField()
     48
     49    content_type = models.ForeignKey(ContentType, related_name='modeladmin_image_set')
     50    object_id = models.PositiveIntegerField()
     51    object = generic.GenericForeignKey('content_type', 'object_id')
     52 No newline at end of file
  • tests/regressiontests/modeladmin/tests.py

    diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py
    index 872fb0c..819a779 100644
    a b from django.test import TestCase  
    1616from django.utils import unittest
    1717
    1818from models import (Band, Concert, ValidationTestModel,
    19     ValidationTestInlineModel)
     19    ValidationTestInlineModel, Image)
    2020
    2121
    2222# None of the following tests really depend on the content of the request,
    class ValidationTests(unittest.TestCase):  
    14861486            inlines = [ValidationTestInline]
    14871487
    14881488        validate(ValidationTestModelAdmin, ValidationTestModel)
     1489
     1490    def test_generic_fields_validation(self):
     1491        class GenericModelAdmin(ModelAdmin):
     1492            generic_fields = None
     1493
     1494        self.assertRaisesRegexp(
     1495            ImproperlyConfigured,
     1496                "'GenericModelAdmin.generic_fields' must be a list or tuple.",
     1497            validate,
     1498            GenericModelAdmin,
     1499            Image
     1500        )
     1501
     1502        class GenericModelAdmin(ModelAdmin):
     1503            generic_fields = ["abc"]
     1504
     1505        self.assertRaisesRegexp(
     1506            ImproperlyConfigured,
     1507                "Item number 0 in GenericModelAdmin.generic_fieldsis not a GenericForeignKey on Image",
     1508            validate,
     1509            GenericModelAdmin,
     1510            Image
     1511        )
     1512
     1513        class GenericModelAdmin(ModelAdmin):
     1514            generic_fields = ["object"]
     1515
     1516        validate(GenericModelAdmin, Image)
     1517 No newline at end of file
Back to Top