diff --git a/.travis.yml b/.travis.yml index 1d2e7744..78c57715 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -sudo: false +sudo: true dist: trusty language: python diff --git a/RIGS/models.py b/RIGS/models.py index 84b8ba39..5d71c898 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -9,6 +9,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.encoding import python_2_unicode_compatible from reversion import revisions as reversion +from reversion.models import Version import string import random @@ -61,32 +62,31 @@ class Profile(AbstractUser): class RevisionMixin(object): + @property + def current_version(self): + version = Version.objects.get_for_object(self).select_related('revision').first() + return version + @property def last_edited_at(self): - versions = reversion.get_for_object(self) - if versions: - version = reversion.get_for_object(self)[0] - return version.revision.date_created - else: + version = self.current_version + if version is None: return None + return version.revision.date_created @property def last_edited_by(self): - versions = reversion.get_for_object(self) - if versions: - version = reversion.get_for_object(self)[0] - return version.revision.user - else: + version = self.current_version + if version is None: return None + return version.revision.user @property def current_version_id(self): - versions = reversion.get_for_object(self) - if versions: - version = reversion.get_for_object(self)[0] - return "V{0} | R{1}".format(version.pk, version.revision.pk) - else: + version = self.current_version + if version is None: return None + return "V{0} | R{1}".format(version.pk, version.revision.pk) @reversion.register diff --git a/RIGS/signals.py b/RIGS/signals.py index faf0f873..ab37c2af 100644 --- a/RIGS/signals.py +++ b/RIGS/signals.py @@ -3,7 +3,7 @@ import re import urllib2 from io import BytesIO -import reversion +from django.db.models.signals import post_save from PyPDF2 import PdfFileReader, PdfFileMerger from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage @@ -89,10 +89,9 @@ def send_eventauthorisation_success_email(instance): mic_email.send(fail_silently=True) -def on_revision_commit(instances, **kwargs): - for instance in instances: - if isinstance(instance, models.EventAuthorisation): - send_eventauthorisation_success_email(instance) +def on_revision_commit(sender, instance, created, **kwargs): + if created: + send_eventauthorisation_success_email(instance) -reversion.revisions.post_revision_commit.connect(on_revision_commit) +post_save.connect(on_revision_commit, sender=models.EventAuthorisation) diff --git a/RIGS/templates/RIGS/activity_feed_data.html b/RIGS/templates/RIGS/activity_feed_data.html index e99baf8e..4de8bf7b 100644 --- a/RIGS/templates/RIGS/activity_feed_data.html +++ b/RIGS/templates/RIGS/activity_feed_data.html @@ -33,13 +33,13 @@ {% endif %}

- {% if version.old == None %} + {% if version.changes.old == None %} Created {% else %} Changed {% include 'RIGS/version_changes.html' %} in {% endif %} - {% include 'RIGS/object_button.html' with object=version.new %} + {% include 'RIGS/object_button.html' with object=version.changes.new %} {% if version.revision.comment %} ({{ version.revision.comment }}) {% endif %} diff --git a/RIGS/templates/RIGS/activity_table.html b/RIGS/templates/RIGS/activity_table.html index 1a1c8c1d..e12dfd7a 100644 --- a/RIGS/templates/RIGS/activity_table.html +++ b/RIGS/templates/RIGS/activity_table.html @@ -67,16 +67,16 @@ {{ version.revision.date_created }} - {{version.new|to_class_name}} {{ version.new.pk|stringformat:"05d" }} - {{ version.version.pk }}|{{ version.revision.pk }} + {{version.changes.new|to_class_name}} {{ version.changes.new.pk|stringformat:"05d" }} + {{ version.pk }}|{{ version.revision.pk }} {{ version.revision.user.name }} - {% if version.old == None %} - {{version.new|to_class_name}} Created + {% if version.changes.old == None %} + {{version.changes.new|to_class_name}} Created {% else %} {% include 'RIGS/version_changes.html' %} {% endif %} - {{ version.revision.comment }} + {{ version.changes.revision.comment }} {% endfor %} diff --git a/RIGS/templates/RIGS/version_changes.html b/RIGS/templates/RIGS/version_changes.html index ca4e1569..32685d3a 100644 --- a/RIGS/templates/RIGS/version_changes.html +++ b/RIGS/templates/RIGS/version_changes.html @@ -1,21 +1,26 @@ -{% for change in version.field_changes %} +{% if version.changes.item_changes or version.changes.field_changes or version.changes.old == None %} - + {% for change in version.changes.field_changes %} -{% endfor %} + -{% for itemChange in version.item_changes %} - -{% endfor %} \ No newline at end of file + {% endfor %} + + {% for itemChange in version.changes.item_changes %} + + {% endfor %} +{% else %} + nothing useful +{% endif %} \ No newline at end of file diff --git a/RIGS/templates/RIGS/version_history.html b/RIGS/templates/RIGS/version_history.html index 3261c5df..18ff22d4 100644 --- a/RIGS/templates/RIGS/version_history.html +++ b/RIGS/templates/RIGS/version_history.html @@ -40,13 +40,13 @@ {% for version in object_list %} - {% if version.item_changes or version.field_changes or version.old == None %} + {{ version.revision.date_created }} - {{ version.version.pk }}|{{ version.revision.pk }} + {{ version.pk }}|{{ version.revision.pk }} {{ version.revision.user.name }} - {% if version.old == None %} + {% if version.changes.old is None %} {{object|to_class_name}} Created {% else %} {% include 'RIGS/version_changes.html' %} @@ -56,7 +56,8 @@ {{ version.revision.comment }} - {% endif %} + + {% endfor %} diff --git a/RIGS/test_models.py b/RIGS/test_models.py index b5dfd1e3..0a7a72e6 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -1,9 +1,11 @@ +from __future__ import unicode_literals + import pytz -import reversion +from reversion import revisions as reversion from django.conf import settings from django.core.exceptions import ValidationError from django.test import TestCase -from RIGS import models +from RIGS import models, versioning from datetime import date, timedelta, datetime, time from decimal import * @@ -386,7 +388,191 @@ class EventAuthorisationTestCase(TestCase): self.assertTrue(self.event.authorised) def test_last_edited(self): - with reversion.revisions.create_revision(): + with reversion.create_revision(): auth = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", name="Test Auth", amount=self.event.total, sent_by=self.profile) self.assertIsNotNone(auth.last_edited_at) + + +class RIGSVersionTestCase(TestCase): + def setUp(self): + models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01') + + self.profile = models.Profile.objects.get_or_create( + first_name='Test', + last_name='TEC User', + username='eventauthtest', + email='teccie@functional.test', + is_superuser=True # lazily grant all permissions + )[0] + with reversion.create_revision(): + reversion.set_user(self.profile) + self.person = models.Person.objects.create(name='Authorisation Test Person') + + with reversion.create_revision(): + reversion.set_user(self.profile) + self.organisation = models.Organisation.objects.create(name='Authorisation Test Organisation') + + with reversion.create_revision(): + reversion.set_user(self.profile) + self.event = models.Event.objects.create(name="AuthorisationTestCase", person=self.person, + start_date=date.today()) + + with reversion.create_revision(): + reversion.set_user(self.profile) + self.event.notes = "A new note on the event" + self.event.save() + + def test_find_parent_version(self): + # Find the most recent version + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + self.assertEqual(currentVersion._object_version.object.notes, "A new note on the event") + + # Check the prev version is loaded correctly + previousVersion = currentVersion.parent + self.assertEqual(previousVersion._object_version.object.notes, None) + + # Check that finding the parent of the first version fails gracefully + self.assertFalse(previousVersion.parent) + + def test_changes_since(self): + # Find the most recent version + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + + changes = currentVersion.changes + self.assertEqual(len(changes.field_changes), 1) + + def test_manager(self): + objs = versioning.RIGSVersion.objects.get_for_multiple_models([models.Event, models.Person, models.Organisation]) + self.assertEqual(len(objs), 4) + + def test_text_field_types(self): + with reversion.create_revision(): + reversion.set_user(self.profile) + self.event.name = "New event name" # Simple text + self.event.description = "hello world" # Long text + self.event.save() + + # Find the most recent version + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + diff = currentVersion.changes + + # There are two changes + self.assertEqual(len(diff.field_changes), 2) + self.assertFalse(currentVersion.changes.items_changed) + self.assertTrue(currentVersion.changes.fields_changed) + self.assertTrue(currentVersion.changes.anything_changed) + + # Only one has "linebreaks" + self.assertEqual(sum([x.linebreaks for x in diff.field_changes]), 1) + + # None are "long" (email address) + self.assertEqual(sum([x.long for x in diff.field_changes]), 0) + + # Try changing email field in person + with reversion.create_revision(): + reversion.set_user(self.profile) + self.person.email = "hello@world.com" + self.person.save() + + # Find the most recent version + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.person).latest(field_name='revision__date_created') + diff = currentVersion.changes + + # Should be declared as long + self.assertTrue(diff.field_changes[0].long) + + def test_text_diff(self): + with reversion.create_revision(): + reversion.set_user(self.profile) + self.event.notes = "An old note on the event" # Simple text + self.event.save() + + # Find the most recent version + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + + # Check the diff is correct + self.assertEqual(currentVersion.changes.field_changes[0].diff, + [{'type': 'equal', 'text': "A"}, + {'type': 'delete', 'text': " new"}, + {'type': 'insert', 'text': "n old"}, + {'type': 'equal', 'text': " note on the event"} + ]) + + def test_choice_field(self): + with reversion.create_revision(): + reversion.set_user(self.profile) + self.event.status = models.Event.CONFIRMED + self.event.save() + + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + self.assertEqual(currentVersion.changes.field_changes[0].old, 'Provisional') + self.assertEqual(currentVersion.changes.field_changes[0].new, 'Confirmed') + + def test_creation_behaviour(self): + firstVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created').parent + diff = firstVersion.changes + + # Mainly to check for exceptions: + self.assertTrue(len(diff.field_changes) > 0) + + def test_event_items(self): + with reversion.create_revision(): + reversion.set_user(self.profile) + item1 = models.EventItem.objects.create(event=self.event, name="TI I1", quantity=1, cost=1.00, order=1) + self.event.save() + + # Find the most recent version + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + + diffs = currentVersion.changes.item_changes + + self.assertEqual(len(diffs), 1) + self.assertTrue(currentVersion.changes.items_changed) + self.assertFalse(currentVersion.changes.fields_changed) + self.assertTrue(currentVersion.changes.anything_changed) + + self.assertTrue(diffs[0].old is None) + self.assertEqual(diffs[0].new.name, "TI I1") + + # Edit the item + with reversion.create_revision(): + reversion.set_user(self.profile) + item1.name = "New Name" + item1.save() + self.event.save() + + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + + diffs = currentVersion.changes.item_changes + + self.assertEqual(len(diffs), 1) + + self.assertEqual(diffs[0].old.name, "TI I1") + self.assertEqual(diffs[0].new.name, "New Name") + + # Check the diff + self.assertEqual(currentVersion.changes.item_changes[0].field_changes[0].diff, + [{'type': 'delete', 'text': "TI I1"}, + {'type': 'insert', 'text': "New Name"}, + ]) + + # Delete the item + + with reversion.create_revision(): + item1.delete() + self.event.save() + + # Find the most recent version + currentVersion = versioning.RIGSVersion.objects.get_for_object(self.event).latest(field_name='revision__date_created') + + diffs = currentVersion.changes.item_changes + + self.assertEqual(len(diffs), 1) + self.assertTrue(currentVersion.changes.items_changed) + self.assertFalse(currentVersion.changes.fields_changed) + self.assertTrue(currentVersion.changes.anything_changed) + + self.assertEqual(diffs[0].old.name, "New Name") + self.assertTrue(diffs[0].new is None) + diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index c264b9c1..8216791a 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -294,6 +294,39 @@ class TestVersioningViews(TestCase): response = self.client.get(request_url, follow=True) self.assertEqual(response.status_code, 200) + # Some edge cases that have caused server errors in the past + def test_deleted_event(self): + request_url = reverse('activity_feed') + + self.events[2].delete() + + response = self.client.get(request_url, follow=True) + self.assertContains(response, "TE E2") + self.assertEqual(response.status_code, 200) + + def test_deleted_relation(self): + request_url = reverse('activity_feed') + + with reversion.create_revision(): + person = models.Person.objects.create(name="Test Person") + with reversion.create_revision(): + self.events[1].person = person + self.events[1].save() + + # Check response contains person + response = self.client.get(request_url, follow=True) + self.assertContains(response, "Test Person") + self.assertEqual(response.status_code, 200) + + # Delete person + person.delete() + + # Check response still contains person + response = self.client.get(request_url, follow=True) + self.assertContains(response, "Test Person") + self.assertEqual(response.status_code, 200) + + class TestEmbeddedViews(TestCase): @classmethod diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 958b3a82..d055a12d 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -1,303 +1,259 @@ +from __future__ import unicode_literals + import logging +import datetime from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import get_object_or_404 from django.views import generic - -# Versioning -import reversion -from reversion.models import Version -from django.contrib.contenttypes.models import ContentType # Used to lookup the content_type +from django.utils.functional import cached_property from django.db.models import IntegerField, EmailField, TextField +from django.contrib.contenttypes.models import ContentType + +from reversion.models import Version, VersionQuerySet from diff_match_patch import diff_match_patch from RIGS import models -import datetime logger = logging.getLogger('tec.pyrigs') -def model_compare(oldObj, newObj, excluded_keys=[]): - # recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects - try: - theFields = oldObj._meta.fields # This becomes deprecated in Django 1.8!!!!!!!!!!!!! (but an alternative becomes available) - except AttributeError: - theFields = newObj._meta.fields +class FieldComparison(object): + def __init__(self, field=None, old=None, new=None): + self.field = field + self._old = old + self._new = new - class FieldCompare(object): - def __init__(self, field=None, old=None, new=None): - self.field = field - self._old = old - self._new = new + def display_value(self, value): + if isinstance(self.field, IntegerField) and len(self.field.choices) > 0: + return [x[1] for x in self.field.choices if x[0] == value][0] + return value - def display_value(self, value): - if isinstance(self.field, IntegerField) and len(self.field.choices) > 0: - return [x[1] for x in self.field.choices if x[0] == value][0] - return value + @property + def old(self): + return self.display_value(self._old) - @property - def old(self): - return self.display_value(self._old) + @property + def new(self): + return self.display_value(self._new) - @property - def new(self): - return self.display_value(self._new) - - @property - def long(self): - if isinstance(self.field, EmailField): - return True - return False - - @property - def linebreaks(self): - if isinstance(self.field, TextField): - return True - return False - - @property - def diff(self): - oldText = unicode(self.display_value(self._old)) or "" - newText = unicode(self.display_value(self._new)) or "" - dmp = diff_match_patch() - diffs = dmp.diff_main(oldText, newText) - dmp.diff_cleanupSemantic(diffs) - - outputDiffs = [] - - for (op, data) in diffs: - if op == dmp.DIFF_INSERT: - outputDiffs.append({'type': 'insert', 'text': data}) - elif op == dmp.DIFF_DELETE: - outputDiffs.append({'type': 'delete', 'text': data}) - elif op == dmp.DIFF_EQUAL: - outputDiffs.append({'type': 'equal', 'text': data}) - return outputDiffs - - changes = [] - - for thisField in theFields: - name = thisField.name - - if name in excluded_keys: - continue # if we're excluding this field, skip over it - - try: - oldValue = getattr(oldObj, name, None) - except ObjectDoesNotExist: - oldValue = None - - try: - newValue = getattr(newObj, name, None) - except ObjectDoesNotExist: - newValue = None - - try: - bothBlank = (not oldValue) and (not newValue) - if oldValue != newValue and not bothBlank: - compare = FieldCompare(thisField, oldValue, newValue) - changes.append(compare) - except TypeError: # logs issues with naive vs tz-aware datetimes - logger.error('TypeError when comparing models') - - return changes - - -def compare_event_items(old, new): - # Recieves two event version objects and compares their items, returns an array of ItemCompare objects - - item_type = ContentType.objects.get_for_model(models.EventItem) - old_item_versions = old.revision.version_set.filter(content_type=item_type) - new_item_versions = new.revision.version_set.filter(content_type=item_type) - - class ItemCompare(object): - def __init__(self, old=None, new=None, changes=None): - self.old = old - self.new = new - self.changes = changes - - # Build some dicts of what we have - item_dict = {} # build a list of items, key is the item_pk - for version in old_item_versions: # put all the old versions in a list - if version.field_dict["event"] == old.object_id_int: - compare = ItemCompare(old=version.object_version.object) - item_dict[version.object_id] = compare - - for version in new_item_versions: # go through the new versions - if version.field_dict["event"] == new.object_id_int: - try: - compare = item_dict[version.object_id] # see if there's a matching old version - compare.new = version.object_version.object # then add the new version to the dictionary - except KeyError: # there's no matching old version, so add this item to the dictionary by itself - compare = ItemCompare(new=version.object_version.object) - - item_dict[version.object_id] = compare # update the dictionary with the changes - - changes = [] - for (_, compare) in item_dict.items(): - compare.changes = model_compare(compare.old, compare.new, ['id', 'event', 'order']) # see what's changed - if len(compare.changes) >= 1: - changes.append(compare) # transfer into a sequential array to make it easier to deal with later - - return changes - - -def get_versions_for_model(models): - content_types = [] - for model in models: - content_types.append(ContentType.objects.get_for_model(model)) - - versions = reversion.models.Version.objects.filter( - content_type__in=content_types, - ).select_related("revision").order_by("-pk") - - return versions - - -def get_previous_version(version): - thisId = version.object_id - thisVersionId = version.pk - - versions = reversion.revisions.get_for_object_reference(version.content_type.model_class(), thisId) - - try: - previousVersions = versions.filter(revision_id__lt=version.revision_id).latest( - field_name='revision__date_created') - except ObjectDoesNotExist: + @property + def long(self): + if isinstance(self.field, EmailField): + return True return False - return previousVersions + @property + def linebreaks(self): + if isinstance(self.field, TextField): + return True + return False + + @property + def diff(self): + oldText = unicode(self.display_value(self._old)) or "" + newText = unicode(self.display_value(self._new)) or "" + dmp = diff_match_patch() + diffs = dmp.diff_main(oldText, newText) + dmp.diff_cleanupSemantic(diffs) + + outputDiffs = [] + + for (op, data) in diffs: + if op == dmp.DIFF_INSERT: + outputDiffs.append({'type': 'insert', 'text': data}) + elif op == dmp.DIFF_DELETE: + outputDiffs.append({'type': 'delete', 'text': data}) + elif op == dmp.DIFF_EQUAL: + outputDiffs.append({'type': 'equal', 'text': data}) + return outputDiffs -def get_changes_for_version(newVersion, oldVersion=None): - # Pass in a previous version if you already know it (for efficiancy) - # if not provided then it will be looked up in the database +class ModelComparison(object): - if oldVersion == None: - oldVersion = get_previous_version(newVersion) + def __init__(self, old=None, new=None, version=None, excluded_keys=[]): + # recieves two objects of the same model, and compares them. Returns an array of FieldCompare objects + try: + self.fields = old._meta.get_fields() + except AttributeError: + self.fields = new._meta.get_fields() - modelClass = newVersion.content_type.model_class() + self.old = old + self.new = new + self.excluded_keys = excluded_keys + self.version = version - compare = { - 'revision': newVersion.revision, - 'new': newVersion.object_version.object, - 'current': modelClass.objects.filter(pk=newVersion.pk).first(), - 'version': newVersion, + @cached_property + def revision(self): + return self.version.revision - # Old things that may not be used - 'old': None, - 'field_changes': None, - 'item_changes': None, - } + @cached_property + def field_changes(self): + changes = [] + for field in self.fields: + field_name = field.name - if oldVersion: - compare['old'] = oldVersion.object_version.object - compare['field_changes'] = model_compare(compare['old'], compare['new']) - compare['item_changes'] = compare_event_items(oldVersion, newVersion) + if field_name in self.excluded_keys: + continue # if we're excluding this field, skip over it - return compare + try: + oldValue = getattr(self.old, field_name, None) + except ObjectDoesNotExist: + oldValue = None + + try: + newValue = getattr(self.new, field_name, None) + except ObjectDoesNotExist: + newValue = None + + bothBlank = (not oldValue) and (not newValue) + if oldValue != newValue and not bothBlank: + comparison = FieldComparison(field, oldValue, newValue) + changes.append(comparison) + + return changes + + @cached_property + def fields_changed(self): + return len(self.field_changes) > 0 + + @cached_property + def item_changes(self): + # Recieves two event version objects and compares their items, returns an array of ItemCompare objects + + item_type = ContentType.objects.get_for_model(models.EventItem) + old_item_versions = self.version.parent.revision.version_set.filter(content_type=item_type) + new_item_versions = self.version.revision.version_set.filter(content_type=item_type) + + comparisonParams = {'excluded_keys': ['id', 'event', 'order']} + + # Build some dicts of what we have + item_dict = {} # build a list of items, key is the item_pk + for version in old_item_versions: # put all the old versions in a list + if version.field_dict["event_id"] == int(self.new.pk): + compare = ModelComparison(old=version._object_version.object, **comparisonParams) + item_dict[version.object_id] = compare + + for version in new_item_versions: # go through the new versions + if version.field_dict["event_id"] == int(self.new.pk): + try: + compare = item_dict[version.object_id] # see if there's a matching old version + compare.new = version._object_version.object # then add the new version to the dictionary + except KeyError: # there's no matching old version, so add this item to the dictionary by itself + compare = ModelComparison(new=version._object_version.object, **comparisonParams) + + item_dict[version.object_id] = compare # update the dictionary with the changes + + changes = [] + for (_, compare) in item_dict.items(): + if compare.fields_changed: + changes.append(compare) + + return changes + + @cached_property + def items_changed(self): + return len(self.item_changes) > 0 + + @cached_property + def anything_changed(self): + return self.fields_changed or self.items_changed + + +class RIGSVersionManager(VersionQuerySet): + def get_for_multiple_models(self, model_array): + content_types = [] + for model in model_array: + content_types.append(ContentType.objects.get_for_model(model)) + + return self.filter(content_type__in=content_types).select_related("revision").order_by("-pk") + + +class RIGSVersion(Version): + class Meta: + proxy = True + + objects = RIGSVersionManager.as_manager() + + @cached_property + def parent(self): + thisId = self.object_id + + versions = RIGSVersion.objects.get_for_object_reference(self.content_type.model_class(), thisId).select_related("revision", "revision__user").all() + + try: + previousVersion = versions.filter(revision_id__lt=self.revision_id).latest( + field_name='revision__date_created') + except ObjectDoesNotExist: + return False + + return previousVersion + + @cached_property + def changes(self): + return ModelComparison( + version=self, + new=self._object_version.object, + old=self.parent._object_version.object if self.parent else None + ) class VersionHistory(generic.ListView): - model = Version + model = RIGSVersion template_name = "RIGS/version_history.html" paginate_by = 25 def get_queryset(self, **kwargs): thisModel = self.kwargs['model'] - # thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk']) - versions = reversion.revisions.get_for_object_reference(thisModel, self.kwargs['pk']) + versions = RIGSVersion.objects.get_for_object_reference(thisModel, self.kwargs['pk']).select_related("revision", "revision__user").all() return versions def get_context_data(self, **kwargs): thisModel = self.kwargs['model'] - context = super(VersionHistory, self).get_context_data(**kwargs) - - versions = context['object_list'] thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk']) - - items = [] - - for versionNo, thisVersion in enumerate(versions): - if versionNo >= len(versions) - 1: - thisItem = get_changes_for_version(thisVersion, None) - else: - thisItem = get_changes_for_version(thisVersion, versions[versionNo + 1]) - - items.append(thisItem) - - context['object_list'] = items context['object'] = thisObject return context class ActivityTable(generic.ListView): - model = Version + model = RIGSVersion template_name = "RIGS/activity_table.html" paginate_by = 25 def get_queryset(self): - versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) + versions = RIGSVersion.objects.get_for_multiple_models([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) return versions - def get_context_data(self, **kwargs): - # Call the base implementation first to get a context - context = super(ActivityTable, self).get_context_data(**kwargs) - - items = [] - - for thisVersion in context['object_list']: - thisItem = get_changes_for_version(thisVersion, None) - items.append(thisItem) - - context['object_list'] = items - - return context - class ActivityFeed(generic.ListView): - model = Version + model = RIGSVersion template_name = "RIGS/activity_feed_data.html" paginate_by = 25 def get_queryset(self): - versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) + versions = RIGSVersion.objects.get_for_multiple_models([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) return versions def get_context_data(self, **kwargs): - maxTimeDelta = [] - - maxTimeDelta.append({'maxAge': datetime.timedelta(days=1), 'group': datetime.timedelta(hours=1)}) - maxTimeDelta.append({'maxAge': None, 'group': datetime.timedelta(days=1)}) - # Call the base implementation first to get a context context = super(ActivityFeed, self).get_context_data(**kwargs) + maxTimeDelta = datetime.timedelta(hours=1) + items = [] for thisVersion in context['object_list']: - thisItem = get_changes_for_version(thisVersion, None) - if thisItem['item_changes'] or thisItem['field_changes'] or thisItem['old'] == None: - thisItem['withPrevious'] = False - if len(items) >= 1: - timeAgo = datetime.datetime.now(thisItem['revision'].date_created.tzinfo) - thisItem[ - 'revision'].date_created - timeDiff = items[-1]['revision'].date_created - thisItem['revision'].date_created - timeTogether = False - for params in maxTimeDelta: - if params['maxAge'] is None or timeAgo <= params['maxAge']: - timeTogether = timeDiff < params['group'] - break + thisVersion.withPrevious = False + if len(items) >= 1: + timeDiff = items[-1].revision.date_created - thisVersion.revision.date_created + timeTogether = timeDiff < maxTimeDelta + sameUser = thisVersion.revision.user_id == items[-1].revision.user_id + thisVersion.withPrevious = timeTogether & sameUser - sameUser = thisItem['revision'].user == items[-1]['revision'].user - thisItem['withPrevious'] = timeTogether & sameUser - - items.append(thisItem) - - context['object_list'] = items + items.append(thisVersion) return context diff --git a/requirements.txt b/requirements.txt index 1c38b09c..e98f7a60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,12 +3,12 @@ contextlib2==0.5.5 diff-match-patch==20121119 dj-database-url==0.4.2 dj-static==0.0.6 -Django==1.11.1 +Django==1.11.2 django-debug-toolbar==1.8 django-ical==1.4 django-recaptcha==1.3.0 django-registration-redux==1.6 -django-reversion==1.10.2 +django-reversion==2.0.9 django-toolbelt==0.0.1 premailer==3.0.1 django-widget-tweaks==1.4.1 diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 00000000..ba85ab97 --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-2.7.13 \ No newline at end of file