From affb4e42b853c651a68346d8c5da526f76ef1440 Mon Sep 17 00:00:00 2001 From: jmr Date: Tue, 15 Mar 2022 14:50:52 +0100 Subject: [PATCH 1/3] black and isort --- .github/workflows/python-app.yml | 6 ++++++ dev-requirements.txt | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index efb8022..08b68ca 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -55,6 +55,12 @@ jobs: pip install -e . pip install -U -r dev-requirements.txt pip install -U Django~=${{ matrix.django_version }} + - name: isort + run: | + isort tracking_fields --profile black --skip migrations + - name: Lint with black + run: | + black tracking_fields --exclude "migrations" - name: Lint with flake8 run: | flake8 --ignore=E501,W504 tracking_fields diff --git a/dev-requirements.txt b/dev-requirements.txt index 77d0f82..7fa3013 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -11,5 +11,6 @@ flake8-tidy-imports pycodestyle coveralls argparse +black # Sort and lint imports isort From 98ab9627914e4881818ecec2bc95125ec9de7d13 Mon Sep 17 00:00:00 2001 From: jmr Date: Tue, 15 Mar 2022 16:47:56 +0100 Subject: [PATCH 2/3] isort files --- tracking_fields/admin.py | 2 +- tracking_fields/decorators.py | 11 ++++++----- tracking_fields/models.py | 4 +++- tracking_fields/settings.py | 1 + tracking_fields/tests/settings.py | 1 + tracking_fields/tests/tests.py | 13 +++++++++---- tracking_fields/tests/urls.py | 2 +- tracking_fields/tracking.py | 9 ++++++--- 8 files changed, 28 insertions(+), 15 deletions(-) diff --git a/tracking_fields/admin.py b/tracking_fields/admin.py index 6ebe3a4..15cf1fe 100644 --- a/tracking_fields/admin.py +++ b/tracking_fields/admin.py @@ -6,7 +6,7 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import gettext_lazy as _ -from tracking_fields.models import TrackingEvent, TrackedFieldModification +from tracking_fields.models import TrackedFieldModification, TrackingEvent class TrackedObjectMixinAdmin(admin.ModelAdmin): diff --git a/tracking_fields/decorators.py b/tracking_fields/decorators.py index 78c883e..2d7e05a 100644 --- a/tracking_fields/decorators.py +++ b/tracking_fields/decorators.py @@ -1,14 +1,15 @@ from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType -from django.urls import reverse from django.db.models import ManyToManyField -from django.db.models.signals import ( - post_init, post_save, pre_delete, m2m_changed -) +from django.db.models.signals import m2m_changed, post_init, post_save, pre_delete +from django.urls import reverse from tracking_fields.tracking import ( - tracking_init, tracking_save, tracking_delete, tracking_m2m + tracking_delete, + tracking_init, + tracking_m2m, + tracking_save, ) diff --git a/tracking_fields/models.py b/tracking_fields/models.py index 7b84adf..251faa2 100644 --- a/tracking_fields/models.py +++ b/tracking_fields/models.py @@ -6,9 +6,11 @@ from django.contrib.contenttypes.fields import GenericForeignKey except ImportError: from django.contrib.contenttypes.generic import GenericForeignKey + from django.contrib.contenttypes.models import ContentType -from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import pgettext_lazy # Used for object modifications CREATE = 'CREATE' diff --git a/tracking_fields/settings.py b/tracking_fields/settings.py index 44207d5..06a95b6 100644 --- a/tracking_fields/settings.py +++ b/tracking_fields/settings.py @@ -12,6 +12,7 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os + BASE_DIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/tracking_fields/tests/settings.py b/tracking_fields/tests/settings.py index f64da75..ba7dab7 100644 --- a/tracking_fields/tests/settings.py +++ b/tracking_fields/tests/settings.py @@ -10,6 +10,7 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os + BASE_DIR = os.path.dirname(os.path.dirname(__file__)) diff --git a/tracking_fields/tests/tests.py b/tracking_fields/tests/tests.py index d2bed8a..4d761ed 100644 --- a/tracking_fields/tests/tests.py +++ b/tracking_fields/tests/tests.py @@ -4,6 +4,7 @@ import datetime import json +from cuser.middleware import CuserMiddleware from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.core.files import File @@ -11,12 +12,16 @@ from django.utils import timezone from django.utils.html import escape -from cuser.middleware import CuserMiddleware - from tracking_fields.models import ( - TrackingEvent, CREATE, UPDATE, DELETE, ADD, REMOVE, CLEAR, + ADD, + CLEAR, + CREATE, + DELETE, + REMOVE, + UPDATE, + TrackingEvent, ) -from tracking_fields.tests.models import Human, Pet, House, UuidModel +from tracking_fields.tests.models import House, Human, Pet, UuidModel class TrackingEventTestCase(TestCase): diff --git a/tracking_fields/tests/urls.py b/tracking_fields/tests/urls.py index 2fea9eb..51a9857 100644 --- a/tracking_fields/tests/urls.py +++ b/tracking_fields/tests/urls.py @@ -2,8 +2,8 @@ from django.urls import re_path except ImportError: from django.conf.urls import url as re_path -from django.contrib import admin +from django.contrib import admin urlpatterns = ( re_path(r'^admin/', admin.site.urls), diff --git a/tracking_fields/tracking.py b/tracking_fields/tracking.py index ab697be..e2a2e57 100644 --- a/tracking_fields/tracking.py +++ b/tracking_fields/tracking.py @@ -6,7 +6,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Model, ManyToManyField +from django.db.models import ManyToManyField, Model from django.db.models.fields.files import FieldFile from django.db.models.fields.related import ForeignKey @@ -22,8 +22,11 @@ CUSER = False from tracking_fields.models import ( - TrackingEvent, TrackedFieldModification, - CREATE, UPDATE, DELETE + CREATE, + DELETE, + UPDATE, + TrackedFieldModification, + TrackingEvent, ) logger = logging.getLogger(__name__) From ed620dd2ec30457e1d6557524c11a02efd38c0b0 Mon Sep 17 00:00:00 2001 From: jmr Date: Tue, 15 Mar 2022 16:48:27 +0100 Subject: [PATCH 3/3] black --- tracking_fields/admin.py | 116 +++++++++++++---------- tracking_fields/decorators.py | 56 ++++++----- tracking_fields/models.py | 64 ++++++------- tracking_fields/settings.py | 14 +-- tracking_fields/tests/models.py | 21 +++-- tracking_fields/tests/settings.py | 52 +++++----- tracking_fields/tests/tests.py | 152 ++++++++++++++---------------- tracking_fields/tests/urls.py | 4 +- tracking_fields/tracking.py | 121 ++++++++++++------------ 9 files changed, 296 insertions(+), 304 deletions(-) diff --git a/tracking_fields/admin.py b/tracking_fields/admin.py index 15cf1fe..2a6704b 100644 --- a/tracking_fields/admin.py +++ b/tracking_fields/admin.py @@ -14,75 +14,77 @@ class TrackedObjectMixinAdmin(admin.ModelAdmin): Use this mixin to add a "Tracking" button next to history one on tracked object """ + class Meta: abstract = True - change_form_template = 'tracking_fields/admin/change_form_object.html' - def change_view(self, request, object_id, form_url='', extra_context=None): + change_form_template = "tracking_fields/admin/change_form_object.html" + + def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} if object_id is not None: - extra_context['tracking_opts'] = TrackingEvent._meta + extra_context["tracking_opts"] = TrackingEvent._meta opts = self.model._meta content_type = ContentType.objects.get( app_label=opts.app_label, model=opts.model_name, ) - extra_context['tracking_value'] = quote(u'{0}:{1}'.format( - content_type.pk, object_id - )) + extra_context["tracking_value"] = quote( + "{0}:{1}".format(content_type.pk, object_id) + ) return super(TrackedObjectMixinAdmin, self).change_view( request, object_id, form_url, extra_context ) class TrackerEventListFilter(admin.SimpleListFilter): - """ Hidden filter used to get history of a particular object. """ + """Hidden filter used to get history of a particular object.""" + title = _("Object") - parameter_name = 'object' - template = 'tracking_fields/admin/filter.html' # Empty template + parameter_name = "object" + template = "tracking_fields/admin/filter.html" # Empty template def lookups(self, request, model_admin): qs = model_admin.get_queryset(request) - objects = qs.values('object_content_type', 'object_id',) + objects = qs.values( + "object_content_type", + "object_id", + ) lookups = {} for obj in objects: - value = u'{0}:{1}'.format( - obj['object_content_type'], obj['object_id'] - ) + value = "{0}:{1}".format(obj["object_content_type"], obj["object_id"]) lookups[value] = value return [(lookup[0], lookup[1]) for lookup in lookups.items()] def queryset(self, request, queryset): if self.value() is None: return queryset - value = self.value().split(':') - return queryset.filter( - object_content_type_id=value[0], - object_id=value[1] - ) + value = self.value().split(":") + return queryset.filter(object_content_type_id=value[0], object_id=value[1]) class TrackerEventUserFilter(admin.SimpleListFilter): - """ Filter on users. """ + """Filter on users.""" + title = _("User") - parameter_name = 'user' + parameter_name = "user" def lookups(self, request, model_admin): qs = model_admin.get_queryset(request) - users = qs.values('user_content_type', 'user_id',) + users = qs.values( + "user_content_type", + "user_id", + ) lookups = {} for user in users: - if user['user_content_type'] is None: + if user["user_content_type"] is None: continue - value = u'{0}:{1}'.format( - user['user_content_type'], user['user_id'] - ) + value = "{0}:{1}".format(user["user_content_type"], user["user_id"]) try: - user_obj = ( - ContentType.objects.get_for_id(user['user_content_type']) - .get_object_for_this_type(pk=user['user_id']) - ) - lookups[value] = getattr(user_obj, 'username', str(user_obj)) + user_obj = ContentType.objects.get_for_id( + user["user_content_type"] + ).get_object_for_this_type(pk=user["user_id"]) + lookups[value] = getattr(user_obj, "username", str(user_obj)) except ObjectDoesNotExist: lookups[value] = f"" return [(lookup[0], lookup[1]) for lookup in lookups.items()] @@ -90,42 +92,53 @@ def lookups(self, request, model_admin): def queryset(self, request, queryset): if self.value() is None: return queryset - value = self.value().split(':') + value = self.value().split(":") if len(value) == 2: - return queryset.filter( - user_content_type_id=value[0], - user_id=value[1] - ) + return queryset.filter(user_content_type_id=value[0], user_id=value[1]) return queryset class TrackedFieldModificationAdmin(admin.TabularInline): can_delete = False model = TrackedFieldModification - readonly_fields = ('field', 'old_value', 'new_value',) + readonly_fields = ( + "field", + "old_value", + "new_value", + ) def has_add_permission(self, request, obj=None): return False class TrackingEventAdmin(admin.ModelAdmin): - date_hierarchy = 'date' - list_display = ('date', 'action', 'object', 'object_repr') - list_filter = ('action', TrackerEventUserFilter, TrackerEventListFilter,) - search_fields = ('object_repr', 'user_repr',) - readonly_fields = ( - 'date', 'action', 'object', 'object_repr', 'user', 'user_repr', + date_hierarchy = "date" + list_display = ("date", "action", "object", "object_repr") + list_filter = ( + "action", + TrackerEventUserFilter, + TrackerEventListFilter, ) - inlines = ( - TrackedFieldModificationAdmin, + search_fields = ( + "object_repr", + "user_repr", + ) + readonly_fields = ( + "date", + "action", + "object", + "object_repr", + "user", + "user_repr", ) - change_list_template = 'tracking_fields/admin/change_list_event.html' + inlines = (TrackedFieldModificationAdmin,) + change_list_template = "tracking_fields/admin/change_list_event.html" def changelist_view(self, request, extra_context=None): - """ Get object currently tracked and add a button to get back to it """ + """Get object currently tracked and add a button to get back to it""" extra_context = extra_context or {} - if 'object' in request.GET.keys(): - value = request.GET['object'].split(':') + if "object" in request.GET.keys(): + value = request.GET["object"].split(":") content_type = get_object_or_404( ContentType, id=value[0], @@ -134,10 +147,9 @@ def changelist_view(self, request, extra_context=None): content_type.model_class(), id=value[1], ) - extra_context['tracked_object'] = tracked_object - extra_context['tracked_object_opts'] = tracked_object._meta - return super(TrackingEventAdmin, self).changelist_view( - request, extra_context) + extra_context["tracked_object"] = tracked_object + extra_context["tracked_object_opts"] = tracked_object._meta + return super(TrackingEventAdmin, self).changelist_view(request, extra_context) admin.site.register(TrackingEvent, TrackingEventAdmin) diff --git a/tracking_fields/decorators.py b/tracking_fields/decorators.py index 2d7e05a..9dd14cc 100644 --- a/tracking_fields/decorators.py +++ b/tracking_fields/decorators.py @@ -34,16 +34,16 @@ def _add_signals_to_cls(cls): def _track_class_related_field(cls, field): - """ Track a field on a related model """ + """Track a field on a related model""" # field = field on current model # related_field = field on related model - (field, related_field) = field.split('__', 1) + (field, related_field) = field.split("__", 1) field_obj = cls._meta.get_field(field) related_cls = field_obj.remote_field.model related_name = field_obj.remote_field.get_accessor_name() - if not hasattr(related_cls, '_tracked_related_fields'): - setattr(related_cls, '_tracked_related_fields', {}) + if not hasattr(related_cls, "_tracked_related_fields"): + setattr(related_cls, "_tracked_related_fields", {}) if related_field not in related_cls._tracked_related_fields.keys(): related_cls._tracked_related_fields[related_field] = [] @@ -59,9 +59,7 @@ def _track_class_related_field(cls, field): # ... # } - related_cls._tracked_related_fields[related_field].append( - (field, related_name) - ) + related_cls._tracked_related_fields[related_field].append((field, related_name)) _add_signals_to_cls(related_cls) # Detect m2m fields changes if isinstance(related_cls._meta.get_field(related_field), ManyToManyField): @@ -73,8 +71,8 @@ def _track_class_related_field(cls, field): def _track_class_field(cls, field): - """ Track a field on the current model """ - if '__' in field: + """Track a field on the current model""" + if "__" in field: _track_class_related_field(cls, field) return # Will raise FieldDoesNotExist if there is an error @@ -89,9 +87,9 @@ def _track_class_field(cls, field): def _track_class(cls, fields): - """ Track fields on the specified model """ + """Track fields on the specified model""" # Small tests to ensure everything is all right - assert not getattr(cls, '_is_tracked', False) + assert not getattr(cls, "_is_tracked", False) for field in fields: _track_class_field(cls, field) @@ -102,37 +100,37 @@ def _track_class(cls, fields): cls._is_tracked = True # Do not directly track related fields (tracked on related model) # or m2m fields (tracked by another signal) - cls._tracked_fields = [ - field for field in fields - if '__' not in field - ] + cls._tracked_fields = [field for field in fields if "__" not in field] def _add_get_tracking_url(cls): - """ Add a method to get the tracking url of an object. """ + """Add a method to get the tracking url of an object.""" + def get_tracking_url(self): - """ return url to tracking view in admin panel """ - url = reverse('admin:tracking_fields_trackingevent_changelist') - object_id = '{0}%3A{1}'.format( - ContentType.objects.get_for_model(self).pk, - self.pk + """return url to tracking view in admin panel""" + url = reverse("admin:tracking_fields_trackingevent_changelist") + object_id = "{0}%3A{1}".format( + ContentType.objects.get_for_model(self).pk, self.pk ) - return '{0}?object={1}'.format(url, object_id) - if not hasattr(cls, 'get_tracking_url'): - setattr(cls, 'get_tracking_url', get_tracking_url) + return "{0}?object={1}".format(url, object_id) + + if not hasattr(cls, "get_tracking_url"): + setattr(cls, "get_tracking_url", get_tracking_url) def track(*fields): """ - Decorator used to track changes on Model's fields. + Decorator used to track changes on Model's fields. - :Example: - >>> @track('name') - ... class Human(models.Model): - ... name = models.CharField(max_length=30) + :Example: + >>> @track('name') + ... class Human(models.Model): + ... name = models.CharField(max_length=30) """ + def inner(cls): _track_class(cls, fields) _add_get_tracking_url(cls) return cls + return inner diff --git a/tracking_fields/models.py b/tracking_fields/models.py index 251faa2..48f54bc 100644 --- a/tracking_fields/models.py +++ b/tracking_fields/models.py @@ -13,76 +13,70 @@ from django.utils.translation import pgettext_lazy # Used for object modifications -CREATE = 'CREATE' -UPDATE = 'UPDATE' -DELETE = 'DELETE' +CREATE = "CREATE" +UPDATE = "UPDATE" +DELETE = "DELETE" # Used for m2m modifications -ADD = 'ADD' -REMOVE = 'REMOVE' -CLEAR = 'CLEAR' +ADD = "ADD" +REMOVE = "REMOVE" +CLEAR = "CLEAR" class TrackingEvent(models.Model): ACTIONS = ( - (CREATE, _('Create')), - (UPDATE, _('Update')), - (DELETE, _('Delete')), - (ADD, _('Add')), - (REMOVE, pgettext_lazy('Remove from something', 'Remove')), - (CLEAR, _('Clear')), + (CREATE, _("Create")), + (UPDATE, _("Update")), + (DELETE, _("Delete")), + (ADD, _("Add")), + (REMOVE, pgettext_lazy("Remove from something", "Remove")), + (CLEAR, _("Clear")), ) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - date = models.DateTimeField( - _("Date"), auto_now_add=True, editable=False - ) + date = models.DateTimeField(_("Date"), auto_now_add=True, editable=False) action = models.CharField( - _('Action'), max_length=6, choices=ACTIONS, editable=False + _("Action"), max_length=6, choices=ACTIONS, editable=False ) object_content_type = models.ForeignKey( ContentType, - related_name='tracking_object_content_type', + related_name="tracking_object_content_type", editable=False, on_delete=models.CASCADE, ) object_id = models.PositiveIntegerField(editable=False, null=True) - object = GenericForeignKey('object_content_type', 'object_id') + object = GenericForeignKey("object_content_type", "object_id") object_repr = models.CharField( _("Object representation"), - help_text=_( - "Object representation, useful if the object is deleted later." - ), + help_text=_("Object representation, useful if the object is deleted later."), max_length=250, - editable=False + editable=False, ) user_content_type = models.ForeignKey( ContentType, - related_name='tracking_user_content_type', + related_name="tracking_user_content_type", editable=False, null=True, on_delete=models.CASCADE, ) user_id = models.PositiveIntegerField(editable=False, null=True) - user = GenericForeignKey('user_content_type', 'user_id') + user = GenericForeignKey("user_content_type", "user_id") user_repr = models.CharField( _("User representation"), - help_text=_( - "User representation, useful if the user is deleted later." - ), + help_text=_("User representation, useful if the user is deleted later."), max_length=250, - editable=False + editable=False, ) class Meta: - verbose_name = _('Tracking event') - verbose_name_plural = _('Tracking events') - ordering = ['-date'] + verbose_name = _("Tracking event") + verbose_name_plural = _("Tracking events") + ordering = ["-date"] def get_object_model(self): if self.object_id is None: @@ -99,7 +93,9 @@ class TrackedFieldModification(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) event = models.ForeignKey( - TrackingEvent, verbose_name=_("Event"), related_name='fields', + TrackingEvent, + verbose_name=_("Event"), + related_name="fields", editable=False, on_delete=models.CASCADE, ) @@ -121,5 +117,5 @@ class TrackedFieldModification(models.Model): ) class Meta: - verbose_name = _('Tracking field modification') - verbose_name_plural = _('Tracking field modifications') + verbose_name = _("Tracking field modification") + verbose_name_plural = _("Tracking field modifications") diff --git a/tracking_fields/settings.py b/tracking_fields/settings.py index 06a95b6..84558d4 100644 --- a/tracking_fields/settings.py +++ b/tracking_fields/settings.py @@ -20,7 +20,7 @@ # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '%h13inv2($))j26yv825+*0kpmes3sf4ohy*@#83a07^rck$kl' +SECRET_KEY = "%h13inv2($))j26yv825+*0kpmes3sf4ohy*@#83a07^rck$kl" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -28,10 +28,10 @@ # Application definition INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'tracking_fields', + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "tracking_fields", ) diff --git a/tracking_fields/tests/models.py b/tracking_fields/tests/models.py index 3ab9d48..8969c8a 100644 --- a/tracking_fields/tests/models.py +++ b/tracking_fields/tests/models.py @@ -5,16 +5,16 @@ from tracking_fields.decorators import track -@track('value') +@track("value") class UuidModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) value = models.CharField(max_length=30) def __unicode__(self): - return u'{0}'.format(self.value) + return "{0}".format(self.value) -@track('vet_appointment', 'name', 'age', 'picture') +@track("vet_appointment", "name", "age", "picture") class Pet(models.Model): vet_appointment = models.DateTimeField(null=True) name = models.CharField(max_length=30) @@ -22,27 +22,30 @@ class Pet(models.Model): picture = models.ImageField(upload_to=".", null=True) def __unicode__(self): - return u'{0}'.format(self.name) + return "{0}".format(self.name) -@track('birthday', 'name', 'age', 'pets', 'favourite_pet') +@track("birthday", "name", "age", "pets", "favourite_pet") class Human(models.Model): birthday = models.DateField(null=True) name = models.CharField(max_length=30) age = models.PositiveSmallIntegerField() pets = models.ManyToManyField(Pet, null=True) favourite_pet = models.ForeignKey( - Pet, related_name="favorited_by", null=True, on_delete=models.CASCADE, + Pet, + related_name="favorited_by", + null=True, + on_delete=models.CASCADE, ) height = models.PositiveIntegerField(help_text="Not tracked") def __unicode__(self): - return u'{0}'.format(self.name) + return "{0}".format(self.name) -@track('tenant__name', 'tenant__pets', 'tenant__favourite_pet') +@track("tenant__name", "tenant__pets", "tenant__favourite_pet") class House(models.Model): tenant = models.OneToOneField(Human, null=True, on_delete=models.CASCADE) def __unicode__(self): - return u'House of {0}'.format(self.tenant) + return "House of {0}".format(self.tenant) diff --git a/tracking_fields/tests/settings.py b/tracking_fields/tests/settings.py index ba7dab7..885204d 100644 --- a/tracking_fields/tests/settings.py +++ b/tracking_fields/tests/settings.py @@ -18,7 +18,7 @@ # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '%h13inv2($))j26yv825+*0kpmes3sf4ohy*@#83a07^rck$kl' +SECRET_KEY = "%h13inv2($))j26yv825+*0kpmes3sf4ohy*@#83a07^rck$kl" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,34 +31,34 @@ # Application definition INSTALLED_APPS = ( - 'tracking_fields', - 'tracking_fields.tests', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "tracking_fields", + "tracking_fields.tests", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", ) MIDDLEWARE = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ) -ROOT_URLCONF = 'tracking_fields.tests.urls' +ROOT_URLCONF = "tracking_fields.tests.urls" -WSGI_APPLICATION = 'tracking_fields.wsgi.application' +WSGI_APPLICATION = "tracking_fields.wsgi.application" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, }, ] @@ -66,18 +66,18 @@ # https://docs.djangoproject.com/en/1.7/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } # Internationalization # https://docs.djangoproject.com/en/1.7/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -87,9 +87,9 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.7/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" # Used to speed up testing PASSWORD_HASHERS = { - 'django.contrib.auth.hashers.MD5PasswordHasher', + "django.contrib.auth.hashers.MD5PasswordHasher", } diff --git a/tracking_fields/tests/tests.py b/tracking_fields/tests/tests.py index 4d761ed..7565ef7 100644 --- a/tracking_fields/tests/tests.py +++ b/tracking_fields/tests/tests.py @@ -45,7 +45,7 @@ def test_create(self): """ Test the CREATE event """ - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 2 human_event = events.last() assert human_event.date is not None @@ -60,10 +60,11 @@ def test_update_without_cuser(self): Test the CREATE event without the cuser module """ from tracking_fields import tracking + tracking.CUSER = False self.human.age = 43 self.human.save() - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 3 human_event = events.last() assert human_event.date is not None @@ -71,7 +72,7 @@ def test_update_without_cuser(self): assert human_event.object == self.human assert human_event.object_repr == self.human_repr assert human_event.user is None - assert human_event.user_repr == 'None' + assert human_event.user_repr == "None" tracking.CUSER = True def test_update(self): @@ -80,7 +81,7 @@ def test_update(self): """ self.human.age = 43 self.human.save() - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 3 human_event = events.last() assert human_event.date is not None @@ -95,7 +96,7 @@ def test_delete(self): Test the DELETE event """ self.human.delete() - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 3 human_event = events.last() assert human_event.date is not None @@ -110,7 +111,7 @@ def test_add(self): Test the ADD event """ self.human.pets.add(self.pet) - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 3 human_event = events.last() assert human_event.date is not None @@ -126,7 +127,7 @@ def test_add_reverse(self): """ # Event should be the same than the one from ``test_add`` self.pet.human_set.add(self.human) - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 3 human_event = events.last() assert human_event.date is not None @@ -142,7 +143,7 @@ def test_remove(self): """ self.human.pets.add(self.pet) self.human.pets.remove(self.pet) - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 4 human_event = events.last() assert human_event.date is not None @@ -159,7 +160,7 @@ def test_remove_reverse(self): # Event should be the same than the one from ``test_remove`` self.human.pets.add(self.pet) self.pet.human_set.remove(self.human) - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 4 human_event = events.last() assert human_event.date is not None @@ -175,7 +176,7 @@ def test_clear(self): """ self.human.pets.add(self.pet) self.human.pets.clear() - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 4 human_event = events.last() assert human_event.date is not None @@ -191,7 +192,7 @@ def test_clear_reverse(self): """ self.human.pets.add(self.pet) self.pet.human_set.clear() - events = TrackingEvent.objects.order_by('date').all() + events = TrackingEvent.objects.order_by("date").all() assert events.count() == 4 human_event = events.last() assert human_event.date is not None @@ -214,7 +215,7 @@ def test_utf8_in_repr(self): """ Test to save object with utf8 in its repr """ - self.human.name = u'😵' + self.human.name = "😵" self.human.save() events = TrackingEvent.objects.all() assert events.count() == 3 @@ -234,54 +235,56 @@ def setUp(self): self.human_repr = repr(self.human) def test_create(self): - human_event = TrackingEvent.objects.filter(action=CREATE).order_by('date').last() + human_event = ( + TrackingEvent.objects.filter(action=CREATE).order_by("date").last() + ) assert human_event.fields.all().count() == 4 - field = human_event.fields.get(field='birthday') + field = human_event.fields.get(field="birthday") assert field.new_value == json.dumps(self.human.birthday) - field = human_event.fields.get(field='name') + field = human_event.fields.get(field="name") assert field.new_value == json.dumps(self.human.name) - field = human_event.fields.get(field='age') + field = human_event.fields.get(field="age") assert field.new_value == json.dumps(self.human.age) - field = human_event.fields.get(field='favourite_pet') + field = human_event.fields.get(field="favourite_pet") assert field.new_value == json.dumps(self.human.favourite_pet) def test_update(self): self.human.age = 43 self.human.save() - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='age') + field = human_event.fields.get(field="age") assert field.old_value == json.dumps(42) assert field.new_value == json.dumps(43) def test_foreign_key(self): self.human.favourite_pet = self.pet self.human.save() - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='favourite_pet') + field = human_event.fields.get(field="favourite_pet") assert field.old_value == json.dumps(None) assert field.new_value == json.dumps(str(self.pet)) def test_foreign_key_not_changed(self): - """ Test a foreign key does not change if only other values change """ + """Test a foreign key does not change if only other values change""" self.human.favourite_pet = self.pet self.human.save() self.human.name = "Toto" self.human.save() - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 field = human_event.fields.first() assert field.old_value == json.dumps("George") assert field.new_value == json.dumps("Toto") def test_foreign_key_label(self): - """ Test label of foreign keys are used in tracked fields """ + """Test label of foreign keys are used in tracked fields""" self.human.favourite_pet = self.pet self.human.save() self.human.favourite_pet = self.pet2 self.human.save() - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 field = human_event.fields.first() assert field.old_value == json.dumps(str(self.pet)) @@ -289,92 +292,80 @@ def test_foreign_key_label(self): def test_add(self): self.human.pets.add(self.pet2) - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='pets') + field = human_event.fields.get(field="pets") assert field.old_value == json.dumps([str(self.pet)]) - assert field.new_value == json.dumps([ - str(self.pet), str(self.pet2) - ]) + assert field.new_value == json.dumps([str(self.pet), str(self.pet2)]) def test_add_reverse(self): self.pet2.human_set.add(self.human) - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='pets') + field = human_event.fields.get(field="pets") assert field.old_value == json.dumps([str(self.pet)]) - assert field.new_value == json.dumps([ - str(self.pet), str(self.pet2) - ]) + assert field.new_value == json.dumps([str(self.pet), str(self.pet2)]) def test_remove(self): self.human.pets.add(self.pet2) self.human.pets.remove(self.pet2) - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='pets') - assert field.old_value == json.dumps([ - str(self.pet), str(self.pet2) - ]) + field = human_event.fields.get(field="pets") + assert field.old_value == json.dumps([str(self.pet), str(self.pet2)]) assert field.new_value == json.dumps([str(self.pet)]) def test_remove_reverse(self): self.human.pets.add(self.pet2) self.pet2.human_set.remove(self.human) - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='pets') - assert field.old_value == json.dumps([ - str(self.pet), str(self.pet2) - ]) + field = human_event.fields.get(field="pets") + assert field.old_value == json.dumps([str(self.pet), str(self.pet2)]) assert field.new_value == json.dumps([str(self.pet)]) def test_clear(self): self.human.pets.add(self.pet2) self.human.pets.clear() - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='pets') - assert field.old_value == json.dumps([ - str(self.pet), str(self.pet2) - ]) + field = human_event.fields.get(field="pets") + assert field.old_value == json.dumps([str(self.pet), str(self.pet2)]) assert field.new_value == json.dumps([]) def test_clear_reverse(self): self.human.pets.add(self.pet2) self.pet2.human_set.clear() - human_event = TrackingEvent.objects.order_by('date').last() + human_event = TrackingEvent.objects.order_by("date").last() assert human_event.fields.all().count() == 1 - field = human_event.fields.get(field='pets') - assert field.old_value == json.dumps([ - str(self.pet), str(self.pet2) - ]) + field = human_event.fields.get(field="pets") + assert field.old_value == json.dumps([str(self.pet), str(self.pet2)]) assert field.new_value == json.dumps([str(self.pet)]) def test_date(self): today = datetime.date.today() self.human.birthday = today self.human.save() - human_event = TrackingEvent.objects.order_by('date').last() - field = human_event.fields.get(field='birthday') + human_event = TrackingEvent.objects.order_by("date").last() + field = human_event.fields.get(field="birthday") assert field.old_value == json.dumps(None) - assert field.new_value == json.dumps(today.strftime('%Y-%m-%d')) + assert field.new_value == json.dumps(today.strftime("%Y-%m-%d")) def test_datetime(self): now = timezone.now() self.pet.vet_appointment = now self.pet.save() - pet_event = TrackingEvent.objects.order_by('date').last() - field = pet_event.fields.get(field='vet_appointment') + pet_event = TrackingEvent.objects.order_by("date").last() + field = pet_event.fields.get(field="vet_appointment") assert field.old_value == json.dumps(None) - assert field.new_value == json.dumps(now.strftime('%Y-%m-%d %H:%M:%S')) + assert field.new_value == json.dumps(now.strftime("%Y-%m-%d %H:%M:%S")) def test_imagefield(self): - with File(open('tracking_fields/tests/__init__.py'), 'picture.png') as picture: + with File(open("tracking_fields/tests/__init__.py"), "picture.png") as picture: self.pet.picture = picture self.pet.save() - pet_event = TrackingEvent.objects.order_by('date').last() - field = pet_event.fields.get(field='picture') + pet_event = TrackingEvent.objects.order_by("date").last() + field = pet_event.fields.get(field="picture") assert field.old_value == json.dumps(None) assert field.new_value == json.dumps(self.pet.picture.path) self.pet.picture.delete() @@ -390,25 +381,27 @@ def test_simple_change(self): self.human.name = "Tutu" self.human.save() house_event = TrackingEvent.objects.filter( - object_content_type=self.content_type) + object_content_type=self.content_type + ) assert house_event.count() == 1 house_event = house_event.last() assert house_event.fields.count() == 1 field = house_event.fields.last() assert field.old_value == '"Toto"' assert field.new_value == '"Tutu"' - assert field.field == 'tenant__name' + assert field.field == "tenant__name" def test_m2m_change(self): pet = Pet.objects.create(name="Pet", age=4) self.human.pets.add(pet) house_event = TrackingEvent.objects.filter( - object_content_type=self.content_type) + object_content_type=self.content_type + ) assert house_event.count() == 1 house_event = house_event.last() assert house_event.fields.count() == 1 field = house_event.fields.last() - assert field.field == 'tenant__pets' + assert field.field == "tenant__pets" assert field.old_value == json.dumps([]) assert field.new_value == json.dumps([str(pet)]) @@ -441,7 +434,7 @@ def test_tracker_event_user_filter_with_incorrect_user(self): def test_list(self): """Test the admin view listing all objects.""" - response = self.c.get('/admin/tracking_fields/trackingevent/') + response = self.c.get("/admin/tracking_fields/trackingevent/") self.assertContains( response, escape(repr(self.human)), @@ -463,8 +456,7 @@ def test_list_with_incorrect_filter_on_user_content_type(self): """Test the admin view listing all objects with incorrect user content type filter.""" response = self.c.get( - f"/admin/tracking_fields/trackingevent/" - f"?user=99999:{self.user.id}" + f"/admin/tracking_fields/trackingevent/" f"?user=99999:{self.user.id}" ) self.assertNotContains( response, @@ -492,9 +484,9 @@ def test_list_with_incorrect_filter(self): ) def test_single(self): - """ Test the admin view listing all objects. """ + """Test the admin view listing all objects.""" event = TrackingEvent.objects.first() - url = '/admin/tracking_fields/trackingevent/{0}'.format(event.pk) + url = "/admin/tracking_fields/trackingevent/{0}".format(event.pk) response = self.c.get(url, follow=True) self.assertContains( response, @@ -506,9 +498,9 @@ def test_single(self): ) def test_history_btn(self): - """ Test the tracking button is present. """ + """Test the tracking button is present.""" response = self.c.get( - '/admin/tests/human/{}'.format(self.human.pk), + "/admin/tests/human/{}".format(self.human.pk), follow=True, ) self.assertContains( @@ -521,12 +513,10 @@ def test_history_back_btn_is_present(self): Test the button back to the button is present where there is an object filter. """ - content_type = ContentType.objects.get( - app_label="tests", model="human" - ) - param = 'object={0}%3A{1}'.format(content_type.pk, self.human.pk) + content_type = ContentType.objects.get(app_label="tests", model="human") + param = "object={0}%3A{1}".format(content_type.pk, self.human.pk) response = self.c.get( - '/admin/tracking_fields/trackingevent/?{0}'.format(param), + "/admin/tracking_fields/trackingevent/?{0}".format(param), follow=True, ) self.assertContains( @@ -540,7 +530,7 @@ def test_history_back_btn_is_not_present(self): where there is no object filter. """ response = self.c.get( - '/admin/tracking_fields/trackingevent/', + "/admin/tracking_fields/trackingevent/", follow=True, ) self.assertNotContains( diff --git a/tracking_fields/tests/urls.py b/tracking_fields/tests/urls.py index 51a9857..306bd6d 100644 --- a/tracking_fields/tests/urls.py +++ b/tracking_fields/tests/urls.py @@ -5,6 +5,4 @@ from django.contrib import admin -urlpatterns = ( - re_path(r'^admin/', admin.site.urls), -) +urlpatterns = (re_path(r"^admin/", admin.site.urls),) diff --git a/tracking_fields/tracking.py b/tracking_fields/tracking.py index e2a2e57..4c87e7b 100644 --- a/tracking_fields/tracking.py +++ b/tracking_fields/tracking.py @@ -13,10 +13,11 @@ try: from xworkflows.base import StateWrapper except ImportError: - StateWrapper = type('StateWrapper', (object,), dict()) + StateWrapper = type("StateWrapper", (object,), dict()) try: from cuser.middleware import CuserMiddleware + CUSER = True except ImportError: CUSER = False @@ -34,6 +35,7 @@ # ======================= HELPERS ==================== + def _set_original_fields(instance): """ Save fields value, only for non-m2m fields. @@ -47,21 +49,20 @@ def _set_original_field(instance, field): if isinstance(instance._meta.get_field(field), ForeignKey): # Only get the PK, we don't want to get the object # (which would make an additional request) - original_fields[field] = getattr(instance, - '{0}_id'.format(field)) + original_fields[field] = getattr(instance, "{0}_id".format(field)) else: # Do not store deferred fields if field in instance.__dict__: original_fields[field] = getattr(instance, field) - for field in getattr(instance, '_tracked_fields', []): + for field in getattr(instance, "_tracked_fields", []): _set_original_field(instance, field) - for field in getattr(instance, '_tracked_related_fields', {}).keys(): + for field in getattr(instance, "_tracked_related_fields", {}).keys(): _set_original_field(instance, field) instance._original_fields = original_fields # Include pk to detect the creation of an object - instance._original_fields['pk'] = instance.pk + instance._original_fields["pk"] = instance.pk def _has_changed(instance): @@ -69,12 +70,13 @@ def _has_changed(instance): Check if some tracked fields have changed """ for field, value in instance._original_fields.items(): - if field != 'pk' and \ - not isinstance(instance._meta.get_field(field), ManyToManyField): + if field != "pk" and not isinstance( + instance._meta.get_field(field), ManyToManyField + ): try: - if field in getattr(instance, '_tracked_fields', []): + if field in getattr(instance, "_tracked_fields", []): if isinstance(instance._meta.get_field(field), ForeignKey): - if getattr(instance, '{0}_id'.format(field)) != value: + if getattr(instance, "{0}_id".format(field)) != value: return True else: if getattr(instance, field) != value: @@ -89,17 +91,14 @@ def _has_changed_related(instance): """ Check if some related tracked fields have changed """ - tracked_related_fields = getattr( - instance, - '_tracked_related_fields', - {} - ).keys() + tracked_related_fields = getattr(instance, "_tracked_related_fields", {}).keys() for field, value in instance._original_fields.items(): - if field != 'pk' and \ - not isinstance(instance._meta.get_field(field), ManyToManyField): + if field != "pk" and not isinstance( + instance._meta.get_field(field), ManyToManyField + ): if field in tracked_related_fields: if isinstance(instance._meta.get_field(field), ForeignKey): - if getattr(instance, '{0}_id'.format(field)) != value: + if getattr(instance, "{0}_id".format(field)) != value: return True else: if getattr(instance, field) != value: @@ -130,13 +129,9 @@ def _create_event(instance, action): def _serialize_field(field): if isinstance(field, datetime.datetime): - return json.dumps( - field.strftime('%Y-%m-%d %H:%M:%S'), ensure_ascii=False - ) + return json.dumps(field.strftime("%Y-%m-%d %H:%M:%S"), ensure_ascii=False) if isinstance(field, datetime.date): - return json.dumps( - field.strftime('%Y-%m-%d'), ensure_ascii=False - ) + return json.dumps(field.strftime("%Y-%m-%d"), ensure_ascii=False) if isinstance(field, FieldFile): try: return json.dumps(field.path, ensure_ascii=False) @@ -144,11 +139,9 @@ def _serialize_field(field): # No file return json.dumps(None, ensure_ascii=False) if isinstance(field, Model): - return json.dumps(str(field), - ensure_ascii=False) + return json.dumps(str(field), ensure_ascii=False) if isinstance(field, StateWrapper): - return json.dumps(field.name, - ensure_ascii=False) + return json.dumps(field.name, ensure_ascii=False) try: return json.dumps(field, ensure_ascii=False) except TypeError: @@ -180,7 +173,7 @@ def _build_tracked_field(event, instance, field, fieldname=None): event=event, field=fieldname, old_value=_serialize_field(old_value), - new_value=_serialize_field(getattr(instance, field)) + new_value=_serialize_field(getattr(instance, field)), ) @@ -208,7 +201,7 @@ def _create_update_tracking_event(instance): try: if isinstance(instance._meta.get_field(field), ForeignKey): # Compare pk - value = getattr(instance, '{0}_id'.format(field)) + value = getattr(instance, "{0}_id".format(field)) else: value = getattr(instance, field) if instance._original_fields[field] != value: @@ -230,7 +223,7 @@ def _create_update_tracking_related_event(instance): if not isinstance(instance._meta.get_field(field), ManyToManyField): if isinstance(instance._meta.get_field(field), ForeignKey): # Compare pk - value = getattr(instance, '{0}_id'.format(field)) + value = getattr(instance, "{0}_id".format(field)) else: value = getattr(instance, field) if instance._original_fields[field] != value: @@ -240,7 +233,7 @@ def _create_update_tracking_related_event(instance): # Create the events from the events dict tracked_fields = [] for related_field, fields in events.items(): - if related_field[1] == '+': + if related_field[1] == "+": continue try: related_instances = getattr(instance, related_field[1]) @@ -248,17 +241,17 @@ def _create_update_tracking_related_event(instance): continue # FIXME: isinstance(related_instances, RelatedManager ?) - if hasattr(related_instances, 'all'): + if hasattr(related_instances, "all"): related_instances = related_instances.all() else: related_instances = [related_instances] for related_instance in related_instances: event = _create_event(related_instance, UPDATE) for field in fields: - fieldname = '{0}__{1}'.format(related_field[0], field) - tracked_fields.append(_build_tracked_field( - event, instance, field, fieldname=fieldname - )) + fieldname = "{0}__{1}".format(related_field[0], field) + tracked_fields.append( + _build_tracked_field(event, instance, field, fieldname=fieldname) + ) TrackedFieldModification.objects.bulk_create(tracked_fields) @@ -273,25 +266,24 @@ def _get_m2m_field(model, sender): """ Get the field name from a model and a sender from m2m_changed signal. """ - for field in getattr(model, '_tracked_fields', []): + for field in getattr(model, "_tracked_fields", []): if isinstance(model._meta.get_field(field), ManyToManyField): if getattr(model, field).through == sender: return field - for field in getattr(model, '_tracked_related_fields', {}).keys(): + for field in getattr(model, "_tracked_related_fields", {}).keys(): if isinstance(model._meta.get_field(field), ManyToManyField): if getattr(model, field).through == sender: return field -def _build_tracked_field_m2m(event, instance, field, objects, action, - fieldname=None): +def _build_tracked_field_m2m(event, instance, field, objects, action, fieldname=None): fieldname = fieldname or field before = list(getattr(instance, field).all()) - if action == 'ADD': + if action == "ADD": after = before + objects - elif action == 'REMOVE': + elif action == "REMOVE": after = [obj for obj in before if obj not in objects] - elif action == 'CLEAR': + elif action == "CLEAR": after = [] before = list(map(str, before)) after = list(map(str, after)) @@ -299,7 +291,7 @@ def _build_tracked_field_m2m(event, instance, field, objects, action, event=event, field=fieldname, old_value=json.dumps(before), - new_value=json.dumps(after) + new_value=json.dumps(after), ) @@ -320,7 +312,7 @@ def _create_tracked_event_m2m(model, instance, sender, objects, action): """ tracked_fields = [] field = _get_m2m_field(model, sender) - if field in getattr(model, '_tracked_related_fields', {}).keys(): + if field in getattr(model, "_tracked_related_fields", {}).keys(): # In case of a m2m tracked on a related model related_fields = model._tracked_related_fields[field] for related_field in related_fields: @@ -329,24 +321,29 @@ def _create_tracked_event_m2m(model, instance, sender, objects, action): except ObjectDoesNotExist: continue # FIXME: isinstance(related_instances, RelatedManager ?) - if hasattr(related_instances, 'all'): + if hasattr(related_instances, "all"): related_instances = related_instances.all() else: related_instances = [related_instances] for related_instance in related_instances: event = _create_event(related_instance, action) - fieldname = '{0}__{1}'.format(related_field[0], field) - tracked_fields.append(_build_tracked_field_m2m( - event, instance, field, objects, action, fieldname - )) - if field in getattr(model, '_tracked_fields', []): + fieldname = "{0}__{1}".format(related_field[0], field) + tracked_fields.append( + _build_tracked_field_m2m( + event, instance, field, objects, action, fieldname + ) + ) + if field in getattr(model, "_tracked_fields", []): event = _create_event(instance, action) - tracked_fields.append(_build_tracked_field_m2m(event, instance, field, objects, action)) + tracked_fields.append( + _build_tracked_field_m2m(event, instance, field, objects, action) + ) TrackedFieldModification.objects.bulk_create(tracked_fields) # ======================= CALLBACKS ==================== + def tracking_init(sender, instance, **kwargs): """ Post init, save the current state of the object to compare it before a save @@ -360,7 +357,7 @@ def tracking_save(sender, instance, raw, using, update_fields, **kwargs): We need post_save to have the object for a create. """ if _has_changed(instance): - if instance._original_fields['pk'] is None: + if instance._original_fields["pk"] is None: # Create _create_create_tracking_event(instance) else: @@ -381,9 +378,7 @@ def tracking_delete(sender, instance, using, **kwargs): _create_delete_tracking_event(instance) -def tracking_m2m( - sender, instance, action, reverse, model, pk_set, using, **kwargs -): +def tracking_m2m(sender, instance, action, reverse, model, pk_set, using, **kwargs): """ m2m_changed callback. The idea is to get the model and the instance of the object being tracked, @@ -392,17 +387,17 @@ def tracking_m2m( the TrackedFieldModification. """ action_event = { - 'pre_clear': 'CLEAR', - 'pre_add': 'ADD', - 'pre_remove': 'REMOVE', + "pre_clear": "CLEAR", + "pre_add": "ADD", + "pre_remove": "REMOVE", } - if (action not in action_event.keys()): + if action not in action_event.keys(): return if reverse: - if action == 'pre_clear': + if action == "pre_clear": # It will actually be a remove of ``instance`` on every # tracked object being related - action = 'pre_remove' + action = "pre_remove" # pk_set is None for clear events, we need to get objects' pk. field = _get_m2m_field(model, sender) field = model._meta.get_field(field).remote_field.get_accessor_name()