Skip to content
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

[Not intended for use] Historical records #1154

Draft
wants to merge 1 commit into
base: Development
Choose a base branch
from

Conversation

JackScanlon
Copy link
Collaborator

@JackScanlon JackScanlon commented Apr 26, 2023

DEPRECATED

[!] Note: Created before we decided we didn't want to continue with removing Django-Simple-History as a dependency, but may be of use in the future at some point

Details

Single-table implementation of historical records with associated historical querysets, historical managers and diff methods via signals and mixins.

Intended to collapse current models and their assoc. historical models to avoid multiple joins. In this implementation, we have reduced the number of tables that the Concept implementation from 14 tables down to 3 tables (technically 5 with the relational tables to handle M2M fields).

Related issue

Relates to following issue:

Example

In this implementation of Concepts, one could retrieve the final codelist in a manner similar to:

def get_codelist_for_concept(concept):
    codes = [ ]
    for ruleset in concept.rulesets.all():
        codes += list(ruleset.codes.all().values_list('code', flat=True))
    return codes

Implementation

Version ID handling

Versions are sequential for each instance instead of a global increment.

Relational support

Supports relational fields, e.g. M2M fields - can be seen within the concept_migrate command files.

Basic features

Setup

Simply extend the historical model mixin:

from .mixins.HistoricalModelMixin import HistoricalModelMixin

class SomeModel(HistoricalModelMixin):
    name = models.CharField(max_length=250)

    class Meta:
        ordering = ('name', )
        
    def __str__(self):
        return self.name

Historic fields

Each historic model includes:

  • entity_id i.e. the id of the instance
  • version_id i.e. sequential version id for each entity instance (distinct on entity_id)
  • version_date i.e. this version's creation date
  • created_date i.e. when the instance was first created
  • change_type i.e. whether this version was created/edited/deleted
  • change_reason i.e. why this entity was changed

Historic methods

is_historic()

Used to determine whether an entity is the current version or a historic version, e.g.:

from .models import SomeModel

instance = SomeModel.history.get(entity_id=1, version_id=2)
print(instance.is_historic())

HistoricalQuerySets & HistoricalManagers

Querying only the current objects

Can query only the current objects via the .objects property, e.g.:

from .models import SomeModel

all_current_instances = SomeModel.objects.all()
print(all_current_instances)

current_instance = SomeModel.objects.filter(entity_id=1).first()
print(current_instance) # Would return just the most recent version

Querying history by class

Ability to filter history of all instances within a model

from .models import SomeModel

print(SomeModel.history.all())
print(SomeModel.history.filter(entity_id=1))

Querying history by instance

Ability to filter history of a specific instance of a model

from .models import SomeModel

some_instance = SomeModel.objects.get(entity_id=1)
all_versions_of_this_instance = some_instance.history.all()

HistoricQuerySet methods

latest_of_each()

Used to find the latest version of each entity in a queryset, e.g.:

from .models import SomeModel

entities = SomeModel.history.get(entity_id__in=[1, 2, 3])
latest_entities = entities.latest_of_each()

HistoricManager methods

most_recent()

Returns the most recent record of an instance, e.g.:

some_instance = SomeModel.history.get(entity_id=1, version_id=2)
most_recent = some_instance.history.most_recent()

earliest()

Returns the earliest record of an instance, e.g.:

some_instance = SomeModel.history.get(entity_id=1, version_id=2)
earliest= some_instance.history.earliest()

Versioning & Diffing

Diff methods

get_delta()

Returns the diff between two instances, e.g.:

some_instance = SomeModel.history.get(entity_id=1, version_id=2)
earliest_version = some_instance.history.earliest()

difference = earliest_version.get_delta(some_instance)
print(difference)

get_field_diff()

Returns the diff of a specific field, e.g.:

from .models import SomeModel

some_instance = SomeModel.objects.get(entity_id=1)
some_instance.name = 'Some change'
print(some_instance.get_field_diff('name'))

Diff properties

_dict

Attempts to dictify the model for comparison, e.g.:

from .models import SomeModel

dict_representation = SomeModel.objects.get(entity_id=1)._dict

has_changed

Returns boolean that represents whether the object has been changed since it was last saved, e.g.

from .models import SomeModel

some_instance = SomeModel.objects.get(entity_id=1)
some_instance.name = 'Changed now'
print(some_instance.has_changed)

changed_fields

Returns a list of names describing the fields that have changed since it was last saved, e.g.:

from .models import SomeModel

some_instance = SomeModel.objects.get(entity_id=1)
some_instance.name = 'Changed now'
print(some_instance.last_changed)

diff

Returns the dict that describes the diff of now v.s. when the instance was last saved, e.g.:

from .models import SomeModel

some_instance = SomeModel.objects.get(entity_id=1)
some_instance.name = 'Changed now'
print(some_instance.diff)

Base automatically changed from Dynamic-Template-Feature-Master to elmessary/stat-cs June 20, 2023 19:41
@JackScanlon JackScanlon changed the base branch from elmessary/stat-cs to Development October 11, 2023 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant