Commits

Jannis Leidel  committed 22a5a2f

Added TimeFramedModel and ConditionalModel.

  • Participants
  • Parent commits fdf94e6

Comments (0)

Files changed (4)

File model_utils/fields.py

         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.
         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

File model_utils/models.py

+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):
     """
     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
+

File model_utils/tests/models.py

 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
 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()

File model_utils/tests/tests.py

+from datetime import datetime, timedelta
+
 from django.test import TestCase
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 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):
         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),