# src/core/models/abstract.pyfromdjango.dbimportmodelsclassTimeStampedModel(models.Model):"""
An abstract base class model that provides self-updating
`created_at` and `updated_at` fields.
"""created_at=models.DateTimeField(auto_now_add=True)updated_at=models.DateTimeField(auto_now=True)classMeta:abstract=Trueordering=["-updated_at"]get_latest_by="-updated_at"
Above I used TimeStampedModel as an Abstract Model and I always recommend to
move your common fields into Abstract Model aligned with clean architecture
and DRY method.
In a more common way you could write the model like below;
Unfortunately, auto_now and auto_now_add make writing unit tests which
depend on creation or modification times difficult, since there is no simple way
to set these fields to a specific time for testing.
For an example, assume you have a business rule; you’re giving 7 days to authors
to be able publish a blog post after creation. Maybe you want them to re-read
their posts to eliminate typo or logic errors — yes, it makes no sense, I’m just
making some company policy.
# src/main/models.pyimportdatetimefromdjango.utils.translationimportugettext_lazyas_fromdjango.utilsimporttimezonefromsrc.core.models.abstractimportTimeStampedModelclassPost(TimeStampedModel):title=models.CharField()author=models.ForeignKey("author")body=models.TextField()is_published=models.BooleanField(default=False)classMeta:verbose_name=_("Post")verbose_name_plural=_("Posts")def__repr__(self):returnf"<Post {self.author}:{self.title}>"def__str__(self):returnf"{self.author}:{self.title}"defpublish(self):"""Publish a post which created >=7 days before"""iftimezone.now()-self.created_at<=datetime.timedelta(days=7):self.is_published=Trueself.save()
As you can see we’re making is_published attr True if a post created 7 days
before or more.
In order to test this behavior, let’s write some unittest;
I’m using FactoryBoy library to create an User instance, however you can use
default methods like User.objects.create(...).
Above test will fail since the created_at field of the created Post model
instance will always be equal to the time you run the tests. So there is no way to
make is_publishedTrue in a test.
Solution
Solution comes from Python’s unittest.mock library: Mock;
We are patching the method with mock.patch decorator to return a specific time
when the factory creates the object for testing. So with mock in this method
current now will be 60 days before actual now.
When you run the test you’ll see the test will pass.
Instead of decorator you can also use context manager —I don’t usually use
this method since it creates hard-to-read, nested methods when you
mock/patch multiple stuff: