From c7d31b44c3873aea57effb8bd5d81d16c50c1b32 Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Fri, 27 Apr 2018 17:36:20 +0000 Subject: [PATCH] Added django-simple-history and replaced the old (and unused) PersonHistory class with a history=HistoricalRecords() field on Person. Added the needed migrations and changes to admin, resources, and settings. Related to issues #2505 and #2507. - Legacy-Id: 15096 --- ietf/help/tests_views.py | 6 +- ietf/person/admin.py | 23 ++++---- .../migrations/0004_auto_20180427_0646.py | 55 +++++++++++++++++++ ietf/person/models.py | 18 +++--- ietf/person/resources.py | 55 ++++++++++--------- ietf/settings.py | 2 + requirements.txt | 1 + 7 files changed, 112 insertions(+), 48 deletions(-) create mode 100644 ietf/person/migrations/0004_auto_20180427_0646.py diff --git a/ietf/help/tests_views.py b/ietf/help/tests_views.py index 2d6207ea2..714dab39e 100644 --- a/ietf/help/tests_views.py +++ b/ietf/help/tests_views.py @@ -7,7 +7,7 @@ import debug # pyflakes:ignore from ietf.utils.test_utils import TestCase from ietf.doc.models import StateType -class StateHelpTest(TestCase): +class HelpPageTests(TestCase): def test_state_index(self): url = reverse('ietf.help.views.state_index') @@ -21,3 +21,7 @@ class StateHelpTest(TestCase): self.assertIn(name, content) + def test_personal_information_help(self): + r = self.client.get('/help/personal-information') + self.assertContains(r, 'personal information') + self.assertContains(r, 'GDPR') diff --git a/ietf/person/admin.py b/ietf/person/admin.py index 86c0c851f..53978fae4 100644 --- a/ietf/person/admin.py +++ b/ietf/person/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin -from ietf.person.models import Email, Alias, Person, PersonHistory, PersonalApiKey, PersonEvent, PersonApiKeyEvent +from ietf.person.models import Email, Alias, Person, PersonalApiKey, PersonEvent, PersonApiKeyEvent, HistoricalPerson from ietf.person.name import name_parts class EmailAdmin(admin.ModelAdmin): @@ -33,16 +33,6 @@ class PersonAdmin(admin.ModelAdmin): # actions = None admin.site.register(Person, PersonAdmin) -class PersonHistoryAdmin(admin.ModelAdmin): - def plain_name(self, obj): - prefix, first, middle, last, suffix = name_parts(obj.name) - return "%s %s" % (first, last) - list_display = ['name', 'short', 'plain_name', 'time', 'user', 'person', ] - list_filter = ['time'] - raw_id_fields = ['person', 'user'] - search_fields = ['name', 'ascii'] -admin.site.register(PersonHistory, PersonHistoryAdmin) - class PersonalApiKeyAdmin(admin.ModelAdmin): list_display = ['id', 'person', 'created', 'endpoint', 'valid', 'count', 'latest', ] list_filter = ['endpoint', 'created', ] @@ -61,3 +51,14 @@ class PersonApiKeyEventAdmin(admin.ModelAdmin): search_fields = ["person__name", ] raw_id_fields = ['person', ] admin.site.register(PersonApiKeyEvent, PersonApiKeyEventAdmin) + + +class HistoricalPersonAdmin(admin.ModelAdmin): + def plain_name(self, obj): + prefix, first, middle, last, suffix = name_parts(obj.name) + return "%s %s" % (first, last) + list_display = ["history_date", "name", "plain_name", "time", "history_user", "history_change_reason", ] + search_fields = ["name", "ascii"] + raw_id_fields = ["user", "history_user", ] +# actions = None +admin.site.register(HistoricalPerson, HistoricalPersonAdmin) diff --git a/ietf/person/migrations/0004_auto_20180427_0646.py b/ietf/person/migrations/0004_auto_20180427_0646.py new file mode 100644 index 000000000..c9d83578c --- /dev/null +++ b/ietf/person/migrations/0004_auto_20180427_0646.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.12 on 2018-04-27 06:46 +from __future__ import unicode_literals + +import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('person', '0003_auto_20180426_0517'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalPerson', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('time', models.DateTimeField(default=datetime.datetime.now)), + ('name', models.CharField(db_index=True, help_text=b'Preferred form of name.', max_length=255, verbose_name=b'Full Name (Unicode)')), + ('ascii', models.CharField(help_text=b'Name as rendered in ASCII (Latin, unaccented) characters.', max_length=255, verbose_name=b'Full Name (ASCII)')), + ('ascii_short', models.CharField(blank=True, help_text=b'Example: A. Nonymous. Fill in this with initials and surname only if taking the initials and surname of the ASCII name above produces an incorrect initials-only form. (Blank is OK).', max_length=32, null=True, verbose_name=b'Abbreviated Name (ASCII)')), + ('affiliation', models.CharField(blank=True, help_text=b'Employer, university, sponsor, etc.', max_length=255)), + ('biography', models.TextField(blank=True, help_text=b'Short biography for use on leadership pages. Use plain text or reStructuredText markup.')), + ('photo', models.TextField(blank=True, default=None, max_length=100)), + ('photo_thumb', models.TextField(blank=True, default=None, max_length=100)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField()), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + 'verbose_name': 'historical person', + }, + ), + migrations.RemoveField( + model_name='personhistory', + name='person', + ), + migrations.RemoveField( + model_name='personhistory', + name='user', + ), + migrations.DeleteModel( + name='PersonHistory', + ), + ] diff --git a/ietf/person/models.py b/ietf/person/models.py index 36a51d00e..acfcbb528 100644 --- a/ietf/person/models.py +++ b/ietf/person/models.py @@ -16,6 +16,7 @@ from django.db import models from django.contrib.auth.models import User from django.template.loader import render_to_string from django.utils.text import slugify +from simple_history.models import HistoricalRecords import debug # pyflakes:ignore @@ -27,7 +28,9 @@ from ietf.person.name import unidecode_name from ietf.utils.models import ForeignKey, OneToOneField -class PersonInfo(models.Model): +class Person(models.Model): + history = HistoricalRecords() + user = OneToOneField(User, blank=True, null=True) time = models.DateTimeField(default=datetime.datetime.now) # When this Person record entered the system # The normal unicode form of the name. This must be # set to the same value as the ascii-form if equal. @@ -143,24 +146,21 @@ class PersonInfo(models.Model): def has_drafts(self): from ietf.doc.models import Document return Document.objects.filter(documentauthor__person=self, type='draft').exists() + def rfcs(self): from ietf.doc.models import Document rfcs = list(Document.objects.filter(documentauthor__person=self, type='draft', states__slug='rfc')) rfcs.sort(key=lambda d: d.canonical_name() ) return rfcs + def active_drafts(self): from ietf.doc.models import Document return Document.objects.filter(documentauthor__person=self, type='draft', states__slug='active').order_by('-time') + def expired_drafts(self): from ietf.doc.models import Document return Document.objects.filter(documentauthor__person=self, type='draft', states__slug__in=['repl', 'expired', 'auth-rm', 'ietf-rm']).order_by('-time') - class Meta: - abstract = True - -class Person(PersonInfo): - user = OneToOneField(User, blank=True, null=True) - def save(self, *args, **kwargs): created = not self.pk super(Person, self).save(*args, **kwargs) @@ -198,10 +198,6 @@ class Person(PersonInfo): ct1['affiliation']= self.affiliation return ct1 -class PersonHistory(PersonInfo): - person = ForeignKey(Person, related_name="history_set") - user = ForeignKey(User, blank=True, null=True) - class Alias(models.Model): """This is used for alternative forms of a name. This is the primary lookup point for names, and should always contain the diff --git a/ietf/person/resources.py b/ietf/person/resources.py index 1e842a69f..d2426a654 100644 --- a/ietf/person/resources.py +++ b/ietf/person/resources.py @@ -6,7 +6,7 @@ from tastypie.cache import SimpleCache from ietf import api -from ietf.person.models import (Person, Email, Alias, PersonHistory, PersonalApiKey, PersonEvent, PersonApiKeyEvent) +from ietf.person.models import (Person, Email, Alias, PersonalApiKey, PersonEvent, PersonApiKeyEvent, HistoricalPerson) from ietf.utils.resources import UserResource @@ -24,7 +24,6 @@ class PersonResource(ModelResource): "name": ALL, "ascii": ALL, "ascii_short": ALL, - "address": ALL, "affiliation": ALL, "photo": ALL, "biography": ALL, @@ -61,29 +60,6 @@ class AliasResource(ModelResource): } api.person.register(AliasResource()) -from ietf.utils.resources import UserResource -class PersonHistoryResource(ModelResource): - person = ToOneField(PersonResource, 'person') - user = ToOneField(UserResource, 'user', null=True) - class Meta: - cache = SimpleCache() - queryset = PersonHistory.objects.all() - serializer = api.Serializer() - #resource_name = 'personhistory' - filtering = { - "id": ALL, - "time": ALL, - "name": ALL, - "ascii": ALL, - "ascii_short": ALL, - "address": ALL, - "affiliation": ALL, - "person": ALL_WITH_RELATIONS, - "user": ALL_WITH_RELATIONS, - } -api.person.register(PersonHistoryResource()) - - class PersonalApiKeyResource(ModelResource): person = ToOneField(PersonResource, 'person') class Meta: @@ -141,3 +117,32 @@ class PersonApiKeyEventResource(ModelResource): "key": ALL_WITH_RELATIONS, } api.person.register(PersonApiKeyEventResource()) + + +from ietf.utils.resources import UserResource +class HistoricalPersonResource(ModelResource): + user = ToOneField(UserResource, 'user', null=True) + history_user = ToOneField(UserResource, 'history_user', null=True) + class Meta: + queryset = HistoricalPerson.objects.all() + serializer = api.Serializer() + cache = SimpleCache() + #resource_name = 'historicalperson' + filtering = { + "id": ALL, + "time": ALL, + "name": ALL, + "ascii": ALL, + "ascii_short": ALL, + "affiliation": ALL, + "biography": ALL, + "photo": ALL, + "photo_thumb": ALL, + "history_id": ALL, + "history_date": ALL, + "history_change_reason": ALL, + "history_type": ALL, + "user": ALL_WITH_RELATIONS, + "history_user": ALL_WITH_RELATIONS, + } +api.person.register(HistoricalPersonResource()) diff --git a/ietf/settings.py b/ietf/settings.py index 62b3f7bd0..1651ee521 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -349,6 +349,7 @@ MIDDLEWARE = ( 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.http.ConditionalGetMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware', # 'ietf.middleware.sql_log_middleware', 'ietf.middleware.SMTPExceptionMiddleware', 'ietf.middleware.Utf8ExceptionMiddleware', @@ -385,6 +386,7 @@ INSTALLED_APPS = ( 'django_password_strength', 'djangobwr', 'form_utils', + 'simple_history', 'tastypie', 'widget_tweaks', # IETF apps diff --git a/requirements.txt b/requirements.txt index 0b6ff25cb..98ef5c19c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ django-bootstrap3>=8.2.1,<9.0.0 django-formtools>=1.0 # instead of django.contrib.formtools in 1.8 django-markup>=1.1 django-password-strength>=1.2.1 +django-simple-history>=2.0 django-tastypie>=0.13.2 django-widget-tweaks>=1.3 docutils>=0.12