/model_utils/models.py

https://github.com/asavoy/django-model-utils · Python · 133 lines · 76 code · 22 blank · 35 comment · 8 complexity · 1ddfe30ac3dd61ae6a7d9e1736aa8f2f MD5 · raw file

  1. import warnings
  2. from datetime import datetime
  3. from django.db import models
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.utils.translation import ugettext_lazy as _
  6. from django.db.models.fields import FieldDoesNotExist
  7. from django.core.exceptions import ImproperlyConfigured
  8. from model_utils.managers import manager_from, InheritanceCastMixin, \
  9. QueryManager
  10. from model_utils.fields import AutoCreatedField, AutoLastModifiedField, \
  11. StatusField, MonitorField
  12. class InheritanceCastModel(models.Model):
  13. """
  14. An abstract base class that provides a ``real_type`` FK to ContentType.
  15. For use in trees of inherited models, to be able to downcast
  16. parent instances to their child types.
  17. Pending deprecation; use InheritanceManager instead.
  18. """
  19. real_type = models.ForeignKey(ContentType, editable=False, null=True)
  20. objects = manager_from(InheritanceCastMixin)
  21. def __init__(self, *args, **kwargs):
  22. warnings.warn(
  23. "InheritanceCastModel is pending deprecation. "
  24. "Use InheritanceManager instead.",
  25. PendingDeprecationWarning,
  26. stacklevel=2)
  27. super(InheritanceCastModel, self).__init__(*args, **kwargs)
  28. def save(self, *args, **kwargs):
  29. if not self.id:
  30. self.real_type = self._get_real_type()
  31. super(InheritanceCastModel, self).save(*args, **kwargs)
  32. def _get_real_type(self):
  33. return ContentType.objects.get_for_model(type(self))
  34. def cast(self):
  35. return self.real_type.get_object_for_this_type(pk=self.pk)
  36. class Meta:
  37. abstract = True
  38. class TimeStampedModel(models.Model):
  39. """
  40. An abstract base class model that provides self-updating
  41. ``created`` and ``modified`` fields.
  42. """
  43. created = AutoCreatedField(_('created'))
  44. modified = AutoLastModifiedField(_('modified'))
  45. class Meta:
  46. abstract = True
  47. class TimeFramedModel(models.Model):
  48. """
  49. An abstract base class model that provides ``start``
  50. and ``end`` fields to record a timeframe.
  51. """
  52. start = models.DateTimeField(_('start'), null=True, blank=True)
  53. end = models.DateTimeField(_('end'), null=True, blank=True)
  54. class Meta:
  55. abstract = True
  56. class StatusModel(models.Model):
  57. """
  58. An abstract base class model with a ``status`` field that
  59. automatically uses a ``STATUS`` class attribute of choices, a
  60. ``status_changed`` date-time field that records when ``status``
  61. was last modified, and an automatically-added manager for each
  62. status that returns objects with that status only.
  63. """
  64. status = StatusField(_('status'))
  65. status_changed = MonitorField(_('status changed'), monitor='status')
  66. class Meta:
  67. abstract = True
  68. def add_status_query_managers(sender, **kwargs):
  69. """
  70. Add a Querymanager for each status item dynamically.
  71. """
  72. if not issubclass(sender, StatusModel):
  73. return
  74. for value, name in getattr(sender, 'STATUS', ()):
  75. try:
  76. sender._meta.get_field(name)
  77. raise ImproperlyConfigured("StatusModel: Model '%s' has a field "
  78. "named '%s' which conflicts with a "
  79. "status of the same name."
  80. % (sender.__name__, name))
  81. except FieldDoesNotExist:
  82. pass
  83. sender.add_to_class(value, QueryManager(status=value))
  84. def add_timeframed_query_manager(sender, **kwargs):
  85. """
  86. Add a QueryManager for a specific timeframe.
  87. """
  88. if not issubclass(sender, TimeFramedModel):
  89. return
  90. try:
  91. sender._meta.get_field('timeframed')
  92. raise ImproperlyConfigured("Model '%s' has a field named "
  93. "'timeframed' which conflicts with "
  94. "the TimeFramedModel manager."
  95. % sender.__name__)
  96. except FieldDoesNotExist:
  97. pass
  98. sender.add_to_class('timeframed', QueryManager(
  99. (models.Q(start__lte=datetime.now) | models.Q(start__isnull=True)) &
  100. (models.Q(end__gte=datetime.now) | models.Q(end__isnull=True))
  101. ))
  102. models.signals.class_prepared.connect(add_status_query_managers)
  103. models.signals.class_prepared.connect(add_timeframed_query_manager)