1
0
mirror of https://github.com/django/django.git synced 2024-12-26 11:06:07 +00:00
django/tests/regressiontests/model_inheritance_regress/models.py
Russell Keith-Magee ff60c5f9de Fixed #1142 -- Added multiple database support.
This monster of a patch is the result of Alex Gaynor's 2009 Google Summer of Code project.
Congratulations to Alex for a job well done.

Big thanks also go to:
 * Justin Bronn for keeping GIS in line with the changes,
 * Karen Tracey and Jani Tiainen for their help testing Oracle support
 * Brett Hoerner, Jon Loyens, and Craig Kimmerer for their feedback.
 * Malcolm Treddinick for his guidance during the GSoC submission process.
 * Simon Willison for driving the original design process
 * Cal Henderson for complaining about ponies he wanted.

... and everyone else too numerous to mention that helped to bring this feature into fruition.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11952 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2009-12-22 15:18:51 +00:00

390 lines
13 KiB
Python

"""
Regression tests for Model inheritance behaviour.
"""
import datetime
from django.db import models
# Python 2.3 doesn't have sorted()
try:
sorted
except NameError:
from django.utils.itercompat import sorted
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Meta:
ordering = ('name',)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.name
class ItalianRestaurant(Restaurant):
serves_gnocchi = models.BooleanField()
def __unicode__(self):
return u"%s the italian restaurant" % self.name
class ParkingLot(Place):
# An explicit link to the parent (we can control the attribute name).
parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
capacity = models.IntegerField()
def __unicode__(self):
return u"%s the parking lot" % self.name
class ParkingLot2(Place):
# In lieu of any other connector, an existing OneToOneField will be
# promoted to the primary key.
parent = models.OneToOneField(Place)
class ParkingLot3(Place):
# The parent_link connector need not be the pk on the model.
primary_key = models.AutoField(primary_key=True)
parent = models.OneToOneField(Place, parent_link=True)
class Supplier(models.Model):
restaurant = models.ForeignKey(Restaurant)
class Parent(models.Model):
created = models.DateTimeField(default=datetime.datetime.now)
class Child(Parent):
name = models.CharField(max_length=10)
class SelfRefParent(models.Model):
parent_data = models.IntegerField()
self_data = models.ForeignKey('self', null=True)
class SelfRefChild(SelfRefParent):
child_data = models.IntegerField()
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateTimeField()
class Meta:
ordering = ('-pub_date', 'headline')
def __unicode__(self):
return self.headline
class ArticleWithAuthor(Article):
author = models.CharField(max_length=100)
class M2MBase(models.Model):
articles = models.ManyToManyField(Article)
class M2MChild(M2MBase):
name = models.CharField(max_length=50)
class Evaluation(Article):
quality = models.IntegerField()
class Meta:
abstract = True
class QualityControl(Evaluation):
assignee = models.CharField(max_length=50)
class BaseM(models.Model):
base_name = models.CharField(max_length=100)
def __unicode__(self):
return self.base_name
class DerivedM(BaseM):
customPK = models.IntegerField(primary_key=True)
derived_name = models.CharField(max_length=100)
def __unicode__(self):
return "PK = %d, base_name = %s, derived_name = %s" \
% (self.customPK, self.base_name, self.derived_name)
# Check that abstract classes don't get m2m tables autocreated.
class Person(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)
def __unicode__(self):
return self.name
class AbstractEvent(models.Model):
name = models.CharField(max_length=100)
attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
class Meta:
abstract = True
ordering = ('name',)
def __unicode__(self):
return self.name
class BirthdayParty(AbstractEvent):
pass
class BachelorParty(AbstractEvent):
pass
class MessyBachelorParty(BachelorParty):
pass
__test__ = {'API_TESTS':"""
# Regression for #7350, #7202
# Check that when you create a Parent object with a specific reference to an
# existent child instance, saving the Parent doesn't duplicate the child. This
# behaviour is only activated during a raw save - it is mostly relevant to
# deserialization, but any sort of CORBA style 'narrow()' API would require a
# similar approach.
# Create a child-parent-grandparent chain
>>> place1 = Place(name="Guido's House of Pasta", address='944 W. Fullerton')
>>> place1.save_base(raw=True)
>>> restaurant = Restaurant(place_ptr=place1, serves_hot_dogs=True, serves_pizza=False)
>>> restaurant.save_base(raw=True)
>>> italian_restaurant = ItalianRestaurant(restaurant_ptr=restaurant, serves_gnocchi=True)
>>> italian_restaurant.save_base(raw=True)
# Create a child-parent chain with an explicit parent link
>>> place2 = Place(name='Main St', address='111 Main St')
>>> place2.save_base(raw=True)
>>> park = ParkingLot(parent=place2, capacity=100)
>>> park.save_base(raw=True)
# Check that no extra parent objects have been created.
>>> Place.objects.all()
[<Place: Guido's House of Pasta the place>, <Place: Main St the place>]
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
>>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's House of Pasta"), ('serves_hot_dogs', True)]]
True
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's House of Pasta"), ('serves_gnocchi', True), ('serves_hot_dogs', True)]]
True
>>> dicts = ParkingLot.objects.values('name','capacity')
>>> [sorted(d.items()) for d in dicts]
[[('capacity', 100), ('name', u'Main St')]]
# You can also update objects when using a raw save.
>>> place1.name = "Guido's All New House of Pasta"
>>> place1.save_base(raw=True)
>>> restaurant.serves_hot_dogs = False
>>> restaurant.save_base(raw=True)
>>> italian_restaurant.serves_gnocchi = False
>>> italian_restaurant.save_base(raw=True)
>>> place2.name='Derelict lot'
>>> place2.save_base(raw=True)
>>> park.capacity = 50
>>> park.save_base(raw=True)
# No extra parent objects after an update, either.
>>> Place.objects.all()
[<Place: Derelict lot the place>, <Place: Guido's All New House of Pasta the place>]
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
>>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's All New House of Pasta"), ('serves_hot_dogs', False)]]
True
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
True
>>> dicts = ParkingLot.objects.values('name','capacity')
>>> [sorted(d.items()) for d in dicts]
[[('capacity', 50), ('name', u'Derelict lot')]]
# If you try to raw_save a parent attribute onto a child object,
# the attribute will be ignored.
>>> italian_restaurant.name = "Lorenzo's Pasta Hut"
>>> italian_restaurant.save_base(raw=True)
# Note that the name has not changed
# - name is an attribute of Place, not ItalianRestaurant
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
True
# Regressions tests for #7105: dates() queries should be able to use fields
# from the parent model as easily as the child.
>>> obj = Child.objects.create(name='child', created=datetime.datetime(2008, 6, 26, 17, 0, 0))
>>> Child.objects.dates('created', 'month')
[datetime.datetime(2008, 6, 1, 0, 0)]
# Regression test for #7276: calling delete() on a model with multi-table
# inheritance should delete the associated rows from any ancestor tables, as
# well as any descendent objects.
>>> ident = ItalianRestaurant.objects.all()[0].id
>>> Place.objects.get(pk=ident)
<Place: Guido's All New House of Pasta the place>
>>> xx = Restaurant.objects.create(name='a', address='xx', serves_hot_dogs=True, serves_pizza=False)
# This should delete both Restuarants, plus the related places, plus the ItalianRestaurant.
>>> Restaurant.objects.all().delete()
>>> Place.objects.get(pk=ident)
Traceback (most recent call last):
...
DoesNotExist: Place matching query does not exist.
>>> ItalianRestaurant.objects.get(pk=ident)
Traceback (most recent call last):
...
DoesNotExist: ItalianRestaurant matching query does not exist.
# Regression test for #6755
>>> r = Restaurant(serves_pizza=False)
>>> r.save()
>>> r.id == r.place_ptr_id
True
>>> orig_id = r.id
>>> r = Restaurant(place_ptr_id=orig_id, serves_pizza=True)
>>> r.save()
>>> r.id == orig_id
True
>>> r.id == r.place_ptr_id
True
# Regression test for #7488. This looks a little crazy, but it's the equivalent
# of what the admin interface has to do for the edit-inline case.
>>> Supplier.objects.filter(restaurant=Restaurant(name='xx', address='yy'))
[]
# Regression test for #7853
# If the parent class has a self-referential link, make sure that any updates
# to that link via the child update the right table.
>>> obj = SelfRefChild.objects.create(child_data=37, parent_data=42)
>>> obj.delete()
# Regression tests for #8076 - get_(next/previous)_by_date should work.
>>> c1 = ArticleWithAuthor(headline='ArticleWithAuthor 1', author="Person 1", pub_date=datetime.datetime(2005, 8, 1, 3, 0))
>>> c1.save()
>>> c2 = ArticleWithAuthor(headline='ArticleWithAuthor 2', author="Person 2", pub_date=datetime.datetime(2005, 8, 1, 10, 0))
>>> c2.save()
>>> c3 = ArticleWithAuthor(headline='ArticleWithAuthor 3', author="Person 3", pub_date=datetime.datetime(2005, 8, 2))
>>> c3.save()
>>> c1.get_next_by_pub_date()
<ArticleWithAuthor: ArticleWithAuthor 2>
>>> c2.get_next_by_pub_date()
<ArticleWithAuthor: ArticleWithAuthor 3>
>>> c3.get_next_by_pub_date()
Traceback (most recent call last):
...
DoesNotExist: ArticleWithAuthor matching query does not exist.
>>> c3.get_previous_by_pub_date()
<ArticleWithAuthor: ArticleWithAuthor 2>
>>> c2.get_previous_by_pub_date()
<ArticleWithAuthor: ArticleWithAuthor 1>
>>> c1.get_previous_by_pub_date()
Traceback (most recent call last):
...
DoesNotExist: ArticleWithAuthor matching query does not exist.
# Regression test for #8825: Make sure all inherited fields (esp. m2m fields, in
# this case) appear on the child class.
>>> M2MChild.objects.filter(articles__isnull=False)
[]
# All fields from an ABC, including those inherited non-abstractly should be
# available on child classes (#7588). Creating this instance should work
# without error.
>>> _ = QualityControl.objects.create(headline="Problems in Django", pub_date=datetime.datetime.now(), quality=10, assignee="adrian")
# Ordering should not include any database column more than once (this is most
# likely to ocurr naturally with model inheritance, so we check it here).
# Regression test for #9390. This necessarily pokes at the SQL string for the
# query, since the duplicate problems are only apparent at that late stage.
>>> qs = ArticleWithAuthor.objects.order_by('pub_date', 'pk')
>>> sql = qs.query.get_compiler(qs.db).as_sql()[0]
>>> fragment = sql[sql.find('ORDER BY'):]
>>> pos = fragment.find('pub_date')
>>> fragment.find('pub_date', pos + 1) == -1
True
# It is possible to call update() and only change a field in an ancestor model
# (regression test for #10362).
>>> article = ArticleWithAuthor.objects.create(author="fred", headline="Hey there!", pub_date = datetime.datetime(2009, 3, 1, 8, 0, 0))
>>> ArticleWithAuthor.objects.filter(author="fred").update(headline="Oh, no!")
1
>>> ArticleWithAuthor.objects.filter(pk=article.pk).update(headline="Oh, no!")
1
>>> DerivedM.objects.create(customPK=44, base_name="b1", derived_name="d1")
<DerivedM: PK = 44, base_name = b1, derived_name = d1>
>>> DerivedM.objects.all()
[<DerivedM: PK = 44, base_name = b1, derived_name = d1>]
# Regression tests for #10406
# If there's a one-to-one link between a child model and the parent and no
# explicit pk declared, we can use the one-to-one link as the pk on the child.
# The ParkingLot2 model shows this behaviour.
>>> ParkingLot2._meta.pk.name
"parent"
# However, the connector from child to parent need not be the pk on the child
# at all.
>>> ParkingLot3._meta.pk.name
"primary_key"
>>> ParkingLot3._meta.get_ancestor_link(Place).name # the child->parent link
"parent"
# Check that many-to-many relations defined on an abstract base class
# are correctly inherited (and created) on the child class.
>>> p1 = Person.objects.create(name='Alice')
>>> p2 = Person.objects.create(name='Bob')
>>> p3 = Person.objects.create(name='Carol')
>>> p4 = Person.objects.create(name='Dave')
>>> birthday = BirthdayParty.objects.create(name='Birthday party for Alice')
>>> birthday.attendees = [p1, p3]
>>> bachelor = BachelorParty.objects.create(name='Bachelor party for Bob')
>>> bachelor.attendees = [p2, p4]
>>> print p1.birthdayparty_set.all()
[<BirthdayParty: Birthday party for Alice>]
>>> print p1.bachelorparty_set.all()
[]
>>> print p2.bachelorparty_set.all()
[<BachelorParty: Bachelor party for Bob>]
# Check that a subclass of a subclass of an abstract model
# doesn't get it's own accessor.
>>> p2.messybachelorparty_set.all()
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'messybachelorparty_set'
# ... but it does inherit the m2m from it's parent
>>> messy = MessyBachelorParty.objects.create(name='Bachelor party for Dave')
>>> messy.attendees = [p4]
>>> p4.bachelorparty_set.all()
[<BachelorParty: Bachelor party for Bob>, <BachelorParty: Bachelor party for Dave>]
"""}