From a18235f122693e27869aa6443ca0277ebbe14859 Mon Sep 17 00:00:00 2001 From: Troy Sankey Date: Thu, 30 May 2019 17:11:31 -0400 Subject: [PATCH] Add history to the Organization model Also, version bump to 2.0.3. DE-1510 --- organizations/__init__.py | 2 +- .../migrations/0007_historicalorganization.py | 45 +++++++++++++++++++ organizations/models.py | 3 ++ organizations/tests/test_api.py | 6 +-- organizations/tests/test_data.py | 2 +- requirements/base.in | 1 + requirements/base.txt | 11 ++--- test-requirements.txt | 18 +++++--- 8 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 organizations/migrations/0007_historicalorganization.py diff --git a/organizations/__init__.py b/organizations/__init__.py index e6c736f0..1fe75f05 100644 --- a/organizations/__init__.py +++ b/organizations/__init__.py @@ -1,4 +1,4 @@ """ edx-organizations app initialization module """ -__version__ = '2.0.2' # pragma: no cover +__version__ = '2.0.3' # pragma: no cover diff --git a/organizations/migrations/0007_historicalorganization.py b/organizations/migrations/0007_historicalorganization.py new file mode 100644 index 00000000..ddf30dc1 --- /dev/null +++ b/organizations/migrations/0007_historicalorganization.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-05-31 15:25 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('organizations', '0006_auto_20171207_0259'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalOrganization', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('name', models.CharField(db_index=True, max_length=255)), + ('short_name', models.CharField(db_index=True, help_text='Please do not use spaces or special characters. Only allowed special characters are period (.), hyphen (-) and underscore (_).', max_length=255, verbose_name=b'Short Name')), + ('description', models.TextField(blank=True, null=True)), + ('logo', models.TextField(blank=True, help_text='Please add only .PNG files for logo images. This logo will be used on certificates.', max_length=255, null=True)), + ('active', models.BooleanField(default=True)), + ('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)), + ], + options={ + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + 'verbose_name': 'historical organization', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/organizations/models.py b/organizations/models.py index 1ec1f3fa..89aa300a 100644 --- a/organizations/models.py +++ b/organizations/models.py @@ -10,6 +10,7 @@ from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from model_utils.models import TimeStampedModel +from simple_history.models import HistoricalRecords @python_2_unicode_compatible @@ -33,6 +34,8 @@ class Organization(TimeStampedModel): ) active = models.BooleanField(default=True) + history = HistoricalRecords() + def __str__(self): return u"{name} ({short_name})".format(name=self.name, short_name=self.short_name) diff --git a/organizations/tests/test_api.py b/organizations/tests/test_api.py index 66340fc7..9b47a196 100644 --- a/organizations/tests/test_api.py +++ b/organizations/tests/test_api.py @@ -24,7 +24,7 @@ def setUp(self): def test_add_organization(self): """ Unit Test: test_add_organization""" - with self.assertNumQueries(2): + with self.assertNumQueries(3): organization = api.add_organization({ 'name': 'local_organizationßßß', 'description': 'Local Organization Descriptionßßß' @@ -173,14 +173,14 @@ def test_get_organization_invalid_organization(self): def test_remove_organization(self): """ Unit Test: test_remove_organization """ - with self.assertNumQueries(3): + with self.assertNumQueries(4): api.remove_organization(self.test_organization['id']) with self.assertRaises(exceptions.InvalidOrganizationException): api.get_organization(self.test_organization['id']) def test_remove_organization_bogus_organization(self): """ Unit Test: test_remove_organization_bogus_organization """ - with self.assertNumQueries(3): + with self.assertNumQueries(4): api.remove_organization(self.test_organization['id']) with self.assertRaises(exceptions.InvalidOrganizationException): diff --git a/organizations/tests/test_data.py b/organizations/tests/test_data.py index f325e52c..ef61ee75 100644 --- a/organizations/tests/test_data.py +++ b/organizations/tests/test_data.py @@ -17,7 +17,7 @@ class OrganizationsDataTestCase(utils.OrganizationsTestCaseBase): """ def test_create_organization(self): """ Unit Test: test_create_organization""" - with self.assertNumQueries(2): + with self.assertNumQueries(3): organization = data.create_organization({ 'name': 'local_organization', 'short_name': 'organizationX', diff --git a/requirements/base.in b/requirements/base.in index 771b6970..80fce0a9 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -2,6 +2,7 @@ django django-model-utils>=1.4.0 +django-simple-history djangorestframework>=3.2.0 djangorestframework-oauth>=1.1.0 edx-django-oauth2-provider>=1.2.0 diff --git a/requirements/base.txt b/requirements/base.txt index 23557772..51004343 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,6 +7,7 @@ certifi==2019.3.9 # via requests chardet==3.0.4 # via requests django-model-utils==3.1.2 +django-simple-history==2.7.2 django-waffle==0.16.0 # via edx-django-utils, edx-drf-extensions django==1.11.20 djangorestframework-jwt==1.11.0 # via edx-drf-extensions @@ -14,15 +15,15 @@ djangorestframework-oauth==1.1.0 djangorestframework==3.9.4 edx-django-oauth2-provider==1.3.5 edx-django-utils==1.0.3 # via edx-drf-extensions -edx-drf-extensions==2.2.2 -edx-opaque-keys==0.4.4 +edx-drf-extensions==2.3.0 +edx-opaque-keys==1.0.0 future==0.17.1 # via pyjwkest idna==2.8 # via requests newrelic==4.20.0.120 # via edx-django-utils -pbr==5.2.0 # via stevedore +pbr==5.2.1 # via stevedore pillow==6.0.0 psutil==1.2.1 # via edx-django-utils, edx-drf-extensions -pycryptodomex==3.8.1 # via pyjwkest +pycryptodomex==3.8.2 # via pyjwkest pyjwkest==1.3.2 # via edx-drf-extensions pyjwt==1.7.1 # via djangorestframework-jwt pymongo==3.8.0 # via edx-opaque-keys @@ -32,6 +33,6 @@ requests==2.22.0 # via edx-drf-extensions, pyjwkest rest-condition==1.0.3 # via edx-drf-extensions semantic-version==2.6.0 # via edx-drf-extensions shortuuid==0.5.0 # via edx-django-oauth2-provider -six==1.12.0 # via edx-drf-extensions, edx-opaque-keys, pyjwkest, python-dateutil, stevedore +six==1.12.0 # via django-simple-history, edx-drf-extensions, edx-opaque-keys, pyjwkest, python-dateutil, stevedore stevedore==1.30.1 # via edx-opaque-keys urllib3==1.25.3 # via requests diff --git a/test-requirements.txt b/test-requirements.txt index 6f9e1b5f..58bc29b3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -10,19 +10,21 @@ certifi==2019.3.9 chardet==3.0.4 click-log==0.1.8 # via edx-lint click==7.0 # via click-log, edx-lint -configparser==3.7.4 # via pylint +configparser==3.7.4 # via importlib-metadata, pylint +contextlib2==0.5.5 # via importlib-metadata coverage==4.5.3 ddt==1.1.0 django-model-utils==3.1.2 django-nose==1.4.6 +django-simple-history==2.7.2 django-waffle==0.16.0 djangorestframework-jwt==1.11.0 djangorestframework-oauth==1.1.0 edx-django-oauth2-provider==1.3.5 edx-django-utils==1.0.3 -edx-drf-extensions==2.2.2 +edx-drf-extensions==2.3.0 edx-lint==0.6.0 -edx-opaque-keys==0.4.4 +edx-opaque-keys==1.0.0 enum34==1.1.6 # via astroid factory-boy==2.12.0 faker==1.0.7 # via factory-boy @@ -31,19 +33,21 @@ future==0.17.1 futures==3.2.0 ; python_version == "2.7" # via isort httpretty==0.9.6 idna==2.8 +importlib-metadata==0.17 # via pluggy ipaddress==1.0.22 # via faker isort==4.3.20 # via pylint lazy-object-proxy==1.4.1 # via astroid mccabe==0.6.1 # via pylint newrelic==4.20.0.120 nose==1.3.7 -pbr==5.2.0 +pathlib2==2.3.3 # via importlib-metadata +pbr==5.2.1 pep8==1.7.1 pillow==6.0.0 -pluggy==0.11.0 # via tox +pluggy==0.12.0 # via tox psutil==1.2.1 py==1.8.0 # via tox -pycryptodomex==3.8.1 +pycryptodomex==3.8.2 pyjwkest==1.3.2 pyjwt==1.7.1 pylint-celery==0.3 # via edx-lint @@ -55,6 +59,7 @@ python-dateutil==2.8.0 pytz==2019.1 requests==2.22.0 rest-condition==1.0.3 +scandir==1.10.0 # via pathlib2 semantic-version==2.6.0 shortuuid==0.5.0 singledispatch==3.4.0.3 # via astroid, pylint @@ -66,3 +71,4 @@ tox==3.12.1 urllib3==1.25.3 virtualenv==16.6.0 # via tox wrapt==1.11.1 # via astroid +zipp==0.5.1 # via importlib-metadata