import copy import datetime from django.contrib.auth.models import User from django.db import models from django.db.models.query import Q from django.utils.datastructures import SortedDict class RevisionableModel(models.Model): base = models.ForeignKey('self', null=True) title = models.CharField(blank=True, max_length=255) when = models.DateTimeField(default=datetime.datetime.now) def __unicode__(self): return u"%s (%s, %s)" % (self.title, self.id, self.base.id) def save(self, force_insert=False, force_update=False): super(RevisionableModel, self).save(force_insert, force_update) if not self.base: self.base = self super(RevisionableModel, self).save() def new_revision(self): new_revision = copy.copy(self) new_revision.pk = None return new_revision class Order(models.Model): created_by = models.ForeignKey(User) text = models.TextField() __test__ = {"API_TESTS": """ # Regression tests for #7314 and #7372 >>> rm = RevisionableModel.objects.create(title='First Revision', when=datetime.datetime(2008, 9, 28, 10, 30, 0)) >>> rm.pk, rm.base.pk (1, 1) >>> rm2 = rm.new_revision() >>> rm2.title = "Second Revision" >>> rm.when = datetime.datetime(2008, 9, 28, 14, 25, 0) >>> rm2.save() >>> print u"%s of %s" % (rm2.title, rm2.base.title) Second Revision of First Revision >>> rm2.pk, rm2.base.pk (2, 1) Queryset to match most recent revision: >>> qs = RevisionableModel.objects.extra(where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % {'table': RevisionableModel._meta.db_table,}],) >>> qs [] Queryset to search for string in title: >>> qs2 = RevisionableModel.objects.filter(title__contains="Revision") >>> qs2 [, ] Following queryset should return the most recent revision: >>> qs & qs2 [] >>> u = User.objects.create_user(username="fred", password="secret", email="fred@example.com") # General regression tests: extra select parameters should stay tied to their # corresponding select portions. Applies when portions are updated or otherwise # moved around. >>> qs = User.objects.extra(select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), select_params=(1, 3)) >>> qs = qs.extra(select={"beta": 4}) >>> qs = qs.extra(select={"alpha": "%s"}, select_params=[5]) >>> result = {'alpha': 5, 'beta': 4, 'gamma': 3} >>> list(qs.filter(id=u.id).values('alpha', 'beta', 'gamma')) == [result] True # Regression test for #7957: Combining extra() calls should leave the # corresponding parameters associated with the right extra() bit. I.e. internal # dictionary must remain sorted. >>> User.objects.extra(select={"alpha": "%s"}, select_params=(1,)).extra(select={"beta": "%s"}, select_params=(2,))[0].alpha 1 >>> User.objects.extra(select={"beta": "%s"}, select_params=(1,)).extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha 2 # Regression test for #7961: When not using a portion of an extra(...) in a # query, remove any corresponding parameters from the query as well. >>> list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,)).filter(id=u.id).values_list('id', flat=True)) == [u.id] True # Regression test for #8063: limiting a query shouldn't discard any extra() # bits. >>> qs = User.objects.all().extra(where=['id=%s'], params=[u.id]) >>> qs [] >>> qs[:1] [] # Regression test for #8039: Ordering sometimes removed relevant tables from # extra(). This test is the critical case: ordering uses a table, but then # removes the reference because of an optimisation. The table should still be # present because of the extra() call. >>> Order.objects.extra(where=["username=%s"], params=["fred"], tables=["auth_user"]).order_by('created_by') [] # Regression test for #8819: Fields in the extra(select=...) list should be # available to extra(order_by=...). >>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}).distinct() [] >>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}, order_by=['extra_field']) [] >>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct() [] # When calling the dates() method on a queryset with extra selection columns, # we can (and should) ignore those columns. They don't change the result and # cause incorrect SQL to be produced otherwise. >>> RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month') [datetime.datetime(2008, 9, 1, 0, 0)] """}