django-model-utils/tests/models.py

439 lines
12 KiB
Python
Raw Normal View History

from __future__ import annotations
from typing import ClassVar, TypeVar
from django.db import models
from django.db.models import Manager
2020-11-29 20:58:00 +00:00
from django.db.models.query_utils import DeferredAttribute
from django.utils.translation import gettext_lazy as _
from model_utils import Choices
2020-11-29 20:58:00 +00:00
from model_utils.fields import MonitorField, SplitField, StatusField, UUIDField
from model_utils.managers import (
InheritanceManager,
JoinManager,
QueryManager,
SoftDeletableManager,
SoftDeletableQuerySet,
)
2016-09-12 11:50:03 +00:00
from model_utils.models import (
SoftDeletableModel,
StatusModel,
TimeFramedModel,
TimeStampedModel,
2019-02-26 16:35:50 +00:00
UUIDModel,
2016-09-12 11:50:03 +00:00
)
2020-11-29 20:58:00 +00:00
from model_utils.tracker import FieldTracker, ModelTracker
from tests.fields import MutableField
ModelT = TypeVar('ModelT', bound=models.Model, covariant=True)
class InheritanceManagerTestRelated(models.Model):
pass
class InheritanceManagerTestParent(models.Model):
# FileField is just a handy descriptor-using field. Refs #6.
non_related_field_using_descriptor = models.FileField(upload_to="test")
related = models.ForeignKey(
2016-03-27 00:43:30 +00:00
InheritanceManagerTestRelated, related_name="imtests", null=True,
on_delete=models.CASCADE)
normal_field = models.TextField()
2016-03-27 00:43:30 +00:00
related_self = models.OneToOneField(
"self", related_name="imtests_self", null=True,
on_delete=models.CASCADE)
objects: ClassVar[InheritanceManager[InheritanceManagerTestParent]] = InheritanceManager()
def __str__(self):
return "{}({})".format(
self.__class__.__name__[len('InheritanceManagerTest'):],
self.pk,
)
class InheritanceManagerTestChild1(InheritanceManagerTestParent):
non_related_field_using_descriptor_2 = models.FileField(upload_to="test")
normal_field_2 = models.TextField()
objects: ClassVar[InheritanceManager[InheritanceManagerTestParent]] = InheritanceManager()
class InheritanceManagerTestGrandChild1(InheritanceManagerTestChild1):
text_field = models.TextField()
class InheritanceManagerTestGrandChild1_2(InheritanceManagerTestChild1):
text_field = models.TextField()
class InheritanceManagerTestChild2(InheritanceManagerTestParent):
non_related_field_using_descriptor_2 = models.FileField(upload_to="test")
normal_field_2 = models.TextField()
class InheritanceManagerTestChild3(InheritanceManagerTestParent):
parent_ptr = models.OneToOneField(
InheritanceManagerTestParent, related_name='manual_onetoone',
2016-03-27 00:43:30 +00:00
parent_link=True, on_delete=models.CASCADE)
class InheritanceManagerTestChild3_1(InheritanceManagerTestParent):
parent_ptr = models.OneToOneField(
InheritanceManagerTestParent, db_column="custom_parent_ptr",
parent_link=True, on_delete=models.CASCADE)
class InheritanceManagerTestChild4(InheritanceManagerTestParent):
other_onetoone = models.OneToOneField(
InheritanceManagerTestParent, related_name='non_inheritance_relation',
parent_link=False, on_delete=models.CASCADE)
# The following is needed because of that Django bug:
# https://code.djangoproject.com/ticket/29998
parent_ptr = models.OneToOneField(
InheritanceManagerTestParent, related_name='child4_onetoone',
parent_link=True, on_delete=models.CASCADE)
class TimeStamp(TimeStampedModel):
test_field = models.PositiveSmallIntegerField(default=0)
class TimeFrame(TimeFramedModel):
pass
class TimeFrameManagerAdded(TimeFramedModel):
pass
class Monitored(models.Model):
name = models.CharField(max_length=25)
name_changed = MonitorField(monitor="name")
name_changed_nullable = MonitorField(monitor="name", null=True)
class MonitorWhen(models.Model):
name = models.CharField(max_length=25)
name_changed = MonitorField(monitor="name", when=["Jose", "Maria"])
class MonitorWhenEmpty(models.Model):
name = models.CharField(max_length=25)
name_changed = MonitorField(monitor="name", when=[])
2016-11-18 00:24:57 +00:00
class DoubleMonitored(models.Model):
name = models.CharField(max_length=25)
name_changed = MonitorField(monitor="name")
name2 = models.CharField(max_length=25)
name_changed2 = MonitorField(monitor="name2")
class Status(StatusModel):
STATUS: Choices[str] = Choices(
("active", _("active")),
("deleted", _("deleted")),
("on_hold", _("on hold")),
)
class StatusPlainTuple(StatusModel):
STATUS = (
("active", _("active")),
("deleted", _("deleted")),
("on_hold", _("on hold")),
)
class StatusManagerAdded(StatusModel):
STATUS = (
("active", _("active")),
("deleted", _("deleted")),
("on_hold", _("on hold")),
)
class StatusCustomManager(Manager):
pass
class AbstractCustomManagerStatusModel(StatusModel):
"""An abstract status model with a custom manager."""
STATUS = Choices(
("first_choice", _("First choice")),
("second_choice", _("Second choice")),
)
objects = StatusCustomManager()
class Meta:
abstract = True
class CustomManagerStatusModel(AbstractCustomManagerStatusModel):
"""A concrete status model with a custom manager."""
title = models.CharField(max_length=50)
class Post(models.Model):
2014-01-26 00:11:26 +00:00
published = models.BooleanField(default=False)
confirmed = models.BooleanField(default=False)
order = models.IntegerField()
objects = models.Manager()
public: ClassVar[QueryManager[Post]] = QueryManager(published=True)
public_confirmed: ClassVar[QueryManager[Post]] = QueryManager(
2019-02-26 17:16:01 +00:00
models.Q(published=True) & models.Q(confirmed=True))
public_reversed: QueryManager[Post] = QueryManager(
published=True).order_by("-order")
class Meta:
ordering = ("order",)
2010-01-15 22:26:59 +00:00
class Article(models.Model):
title = models.CharField(max_length=50)
body = SplitField()
class SplitFieldAbstractParent(models.Model):
content = SplitField()
class Meta:
abstract = True
class AbstractTracked(models.Model):
number: models.IntegerField
class Meta:
abstract = True
2013-02-16 22:52:31 +00:00
class Tracked(models.Model):
name = models.CharField(max_length=20)
number = models.IntegerField()
2015-10-28 21:12:35 +00:00
mutable = MutableField(default=None)
2013-02-16 22:52:31 +00:00
tracker = FieldTracker()
2013-02-16 22:52:31 +00:00
def save(self, *args, **kwargs):
""" No-op save() to ensure that FieldTracker.patch_save() works. """
super().save(*args, **kwargs)
2013-02-16 22:52:31 +00:00
class TrackerTimeStamped(TimeStampedModel):
name = models.CharField(max_length=20)
number = models.IntegerField()
mutable = MutableField(default=None)
tracker = FieldTracker()
def save(self, *args, **kwargs):
""" Automatically add "modified" to update_fields."""
update_fields = kwargs.get('update_fields')
if update_fields is not None:
kwargs['update_fields'] = set(update_fields) | {'modified'}
super().save(*args, **kwargs)
class TrackedFK(models.Model):
2016-03-27 00:43:30 +00:00
fk = models.ForeignKey('Tracked', on_delete=models.CASCADE)
tracker = FieldTracker()
custom_tracker = FieldTracker(fields=['fk_id'])
custom_tracker_without_id = FieldTracker(fields=['fk'])
class TrackedAbstract(AbstractTracked):
name = models.CharField(max_length=20)
number = models.IntegerField() # type: ignore[assignment]
mutable = MutableField(default=None)
tracker = ModelTracker()
class TrackedNotDefault(models.Model):
name = models.CharField(max_length=20)
number = models.IntegerField()
name_tracker = FieldTracker(fields=['name'])
class TrackedNonFieldAttr(models.Model):
number = models.FloatField()
@property
def rounded(self):
return round(self.number) if self.number is not None else None
tracker = FieldTracker(fields=['rounded'])
class TrackedMultiple(models.Model):
name = models.CharField(max_length=20)
number = models.IntegerField()
name_tracker = FieldTracker(fields=['name'])
number_tracker = FieldTracker(fields=['number'])
class TrackedFileField(models.Model):
some_file = models.FileField(upload_to='test_location')
tracker = FieldTracker()
class InheritedTracked(Tracked):
name2 = models.CharField(max_length=20)
class InheritedTrackedFK(TrackedFK):
custom_tracker = FieldTracker(fields=['fk_id'])
custom_tracker_without_id = FieldTracker(fields=['fk'])
class ModelTracked(models.Model):
name = models.CharField(max_length=20)
number = models.IntegerField()
2015-10-28 21:12:35 +00:00
mutable = MutableField(default=None)
tracker = ModelTracker()
class ModelTrackedFK(models.Model):
2016-03-27 00:43:30 +00:00
fk = models.ForeignKey('ModelTracked', on_delete=models.CASCADE)
tracker = ModelTracker()
custom_tracker = ModelTracker(fields=['fk_id'])
custom_tracker_without_id = ModelTracker(fields=['fk'])
class ModelTrackedNotDefault(models.Model):
2013-02-16 22:52:31 +00:00
name = models.CharField(max_length=20)
number = models.IntegerField()
name_tracker = ModelTracker(fields=['name'])
2013-02-19 04:06:52 +00:00
class ModelTrackedMultiple(models.Model):
2013-02-19 04:06:52 +00:00
name = models.CharField(max_length=20)
number = models.IntegerField()
name_tracker = ModelTracker(fields=['name'])
number_tracker = ModelTracker(fields=['number'])
class InheritedModelTracked(ModelTracked):
name2 = models.CharField(max_length=20)
class StatusFieldDefaultFilled(models.Model):
STATUS = Choices((0, "no", "No"), (1, "yes", "Yes"))
status = StatusField(default=STATUS.yes)
class StatusFieldDefaultNotFilled(models.Model):
STATUS = Choices((0, "no", "No"), (1, "yes", "Yes"))
status = StatusField()
class StatusFieldChoicesName(models.Model):
NAMED_STATUS = Choices((0, "no", "No"), (1, "yes", "Yes"))
status = StatusField(choices_name='NAMED_STATUS')
2016-09-12 11:50:03 +00:00
class SoftDeletable(SoftDeletableModel):
"""
Test model with additional manager for full access to model
instances.
"""
name = models.CharField(max_length=20)
all_objects: ClassVar[Manager[SoftDeletable]] = models.Manager()
class CustomSoftDeleteQuerySet(SoftDeletableQuerySet):
def only_read(self):
return self.filter(is_read=True)
class CustomSoftDelete(SoftDeletableModel):
is_read = models.BooleanField(default=False)
available_objects = SoftDeletableManager.from_queryset(CustomSoftDeleteQuerySet)() # type: ignore[misc]
2019-09-30 08:08:52 +00:00
class StringyDescriptor:
"""
Descriptor that returns a string version of the underlying integer value.
"""
def __init__(self, name):
self.name = name
def __get__(self, obj, cls=None):
if obj is None:
return self
if self.name in obj.get_deferred_fields():
# This queries the database, and sets the value on the instance.
2022-08-17 20:49:23 +00:00
fields_map = {f.name: f for f in cls._meta.fields}
field = fields_map[self.name]
DeferredAttribute(field=field).__get__(obj, cls)
return str(obj.__dict__[self.name])
def __set__(self, obj, value):
obj.__dict__[self.name] = int(value)
2018-04-04 17:02:46 +00:00
def __delete__(self, obj):
del obj.__dict__[self.name]
class CustomDescriptorField(models.IntegerField):
def contribute_to_class(self, cls, name, *args, **kwargs):
super().contribute_to_class(cls, name, *args, **kwargs)
setattr(cls, name, StringyDescriptor(name))
class ModelWithCustomDescriptor(models.Model):
custom_field = CustomDescriptorField()
tracked_custom_field = CustomDescriptorField()
regular_field = models.IntegerField()
tracked_regular_field = models.IntegerField()
tracker = FieldTracker(fields=['tracked_custom_field', 'tracked_regular_field'])
class BoxJoinModel(models.Model):
name = models.CharField(max_length=32)
objects: ClassVar[JoinManager[BoxJoinModel]] = JoinManager()
class JoinItemForeignKey(models.Model):
weight = models.IntegerField()
belonging = models.ForeignKey(
BoxJoinModel,
null=True,
on_delete=models.CASCADE
)
objects: ClassVar[JoinManager[JoinItemForeignKey]] = JoinManager()
2019-02-26 16:35:50 +00:00
class CustomUUIDModel(UUIDModel):
pass
class CustomNotPrimaryUUIDModel(models.Model):
uuid = UUIDField(primary_key=False)
2017-07-10 12:35:39 +00:00
class TimeStampWithStatusModel(TimeStampedModel, StatusModel):
STATUS = Choices(
("active", _("active")),
("deleted", _("deleted")),
("on_hold", _("on hold")),
)
test_field = models.PositiveSmallIntegerField(default=0)