mirror of
https://github.com/Hopiu/django-model-utils.git
synced 2026-05-05 17:24:41 +00:00
Added TimeFramedModel and ConditionalModel.
This commit is contained in:
parent
73b211309d
commit
35d25f0a0f
4 changed files with 198 additions and 4 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue