Django

Code

Changeset 7600

Show
Ignore:
Timestamp:
06/09/08 09:03:35 (6 months ago)
Author:
russellm
Message:

Fixed #7350, #7202 -- Fixed serialization for multi-model inheritance, which had multiple problems:

  • Serializers were including all superclass fields in their output. Now only local fields are included.
  • Implicit OneToOne? primary keys were not correctly added to the metamodel, so they were always marked to be serialized, even though they were primary
  • Model saving was too aggressive about creating new parent class instances during deserialization. Raw save on a model now skips saving of the parent class.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/core/serializers/base.py

    r7477 r7600  
    3939        for obj in queryset: 
    4040            self.start_object(obj) 
    41             for field in obj._meta.fields: 
     41            for field in obj._meta.local_fields: 
    4242                if field.serialize: 
    4343                    if field.rel is None: 
  • django/trunk/django/db/models/base.py

    r7526 r7600  
    291291            signal = False 
    292292 
    293         for parent, field in meta.parents.items(): 
    294             self.save_base(raw, parent) 
    295             setattr(self, field.attname, self._get_pk_val(parent._meta)) 
     293        # If we are in a raw save, save the object exactly as presented. 
     294        # That means that we don't try to be smart about saving attributes 
     295        # that might have come from the parent class - we just save the  
     296        # attributes we have been given to the class we have been given. 
     297        if not raw: 
     298            for parent, field in meta.parents.items(): 
     299                self.save_base(raw, parent) 
     300                setattr(self, field.attname, self._get_pk_val(parent._meta)) 
    296301 
    297302        non_pks = [f for f in meta.local_fields if not f.primary_key] 
    298  
     303             
    299304        # First, try an UPDATE. If that doesn't update anything, do an INSERT. 
    300305        pk_val = self._get_pk_val(meta) 
  • django/trunk/django/db/models/options.py

    r7585 r7600  
    103103                field = self.parents.value_for_index(0) 
    104104                field.primary_key = True 
    105                 self.pk = field 
     105                self.setup_pk(field) 
    106106            else: 
    107107                auto = AutoField(verbose_name='ID', primary_key=True, 
  • django/trunk/docs/serialization.txt

    r7548 r7600  
    6363    doesn't specify all the fields that are required by a model, the deserializer 
    6464    will not be able to save deserialized instances. 
     65 
     66Inherited Models 
     67~~~~~~~~~~~~~~~~ 
     68 
     69If you have a model that is defined using an `abstract base class`_, you don't 
     70have to do anything special to serialize that model. Just call the serializer 
     71on the object (or objects) that you want to serialize, and the output will be 
     72a complete representation of the serialized object. 
     73 
     74However, if you have a model that uses `multi-table inheritance`_, you also 
     75need to serialize all of the base classes for the model. This is because only 
     76the fields that are locally defined on the model will be serialized. For 
     77example, consider the following models:: 
     78         
     79        class Place(models.Model): 
     80                name = models.CharField(max_length=50) 
     81                 
     82        class Restaurant(Place): 
     83                serves_hot_dogs = models.BooleanField() 
     84                 
     85If you only serialize the Restaurant model:: 
     86 
     87        data = serializers.serialize('xml', Restaurant.objects.all()) 
     88 
     89the fields on the serialized output will only contain the `serves_hot_dogs` 
     90attribute. The `name` attribute of the base class will be ignored. 
     91 
     92In order to fully serialize your Restaurant instances, you will need to 
     93serialize the Place models as well:: 
     94 
     95        all_objects = list(Restaurant.objects.all()) + list(Place.objects.all()) 
     96        data = serializers.serialize('xml', all_objects) 
     97 
     98.. _abstract base class: http://www.djangoproject.com/documentation/model-api/#abstract-base-classes 
     99.. _multi-table inheritance: http://www.djangoproject.com/documentation/model-api/#multi-table-inheritance 
    65100 
    66101Deserializing data 
  • django/trunk/tests/modeltests/model_inheritance/models.py

    r7477 r7600  
    148148>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c) 
    149149>>> ir.save() 
     150>>> ItalianRestaurant.objects.filter(address='1234 W. Ash') 
     151[<ItalianRestaurant: Ristorante Miron the italian restaurant>] 
     152 
    150153>>> ir.address = '1234 W. Elm' 
    151154>>> ir.save() 
     155>>> ItalianRestaurant.objects.filter(address='1234 W. Elm') 
     156[<ItalianRestaurant: Ristorante Miron the italian restaurant>] 
    152157 
    153158# Make sure Restaurant and ItalianRestaurant have the right fields in the right 
  • django/trunk/tests/regressiontests/serializers_regress/models.py

    r7477 r7600  
    224224        self.data = 666 
    225225        super(ModifyingSaveData, self).save(raw) 
     226 
     227# Tests for serialization of models using inheritance. 
     228# Regression for #7202, #7350 
     229class AbstractBaseModel(models.Model): 
     230    parent_data = models.IntegerField() 
     231    class Meta: 
     232        abstract = True 
     233 
     234class InheritAbstractModel(AbstractBaseModel): 
     235    child_data = models.IntegerField() 
     236     
     237class BaseModel(models.Model): 
     238    parent_data = models.IntegerField() 
     239 
     240class InheritBaseModel(BaseModel): 
     241    child_data = models.IntegerField() 
     242 
     243class ExplicitInheritBaseModel(BaseModel): 
     244    parent = models.OneToOneField(BaseModel) 
     245    child_data = models.IntegerField() 
  • django/trunk/tests/regressiontests/serializers_regress/tests.py

    r7477 r7600  
    3333    instance.data = data 
    3434    models.Model.save_base(instance, raw=True) 
    35     return instance 
     35    return [instance] 
    3636 
    3737def generic_create(pk, klass, data): 
     
    4141    for tag in data[1:]: 
    4242        instance.tags.create(data=tag) 
    43     return instance 
     43    return [instance] 
    4444 
    4545def fk_create(pk, klass, data): 
     
    4747    setattr(instance, 'data_id', data) 
    4848    models.Model.save_base(instance, raw=True) 
    49     return instance 
     49    return [instance] 
    5050 
    5151def m2m_create(pk, klass, data): 
     
    5353    models.Model.save_base(instance, raw=True) 
    5454    instance.data = data 
    55     return instance 
     55    return [instance] 
    5656 
    5757def o2o_create(pk, klass, data): 
     
    5959    instance.data_id = data 
    6060    models.Model.save_base(instance, raw=True) 
    61     return instance 
     61    return [instance] 
    6262 
    6363def pk_create(pk, klass, data): 
     
    6565    instance.data = data 
    6666    models.Model.save_base(instance, raw=True) 
    67     return instance 
    68  
     67    return [instance] 
     68 
     69def inherited_create(pk, klass, data): 
     70    instance = klass(id=pk,**data) 
     71    # This isn't a raw save because: 
     72    #  1) we're testing inheritance, not field behaviour, so none 
     73    #     of the field values need to be protected. 
     74    #  2) saving the child class and having the parent created 
     75    #     automatically is easier than manually creating both.  
     76    models.Model.save(instance) 
     77    created = [instance] 
     78    for klass,field in instance._meta.parents.items(): 
     79        created.append(klass.objects.get(id=pk)) 
     80    return created 
     81     
    6982# A set of functions that can be used to compare 
    7083# test data objects of various kinds 
     
    95108    testcase.assertEqual(data, instance.data) 
    96109 
     110def inherited_compare(testcase, pk, klass, data): 
     111    instance = klass.objects.get(id=pk) 
     112    for key,value in data.items(): 
     113        testcase.assertEqual(value, getattr(instance,key)) 
     114     
    97115# Define some data types. Each data type is 
    98116# actually a pair of functions; one to create 
     
    104122o2o_obj = (o2o_create, o2o_compare) 
    105123pk_obj = (pk_create, pk_compare) 
     124inherited_obj = (inherited_create, inherited_compare) 
    106125 
    107126test_data = [ 
     
    256275    (data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)), 
    257276    (data_obj, 810, ModifyingSaveData, 42), 
     277     
     278    (inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}), 
     279    (inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}), 
     280    (inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}), 
    258281] 
    259282 
     
    278301    # Create all the objects defined in the test data 
    279302    objects = [] 
     303    instance_count = {} 
    280304    transaction.enter_transaction_management() 
    281305    transaction.managed(True) 
    282306    for (func, pk, klass, datum) in test_data: 
    283         objects.append(func[0](pk, klass, datum)) 
     307        objects.extend(func[0](pk, klass, datum)) 
     308        instance_count[klass] = 0 
    284309    transaction.commit() 
    285310    transaction.leave_transaction_management() 
    286311 
     312    # Get a count of the number of objects created for each class 
     313    for klass in instance_count: 
     314        instance_count[klass] = klass.objects.count() 
     315         
    287316    # Add the generic tagged objects to the object list 
    288317    objects.extend(Tag.objects.all()) 
     
    305334        func[1](self, pk, klass, datum) 
    306335 
     336    # Assert that the number of objects deserialized is the 
     337    # same as the number that was serialized. 
     338    for klass, count in instance_count.items(): 
     339        self.assertEquals(count, klass.objects.count()) 
     340 
    307341def fieldsTest(format, self): 
    308342    # Clear the database first