fix object retrieval problem occuring with some custom primary key fields
+ added UUIDField as test casefix_request_path_info
parent
6befe6c733
commit
a4ac6cc91d
|
|
@ -156,27 +156,36 @@ class PolymorphicQuerySet(QuerySet):
|
|||
else:
|
||||
idlist_per_model[base_object.get_real_instance_class()].append(base_object.pk)
|
||||
|
||||
# django's automatic ".pk" field does not always work correctly for
|
||||
# custom fields in derived objects (unclear yet who to put the blame on).
|
||||
# We get different type(o.pk) in this case.
|
||||
# We work around this by using the real name of the field directly
|
||||
# for accessing the primary key of the the derived objects.
|
||||
pk_name = self.model._meta.pk.name
|
||||
|
||||
# For each model in "idlist_per_model" request its objects (the real model)
|
||||
# from the db and store them in results[].
|
||||
# Then we copy the annotate fields from the base objects to the real objects.
|
||||
# Then we copy the extra() select fields from the base objects to the real objects.
|
||||
# TODO: defer(), only(): support for these would be around here
|
||||
for modelclass, idlist in idlist_per_model.items():
|
||||
qs = modelclass.base_objects.filter(id__in=idlist)
|
||||
qs = modelclass.base_objects.filter(pk__in=idlist) # use pk__in instead ####
|
||||
qs.dup_select_related(self) # copy select related configuration to new qs
|
||||
|
||||
for o in qs:
|
||||
o_pk=getattr(o,pk_name)
|
||||
|
||||
if self.query.aggregates:
|
||||
for anno_field_name in self.query.aggregates.keys():
|
||||
attr = getattr(base_result_objects_by_id[o.pk], anno_field_name)
|
||||
attr = getattr(base_result_objects_by_id[o_pk], anno_field_name)
|
||||
setattr(o, anno_field_name, attr)
|
||||
|
||||
if self.query.extra_select:
|
||||
for select_field_name in self.query.extra_select.keys():
|
||||
attr = getattr(base_result_objects_by_id[o.pk], select_field_name)
|
||||
attr = getattr(base_result_objects_by_id[o_pk], select_field_name)
|
||||
setattr(o, select_field_name, attr)
|
||||
|
||||
results[o.pk] = o
|
||||
results[o_pk] = o
|
||||
|
||||
# re-create correct order and return result list
|
||||
resultlist = [ results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results ]
|
||||
|
|
@ -193,7 +202,6 @@ class PolymorphicQuerySet(QuerySet):
|
|||
for o in resultlist:
|
||||
o.polymorphic_extra_select_names=extra_select_names
|
||||
|
||||
|
||||
return resultlist
|
||||
|
||||
def iterator(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
####################################################################
|
||||
|
||||
import uuid
|
||||
|
||||
from django.forms.util import ValidationError
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
class UUIDVersionError(Exception):
|
||||
pass
|
||||
|
||||
class UUIDField(models.CharField):
|
||||
"""Encode and stores a Python uuid.UUID in a manner that is appropriate
|
||||
for the given datatabase that we are using.
|
||||
|
||||
For sqlite3 or MySQL we save it as a 36-character string value
|
||||
For PostgreSQL we save it as a uuid field
|
||||
|
||||
This class supports type 1, 2, 4, and 5 UUID's.
|
||||
"""
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
_CREATE_COLUMN_TYPES = {
|
||||
'postgresql_psycopg2': 'uuid',
|
||||
'postgresql': 'uuid'
|
||||
}
|
||||
|
||||
def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
|
||||
"""Contruct a UUIDField.
|
||||
|
||||
@param verbose_name: Optional verbose name to use in place of what
|
||||
Django would assign.
|
||||
@param name: Override Django's name assignment
|
||||
@param auto: If True, create a UUID value if one is not specified.
|
||||
@param version: By default we create a version 1 UUID.
|
||||
@param node: Used for version 1 UUID's. If not supplied, then the uuid.getnode() function is called to obtain it. This can be slow.
|
||||
@param clock_seq: Used for version 1 UUID's. If not supplied a random 14-bit sequence number is chosen
|
||||
@param namespace: Required for version 3 and version 5 UUID's.
|
||||
@param name: Required for version4 and version 5 UUID's.
|
||||
|
||||
See Also:
|
||||
- Python Library Reference, section 18.16 for more information.
|
||||
- RFC 4122, "A Universally Unique IDentifier (UUID) URN Namespace"
|
||||
|
||||
If you want to use one of these as a primary key for a Django
|
||||
model, do this::
|
||||
id = UUIDField(primary_key=True)
|
||||
This will currently I{not} work with Jython because PostgreSQL support
|
||||
in Jython is not working for uuid column types.
|
||||
"""
|
||||
self.max_length = 36
|
||||
kwargs['max_length'] = self.max_length
|
||||
if auto:
|
||||
kwargs['blank'] = True
|
||||
kwargs.setdefault('editable', False)
|
||||
|
||||
self.auto = auto
|
||||
self.version = version
|
||||
if version==1:
|
||||
self.node, self.clock_seq = node, clock_seq
|
||||
elif version==3 or version==5:
|
||||
self.namespace, self.name = namespace, name
|
||||
|
||||
super(UUIDField, self).__init__(verbose_name=verbose_name,
|
||||
name=name, **kwargs)
|
||||
|
||||
def create_uuid(self):
|
||||
if not self.version or self.version==4:
|
||||
return uuid.uuid4()
|
||||
elif self.version==1:
|
||||
return uuid.uuid1(self.node, self.clock_seq)
|
||||
elif self.version==2:
|
||||
raise UUIDVersionError("UUID version 2 is not supported.")
|
||||
elif self.version==3:
|
||||
return uuid.uuid3(self.namespace, self.name)
|
||||
elif self.version==5:
|
||||
return uuid.uuid5(self.namespace, self.name)
|
||||
else:
|
||||
raise UUIDVersionError("UUID version %s is not valid." % self.version)
|
||||
|
||||
def db_type(self):
|
||||
from django.conf import settings
|
||||
return UUIDField._CREATE_COLUMN_TYPES.get(settings.DATABASE_ENGINE, "char(%s)" % self.max_length)
|
||||
|
||||
def to_python(self, value):
|
||||
"""Return a uuid.UUID instance from the value returned by the database."""
|
||||
#
|
||||
# This is the proper way... But this doesn't work correctly when
|
||||
# working with an inherited model
|
||||
#
|
||||
if not value:
|
||||
return None
|
||||
if isinstance(value, uuid.UUID):
|
||||
return value
|
||||
# attempt to parse a UUID
|
||||
return uuid.UUID(smart_unicode(value))
|
||||
|
||||
#
|
||||
# If I do the following (returning a String instead of a UUID
|
||||
# instance), everything works.
|
||||
#
|
||||
|
||||
#if not value:
|
||||
# return None
|
||||
#if isinstance(value, uuid.UUID):
|
||||
# return smart_unicode(value)
|
||||
#else:
|
||||
# return value
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
if self.auto and add:
|
||||
value = self.create_uuid()
|
||||
setattr(model_instance, self.attname, value)
|
||||
else:
|
||||
value = super(UUIDField, self).pre_save(model_instance,add)
|
||||
if self.auto and not value:
|
||||
value = self.create_uuid()
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
|
||||
def get_db_prep_value(self, value):
|
||||
"""Casts uuid.UUID values into the format expected by the back end for use in queries"""
|
||||
if isinstance(value, uuid.UUID):
|
||||
return smart_unicode(value)
|
||||
return value
|
||||
|
||||
def value_to_string(self, obj):
|
||||
val = self._get_val_from_obj(obj)
|
||||
if val is None:
|
||||
data = ''
|
||||
else:
|
||||
data = smart_unicode(val)
|
||||
return data
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {
|
||||
'form_class': forms.CharField,
|
||||
'max_length': self.max_length
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return super(UUIDField, self).formfield(**defaults)
|
||||
|
|
@ -72,7 +72,6 @@ class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel):
|
|||
class Enhance_Inherit(Enhance_Base, Enhance_Plain):
|
||||
field_i = models.CharField(max_length=10)
|
||||
|
||||
|
||||
class DiamondBase(models.Model):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class DiamondX(DiamondBase):
|
||||
|
|
@ -156,6 +155,19 @@ class InitTestModelSubclass(InitTestModel):
|
|||
def x(self):
|
||||
return 'XYZ'
|
||||
|
||||
try: from polymorphic.test_tools import UUIDField
|
||||
except: pass
|
||||
if 'UUIDField' in globals():
|
||||
import uuid
|
||||
class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
id = UUIDField(primary_key = True)
|
||||
topic = models.CharField(max_length = 30)
|
||||
class UUIDArtProject(UUIDProject):
|
||||
artist = models.CharField(max_length = 30)
|
||||
class UUIDResearchProject(UUIDProject):
|
||||
supervisor = models.CharField(max_length = 30)
|
||||
|
||||
|
||||
|
||||
# test bad field name
|
||||
#class TestBadFieldModel(ShowFieldType, PolymorphicModel):
|
||||
|
|
@ -175,7 +187,9 @@ class testclass(TestCase):
|
|||
print 'DiamondXY fields 1: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||
o = DiamondXY.objects.get()
|
||||
print 'DiamondXY fields 2: field_b "%s", field_x "%s", field_y "%s"' % (o.field_b, o.field_x, o.field_y)
|
||||
if o.field_b != 'b': print '# Django model inheritance diamond problem detected'
|
||||
if o.field_b != 'b':
|
||||
print
|
||||
print '# known django model inheritance diamond problem detected'
|
||||
|
||||
def test_annotate_aggregate_order(self):
|
||||
|
||||
|
|
@ -250,7 +264,6 @@ class testclass(TestCase):
|
|||
x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info'))
|
||||
assert x == expected1 or x == expected2
|
||||
|
||||
#assert False
|
||||
|
||||
def test_limit_choices_to(self):
|
||||
"this is not really a testcase, as limit_choices_to only affects the Django admin"
|
||||
|
|
@ -262,6 +275,31 @@ class testclass(TestCase):
|
|||
entry2 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2')
|
||||
|
||||
|
||||
def test_primary_key_custom_field_problem(self):
|
||||
"object retrieval problem occuring with some custom primary key fields (UUIDField as test case)"
|
||||
if not 'UUIDField' in globals(): return
|
||||
a=UUIDProject.objects.create(topic="John's gathering")
|
||||
b=UUIDArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
|
||||
c=UUIDResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
|
||||
qs=UUIDProject.objects.all()
|
||||
ol=list(qs)
|
||||
a=qs[0]
|
||||
b=qs[1]
|
||||
c=qs[2]
|
||||
assert len(qs)==3
|
||||
assert type(a.id)==uuid.UUID and type(a.pk)==uuid.UUID
|
||||
res=repr(qs)
|
||||
import re
|
||||
res=re.sub(' id ...................................., topic',' id, topic',res)
|
||||
res_exp="""[ <UUIDProject: id, topic (CharField): "John's gathering">,
|
||||
<UUIDArtProject: id, topic (CharField): "Sculpting with Tim", artist (CharField): "T. Turner">,
|
||||
<UUIDResearchProject: id, topic (CharField): "Swallow Aerodynamics", supervisor (CharField): "Dr. Winter"> ]"""
|
||||
assert res==res_exp
|
||||
if (a.pk!= uuid.UUID or c.pk!= uuid.UUID):
|
||||
print
|
||||
print '# known django object inconstency with custom primary key field detected'
|
||||
|
||||
|
||||
def show_base_manager(model):
|
||||
print type(model._base_manager),model._base_manager.model
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue