Added TimeFramedModel and ConditionalModel.

This commit is contained in:
Jannis Leidel 2010-04-15 04:53:55 +02:00
parent 73b211309d
commit 35d25f0a0f
4 changed files with 198 additions and 4 deletions

View file

@ -16,6 +16,7 @@ class AutoCreatedField(models.DateTimeField):
kwargs.setdefault('default', datetime.now)
super(AutoCreatedField, self).__init__(*args, **kwargs)
class AutoLastModifiedField(AutoCreatedField):
"""
A DateTimeField that updates itself on each save() of the model.
@ -28,6 +29,62 @@ class AutoLastModifiedField(AutoCreatedField):
setattr(model_instance, self.attname, value)
return value
def _previous_condition(model_instance, attname, add):
if add:
return None
pk_value = getattr(model_instance, model_instance._meta.pk.attname)
try:
current = model_instance.__class__._default_manager.get(pk=pk_value)
except model_instance.__class__.DoesNotExist:
return None
return getattr(current, attname, None)
class ConditionField(models.PositiveIntegerField):
"""
A PositiveIntegerField that has set conditional choices by default.
"""
def contribute_to_class(self, cls, name):
if not cls._meta.abstract:
assert not not hasattr(cls, 'CONDITIONS'), "The model '%s' doesn't have conditions set." % cls.__name__
setattr(self, '_choices', cls.CONDITIONS)
setattr(self, 'default', tuple(cls.CONDITIONS)[0][0]) # sets first as default
super(ConditionField, self).contribute_to_class(cls, name)
def pre_save(self, model_instance, add):
previous = _previous_condition(model_instance, 'get_%s_display' % self.attname, add)
if previous:
previous = previous()
setattr(model_instance, 'previous_condition', previous)
return super(ConditionField, self).pre_save(model_instance, add)
class ConditionModifedField(models.DateTimeField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('default', datetime.now)
depends_on = kwargs.pop('depends_on', 'condition')
if not depends_on:
raise TypeError(
'%s requires a depends_on parameter' % self.__class__.__name__)
self.depends_on = depends_on
super(ConditionModifedField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
#print cls._meta, cls
assert not getattr(cls._meta, "has_condition_modified_field", False), "A model can't have more than one ConditionModifedField."
super(ConditionModifedField, self).contribute_to_class(cls, name)
setattr(cls._meta, "has_condition_modified_field", True)
def pre_save(self, model_instance, add):
value = datetime.now()
previous = _previous_condition(model_instance, self.depends_on, add)
current = getattr(model_instance, self.depends_on, None)
if (previous and (previous != current)) or (current and not previous):
setattr(model_instance, self.attname, value)
return super(ConditionModifedField, self).pre_save(model_instance, add)
SPLIT_MARKER = getattr(settings, 'SPLIT_MARKER', '<!-- split -->')
# the number of paragraphs after which to split if no marker

View file

@ -1,8 +1,14 @@
from datetime import datetime
from django.db import models
from django.db.models.base import ModelBase
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
from django.db.models.fields import FieldDoesNotExist
from model_utils.fields import AutoCreatedField, AutoLastModifiedField
from model_utils.managers import QueryManager
from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \
ConditionField, ConditionModifedField
class InheritanceCastModel(models.Model):
"""
@ -41,3 +47,76 @@ class TimeStampedModel(models.Model):
class Meta:
abstract = True
class TimeFramedBaseModel(ModelBase):
"""
A model base class for the ``TimeFramedModel`` that adds
a special model manager ``timeframed`` for time frames.
"""
def _prepare(cls):
super(TimeFramedBaseModel, cls)._prepare()
try:
cls._meta.get_field('timeframed')
raise ValueError("Model %s has a field named 'timeframed' and "
"conflicts with a manager." % cls.__name__)
except FieldDoesNotExist:
pass
cls.add_to_class('timeframed', QueryManager(
(models.Q(starts__lte=datetime.now()) | models.Q(starts__isnull=True)) &
(models.Q(ends__gte=datetime.now()) | models.Q(ends__isnull=True))
))
class TimeFramedModel(models.Model):
"""
An abstract base class model that provides ``starts``
and ``ends`` fields to record a timeframe.
"""
__metaclass__ = TimeFramedBaseModel
starts = models.DateTimeField(_('starts'), null=True, blank=True)
ends = models.DateTimeField(_('ends'), null=True, blank=True)
class Meta:
abstract = True
class ConditionalBaseModel(ModelBase):
"""
A model base class for the ``ConditionalModel`` to add
a series of model managers for each given condition.
"""
def _prepare(cls):
super(ConditionalBaseModel, cls)._prepare()
conditions = getattr(cls, 'CONDITIONS', None)
if conditions is None:
return
for value, name in conditions._choices:
try:
cls._meta.get_field(name)
raise ValueError("Model %s has a field named '%s' and "
"conflicts with a condition."
% (cls.__name__, name))
except FieldDoesNotExist:
pass
cls.add_to_class(name, QueryManager(**{'condition': value}))
class ConditionalModel(models.Model):
"""
An abstract base class model that provides self-updating
condition fields like ``deleted`` and ``restored``.
"""
__metaclass__ = ConditionalBaseModel
condition = ConditionField(_('condition'))
condition_date = ConditionModifedField(_('condition date'))
def __unicode__(self):
return self.get_condition_display()
class Meta:
abstract = True

View file

@ -1,9 +1,10 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from model_utils.models import InheritanceCastModel, TimeStampedModel
from model_utils.models import InheritanceCastModel, TimeStampedModel, ConditionalModel, TimeFramedModel
from model_utils.managers import QueryManager
from model_utils.fields import SplitField
from model_utils import ChoiceEnum
class InheritParent(InheritanceCastModel):
pass
@ -14,6 +15,16 @@ class InheritChild(InheritParent):
class TimeStamp(TimeStampedModel):
pass
class TimeFrame(TimeFramedModel):
pass
class Condition(ConditionalModel):
CONDITIONS = ChoiceEnum(
('active', _('active')),
('deleted', _('deleted')),
('on_hold', _('on hold')),
)
class Post(models.Model):
published = models.BooleanField()
confirmed = models.BooleanField()

View file

@ -1,3 +1,5 @@
from datetime import datetime, timedelta
from django.test import TestCase
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
@ -6,7 +8,7 @@ from django.db.models.fields import FieldDoesNotExist
from model_utils import ChoiceEnum
from model_utils.fields import get_excerpt
from model_utils.tests.models import InheritParent, InheritChild, TimeStamp, \
Post, Article
Post, Article, Condition, TimeFrame
class GetExcerptTests(TestCase):
@ -134,6 +136,51 @@ class TimeStampedModelTests(TestCase):
t1.save()
self.assert_(t2.modified < t1.modified)
class TimeFramedModelTests(TestCase):
def testCreated(self):
now = datetime.now()
# objects are out of the timeframe
TimeFrame.objects.create(starts=now+timedelta(days=2))
TimeFrame.objects.create(ends=now-timedelta(days=1))
self.assertEquals(TimeFrame.timeframed.count(), 0)
# objects in the timeframe for various reasons
TimeFrame.objects.create(starts=now-timedelta(days=10))
TimeFrame.objects.create(ends=now+timedelta(days=2))
TimeFrame.objects.create(starts=now-timedelta(days=1), ends=now+timedelta(days=1))
self.assertEquals(TimeFrame.timeframed.count(), 3)
class ConditionalModelTests(TestCase):
def testCreated(self):
c1 = Condition.objects.create()
c2 = Condition.objects.create()
self.assert_(c2.condition_date > c1.condition_date)
self.assertEquals(Condition.active.count(), 2)
def testModification(self):
t1 = Condition.objects.create()
date_created = t1.condition_date
t1.condition = t1.CONDITIONS.on_hold
t1.save()
self.assert_(t1.condition_date > date_created)
date_changed = t1.condition_date
t1.save()
self.assertEquals(t1.condition_date, date_changed)
date_active_again = t1.condition_date
t1.condition = t1.CONDITIONS.active
t1.save()
self.assert_(t1.condition_date > date_active_again)
def testPreviousConditon(self):
c = Condition.objects.create()
self.assertEquals(c.previous_condition, None)
c.condition = c.CONDITIONS.on_hold
c.save()
self.assertEquals(c.previous_condition, c.CONDITIONS.get_active_display())
class QueryManagerTests(TestCase):
def setUp(self):
data = ((True, True, 0),