Code

Ticket #17122: TRUNK_patch_with_widget_tests.diff

File TRUNK_patch_with_widget_tests.diff, 16.8 KB (added by nickname123, 2 years ago)

Run with python ./tests/runtests.py --settings=test_sqlite admin_widgets.TestsFor17122

Line 
1Index: django/contrib/admin/widgets.py
2===================================================================
3--- django/contrib/admin/widgets.py     (revision 17048)
4+++ django/contrib/admin/widgets.py     (working copy)
5@@ -130,6 +130,13 @@
6 
7     def render(self, name, value, attrs=None):
8         rel_to = self.rel.to
9+        # custom fields may need the to_python
10+        # conversion in order to facilitate
11+        # unicode conversion
12+        try:
13+            value = self.rel.to._meta.get_field(self.rel.field_name).to_python(value)
14+        except AttributeError:
15+            pass# 'ManyToManyRel' object has no attribute 'field_name'
16         if attrs is None:
17             attrs = {}
18         extra = []
19@@ -139,7 +146,7 @@
20                                     (rel_to._meta.app_label,
21                                     rel_to._meta.module_name),
22                                     current_app=self.admin_site.name)
23-
24+       
25             params = self.url_parameters()
26             if params:
27                 url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()])
28@@ -248,6 +255,13 @@
29 
30     def render(self, name, value, *args, **kwargs):
31         rel_to = self.rel.to
32+        # custom fields may need the to_python
33+        # conversion in order to facilitate
34+        # unicode conversion
35+        try:
36+            value = self.rel.to._meta.get_field(self.rel.field_name).to_python(value)
37+        except AttributeError:
38+            pass# 'ManyToManyRel' object has no attribute 'field_name'
39         info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
40         self.widget.choices = self.choices
41         output = [self.widget.render(name, value, *args, **kwargs)]
42Index: django/forms/forms.py
43===================================================================
44--- django/forms/forms.py       (revision 17048)
45+++ django/forms/forms.py       (working copy)
46@@ -341,6 +341,14 @@
47                     hidden_widget = field.hidden_widget()
48                     initial_value = hidden_widget.value_from_datadict(
49                         self.data, self.files, initial_prefixed_name)
50+                # custom fields may need the to_python
51+                # conversion in order to facilitate
52+                # unicode conversion
53+                if isinstance(initial_value,list):
54+                    # ManyToManyField uses a list
55+                    initial_value = [field.to_python(v) for v in initial_value]
56+                else:
57+                    initial_value = field.to_python(initial_value)
58                 if field.widget._has_changed(initial_value, data_value):
59                     self._changed_data.append(name)
60         return self._changed_data
61Index: tests/regressiontests/admin_widgets/fields.py
62===================================================================
63--- tests/regressiontests/admin_widgets/fields.py       (revision 0)
64+++ tests/regressiontests/admin_widgets/fields.py       (working copy)
65@@ -0,0 +1,139 @@
66+from django.db import models
67+import uuid
68+from sqlite3 import Binary
69+
70+class UUIDVersionError(Exception):
71+    pass
72+
73+class UUIDField(models.Field):
74+   
75+    __metaclass__ = models.SubfieldBase
76+    empty_strings_allowed = False
77+
78+    def __init__(self, primary_key=False, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
79+        if primary_key and not auto:
80+            auto = True
81+        if auto:
82+            kwargs['blank'] = True
83+            kwargs.setdefault('editable', False)
84+        self.auto = auto
85+        self.version = version
86+        if version == 1:
87+            self.node, self.clock_seq = node, clock_seq
88+        elif version == 3 or version == 5:
89+            self.namespace, self.name = namespace, name
90+        kwargs['max_length'] = 36
91+        super(UUIDField,self).__init__(verbose_name, name, primary_key, **kwargs)
92+
93+    def contribute_to_class(self, cls, name):
94+        if self.primary_key:
95+            assert not cls._meta.has_auto_field, \
96+              "A model can't have more than one AutoField: %s %s %s; have %s" % \
97+               (self, cls, name, cls._meta.auto_field)
98+            super(UUIDField, self).contribute_to_class(cls, name)
99+            cls._meta.has_auto_field = True
100+            cls._meta.auto_field = self
101+        else:
102+            super(UUIDField, self).contribute_to_class(cls, name)
103+
104+    def create_uuid(self):
105+        if not self.version or self.version == 4:
106+            return uuid.uuid4()
107+        elif self.version == 1:
108+            return uuid.uuid1(self.node, self.clock_seq)
109+        elif self.version == 2:
110+            raise UUIDVersionError("UUID version 2 is not supported.")
111+        elif self.version == 3:
112+            return uuid.uuid3(self.namespace, self.name)
113+        elif self.version == 5:
114+            return uuid.uuid5(self.namespace, self.name)
115+        else:
116+            raise UUIDVersionError("UUID version %s is not valid." % self.version)
117+   
118+    def db_type(self, connection):
119+        """
120+            Returns the database column data type for the Field,
121+            taking into account the connection object, and the
122+            settings associated with it.
123+        """
124+        return 'Binary(16)'
125+   
126+    def to_python(self, value):
127+        """
128+            Converts a value as returned by your database
129+            (or a serializer) to a Python object.
130+        """
131+        if isinstance(value,models.Model):
132+            # This happens with related fields
133+            # when the relation uses a UUIDField
134+            # as the primary key.
135+            value = value.pk
136+        if isinstance(value,buffer):
137+            value = "%s" % value
138+        if value is None:
139+            value = ''
140+        if isinstance(value,basestring) and not value:
141+            pass
142+        elif not isinstance(value,uuid.UUID):
143+            try:
144+                value = uuid.UUID(value)
145+            except ValueError:
146+                value = uuid.UUID(bytes=value)
147+        return unicode(value)
148+   
149+    def get_prep_value(self, value):
150+        """
151+            New in Django 1.2
152+            This is the reverse of to_python() when working with
153+            the database backends (as opposed to serialization).
154+        """
155+        if isinstance(value,buffer):
156+            value = "%s" % value
157+        if value is None:
158+            value = ''
159+        if isinstance(value,basestring) and not value:
160+            pass
161+        elif not isinstance(value,uuid.UUID):
162+            try:
163+                value = uuid.UUID(value).bytes
164+            except ValueError:
165+                value = uuid.UUID(bytes=value).bytes
166+        return value
167+
168+    def get_db_prep_value(self, value, connection, prepared=False):
169+        value = super(UUIDField,self).get_db_prep_value(value, connection=connection, prepared=prepared)
170+        if connection.settings_dict['ENGINE'] == 'django.db.backends.sqlite3':
171+            value = Binary(value)
172+        return value
173+
174+    def pre_save(self, model_instance, add):
175+        """
176+            This method is called just prior to get_db_prep_save()
177+            and should return the value of the appropriate attribute
178+            from model_instance for this field. If the model is
179+            being saved to the database for the first time, the add
180+            parameter will be True, otherwise it will be False.
181+        """
182+        if self.auto and add:
183+            value = unicode(self.create_uuid())
184+            setattr(model_instance, self.attname, value)
185+        else:
186+            value = super(UUIDField, self).pre_save(model_instance, add)
187+            if self.auto and not value:
188+                value = unicode(self.create_uuid())
189+                setattr(model_instance, self.attname, value)
190+        return value
191+   
192+    def formfield(self, **kwargs):
193+        if self.primary_key:
194+            return None
195+        else:
196+            return super(UUIDField,self).formfield(**kwargs)
197+   
198+    def value_to_string(self, obj):
199+        """
200+            This method is used by the serializers to convert the
201+            field into a string for output.
202+        """
203+        value = self._get_val_from_obj(obj)
204+        return self.to_python(value)
205\ No newline at end of file
206Index: tests/regressiontests/admin_widgets/models.py
207===================================================================
208--- tests/regressiontests/admin_widgets/models.py       (revision 17048)
209+++ tests/regressiontests/admin_widgets/models.py       (working copy)
210@@ -1,5 +1,7 @@
211 from django.db import models
212 from django.contrib.auth.models import User
213+from django.core.urlresolvers import reverse
214+from fields import UUIDField
215 
216 
217 class MyFileField(models.FileField):
218@@ -99,3 +101,36 @@
219     """
220     name = models.CharField(max_length=20)
221     companies = models.ManyToManyField(Company)
222+
223+class Base(models.Model):
224+    class Meta():
225+        abstract = True
226+   
227+    binary_id = UUIDField(primary_key=True)
228+   
229+    def __unicode__(self):
230+        return u"%s.__unicode__() resulting pk: %s" % (self.__class__.__name__, self.pk)
231+   
232+    def get_change_url(self):
233+        return "/widget_admin/admin_widgets/%s/%s/" % (
234+            self.__class__.__name__.lower(),
235+            self.pk
236+        )
237+   
238+class A(Base):
239+    pass
240+
241+class B(Base):
242+    relation = models.ForeignKey(A)
243+
244+class C(Base):
245+    relation = models.OneToOneField(A)
246+   
247+class D(Base):
248+    relation = models.ManyToManyField(A)
249+
250+class G(Base):
251+    relation = models.ForeignKey(A)
252+   
253+class H(Base):
254+    relation = models.ManyToManyField(A)
255\ No newline at end of file
256Index: tests/regressiontests/admin_widgets/tests.py
257===================================================================
258--- tests/regressiontests/admin_widgets/tests.py        (revision 17048)
259+++ tests/regressiontests/admin_widgets/tests.py        (working copy)
260@@ -7,18 +7,25 @@
261 from django.conf import settings
262 from django.contrib import admin
263 from django.contrib.admin import widgets
264+from django.contrib.auth.models import User
265 from django.core.files.storage import default_storage
266 from django.core.files.uploadedfile import SimpleUploadedFile
267 from django.db.models import DateField
268+from django.http import HttpResponse
269 from django.test import TestCase as DjangoTestCase
270 from django.utils import translation
271+from django.utils.encoding import force_unicode
272 from django.utils.html import conditional_escape
273 from django.utils.unittest import TestCase
274 
275 from . import models
276 from .widgetadmin import site as widget_admin_site
277 
278+from re import search,DOTALL
279 
280+from .models import A,B,C,D,G,H
281+
282+
283 admin_media_prefix = lambda: {
284     'ADMIN_MEDIA_PREFIX': "%sadmin/" % settings.STATIC_URL,
285 }
286@@ -372,3 +379,157 @@
287         # Used to fail with a name error.
288         w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site)
289         self.assertFalse(w.can_add_related)
290+
291+class TestsFor17122(DjangoTestCase):
292+    def setUp(self):
293+        # create the admin user
294+        user = User()
295+        user.username = "admin"
296+        user.set_password("admin")
297+        user.is_staff = True
298+        user.is_superuser = True
299+        user.save()
300+       
301+        # log in
302+        result = self.client.login(username="admin", password="admin")
303+        self.assertEqual(result, True)
304+       
305+        # create instances to work with
306+        a1 = A()
307+        a2 = A()
308+        a3 = A()
309+        a1.save()
310+        a2.save()
311+        a3.save()
312+        self.a1 = a1
313+        self.a2 = a2
314+        self.a3 = a3
315+       
316+        # create foreign keys to test
317+        b1 = B(relation=a1)
318+        b2 = B(relation=a2)
319+        b3 = B(relation=a3)
320+        b1.save()
321+        b2.save()
322+        b3.save()
323+        self.b1 = b1
324+        self.b2 = b2
325+        self.b3 = b3
326+       
327+        # create one to ones for testing
328+        c1 = C(relation=a1)
329+        c1.save()
330+        self.c1 = c1
331+
332+        # create many to manys for testing
333+        d1 = D()
334+        d1.save()
335+        d1.relation.add(a1)
336+        d1.relation.add(a2)
337+        d1.save()
338+        self.d1 = d1
339+       
340+        # create foreign keys to test raw id widget
341+        g1 = G(relation=a1)
342+        g1.save()
343+        self.g1 = g1
344+       
345+        # create m2m to test raw id widget
346+        h1 = H()
347+        h1.save()
348+        h1.relation.add(a1)
349+        h1.relation.add(a2)
350+        h1.save()
351+        self.h1 = h1
352+       
353+    def tearDown(self):
354+        self.client.logout()
355+       
356+    def test_ForeignKey_render(self):
357+        response = self.client.get(self.b1.get_change_url())
358+        self.assertContains(response,
359+            '<option value="%s" selected="selected">%s</option>' % (
360+                    self.b1.relation.pk,
361+                    self.b1.relation
362+            )
363+        )
364+        for a in A.objects.all().exclude(pk=self.b1.relation.pk):
365+            self.assertContains(response,
366+                '<option value="%s">%s</option>' % (
367+                        a.pk,
368+                        a
369+                )
370+            )
371+   
372+    def test_OneToOneField_render(self):
373+        response = self.client.get(self.c1.get_change_url())
374+        self.assertContains(response,
375+            '<option value="%s" selected="selected">%s</option>' % (
376+                    self.c1.relation.pk,
377+                    self.c1.relation
378+            )
379+        )
380+        for a in A.objects.all().exclude(pk=self.c1.relation.pk):
381+            self.assertContains(response,
382+                '<option value="%s">%s</option>' % (
383+                        a.pk,
384+                        a
385+                )
386+            )
387+   
388+    def test_ManyToManyField_render(self):
389+        response = self.client.get(self.d1.get_change_url())
390+        others = A.objects.all()
391+        for a in self.d1.relation.all():
392+            self.assertContains(response,
393+                '<option value="%s" selected="selected">%s</option>' % (
394+                        a.pk,
395+                        a
396+                )
397+            )
398+            others = others.exclude(pk=a.pk)
399+        for a in others:
400+            self.assertContains(response,
401+                '<option value="%s">%s</option>' % (
402+                        a.pk,
403+                        a
404+                )
405+            )
406+   
407+    def test_ForeignKeyRawIdWidget_render(self):
408+        response = self.client.get(self.g1.get_change_url())
409+        result = search(
410+                r'<input.*?(name="relation".*?value="%s"|value="%s".*?name="relation").*?>'%(
411+                        self.g1.relation.pk,
412+                        self.g1.relation.pk,
413+                ),
414+                str(response),
415+                DOTALL
416+        )
417+        self.assertTrue(result,"ForeignKeyRawIdWidget failed with non-unicode pk.")
418+       
419+    def test_ManyToManyRawIdWidget_render(self):
420+        response = self.client.get(self.h1.get_change_url())
421+        result = search(
422+                r'<input.*?(?:name="relation".*?value="(?P<value1>[a-zA-Z0-9,-]*?)"|value="(?P<value2>[a-zA-Z0-9,-]*?)".*?name="relation").*?>',
423+                str(response),
424+                DOTALL
425+        )
426+        if result.group("value1"):
427+            value = result.group("value1")
428+        elif result.group("value2"):
429+            value = result.group("value2")
430+        else:
431+            value = ""
432+        observed_pks = set([force_unicode(pk) for pk in value.split(",")])
433+        relation_pks = set([force_unicode(h.pk) for h in self.h1.relation.all()])
434+        msg = "ManyToManyRawIdWidget did not render properly."
435+        if hasattr(self,"longMessage") and not self.longMessage:
436+            msg = "%s Enable longMessage to see the difference between rendered pks and stored pks." % msg
437+        if hasattr(self,"assertSetEqual"):
438+            self.assertSetEqual(observed_pks, relation_pks, msg)
439+        else:
440+            diff1 = observed_pks.difference(relation_pks)
441+            diff2 = relation_pks.difference(observed_pks)
442+            if diff1 or diff2:
443+                self.fail(msg)
444Index: tests/regressiontests/admin_widgets/widgetadmin.py
445===================================================================
446--- tests/regressiontests/admin_widgets/widgetadmin.py  (revision 17048)
447+++ tests/regressiontests/admin_widgets/widgetadmin.py  (working copy)
448@@ -25,6 +25,13 @@
449 class EventAdmin(admin.ModelAdmin):
450     raw_id_fields = ['band']
451 
452+class GAdmin(admin.ModelAdmin):
453+    raw_id_fields = ("relation",)
454+   
455+class HAdmin(admin.ModelAdmin):
456+    raw_id_fields = ("relation",)
457+
458+
459 site = WidgetAdmin(name='widget-admin')
460 
461 site.register(models.User)
462@@ -41,3 +48,11 @@
463 site.register(models.Bee)
464 
465 site.register(models.Advisor)
466+
467+
468+site.register(models.A)
469+site.register(models.B)
470+site.register(models.C)
471+site.register(models.D)
472+site.register(models.G,GAdmin)
473+site.register(models.H,HAdmin)
474\ No newline at end of file