8000 Add json model snapshots + tests by ckcollab · Pull Request #32 · ckc-org/django-ckc · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add json model snapshots + tests #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10.x]
# TODO: add 3.11.x when it's ready! Last checked Oct 31, 2022
python-version: [3.9, 3.10.x]
django-version: ['<4', '>=4']

steps:
Expand Down
33 changes: 33 additions & 0 deletions ckc/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.db import models
from django.utils.timezone import now


class SoftDeleteQuerySet(models.QuerySet):
Expand All @@ -23,3 +24,35 @@ class Meta:
def delete(self, *args, **kwargs):
self.deleted = True
self.save()


class JsonSnapshotModel(models.Model):
"""This mixin is meant to be inherited by a model class. It creates a snapshot field for the class that it is a part of.
This field is used to solidify data at a given point in time.

The create_json_snapshot() method must be overridden in the class inheriting this mixin. Inside this method you will build
a custom JSON object of your model state. Include the fields you wish to be solidified.

Lastly, call take_snapshot() at the point in your code you want data to be saved. The time and date this occurs will
also be saved in a separate field called snapshot_date.
"""
snapshot = models.JSONField(null=True, blank=True, default=dict)
snapshot_date = models.DateTimeField(null=True, blank=True)

class Meta:
abstract = True

def _create_json_snapshot(self) -> dict:
"""Override this method to take a "snapshot" of the relevant data on this model"""
raise NotImplementedError

def take_snapshot(self, force=False):
if not force:
assert not self.snapshot, "Can not override an existing snapshot instance."

self.snapshot = self._create_json_snapshot()

# TODO: Do we want to test these edge cases?
# assert self.snapshot is not None, ""

self.snapshot_date = now()
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ name = django-ckc
author = Eric Carmichael
author_email = eric@ckcollab.com
description = tools, utilities, etc. we use across projects @ ckc
version = 0.0.8
version = 0.0.9
url = https://github.com/ckcollab/django-ckc
keywords =
django
Expand Down
31 changes: 30 additions & 1 deletion testproject/testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@
from django.contrib.gis.db.models import PointField
from django.db import models

from ckc.models import SoftDeletableModel
from ckc.models import SoftDeletableModel, JsonSnapshotModel


User = get_user_model()


# ----------------------------------------------------------------------------
# Testing soft deletable model
# ----------------------------------------------------------------------------
class AModel(SoftDeletableModel):
title = models.CharField(max_length=255, default="I'm a test!")


# ----------------------------------------------------------------------------
# PrimaryKeyWriteSerializerReadField related model
# ----------------------------------------------------------------------------
class BModel(models.Model):
a = models.ForeignKey(AModel, on_delete=models.CASCADE)


# ----------------------------------------------------------------------------
# DefaultCreatedByMixin models
# ----------------------------------------------------------------------------
class ModelWithACreator(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE)

Expand All @@ -24,5 +33,25 @@ class ModelWithADifferentNamedCreator(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)


# ----------------------------------------------------------------------------
# For testing geo points in factories
# ----------------------------------------------------------------------------
class Location(models.Model):
geo_point = PointField()


# ----------------------------------------------------------------------------
# For testing JSON snapshots
# ----------------------------------------------------------------------------
class SnapshottedModel(JsonSnapshotModel, models.Model):

def _create_json_snapshot(self) -> dict:
return {
"test": "snapshot"
}


class SnapshottedModelMissingOverride(JsonSnapshotModel, models.Model):
# No _create_json_snapshot here! This is for testing purposes, to confirm we raise
# an assertion when this method is missing
pass
42 changes: 42 additions & 0 deletions tests/integration/test_json_model_snapshots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pytest
from rest_framework.test import APITestCase

from testapp.models import SnapshottedModel, SnapshottedModelMissingOverride


class TestJsonSnapshottedModels(APITestCase):
def test_snapshot_model_asserts_method_must_be_implemented_if_it_is_missing(self):
instance = SnapshottedModelMissingOverride()

# make sure proper error raised when we try to snapshot with a missing method
with pytest.raises(NotImplementedError):
instance.take_snapshot()

def test_snapshot_model_save_actually_saves_to_databse(self):
instance = SnapshottedModel()
instance.take_snapshot()

# Snapshot was written to model...
assert instance.snapshot == {"test": "snapshot"}

instance.save()

# Snapshot was saved to database, forreal
assert SnapshottedModel.objects.get(snapshot__test="snapshot")

def test_snapshotting_an_already_snapshotted_model_raises_exception_unless_forced(self):
instance = SnapshottedModel()
instance.take_snapshot()

# trying to snapshot already snapshotted model -> shit the bed
with pytest.raises(Exception):
instance.take_snapshot()

# Clear snapshot to see if we can re-set it..
instance.snapshot = None

# No exception raised here! And data was properly written
instance.take_snapshot(force=True)

# We were able to re-snapshot
assert instance.snapshot == {"test": "snapshot"}
0