From 5b716993924f4460d57c2e5e68ddc85597f51fdc Mon Sep 17 00:00:00 2001 From: Ahmet DAL Date: Tue, 17 Mar 2020 16:23:46 +0100 Subject: [PATCH] [#141 , #143] Support Microsoft SQL Server 17 and 19 * [#141] Support Microsoft SQL Server 17 and 19 --- .codacy.yml | 4 +- .travis.yml | 13 + MANIFEST.in | 3 +- README.rst | 20 ++ docs/conf.py | 4 +- docs/migration/index.rst | 1 + docs/migration/migration_31_to_32.rst | 13 + docs/overview.rst | 7 + manage.py | 11 +- river/__init__.py | 2 - river/config.py | 41 +++- river/core/classworkflowobject.py | 79 ++---- river/core/instanceworkflowobject.py | 2 +- river/driver/__init__.py | 0 river/driver/mssql_driver.py | 52 ++++ river/driver/orm_driver.py | 66 +++++ river/driver/river_driver.py | 14 ++ river/migrations/0001_initial.py | 191 ++++++++++++--- river/migrations/0002_auto_20190920_1640.py | 40 --- river/migrations/0003_auto_20191015_1628.py | 86 ------- river/migrations/0004_auto_20191016_1731.py | 28 --- river/migrations/0005_auto_20191020_1027.py | 44 ---- river/migrations/0006_auto_20191020_1121.py | 20 -- .../0007_transitionapproval_iteration.py | 92 ------- river/migrations/0008_auto_20191109_1130.py | 31 --- river/migrations/0009_auto_20191109_1806.py | 27 --- river/migrations/0010_auto_20191110_1045.py | 229 ------------------ river/migrations/0011_auto_20191110_1411.py | 159 ------------ river/migrations/0012_auto_20191113_1550.py | 46 ---- river/migrations/0013_auto_20191214_0742.py | 23 -- river/migrations/0014_auto_20200128_0558.py | 96 -------- river/models/base_model.py | 4 + river/models/factories.py | 2 - river/models/fields/__init__.py | 2 +- river/models/fields/state.py | 9 +- river/models/managers/__init__.py | 2 +- river/models/managers/rivermanager.py | 18 ++ river/models/managers/state.py | 6 +- river/models/managers/transitionapproval.py | 5 +- river/models/managers/transitionmetada.py | 6 +- river/models/managers/workflowmetada.py | 6 +- river/models/state.py | 2 - river/models/transition.py | 2 - river/models/transitionapproval.py | 6 +- river/models/transitionapprovalmeta.py | 2 - river/models/transitionmeta.py | 2 - river/signals.py | 2 - river/sql/mssql/get_available_approvals.sql | 49 ++++ river/tests/__init__.py | 2 +- river/tests/core/test__class_api.py | 3 +- river/tests/hooking/__init__.py | 2 +- river/tests/hooking/test__approved_hooking.py | 5 +- river/tests/models/factories.py | 2 - river/tests/test__state_field.py | 2 - river/tests/tmigrations/test__migrations.py | 9 +- river/utils/__init__.py | 2 +- river/utils/error_code.py | 3 - river/utils/exceptions.py | 3 - settings/base.py | 1 - settings/with_mssql.py | 46 ++++ setup.py | 4 +- test_urls.py | 1 - tox.ini | 28 ++- 63 files changed, 578 insertions(+), 1104 deletions(-) create mode 100644 docs/migration/migration_31_to_32.rst create mode 100644 river/driver/__init__.py create mode 100644 river/driver/mssql_driver.py create mode 100644 river/driver/orm_driver.py create mode 100644 river/driver/river_driver.py delete mode 100644 river/migrations/0002_auto_20190920_1640.py delete mode 100644 river/migrations/0003_auto_20191015_1628.py delete mode 100644 river/migrations/0004_auto_20191016_1731.py delete mode 100644 river/migrations/0005_auto_20191020_1027.py delete mode 100644 river/migrations/0006_auto_20191020_1121.py delete mode 100644 river/migrations/0007_transitionapproval_iteration.py delete mode 100644 river/migrations/0008_auto_20191109_1130.py delete mode 100644 river/migrations/0009_auto_20191109_1806.py delete mode 100644 river/migrations/0010_auto_20191110_1045.py delete mode 100644 river/migrations/0011_auto_20191110_1411.py delete mode 100644 river/migrations/0012_auto_20191113_1550.py delete mode 100644 river/migrations/0013_auto_20191214_0742.py delete mode 100644 river/migrations/0014_auto_20200128_0558.py create mode 100644 river/models/managers/rivermanager.py create mode 100644 river/sql/mssql/get_available_approvals.sql create mode 100644 settings/with_mssql.py diff --git a/.codacy.yml b/.codacy.yml index 71721bd..29d6fc6 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -2,4 +2,6 @@ exclude_paths: - 'river/migrations/*' - '**/tests/**' - - 'settings/**' \ No newline at end of file + - 'settings/**' + - 'river/sql/**/**' + - 'docs/conf.py' \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 064bae3..744a0bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,20 @@ matrix: env: TOXENV=py36-dj2.2-mysql8.0 services: - docker + - python: "3.6" + env: TOXENV=py36-dj2.2-mssql17 + services: + - docker + - python: "3.6" + env: TOXENV=py36-dj2.2-mssql19 + services: + - docker + install: + - curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + - curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list + - sudo apt-get update + - sudo ACCEPT_EULA=Y apt-get install -y msodbcsql17 g++ unixodbc-dev - pip install tox-travis - pip install tox-docker - pip install coveralls diff --git a/MANIFEST.in b/MANIFEST.in index d7083ee..2ba8c1a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include README.md -include *.txt \ No newline at end of file +include *.txt +include river/sql/mssql/get_available_approvals.sql diff --git a/README.rst b/README.rst index 88cba65..c3739ed 100644 --- a/README.rst +++ b/README.rst @@ -108,6 +108,14 @@ Supported (Tested) Databases: | 8.0 | ✅ | ✅ | +------------+--------+---------+ ++------------+--------+---------+ +| MSSQL | Tested | Support | ++------------+--------+---------+ +| 19 | ✅ | ✅ | ++------------+--------+---------+ +| 17 | ✅ | ✅ | ++------------+--------+---------+ + Usage ----- @@ -242,6 +250,18 @@ out of the box. All you need to do is to run; python manage.py migrate river +3.1.X to 3.2.X +^^^^^^^^^^^^^^ + +``django-river`` started to support **Microsoft SQL Server 17 and 19** after version 3.2.0 but the previous migrations didn't get along with it. We needed to reset all +the migrations to have fresh start. If you have already migrated to version `3.1.X` all you need to do is to pull your migrations back to the beginning. + + + .. code:: bash + + python manage.py migrate --fake river zero + python manage.py migrate --fake river + FAQ --- diff --git a/docs/conf.py b/docs/conf.py index 74adbee..db0aaf2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ # built documents. # # The short X.Y version. -version = '3.1.4' +version = '3.2.0' # The full version, including alpha/beta/rc tags. -release = '3.1.4' +release = '3.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/migration/index.rst b/docs/migration/index.rst index 31d5e43..437d68e 100644 --- a/docs/migration/index.rst +++ b/docs/migration/index.rst @@ -7,5 +7,6 @@ Migration Guide :maxdepth: 2 migration_2_to_3 + migration_31_to_32 hooking \ No newline at end of file diff --git a/docs/migration/migration_31_to_32.rst b/docs/migration/migration_31_to_32.rst new file mode 100644 index 0000000..db5753e --- /dev/null +++ b/docs/migration/migration_31_to_32.rst @@ -0,0 +1,13 @@ +.. _migration_31_to_32: + +3.1.X to 3.2.X +============== + +``django-river`` started to support **Microsoft SQL Server 17 and 19** after version 3.2.0 but the previous migrations didn't get along with it. We needed to reset all +the migrations to have fresh start. If you have already migrated to version `3.1.X` all you need to do is to pull your migrations back to the beginning. + + + .. code:: bash + + python manage.py migrate --fake river zero + python manage.py migrate --fake river diff --git a/docs/overview.rst b/docs/overview.rst index ee2f15c..119941b 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -45,6 +45,13 @@ Supported (Tested) Databases: | 8.0 | ✅ | ✅ | +------------+--------+---------+ ++------------+--------+---------+ +| MSSQL | Tested | Support | ++------------+--------+---------+ +| 19 | ✅ | ✅ | ++------------+--------+---------+ +| 17 | ✅ | ✅ | ++------------+--------+---------+ Example Scenarios ----------------- diff --git a/manage.py b/manage.py index e38fec9..c969506 100644 --- a/manage.py +++ b/manage.py @@ -1,13 +1,10 @@ -__author__ = 'ahmetdal' - - -#!/usr/bin/env python -import os +# !/usr/bin/env python +import os import sys -if __name__ == "__main__": +if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.with_sqlite3") from django.core.management import execute_from_command_line - execute_from_command_line(sys.argv) \ No newline at end of file + execute_from_command_line(sys.argv) diff --git a/river/__init__.py b/river/__init__.py index 29a652b..cd4d0db 100644 --- a/river/__init__.py +++ b/river/__init__.py @@ -1,3 +1 @@ -__author__ = 'ahmetdal' - default_app_config = 'river.apps.RiverApp' diff --git a/river/config.py b/river/config.py index 85e34a9..9cb9590 100644 --- a/river/config.py +++ b/river/config.py @@ -1,28 +1,43 @@ from django.contrib.auth.models import Permission, Group from django.contrib.contenttypes.models import ContentType -__author__ = 'ahmetdal' +from django.db import connection class RiverConfig(object): - # from settings prefix = 'RIVER' + def __init__(self): + self.cached_settings = None + + @property + def settings(self): + if self.cached_settings: + return self.cached_settings + else: + from django.conf import settings + allowed_configurations = { + 'CONTENT_TYPE_CLASS': ContentType, + 'USER_CLASS': settings.AUTH_USER_MODEL, + 'PERMISSION_CLASS': Permission, + 'GROUP_CLASS': Group, + 'INJECT_MODEL_ADMIN': False + } + river_settings = {} + for key, default in allowed_configurations.items(): + river_settings[key] = getattr(settings, self.get_with_prefix(key), default) + + river_settings['IS_MSSQL'] = connection.vendor == 'microsoft' + self.cached_settings = river_settings + + return self.cached_settings + def get_with_prefix(self, config): return '%s_%s' % (self.prefix, config) def __getattr__(self, item): - from django.conf import settings - allowed_configurations = { - 'CONTENT_TYPE_CLASS': ContentType, - 'USER_CLASS': settings.AUTH_USER_MODEL, - 'PERMISSION_CLASS': Permission, - 'GROUP_CLASS': Group, - 'INJECT_MODEL_ADMIN': False - } - if item in allowed_configurations.keys(): - default_value = allowed_configurations[item] - return getattr(settings, self.get_with_prefix(item), default_value) + if item in self.settings: + return self.settings[item] else: raise AttributeError(item) diff --git a/river/core/classworkflowobject.py b/river/core/classworkflowobject.py index 2703795..99b53c3 100644 --- a/river/core/classworkflowobject.py +++ b/river/core/classworkflowobject.py @@ -1,10 +1,8 @@ -from django.contrib import auth from django.contrib.contenttypes.models import ContentType -from django.db.models import F, Q, Min, CharField -from django.db.models.functions import Cast -from django_cte import With -from river.models import State, TransitionApprovalMeta, TransitionApproval, PENDING, Workflow +from river.driver.mssql_driver import MsSqlDriver +from river.driver.orm_driver import OrmDriver +from river.models import State, TransitionApprovalMeta, Workflow, app_config class ClassWorkflowObject(object): @@ -12,13 +10,19 @@ class ClassWorkflowObject(object): def __init__(self, wokflow_object_class, field_name): self.wokflow_object_class = wokflow_object_class self.field_name = field_name - self._cached_workflow = None + self.workflow = Workflow.objects.filter(field_name=self.field_name, content_type=self._content_type).first() + self._cached_river_driver = None @property - def workflow(self): - if not self._cached_workflow: - self._cached_workflow = Workflow.objects.filter(field_name=self.field_name, content_type=self._content_type).first() - return self._cached_workflow + def _river_driver(self): + if self._cached_river_driver: + return self._cached_river_driver + else: + if app_config.IS_MSSQL: + self._cached_river_driver = MsSqlDriver(self.workflow, self.wokflow_object_class, self.field_name) + else: + self._cached_river_driver = OrmDriver(self.workflow, self.wokflow_object_class, self.field_name) + return self._cached_river_driver def get_on_approval_objects(self, as_user): approvals = self.get_available_approvals(as_user) @@ -26,36 +30,7 @@ def get_on_approval_objects(self, as_user): return self.wokflow_object_class.objects.filter(pk__in=object_ids) def get_available_approvals(self, as_user): - those_with_max_priority = With( - TransitionApproval.objects.filter( - workflow=self.workflow, status=PENDING - ).values( - 'workflow', 'object_id', 'transition' - ).annotate(min_priority=Min('priority')) - ) - - workflow_objects = With( - self.wokflow_object_class.objects.all(), - name="workflow_object" - ) - - approvals_with_max_priority = those_with_max_priority.join( - self._authorized_approvals(as_user), - workflow_id=those_with_max_priority.col.workflow_id, - object_id=those_with_max_priority.col.object_id, - transition_id=those_with_max_priority.col.transition_id, - ).with_cte( - those_with_max_priority - ).annotate( - object_id_as_str=Cast('object_id', CharField(max_length=200)), - min_priority=those_with_max_priority.col.min_priority - ).filter(min_priority=F("priority")) - - return workflow_objects.join( - approvals_with_max_priority, object_id_as_str=Cast(workflow_objects.col.pk, CharField(max_length=200)) - ).with_cte( - workflow_objects - ).filter(transition__source_state=getattr(workflow_objects.col, self.field_name + "_id")) + return self._river_driver.get_available_approvals(as_user) @property def initial_state(self): @@ -67,30 +42,6 @@ def final_states(self): final_approvals = TransitionApprovalMeta.objects.filter(workflow=self.workflow, children__isnull=True) return State.objects.filter(pk__in=final_approvals.values_list("transition_meta__destination_state", flat=True)) - def _authorized_approvals(self, as_user): - group_q = Q() - for g in as_user.groups.all(): - group_q = group_q | Q(groups__in=[g]) - - permissions = [] - for backend in auth.get_backends(): - permissions.extend(backend.get_all_permissions(as_user)) - - permission_q = Q() - for p in permissions: - label, codename = p.split('.') - permission_q = permission_q | Q(permissions__content_type__app_label=label, - permissions__codename=codename) - - return TransitionApproval.objects.filter( - Q(workflow=self.workflow, status=PENDING) & - ( - (Q(transactioner__isnull=True) | Q(transactioner=as_user)) & - (Q(permissions__isnull=True) | permission_q) & - (Q(groups__isnull=True) | group_q) - ) - ) - @property def _content_type(self): return ContentType.objects.get_for_model(self.wokflow_object_class) diff --git a/river/core/instanceworkflowobject.py b/river/core/instanceworkflowobject.py index 8a6c256..65b850a 100644 --- a/river/core/instanceworkflowobject.py +++ b/river/core/instanceworkflowobject.py @@ -109,7 +109,7 @@ def get_available_states(self, as_user=None): return State.objects.filter(pk__in=all_destination_state_ids) def get_available_approvals(self, as_user=None, destination_state=None): - qs = self.class_workflow.get_available_approvals(as_user).filter(object_id=self.workflow_object.pk) + qs = self.class_workflow.get_available_approvals(as_user, ).filter(object_id=self.workflow_object.pk) if destination_state: qs = qs.filter(transition__destination_state=destination_state) diff --git a/river/driver/__init__.py b/river/driver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/river/driver/mssql_driver.py b/river/driver/mssql_driver.py new file mode 100644 index 0000000..b315b73 --- /dev/null +++ b/river/driver/mssql_driver.py @@ -0,0 +1,52 @@ +import os +from os.path import dirname + +import six +from django.db import connection +from django.contrib.auth.models import Permission + +from river.driver.river_driver import RiverDriver +from river.models import TransitionApproval + + +class MsSqlDriver(RiverDriver): + + def __init__(self, *args, **kwargs): + super(MsSqlDriver, self).__init__(*args, **kwargs) + with open(os.path.join(dirname(dirname(__file__)), "sql", "mssql", "get_available_approvals.sql")) as f: + self.available_approvals_sql_template = f.read().replace("river.dbo.", "") + self.cursor = connection.cursor() + + def get_available_approvals(self, as_user): + with connection.cursor() as cursor: + cursor.execute(self._clean_sql % { + "workflow_id": self.workflow.pk, + "transactioner_id": as_user.pk, + "field_name": self.field_name, + "permission_ids": self._permission_ids_str(as_user), + "group_ids": self._group_ids_str(as_user), + "workflow_object_table": self.wokflow_object_class._meta.db_table, + "object_pk_name": self.wokflow_object_class._meta.pk.name + }) + + return TransitionApproval.objects.filter(pk__in=[row[0] for row in cursor.fetchall()]) + + @staticmethod + def _permission_ids_str(as_user): + permissions = as_user.user_permissions.all() | Permission.objects.filter(group__user=as_user) + return ",".join(list(six.moves.map(str, permissions.values_list("pk", flat=True))) or ["-1"]) + + @staticmethod + def _group_ids_str(as_user): + return ",".join(list(six.moves.map(str, as_user.groups.all().values_list("pk", flat=True))) or ["-1"]) + + @property + def _clean_sql(self): + return self.available_approvals_sql_template \ + .replace("'%(workflow_id)s'", "%(workflow_id)s") \ + .replace("'%(transactioner_id)s'", "%(transactioner_id)s") \ + .replace("'%(field_name)s'", "%(field_name)s") \ + .replace("'%(permission_ids)s'", "%(permission_ids)s") \ + .replace("'%(group_ids)s'", "%(group_ids)s") \ + .replace("'%(workflow_object_table)s'", "%(workflow_object_table)s") \ + .replace("'%(object_pk_name)s'", "%(object_pk_name)s") diff --git a/river/driver/orm_driver.py b/river/driver/orm_driver.py new file mode 100644 index 0000000..163df96 --- /dev/null +++ b/river/driver/orm_driver.py @@ -0,0 +1,66 @@ +from django.contrib import auth +from django.db.models import Min, CharField, Q, F +from django.db.models.functions import Cast +from django_cte import With + +from river.driver.river_driver import RiverDriver +from river.models import TransitionApproval, PENDING + + +class OrmDriver(RiverDriver): + + def get_available_approvals(self, as_user): + those_with_max_priority = With( + TransitionApproval.objects.filter( + workflow=self.workflow, status=PENDING + ).values( + 'workflow', 'object_id', 'transition' + ).annotate(min_priority=Min('priority')) + ) + + workflow_objects = With( + self.wokflow_object_class.objects.all(), + name="workflow_object" + ) + + approvals_with_max_priority = those_with_max_priority.join( + self._authorized_approvals(as_user), + workflow_id=those_with_max_priority.col.workflow_id, + object_id=those_with_max_priority.col.object_id, + transition_id=those_with_max_priority.col.transition_id, + ).with_cte( + those_with_max_priority + ).annotate( + object_id_as_str=Cast('object_id', CharField(max_length=200)), + min_priority=those_with_max_priority.col.min_priority + ).filter(min_priority=F("priority")) + + return workflow_objects.join( + approvals_with_max_priority, object_id_as_str=Cast(workflow_objects.col.pk, CharField(max_length=200)) + ).with_cte( + workflow_objects + ).filter(transition__source_state=getattr(workflow_objects.col, self.field_name + "_id")) + + def _authorized_approvals(self, as_user): + group_q = Q() + for g in as_user.groups.all(): + group_q = group_q | Q(groups__in=[g]) + + permissions = [] + for backend in auth.get_backends(): + permissions.extend(backend.get_all_permissions(as_user)) + + permission_q = Q() + for p in permissions: + label, codename = p.split('.') + permission_q = permission_q | Q(permissions__content_type__app_label=label, + permissions__codename=codename) + + return TransitionApproval.objects.filter( + Q(workflow=self.workflow, status=PENDING) & + ( + (Q(transactioner__isnull=True) | Q(transactioner=as_user)) & + (Q(permissions__isnull=True) | permission_q) & + (Q(groups__isnull=True) | group_q) + ) + ) diff --git a/river/driver/river_driver.py b/river/driver/river_driver.py new file mode 100644 index 0000000..b22cee2 --- /dev/null +++ b/river/driver/river_driver.py @@ -0,0 +1,14 @@ +from abc import abstractmethod + + +class RiverDriver(object): + + def __init__(self, workflow, wokflow_object_class, field_name): + self.workflow = workflow + self.wokflow_object_class = wokflow_object_class + self.field_name = field_name + self._cached_workflow = None + + @abstractmethod + def get_available_approvals(self, as_user): + raise NotImplementedError() diff --git a/river/migrations/0001_initial.py b/river/migrations/0001_initial.py index 1e13af5..38a7350 100644 --- a/river/migrations/0001_initial.py +++ b/river/migrations/0001_initial.py @@ -1,4 +1,6 @@ -# Generated by Django 2.1.4 on 2019-09-05 09:43 +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2020-03-16 11:46 +from __future__ import unicode_literals from django.conf import settings from django.db import migrations, models @@ -11,26 +13,62 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0008_alter_user_username_max_length'), ] operations = [ migrations.CreateModel( - name='Callback', + name='Function', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), - ('hash', models.CharField(max_length=200, unique=True, verbose_name='Hash')), - ('method', models.CharField(max_length=200, verbose_name='Callback Method')), - ('hooking_cls', models.CharField(max_length=200, verbose_name='HookingClass')), - ('enabled', models.BooleanField(default=True, verbose_name='Enabled')), + ('name', models.CharField(max_length=200, unique=True, verbose_name='Function Name')), + ('body', models.TextField(max_length=100000, verbose_name='Function Body')), + ('version', models.IntegerField(default=0, verbose_name='Function Version')), ], options={ - 'verbose_name': 'Callback', - 'verbose_name_plural': 'Callbacks', + 'abstract': False, }, ), + migrations.CreateModel( + name='OnApprovedHook', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), + ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), + ('object_id', models.CharField(blank=True, max_length=200, null=True)), + ('hook_type', models.CharField(choices=[(b'BEFORE', 'Before'), (b'AFTER', 'After')], max_length=50, verbose_name='When?')), + ('callback_function', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_onapprovedhook_hooks', to='river.Function', verbose_name='Function')), + ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.ContentType')), + ], + ), + migrations.CreateModel( + name='OnCompleteHook', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), + ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), + ('object_id', models.CharField(blank=True, max_length=200, null=True)), + ('hook_type', models.CharField(choices=[(b'BEFORE', 'Before'), (b'AFTER', 'After')], max_length=50, verbose_name='When?')), + ('callback_function', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_oncompletehook_hooks', to='river.Function', verbose_name='Function')), + ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.ContentType')), + ], + ), + migrations.CreateModel( + name='OnTransitHook', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), + ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), + ('object_id', models.CharField(blank=True, max_length=200, null=True)), + ('hook_type', models.CharField(choices=[(b'BEFORE', 'Before'), (b'AFTER', 'After')], max_length=50, verbose_name='When?')), + ('callback_function', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_ontransithook_hooks', to='river.Function', verbose_name='Function')), + ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.ContentType')), + ], + ), migrations.CreateModel( name='State', fields=[ @@ -46,6 +84,23 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'States', }, ), + migrations.CreateModel( + name='Transition', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), + ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), + ('object_id', models.CharField(max_length=50, verbose_name='Related Object')), + ('status', models.CharField(choices=[(b'pending', 'Pending'), (b'cancelled', 'Cancelled'), (b'done', 'Done'), (b'jumped', 'Jumped')], default=b'pending', max_length=100, verbose_name='Status')), + ('iteration', models.IntegerField(default=0, verbose_name='Priority')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Content Type')), + ('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_as_destination', to='river.State', verbose_name='Destination State')), + ], + options={ + 'verbose_name': 'Transition', + 'verbose_name_plural': 'Transitions', + }, + ), migrations.CreateModel( name='TransitionApproval', fields=[ @@ -54,12 +109,9 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), ('object_id', models.CharField(max_length=50, verbose_name='Related Object')), ('transaction_date', models.DateTimeField(blank=True, null=True)), - ('status', models.IntegerField(choices=[(0, 'Pending'), (1, 'Approved')], default=0, verbose_name='Status')), - ('skipped', models.BooleanField(default=False, verbose_name='Skip')), + ('status', models.CharField(choices=[(b'pending', 'Pending'), (b'approved', 'Approved'), (b'cancelled', 'Cancelled'), (b'jumped', 'Jumped')], default=b'pending', max_length=100, verbose_name='Status')), ('priority', models.IntegerField(default=0, verbose_name='Priority')), - ('enabled', models.BooleanField(default=True, verbose_name='Enabled?')), ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Content Type')), - ('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_approvals_as_destination', to='river.State', verbose_name='Next State')), ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups')), ], options={ @@ -74,17 +126,29 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), ('priority', models.IntegerField(default=0, null=True, verbose_name='Priority')), - ('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_approval_meta_as_destination', to='river.State', verbose_name='Next State')), ('groups', models.ManyToManyField(blank=True, to='auth.Group', verbose_name='Groups')), ('parents', models.ManyToManyField(blank=True, db_index=True, related_name='children', to='river.TransitionApprovalMeta', verbose_name='parents')), ('permissions', models.ManyToManyField(blank=True, to='auth.Permission', verbose_name='Permissions')), - ('source_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_approval_meta_as_source', to='river.State', verbose_name='Source State')), ], options={ 'verbose_name': 'Transition Approval Meta', 'verbose_name_plural': 'Transition Approval Meta', }, ), + migrations.CreateModel( + name='TransitionMeta', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), + ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), + ('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_meta_as_destination', to='river.State', verbose_name='Destination State')), + ('source_state', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_meta_as_source', to='river.State', verbose_name='Source State')), + ], + options={ + 'verbose_name': 'Transition Meta', + 'verbose_name_plural': 'Transition Meta', + }, + ), migrations.CreateModel( name='Workflow', fields=[ @@ -92,23 +156,33 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), ('field_name', models.CharField(max_length=200, verbose_name='Field Name')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Content Type')), - ('initial_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workflow_this_set_as_initial_state', to='river.State', verbose_name='Initial State')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType', verbose_name='Content Type')), + ('initial_state', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='workflow_this_set_as_initial_state', to='river.State', verbose_name='Initial State')), ], options={ 'verbose_name': 'Workflow', 'verbose_name_plural': 'Workflows', }, ), + migrations.AddField( + model_name='transitionmeta', + name='workflow', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_metas', to='river.Workflow', verbose_name='Workflow'), + ), + migrations.AddField( + model_name='transitionapprovalmeta', + name='transition_meta', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approval_meta', to='river.TransitionMeta', verbose_name='Transition Meta'), + ), migrations.AddField( model_name='transitionapprovalmeta', name='workflow', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_approval_metas', to='river.Workflow', verbose_name='Workflow'), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approval_metas', to='river.Workflow', verbose_name='Workflow'), ), migrations.AddField( model_name='transitionapproval', name='meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_approvals', to='river.TransitionApprovalMeta', verbose_name='Meta'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_approvals', to='river.TransitionApprovalMeta', verbose_name='Meta'), ), migrations.AddField( model_name='transitionapproval', @@ -122,30 +196,91 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='transitionapproval', - name='skipped_from', - field=models.ManyToManyField(related_name='_transitionapproval_skipped_from_+', to='river.TransitionApproval', verbose_name='Skipped from'), + name='transactioner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Transactioner'), ), migrations.AddField( model_name='transitionapproval', - name='source_state', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_approvals_as_source', to='river.State', verbose_name='Source State'), + name='transition', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approvals', to='river.Transition', verbose_name='Transition'), ), migrations.AddField( model_name='transitionapproval', - name='transactioner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Transactioner'), + name='workflow', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approvals', to='river.Workflow', verbose_name='Workflow'), ), migrations.AddField( - model_name='transitionapproval', + model_name='transition', + name='meta', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transitions', to='river.TransitionMeta', verbose_name='Meta'), + ), + migrations.AddField( + model_name='transition', + name='source_state', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_as_source', to='river.State', verbose_name='Source State'), + ), + migrations.AddField( + model_name='transition', + name='workflow', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transitions', to='river.Workflow', verbose_name='Workflow'), + ), + migrations.AddField( + model_name='ontransithook', + name='transition', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_transit_hooks', to='river.Transition', verbose_name='Transition'), + ), + migrations.AddField( + model_name='ontransithook', + name='transition_meta', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_transit_hooks', to='river.TransitionMeta', verbose_name='Transition Meta'), + ), + migrations.AddField( + model_name='ontransithook', + name='workflow', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_ontransithook_hooks', to='river.Workflow', verbose_name='Workflow'), + ), + migrations.AddField( + model_name='oncompletehook', name='workflow', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_approvals', to='river.Workflow', verbose_name='Workflow'), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_oncompletehook_hooks', to='river.Workflow', verbose_name='Workflow'), + ), + migrations.AddField( + model_name='onapprovedhook', + name='transition_approval', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_approved_hooks', to='river.TransitionApproval', verbose_name='Transition Approval'), + ), + migrations.AddField( + model_name='onapprovedhook', + name='transition_approval_meta', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_approved_hooks', to='river.TransitionApprovalMeta', verbose_name='Transition Approval Meta'), + ), + migrations.AddField( + model_name='onapprovedhook', + name='workflow', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_onapprovedhook_hooks', to='river.Workflow', verbose_name='Workflow'), ), migrations.AlterUniqueTogether( name='workflow', - unique_together={('content_type', 'field_name')}, + unique_together=set([('content_type', 'field_name')]), + ), + migrations.AlterUniqueTogether( + name='transitionmeta', + unique_together=set([('workflow', 'source_state', 'destination_state')]), ), migrations.AlterUniqueTogether( name='transitionapprovalmeta', - unique_together={('workflow', 'source_state', 'destination_state', 'priority')}, + unique_together=set([('workflow', 'transition_meta', 'priority')]), + ), + migrations.AlterUniqueTogether( + name='ontransithook', + unique_together=set([('callback_function', 'workflow', 'transition_meta', 'content_type', 'object_id', 'transition')]), + ), + migrations.AlterUniqueTogether( + name='oncompletehook', + unique_together=set([('callback_function', 'workflow', 'content_type', 'object_id')]), + ), + migrations.AlterUniqueTogether( + name='onapprovedhook', + unique_together=set([('callback_function', 'workflow', 'transition_approval_meta', 'content_type', 'object_id', 'transition_approval')]), ), ] diff --git a/river/migrations/0002_auto_20190920_1640.py b/river/migrations/0002_auto_20190920_1640.py deleted file mode 100644 index 7d57fdc..0000000 --- a/river/migrations/0002_auto_20190920_1640.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 2.1.4 on 2019-09-20 21:40 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('river', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='transitionapproval', - name='destination_state', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approvals_as_destination', to='river.State', verbose_name='Next State'), - ), - migrations.AlterField( - model_name='transitionapproval', - name='meta', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_approvals', to='river.TransitionApprovalMeta', verbose_name='Meta'), - ), - migrations.AlterField( - model_name='transitionapproval', - name='source_state', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approvals_as_source', to='river.State', verbose_name='Source State'), - ), - migrations.AlterField( - model_name='transitionapproval', - name='transactioner', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Transactioner'), - ), - migrations.AlterField( - model_name='transitionapproval', - name='workflow', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approvals', to='river.Workflow', verbose_name='Workflow'), - ), - ] diff --git a/river/migrations/0003_auto_20191015_1628.py b/river/migrations/0003_auto_20191015_1628.py deleted file mode 100644 index f84821f..0000000 --- a/river/migrations/0003_auto_20191015_1628.py +++ /dev/null @@ -1,86 +0,0 @@ -# Generated by Django 2.1.4 on 2019-10-15 21:28 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('river', '0002_auto_20190920_1640'), - ] - - operations = [ - migrations.CreateModel( - name='Function', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), - ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), - ('name', models.CharField(max_length=200, unique=True, verbose_name='Function Name')), - ('body', models.TextField(max_length=100000, verbose_name='Function Body')), - ('version', models.IntegerField(default=0, verbose_name='Function Version')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='OnApprovedHook', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), - ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), - ('object_id', models.PositiveIntegerField(blank=True, null=True)), - ('hook_type', models.CharField(choices=[('BEFORE', 'Before'), ('AFTER', 'After')], max_length=50, verbose_name='Status')), - ('callback_function', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_onapprovedhook_hooks', to='river.Function', verbose_name='Function')), - ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.ContentType')), - ('transition_approval_meta', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_approved_hooks', to='river.TransitionApprovalMeta', verbose_name='Transition Approval')), - ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_onapprovedhook_hooks', to='river.Workflow', verbose_name='Workflow')), - ], - ), - migrations.CreateModel( - name='OnCompleteHook', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), - ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), - ('object_id', models.PositiveIntegerField(blank=True, null=True)), - ('hook_type', models.CharField(choices=[('BEFORE', 'Before'), ('AFTER', 'After')], max_length=50, verbose_name='Status')), - ('callback_function', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_oncompletehook_hooks', to='river.Function', verbose_name='Function')), - ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.ContentType')), - ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_oncompletehook_hooks', to='river.Workflow', verbose_name='Workflow')), - ], - ), - migrations.CreateModel( - name='OnTransitHook', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), - ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), - ('object_id', models.PositiveIntegerField(blank=True, null=True)), - ('hook_type', models.CharField(choices=[('BEFORE', 'Before'), ('AFTER', 'After')], max_length=50, verbose_name='Status')), - ('callback_function', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_ontransithook_hooks', to='river.Function', verbose_name='Function')), - ('content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.ContentType')), - ('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_transition_hook_as_destination', to='river.State', verbose_name='Next State')), - ('source_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_transition_hook_as_source', to='river.State', verbose_name='Source State')), - ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='river_ontransithook_hooks', to='river.Workflow', verbose_name='Workflow')), - ], - ), - migrations.DeleteModel( - name='Callback', - ), - migrations.AlterUniqueTogether( - name='ontransithook', - unique_together={('callback_function', 'workflow', 'source_state', 'destination_state', 'content_type', 'object_id')}, - ), - migrations.AlterUniqueTogether( - name='oncompletehook', - unique_together={('callback_function', 'workflow', 'content_type', 'object_id')}, - ), - migrations.AlterUniqueTogether( - name='onapprovedhook', - unique_together={('callback_function', 'workflow', 'transition_approval_meta', 'content_type', 'object_id')}, - ), - ] diff --git a/river/migrations/0004_auto_20191016_1731.py b/river/migrations/0004_auto_20191016_1731.py deleted file mode 100644 index 66c4cb2..0000000 --- a/river/migrations/0004_auto_20191016_1731.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1.4 on 2019-10-16 22:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('river', '0003_auto_20191015_1628'), - ] - - operations = [ - migrations.AlterField( - model_name='onapprovedhook', - name='hook_type', - field=models.CharField(choices=[('BEFORE', 'Before'), ('AFTER', 'After')], max_length=50, verbose_name='When?'), - ), - migrations.AlterField( - model_name='oncompletehook', - name='hook_type', - field=models.CharField(choices=[('BEFORE', 'Before'), ('AFTER', 'After')], max_length=50, verbose_name='When?'), - ), - migrations.AlterField( - model_name='ontransithook', - name='hook_type', - field=models.CharField(choices=[('BEFORE', 'Before'), ('AFTER', 'After')], max_length=50, verbose_name='When?'), - ), - ] diff --git a/river/migrations/0005_auto_20191020_1027.py b/river/migrations/0005_auto_20191020_1027.py deleted file mode 100644 index 6e98188..0000000 --- a/river/migrations/0005_auto_20191020_1027.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.24 on 2019-10-20 15:27 -from __future__ import unicode_literals - -from django.db import migrations, models -from django.db.models import Case, When, Value - - -def migrate_status(apps, schema_editor): - from river.models import PENDING, APPROVED - TransitionApproval = apps.get_model('river', 'TransitionApproval') - TransitionApproval.objects.update(status2=Case( - When(status=0, then=Value(PENDING)), - When(status=1, then=Value(APPROVED)) - )) - - -def reverse_migrate_status(apps, schema_editor): - from river.models import PENDING, APPROVED - TransitionApproval = apps.get_model('river', 'TransitionApproval') - TransitionApproval.objects.update(status=Case( - When(status2=PENDING, then=Value(0)), - When(status2=APPROVED, then=Value(1)), - )) - - -class Migration(migrations.Migration): - dependencies = [ - ('river', '0004_auto_20191016_1731'), - ] - - operations = [ - migrations.AddField( - model_name='transitionapproval', - name='status2', - field=models.CharField(choices=[('pending', 'Pending'), ('approved', 'Approved')], default='pending', max_length=100, verbose_name='Status'), - ), - migrations.RunPython(migrate_status, reverse_code=reverse_migrate_status), - migrations.RemoveField( - model_name='transitionapproval', - name='status', - ), - migrations.RenameField('transitionapproval', 'status2', 'status'), - ] diff --git a/river/migrations/0006_auto_20191020_1121.py b/river/migrations/0006_auto_20191020_1121.py deleted file mode 100644 index 63a77b5..0000000 --- a/river/migrations/0006_auto_20191020_1121.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.24 on 2019-10-20 16:21 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('river', '0005_auto_20191020_1027'), - ] - - operations = [ - migrations.AlterField( - model_name='transitionapproval', - name='status', - field=models.CharField(choices=[('pending', 'Pending'), ('approved', 'Approved'), ('cancelled', 'Cancelled')], default='pending', max_length=100, verbose_name='Status'), - ), - ] diff --git a/river/migrations/0007_transitionapproval_iteration.py b/river/migrations/0007_transitionapproval_iteration.py deleted file mode 100644 index d2a8cf3..0000000 --- a/river/migrations/0007_transitionapproval_iteration.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.24 on 2019-10-25 21:43 -from __future__ import unicode_literals - -from datetime import timedelta -from operator import itemgetter - -from django.db import migrations, models -from django.db.migrations import RunPython - -from river.models import APPROVED - - -def approval_key(approval): - return "%s-%s-%s" % (approval.priority, approval.source_state.pk, approval.destination_state.pk) - - -sql = """ -SELECT - ta.id, - ta.source_state_id, - ta.destination_state_id, - ta.transaction_date, - ta.status, - ( - select - count(*) - from river_transitionapproval ta_inner - where ta_inner.workflow_id=ta.workflow_id AND - ta_inner.object_id = ta.object_id AND - ta_inner.source_state_id=ta.source_state_id AND - ta_inner.destination_state_id=ta.destination_state_id AND - ta_inner.priority=ta.priority AND - ta_inner.date_created < ta.date_created - ) as rw_number -FROM river_transitionapproval ta -WHERE rw_number=%d AND ta.workflow_id='%s' and ta.object_id='%s'; -""" - - -def migrate_iteration(apps, schema_editor): - def _iterate(workflow, initial_state, workflow_object, last_approved_iteration=-1, generation=0): - with schema_editor.connection.cursor() as cursor: - output = cursor.execute(sql % (generation, workflow.pk, workflow_obj.pk)).fetchall() - if output: - output = [ - { - "pk": row[0], - "source_state_pk": row[1], - "destination_state_pk": row[2], - "approved_at": row[3], - "status": row[4] - } - for row in output - ] - - last_approved = None - approvals = list([approval for approval in output if approval["source_state_pk"] == initial_state.pk]) - iteration = last_approved_iteration + 1 - processed_source_states = set() - while approvals: - workflow.transition_approvals.filter(pk__in=[approval['pk'] for approval in approvals]).update(iteration=iteration) - destination_state_pks = [approval['destination_state_pk'] for approval in approvals] - last_approved = next(reversed(sorted(filter(lambda a: a['status'] == APPROVED, approvals), key=itemgetter("approved_at"))), None) - processed_source_states.update([approval['source_state_pk'] for approval in approvals]) - approvals = list([approval for approval in output if approval["source_state_pk"] in destination_state_pks and approval["source_state_pk"] not in processed_source_states]) - iteration += 1 - - if last_approved: - last_approved_object = workflow.transition_approvals.get(pk=last_approved["pk"]) - _iterate(workflow, last_approved_object.destination_state, workflow_object, last_approved_object.iteration, generation + 1) - - Workflow = apps.get_model('river', 'Workflow') - for workflow in Workflow.objects.all(): - model_class = apps.get_model(workflow.content_type.app_label, workflow.content_type.model) - for workflow_obj in model_class.objects.all(): - _iterate(workflow, workflow.initial_state, workflow_obj) - - -class Migration(migrations.Migration): - dependencies = [ - ('river', '0006_auto_20191020_1121'), - ] - - operations = [ - migrations.AddField( - model_name='transitionapproval', - name='iteration', - field=models.IntegerField(default=0, verbose_name='Priority'), - ), - migrations.RunPython(migrate_iteration, reverse_code=RunPython.noop), - ] diff --git a/river/migrations/0008_auto_20191109_1130.py b/river/migrations/0008_auto_20191109_1130.py deleted file mode 100644 index 26899dd..0000000 --- a/river/migrations/0008_auto_20191109_1130.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.25 on 2019-11-09 17:30 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('river', '0007_transitionapproval_iteration'), - ] - - operations = [ - migrations.AddField( - model_name='onapprovedhook', - name='transition_approval', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_approved_hooks', to='river.TransitionApproval', - verbose_name='Transition Approval'), - ), - migrations.AlterField( - model_name='onapprovedhook', - name='transition_approval_meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_approved_hooks', to='river.TransitionApprovalMeta', verbose_name='Transition Approval Meta'), - ), - migrations.AlterUniqueTogether( - name='onapprovedhook', - unique_together=set([('callback_function', 'workflow', 'transition_approval_meta', 'content_type', 'object_id', 'transition_approval')]), - ) - ] diff --git a/river/migrations/0009_auto_20191109_1806.py b/river/migrations/0009_auto_20191109_1806.py deleted file mode 100644 index 1f6dcd2..0000000 --- a/river/migrations/0009_auto_20191109_1806.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.25 on 2019-11-10 00:06 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('river', '0008_auto_20191109_1130'), - ] - - operations = [ - migrations.RemoveField( - model_name='transitionapproval', - name='enabled', - ), - migrations.RemoveField( - model_name='transitionapproval', - name='skipped', - ), - migrations.RemoveField( - model_name='transitionapproval', - name='skipped_from', - ), - ] diff --git a/river/migrations/0010_auto_20191110_1045.py b/river/migrations/0010_auto_20191110_1045.py deleted file mode 100644 index 0916803..0000000 --- a/river/migrations/0010_auto_20191110_1045.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.25 on 2019-11-10 15:33 -from __future__ import unicode_literals - -from uuid import uuid4 - -import django.db.models.deletion -from django.db import migrations, models - -from river.models import CANCELLED, APPROVED -from river.models.transition import DONE - - -def migrate_transition_meta(apps, schema_editor): - TransitionApprovalMeta = apps.get_model('river', 'TransitionApprovalMeta') - TransitionMeta = apps.get_model('river', 'TransitionMeta') - - for transition_approval_meta in TransitionApprovalMeta.objects.all(): - transition_meta, _ = TransitionMeta.objects.get_or_create( - workflow=transition_approval_meta.workflow, - source_state=transition_approval_meta.source_state, - destination_state=transition_approval_meta.destination_state - ) - - transition_approval_meta.transition_meta = transition_meta - transition_approval_meta.save() - - -def reverse_transition_meta_migration(apps, schema_editor): - TransitionApprovalMeta = apps.get_model('river', 'TransitionApprovalMeta') - TransitionMeta = apps.get_model('river', 'TransitionMeta') - - for transition_meta in TransitionMeta.objects.all(): - transition_meta.transition_approval_meta.all().update(source_state=transition_meta.source_state, destination_state=transition_meta.destination_state) - - TransitionApprovalMeta.objects.update(transition_meta=None) - TransitionMeta.objects.all().delete() - - -def migrate_transition(apps, schema_editor): - TransitionApproval = apps.get_model('river', 'TransitionApproval') - Transition = apps.get_model('river', 'Transition') - - for transition_approval in TransitionApproval.objects.all(): - transition, _ = Transition.objects.get_or_create( - workflow=transition_approval.workflow, - source_state=transition_approval.source_state, - destination_state=transition_approval.destination_state, - meta=transition_approval.meta.transition_meta, - object_id=transition_approval.object_id, - content_type=transition_approval.content_type, - iteration=transition_approval.iteration - ) - - transition_approval.transition = transition - transition_approval.save() - - for transition in Transition.objects.all(): - if len(list(filter(lambda approval: approval.status == CANCELLED, transition.transition_approvals.all()))) > 0: - transition.status = CANCELLED - transition.save() - - elif len(list(filter(lambda approval: approval.status == APPROVED, transition.transition_approvals.all()))) == len(transition.transition_approvals.all()): - transition.status = DONE - transition.save() - - -def reverse_transition_migration(apps, schema_editor): - TransitionApproval = apps.get_model('river', 'TransitionApproval') - Transition = apps.get_model('river', 'Transition') - - for transition in Transition.objects.all(): - transition.transition_approvals.all().update(source_state=transition.source_state, destination_state=transition.destination_state, iteration=transition.iteration) - - TransitionApproval.objects.update(transition=None) - Transition.objects.all().delete() - - -class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('river', '0009_auto_20191109_1806'), - ] - - operations = [ - migrations.CreateModel( - name='TransitionMeta', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), - ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), - ('source_state', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_meta_as_source', to='river.State', verbose_name='Source State')), - ( - 'destination_state', - models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_meta_as_destination', to='river.State', verbose_name='Destination State')), - ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transition_metas', to='river.Workflow', verbose_name='Workflow')), - ], - options={ - 'verbose_name': 'Transition Meta', - 'verbose_name_plural': 'Transition Meta', - 'unique_together': set([('workflow', 'source_state', 'destination_state')]), - }, - - ), - migrations.CreateModel( - name='Transition', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date Created')), - ('date_updated', models.DateTimeField(auto_now=True, null=True, verbose_name='Date Updated')), - ('object_id', models.CharField(max_length=50, verbose_name='Related Object')), - ('status', models.CharField(choices=[('pending', 'Pending'), ('done', 'Done')], default='pending', max_length=100, verbose_name='Status')), - ('iteration', models.IntegerField(default=0, verbose_name='Priority')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Content Type')), - ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transitions', to='river.Workflow', verbose_name='Workflow')), - ('meta', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transitions', to='river.TransitionMeta', verbose_name='Meta')), - ('source_state', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_as_source', to='river.State', verbose_name='Source State')), - ('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_as_destination', to='river.State', verbose_name='Destination State')), - ], - options={ - 'verbose_name': 'Transition', - 'verbose_name_plural': 'Transitions', - }, - ), - migrations.AddField( - model_name='transitionapprovalmeta', - name='transition_meta', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transition_approval_meta', to='river.TransitionMeta', - verbose_name='Transition Meta'), - ), - - migrations.AlterUniqueTogether( - name='transitionapprovalmeta', - unique_together=set([('workflow', 'transition_meta', 'priority')]), - ), - migrations.RunPython(migrate_transition_meta, reverse_code=reverse_transition_meta_migration), - - migrations.AlterField( - model_name='transitionapprovalmeta', - name='transition_meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approval_meta', to='river.TransitionMeta', verbose_name='Transition Meta'), - ), - - migrations.AlterField( - model_name='transitionapprovalmeta', - name='destination_state', - field=models.CharField(verbose_name='destination_state', max_length=200, default=uuid4), - preserve_default=True, - ), - - migrations.RemoveField( - model_name='transitionapprovalmeta', - name='destination_state', - ), - - migrations.AlterField( - model_name='transitionapprovalmeta', - name='source_state', - field=models.CharField(verbose_name='source_state', max_length=200, default=uuid4), - preserve_default=True, - ), - - migrations.RemoveField( - model_name='transitionapprovalmeta', - name='source_state', - ), - - migrations.AddField( - model_name='transitionapproval', - name='transition', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transition_approvals', to='river.Transition', verbose_name='Transition'), - ), - migrations.RunPython(migrate_transition, reverse_code=reverse_transition_migration), - - migrations.AlterField( - model_name='transitionapproval', - name='destination_state', - field=models.CharField(verbose_name='destination_state', max_length=200, default=uuid4), - preserve_default=True, - ), - - migrations.RemoveField( - model_name='transitionapproval', - name='destination_state', - ), - - migrations.AlterField( - model_name='transitionapproval', - name='iteration', - field=models.CharField(verbose_name='iteration', max_length=200, default=uuid4), - preserve_default=True, - ), - - migrations.RemoveField( - model_name='transitionapproval', - name='iteration', - ), - - migrations.AlterField( - model_name='transitionapproval', - name='source_state', - field=models.CharField(verbose_name='source_state', max_length=200, default=uuid4), - preserve_default=True, - ), - migrations.RemoveField( - model_name='transitionapproval', - name='source_state', - ), - migrations.AlterField( - model_name='transition', - name='status', - field=models.CharField(choices=[('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], default='pending', max_length=100, verbose_name='Status'), - ), - migrations.AlterField( - model_name='transitionapproval', - name='transition', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approvals', to='river.Transition', verbose_name='Transition'), - ), - migrations.AlterField( - model_name='transitionapprovalmeta', - name='workflow', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_approval_metas', to='river.Workflow', verbose_name='Workflow'), - ), - migrations.AlterField( - model_name='transitionmeta', - name='workflow', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transition_metas', to='river.Workflow', verbose_name='Workflow'), - ), - ] diff --git a/river/migrations/0011_auto_20191110_1411.py b/river/migrations/0011_auto_20191110_1411.py deleted file mode 100644 index dad915d..0000000 --- a/river/migrations/0011_auto_20191110_1411.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.25 on 2019-11-10 20:11 -from __future__ import unicode_literals - -from uuid import uuid4 - -import django.db.models.deletion -from django.db import migrations, models - - -def migrate_transition_hook(apps, schema_editor): - OnTransitHook = apps.get_model('river', 'OnTransitHook') - Transition = apps.get_model('river', 'Transition') - TransitionMeta = apps.get_model('river', 'TransitionMeta') - - for on_transition_hook in OnTransitHook.objects.all(): - if on_transition_hook.object_id: - transition = Transition.objects.filter( - workflow=on_transition_hook.workflow, - source_state=on_transition_hook.source_state, - destination_state=on_transition_hook.destination_state, - object_id=on_transition_hook.object_id, - ) - if on_transition_hook.iteration: - transition = transition.include(iteration=on_transition_hook.iteration) - - transition = transition.first() - if transition: - on_transition_hook.transition_meta = transition.meta - on_transition_hook.transition = transition - on_transition_hook.save() - - - else: - transition_meta = TransitionMeta.objects.filter( - workflow=on_transition_hook.workflow, - source_state=on_transition_hook.source_state, - destination_state=on_transition_hook.destination_state, - - ).first() - if transition_meta: - on_transition_hook.transition_meta = transition_meta - on_transition_hook.save() - - -def reverse_transition_hook_migration(apps, schema_editor): - OnTransitHook = apps.get_model('river', 'OnTransitHook') - - for on_transition_hook in OnTransitHook.objects.all(): - on_transition_hook.source_state = on_transition_hook.transition_meta.source_state - on_transition_hook.destination_state = on_transition_hook.transition_meta.destination_state - - if on_transition_hook.transition: - on_transition_hook.iteration = on_transition_hook.iteration - - -class Migration(migrations.Migration): - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('river', '0010_auto_20191110_1045'), - ] - - operations = [ - migrations.AddField( - model_name='ontransithook', - name='iteration', - field=models.IntegerField(blank=True, default=0, null=True, verbose_name='Priority'), - ), - migrations.AddField( - model_name='ontransithook', - name='transition', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_transit_hooks', to='river.Transition', verbose_name='Transition'), - ), - migrations.AddField( - model_name='ontransithook', - name='transition_meta', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_transit_hooks', to='river.TransitionMeta', verbose_name='Transition Meta'), - ), - migrations.AlterField( - model_name='ontransithook', - name='destination_state', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_transition_hook_as_destination', to='river.State', verbose_name='Next State'), - ), - migrations.AlterField( - model_name='ontransithook', - name='source_state', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_transition_hook_as_source', to='river.State', verbose_name='Source State'), - ), - migrations.AlterUniqueTogether( - name='ontransithook', - unique_together=set([('callback_function', 'workflow', 'transition_meta', 'content_type', 'object_id', 'transition')]), - ), - - migrations.RunPython(migrate_transition_hook, reverse_code=reverse_transition_hook_migration), - - migrations.AlterField( - model_name='ontransithook', - name='destination_state', - field=models.CharField(verbose_name='destination_state', max_length=200, default=uuid4), - preserve_default=True, - ), - - migrations.RemoveField( - model_name='ontransithook', - name='destination_state', - ), - - migrations.AlterField( - model_name='ontransithook', - name='iteration', - field=models.CharField(verbose_name='iteration', max_length=200, default=uuid4), - preserve_default=True, - ), - - migrations.RemoveField( - model_name='ontransithook', - name='iteration', - ), - - migrations.AlterField( - model_name='ontransithook', - name='source_state', - field=models.CharField(verbose_name='source_state', max_length=200, default=uuid4), - preserve_default=True, - ), - - migrations.RemoveField( - model_name='ontransithook', - name='source_state', - ), - - migrations.AlterField( - model_name='ontransithook', - name='transition_meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_transit_hooks', to='river.TransitionMeta', verbose_name='Transition Meta'), - ), - - migrations.AlterField( - model_name='onapprovedhook', - name='transition_approval', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='on_approved_hooks', to='river.TransitionApproval', - verbose_name='Transition Approval'), - ), - migrations.AlterField( - model_name='onapprovedhook', - name='transition_approval_meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='on_approved_hooks', to='river.TransitionApprovalMeta', verbose_name='Transition Approval Meta'), - ), - migrations.AlterField( - model_name='ontransithook', - name='transition', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='on_transit_hooks', to='river.Transition', verbose_name='Transition'), - ), - migrations.AlterField( - model_name='ontransithook', - name='transition_meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='on_transit_hooks', to='river.TransitionMeta', verbose_name='Transition Meta'), - ), - ] diff --git a/river/migrations/0012_auto_20191113_1550.py b/river/migrations/0012_auto_20191113_1550.py deleted file mode 100644 index 67ff40c..0000000 --- a/river/migrations/0012_auto_20191113_1550.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.25 on 2019-11-13 21:50 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - dependencies = [ - ('river', '0011_auto_20191110_1411'), - ] - - operations = [ - migrations.AlterField( - model_name='onapprovedhook', - name='transition_approval', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_approved_hooks', to='river.TransitionApproval', - verbose_name='Transition Approval'), - ), - migrations.AlterField( - model_name='onapprovedhook', - name='transition_approval_meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_approved_hooks', to='river.TransitionApprovalMeta', verbose_name='Transition Approval Meta'), - ), - migrations.AlterField( - model_name='ontransithook', - name='transition', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='on_transit_hooks', to='river.Transition', verbose_name='Transition'), - ), - migrations.AlterField( - model_name='ontransithook', - name='transition_meta', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='on_transit_hooks', to='river.TransitionMeta', verbose_name='Transition Meta'), - ), - migrations.AlterField( - model_name='workflow', - name='content_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType', verbose_name='Content Type'), - ), - migrations.AlterField( - model_name='workflow', - name='initial_state', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='workflow_this_set_as_initial_state', to='river.State', verbose_name='Initial State'), - ), - ] diff --git a/river/migrations/0013_auto_20191214_0742.py b/river/migrations/0013_auto_20191214_0742.py deleted file mode 100644 index e40f8d2..0000000 --- a/river/migrations/0013_auto_20191214_0742.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-14 13:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('river', '0012_auto_20191113_1550'), - ] - - operations = [ - migrations.AlterField( - model_name='transition', - name='status', - field=models.CharField(choices=[('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done'), ('jumped', 'Jumped')], default='pending', max_length=100, verbose_name='Status'), - ), - migrations.AlterField( - model_name='transitionapproval', - name='status', - field=models.CharField(choices=[('pending', 'Pending'), ('approved', 'Approved'), ('cancelled', 'Cancelled'), ('jumped', 'Jumped')], default='pending', max_length=100, verbose_name='Status'), - ), - ] diff --git a/river/migrations/0014_auto_20200128_0558.py b/river/migrations/0014_auto_20200128_0558.py deleted file mode 100644 index 657f0a1..0000000 --- a/river/migrations/0014_auto_20200128_0558.py +++ /dev/null @@ -1,96 +0,0 @@ -from django.db import migrations, models -from django.db.models import F, PositiveIntegerField -from django.db.models.functions import Cast - - -def backup_values(apps, schema_editor): - def backup_hook(hook_cls): - hook_cls.objects.update(object_id2=F("object_id")) - hook_cls.objects.update(object_id=None) - - backup_hook(apps.get_model("river", "OnApprovedHook")) - backup_hook(apps.get_model("river", "OnCompleteHook")) - backup_hook(apps.get_model("river", "OnTransitHook")) - - -def revert_backup_values(apps, schema_editor): - def revert_backup_hook(hook_cls): - hook_cls.objects.update(object_id=Cast(F("object_id2"), output_field=PositiveIntegerField())) - hook_cls.objects.update(object_id2=None) - - revert_backup_hook(apps.get_model("river", "OnApprovedHook")) - revert_backup_hook(apps.get_model("river", "OnCompleteHook")) - revert_backup_hook(apps.get_model("river", "OnTransitHook")) - - -def restore_values(apps, schema_editor): - def restore_hook(hook_cls): - hook_cls.objects.update(object_id=F("object_id2")) - - restore_hook(apps.get_model("river", "OnApprovedHook")) - restore_hook(apps.get_model("river", "OnCompleteHook")) - restore_hook(apps.get_model("river", "OnTransitHook")) - - -def revert_restore_values(apps, schema_editor): - def revert_restore_hook(hook_cls): - hook_cls.objects.update(object_id2=F("object_id")) - - revert_restore_hook(apps.get_model("river", "OnApprovedHook")) - revert_restore_hook(apps.get_model("river", "OnCompleteHook")) - revert_restore_hook(apps.get_model("river", "OnTransitHook")) - - -class Migration(migrations.Migration): - dependencies = [ - ('river', '0013_auto_20191214_0742'), - ] - - operations = [ - migrations.AddField( - model_name='onapprovedhook', - name='object_id2', - field=models.CharField(blank=True, max_length=200, null=True), - ), - migrations.AddField( - model_name='oncompletehook', - name='object_id2', - field=models.CharField(blank=True, max_length=200, null=True), - ), - migrations.AddField( - model_name='ontransithook', - name='object_id2', - field=models.CharField(blank=True, max_length=200, null=True), - ), - - migrations.RunPython(backup_values, reverse_code=revert_backup_values), - migrations.AlterField( - model_name='onapprovedhook', - name='object_id', - field=models.CharField(blank=True, max_length=200, null=True), - ), - migrations.AlterField( - model_name='oncompletehook', - name='object_id', - field=models.CharField(blank=True, max_length=200, null=True), - ), - migrations.AlterField( - model_name='ontransithook', - name='object_id', - field=models.CharField(blank=True, max_length=200, null=True), - ), - migrations.RunPython(restore_values, reverse_code=revert_restore_values), - - migrations.RemoveField( - model_name='onapprovedhook', - name='object_id2', - ), - migrations.RemoveField( - model_name='oncompletehook', - name='object_id2', - ), - migrations.RemoveField( - model_name='ontransithook', - name='object_id2', - ), - ] diff --git a/river/models/base_model.py b/river/models/base_model.py index 84526f3..c34f46b 100644 --- a/river/models/base_model.py +++ b/river/models/base_model.py @@ -3,10 +3,14 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from river.models.managers.rivermanager import RiverManager + AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') class BaseModel(models.Model): + objects = RiverManager() + date_created = models.DateTimeField(_('Date Created'), null=True, blank=True, auto_now_add=True) date_updated = models.DateTimeField(_('Date Updated'), null=True, blank=True, auto_now=True) diff --git a/river/models/factories.py b/river/models/factories.py index 42d4d11..5432734 100644 --- a/river/models/factories.py +++ b/river/models/factories.py @@ -7,8 +7,6 @@ from river.models.state import State from river.models.transitionapprovalmeta import TransitionApprovalMeta -__author__ = 'ahmetdal' - class ContentTypeObjectFactory(DjangoModelFactory): class Meta: diff --git a/river/models/fields/__init__.py b/river/models/fields/__init__.py index 0c9135a..8b13789 100644 --- a/river/models/fields/__init__.py +++ b/river/models/fields/__init__.py @@ -1 +1 @@ -__author__ = 'ahmetdal' + diff --git a/river/models/fields/state.py b/river/models/fields/state.py index 4781010..02c30ea 100644 --- a/river/models/fields/state.py +++ b/river/models/fields/state.py @@ -17,8 +17,6 @@ from river.models.transitionapproval import TransitionApproval from river.models.transition import Transition -__author__ = 'ahmetdal' - from django.db import models LOGGER = logging.getLogger(__name__) @@ -42,20 +40,21 @@ def __init__(self, *args, **kwargs): kwargs['related_name'] = "+" super(StateField, self).__init__(*args, **kwargs) - def contribute_to_class(self, cls, name): + def contribute_to_class(self, cls, name, *args, **kwargs): @classproperty def river(_self): return RiverObject(_self) self.field_name = name - self._add_to_class(cls, self.field_name + "_transition_approvals", GenericRelation('%s.%s' % (TransitionApproval._meta.app_label, TransitionApproval._meta.object_name))) + self._add_to_class(cls, self.field_name + "_transition_approvals", + GenericRelation('%s.%s' % (TransitionApproval._meta.app_label, TransitionApproval._meta.object_name))) self._add_to_class(cls, self.field_name + "_transitions", GenericRelation('%s.%s' % (Transition._meta.app_label, Transition._meta.object_name))) if id(cls) not in workflow_registry.workflows: self._add_to_class(cls, "river", river) - super(StateField, self).contribute_to_class(cls, name) + super(StateField, self).contribute_to_class(cls, name, *args, **kwargs) if id(cls) not in workflow_registry.workflows: post_save.connect(_on_workflow_object_saved, self.model, False, dispatch_uid='%s_%s_riverstatefield_post' % (self.model, name)) diff --git a/river/models/managers/__init__.py b/river/models/managers/__init__.py index 66bafcc..6fb66a5 100644 --- a/river/models/managers/__init__.py +++ b/river/models/managers/__init__.py @@ -1,4 +1,4 @@ -__author__ = 'ahmetdal' + diff --git a/river/models/managers/rivermanager.py b/river/models/managers/rivermanager.py new file mode 100644 index 0000000..f9c7734 --- /dev/null +++ b/river/models/managers/rivermanager.py @@ -0,0 +1,18 @@ + + +from django.db.models import QuerySet +from django.db.models.manager import BaseManager + +from river.config import app_config + + +class RiverQuerySet(QuerySet): + def first(self): + if app_config.IS_MSSQL: + return next(iter(self), None) + else: + return super(RiverQuerySet, self).first() + + +class RiverManager(BaseManager.from_queryset(RiverQuerySet)): + pass diff --git a/river/models/managers/state.py b/river/models/managers/state.py index 0ad78cb..d25f402 100644 --- a/river/models/managers/state.py +++ b/river/models/managers/state.py @@ -1,8 +1,6 @@ -from django.db import models +from river.models.managers.rivermanager import RiverManager -__author__ = 'ahmetdal' - -class StateManager(models.Manager): +class StateManager(RiverManager): def get_by_natural_key(self, slug): return self.get(slug=slug) diff --git a/river/models/managers/transitionapproval.py b/river/models/managers/transitionapproval.py index 7b7f084..269c2a4 100644 --- a/river/models/managers/transitionapproval.py +++ b/river/models/managers/transitionapproval.py @@ -1,11 +1,10 @@ from django_cte import CTEManager from river.config import app_config +from river.models.managers.rivermanager import RiverManager -__author__ = 'ahmetdal' - -class TransitionApprovalManager(CTEManager): +class TransitionApprovalManager(RiverManager if app_config.IS_MSSQL else CTEManager): def __init__(self, *args, **kwargs): super(TransitionApprovalManager, self).__init__(*args, **kwargs) diff --git a/river/models/managers/transitionmetada.py b/river/models/managers/transitionmetada.py index 4dae338..e601ff1 100644 --- a/river/models/managers/transitionmetada.py +++ b/river/models/managers/transitionmetada.py @@ -1,8 +1,6 @@ -from django.db import models +from river.models.managers.rivermanager import RiverManager -__author__ = 'ahmetdal' - -class TransitionApprovalMetadataManager(models.Manager): +class TransitionApprovalMetadataManager(RiverManager): def get_by_natural_key(self, workflow, source_state, destination_state, priority): return self.get(workflow=workflow, source_state=source_state, destination_state=destination_state, priority=priority) diff --git a/river/models/managers/workflowmetada.py b/river/models/managers/workflowmetada.py index e8e8dfb..6016a08 100644 --- a/river/models/managers/workflowmetada.py +++ b/river/models/managers/workflowmetada.py @@ -1,8 +1,6 @@ -from django.db import models +from river.models.managers.rivermanager import RiverManager -__author__ = 'ahmetdal' - -class WorkflowManager(models.Manager): +class WorkflowManager(RiverManager): def get_by_natural_key(self, content_type, field_name): return self.get(content_type=content_type, field_name=field_name) diff --git a/river/models/state.py b/river/models/state.py index 6a80614..12ade30 100644 --- a/river/models/state.py +++ b/river/models/state.py @@ -14,8 +14,6 @@ from river.models.base_model import BaseModel from river.models.managers.state import StateManager -__author__ = 'ahmetdal' - @python_2_unicode_compatible class State(BaseModel): diff --git a/river/models/transition.py b/river/models/transition.py index adf8237..b295273 100644 --- a/river/models/transition.py +++ b/river/models/transition.py @@ -16,8 +16,6 @@ from river.models.managers.transitionapproval import TransitionApprovalManager from river.config import app_config -__author__ = 'ahmetdal' - PENDING = "pending" CANCELLED = "cancelled" JUMPED = "jumped" diff --git a/river/models/transitionapproval.py b/river/models/transitionapproval.py index bca13e6..92964d3 100644 --- a/river/models/transitionapproval.py +++ b/river/models/transitionapproval.py @@ -3,7 +3,7 @@ from django.db.models import CASCADE, PROTECT, SET_NULL from mptt.fields import TreeOneToOneField -from river.models import State, TransitionApprovalMeta, Workflow +from river.models import TransitionApprovalMeta, Workflow from river.models.transition import Transition try: @@ -11,15 +11,13 @@ except ImportError: from django.contrib.contenttypes.generic import GenericForeignKey -from django.db import models, transaction +from django.db import models from django.utils.translation import ugettext_lazy as _ from river.models.base_model import BaseModel from river.models.managers.transitionapproval import TransitionApprovalManager from river.config import app_config -__author__ = 'ahmetdal' - PENDING = "pending" APPROVED = "approved" JUMPED = "jumped" diff --git a/river/models/transitionapprovalmeta.py b/river/models/transitionapprovalmeta.py index 13ce2bf..b1c2f74 100644 --- a/river/models/transitionapprovalmeta.py +++ b/river/models/transitionapprovalmeta.py @@ -11,8 +11,6 @@ from river.models.managers.transitionmetada import TransitionApprovalMetadataManager from river.models.transitionmeta import TransitionMeta -__author__ = 'ahmetdal' - class TransitionApprovalMeta(BaseModel): class Meta: diff --git a/river/models/transitionmeta.py b/river/models/transitionmeta.py index e217faf..224bdf9 100644 --- a/river/models/transitionmeta.py +++ b/river/models/transitionmeta.py @@ -7,8 +7,6 @@ from river.models import State, Workflow from river.models.base_model import BaseModel -__author__ = 'ahmetdal' - class TransitionMeta(BaseModel): class Meta: diff --git a/river/signals.py b/river/signals.py index a988f8f..bc1ff44 100644 --- a/river/signals.py +++ b/river/signals.py @@ -10,8 +10,6 @@ from river.models.on_complete_hook import OnCompleteHook from river.models.on_transit_hook import OnTransitHook -__author__ = 'ahmetdal' - pre_on_complete = Signal(providing_args=["workflow_object", "field_name", ]) post_on_complete = Signal(providing_args=["workflow_object", "field_name", ]) diff --git a/river/sql/mssql/get_available_approvals.sql b/river/sql/mssql/get_available_approvals.sql new file mode 100644 index 0000000..2819146 --- /dev/null +++ b/river/sql/mssql/get_available_approvals.sql @@ -0,0 +1,49 @@ +WITH approvals_with_min_priority (workflow_id, transition_id, object_id, min_priority) AS + ( + SELECT workflow_id, + transition_id, + object_id, + min(priority) as min_priority + FROM river.dbo.river_transitionapproval + WHERE workflow_id = '%(workflow_id)s' + AND status = 'PENDING' + group by workflow_id, transition_id, object_id + ), + authorized_approvals(id, workflow_id, transition_id, source_state_id, object_id, priority) AS + ( + SELECT ta.id, + ta.workflow_id, + ta.transition_id, + t.source_state_id, + ta.object_id, + ta.priority + FROM river.dbo.river_transitionapproval ta + INNER JOIN river.dbo.river_transition t on t.id = ta.transition_id + LEFT JOIN river.dbo.river_transitionapproval_permissions tap on tap.transitionapproval_id = ta.id + LEFT JOIN river.dbo.river_transitionapproval_groups tag on tag.transitionapproval_id = ta.id + WHERE ta.workflow_id = '%(workflow_id)s' + AND ta.status = 'PENDING' + AND (ta.transactioner_id is null or ta.transactioner_id = '%(transactioner_id)s') + AND (tap.id is null or tap.permission_id in ('%(permission_ids)s')) + AND (tag.id is null or tag.group_id in ('%(group_ids)s')) + ), + approvals_with_max_priority (id, object_id, source_state_id) AS + ( + SELECT aa.id, aa.object_id, aa.source_state_id + FROM approvals_with_min_priority awmp + INNER JOIN authorized_approvals aa + ON ( + aa.workflow_id = awmp.workflow_id + AND aa.transition_id = awmp.transition_id + AND aa.object_id = awmp.object_id + ) + + WHERE awmp.min_priority = aa.priority + ) +SELECT awmp.id +FROM approvals_with_max_priority awmp + INNER JOIN '%(workflow_object_table)s' wot + ON ( + wot.'%(object_pk_name)s' = awmp.object_id + AND awmp.source_state_id = wot.'%(field_name)s'_id + ) diff --git a/river/tests/__init__.py b/river/tests/__init__.py index bd5e647..139597f 100644 --- a/river/tests/__init__.py +++ b/river/tests/__init__.py @@ -1,2 +1,2 @@ -__author__ = 'ahmetdal' + diff --git a/river/tests/core/test__class_api.py b/river/tests/core/test__class_api.py index 3dbbcbe..d63e62b 100644 --- a/river/tests/core/test__class_api.py +++ b/river/tests/core/test__class_api.py @@ -5,7 +5,8 @@ from hamcrest import assert_that, equal_to, has_item, all_of, has_property, less_than, has_items, has_length from river.models import TransitionApproval -from river.models.factories import PermissionObjectFactory, UserObjectFactory, StateObjectFactory, TransitionApprovalMetaFactory, GroupObjectFactory, WorkflowFactory, TransitionMetaFactory +from river.models.factories import PermissionObjectFactory, UserObjectFactory, StateObjectFactory, TransitionApprovalMetaFactory, GroupObjectFactory, \ + WorkflowFactory, TransitionMetaFactory from river.tests.models import BasicTestModel from river.tests.models.factories import BasicTestModelObjectFactory diff --git a/river/tests/hooking/__init__.py b/river/tests/hooking/__init__.py index 0c9135a..8b13789 100644 --- a/river/tests/hooking/__init__.py +++ b/river/tests/hooking/__init__.py @@ -1 +1 @@ -__author__ = 'ahmetdal' + diff --git a/river/tests/hooking/test__approved_hooking.py b/river/tests/hooking/test__approved_hooking.py index 4c88360..3233b5a 100644 --- a/river/tests/hooking/test__approved_hooking.py +++ b/river/tests/hooking/test__approved_hooking.py @@ -2,14 +2,13 @@ from hamcrest import equal_to, assert_that, none, has_entry, all_of, has_key, has_length, is_not from river.models import TransitionApproval -from river.models.factories import PermissionObjectFactory, UserObjectFactory, StateObjectFactory, WorkflowFactory, TransitionApprovalMetaFactory, TransitionMetaFactory +from river.models.factories import PermissionObjectFactory, UserObjectFactory, StateObjectFactory, WorkflowFactory, TransitionApprovalMetaFactory, \ + TransitionMetaFactory from river.models.hook import BEFORE from river.tests.hooking.base_hooking_test import BaseHookingTest from river.tests.models import BasicTestModel from river.tests.models.factories import BasicTestModelObjectFactory -__author__ = 'ahmetdal' - # noinspection DuplicatedCode class ApprovedHooking(BaseHookingTest): diff --git a/river/tests/models/factories.py b/river/tests/models/factories.py index 8294092..f6e6e2b 100644 --- a/river/tests/models/factories.py +++ b/river/tests/models/factories.py @@ -1,7 +1,5 @@ from river.tests.models import BasicTestModel, ModelWithTwoStateFields -__author__ = 'ahmetdal' - class BasicTestModelObjectFactory(object): def __init__(self): diff --git a/river/tests/test__state_field.py b/river/tests/test__state_field.py index 6b6b0ce..238393f 100644 --- a/river/tests/test__state_field.py +++ b/river/tests/test__state_field.py @@ -10,8 +10,6 @@ from river.models.factories import StateObjectFactory, TransitionApprovalMetaFactory, WorkflowFactory, TransitionMetaFactory from river.tests.models import BasicTestModel -__author__ = 'ahmetdal' - # noinspection PyMethodMayBeStatic class StateFieldTest(TestCase): diff --git a/river/tests/tmigrations/test__migrations.py b/river/tests/tmigrations/test__migrations.py index 554925c..f925c77 100644 --- a/river/tests/tmigrations/test__migrations.py +++ b/river/tests/tmigrations/test__migrations.py @@ -1,7 +1,7 @@ import os import sys from datetime import datetime, timedelta -from unittest import skipUnless +from unittest import skipUnless, skip from uuid import uuid4 import django @@ -99,6 +99,7 @@ def test__shouldNotKeepRecreatingMigrationsWhenNoChange(self): assert_that(self.migrations_after, has_length(len(self.migrations_before))) @skipUnless(*MIGRATION_TEST_ENABLED) + @skip("Migrations are reset") def test__shouldMigrateTransitionApprovalStatusToStringInDB(self): out = StringIO() sys.stout = out @@ -143,6 +144,7 @@ def test__shouldMigrateTransitionApprovalStatusToStringInDB(self): assert_that(result[0][0], equal_to("pending")) @skipUnless(*MIGRATION_TEST_ENABLED) + @skip("Migrations are reset") def test__shouldAssessIterationsForExistingApprovals(self): out = StringIO() sys.stout = out @@ -228,6 +230,7 @@ def test__shouldAssessIterationsForExistingApprovals(self): assert_that(result, has_item(equal_to((meta_4.pk, 1)))) @skipUnless(*MIGRATION_TEST_ENABLED) + @skip("Migrations are reset") def test__shouldAssessIterationsForExistingApprovalsWhenThereIsCycle(self): out = StringIO() sys.stout = out @@ -363,6 +366,7 @@ def test__shouldAssessIterationsForExistingApprovalsWhenThereIsCycle(self): assert_that(result, has_item(equal_to((final_meta.pk, 5)))) @skipUnless(*MIGRATION_TEST_ENABLED) + @skip("Migrations are reset") def test__shouldMigrationForIterationMustFinishInShortAmountOfTimeWithTooManyObject(self): out = StringIO() sys.stout = out @@ -423,6 +427,7 @@ def test__shouldMigrationForIterationMustFinishInShortAmountOfTimeWithTooManyObj assert_that(after - before, less_than(timedelta(minutes=5))) @skipUnless(*MIGRATION_TEST_ENABLED) + @skip("Migrations are reset") def test__shouldAssessIterationsForExistingApprovalsWhenThereIsMoreAdvanceCycle(self): out = StringIO() sys.stout = out @@ -577,6 +582,7 @@ def test__shouldAssessIterationsForExistingApprovalsWhenThereIsMoreAdvanceCycle( assert_that(result, has_item(equal_to((closed_to_final.pk, 6)))) @skipUnless(*MIGRATION_TEST_ENABLED) + @skip("Migrations are reset") def test__shouldCreateTransitionsAndTransitionMetasOutOfApprovalMetaAndApprovals(self): out = StringIO() sys.stout = out @@ -676,6 +682,7 @@ def test__shouldCreateTransitionsAndTransitionMetasOutOfApprovalMetaAndApprovals assert_that(result, has_item(equal_to((meta_4.transition_approvals.first().pk, transition_3.transitions.first().pk)))) @skipUnless(*MIGRATION_TEST_ENABLED) + @skip("Migrations are reset") def test__shouldMigrateObjectIdInHooksByCastingItToString(self): out = StringIO() sys.stout = out diff --git a/river/utils/__init__.py b/river/utils/__init__.py index 0c9135a..8b13789 100644 --- a/river/utils/__init__.py +++ b/river/utils/__init__.py @@ -1 +1 @@ -__author__ = 'ahmetdal' + diff --git a/river/utils/error_code.py b/river/utils/error_code.py index 47696fe..96c8c48 100644 --- a/river/utils/error_code.py +++ b/river/utils/error_code.py @@ -1,6 +1,3 @@ -__author__ = 'ahmetdal' - - class ErrorCode(object): NO_AVAILABLE_INITIAL_STATE = 1 NO_AVAILABLE_FINAL_STATE = 2 diff --git a/river/utils/exceptions.py b/river/utils/exceptions.py index 52b4c0a..09095c6 100644 --- a/river/utils/exceptions.py +++ b/river/utils/exceptions.py @@ -1,6 +1,3 @@ -__author__ = 'ahmetdal' - - class RiverException(Exception): code = None diff --git a/settings/base.py b/settings/base.py index 492f2ad..5418717 100644 --- a/settings/base.py +++ b/settings/base.py @@ -3,7 +3,6 @@ import django -__author__ = 'ahmetdal' BASE_DIR = os.path.dirname(os.path.dirname(__file__)) DEBUG = True diff --git a/settings/with_mssql.py b/settings/with_mssql.py new file mode 100644 index 0000000..b7580ee --- /dev/null +++ b/settings/with_mssql.py @@ -0,0 +1,46 @@ +from time import sleep +from uuid import uuid4 + +import pyodbc + +from .base import * + +DB_DRIVER = 'ODBC Driver 17 for SQL Server' +DB_HOST = os.environ['MCR_MICROSOFT_COM_MSSQL_SERVER_HOST'] +DB_PORT = os.environ['MCR_MICROSOFT_COM_MSSQL_SERVER_1433_TCP'] +DB_USER = 'sa' +DB_PASSWORD = 'River@Credentials' +sleep(10) +db_connection = pyodbc.connect(f"DRIVER={DB_DRIVER};SERVER={DB_HOST},{DB_PORT};DATABASE=master;UID={DB_USER};PWD={DB_PASSWORD}", autocommit=True) +cursor = db_connection.cursor() +cursor.execute( + """ + If(db_id(N'river') IS NULL) + BEGIN + CREATE DATABASE river + END; + """) + +DATABASES = { + 'default': { + 'ENGINE': 'sql_server.pyodbc', + 'NAME': 'river', + 'USER': DB_USER, + 'PASSWORD': DB_PASSWORD, + 'HOST': DB_HOST, + 'PORT': DB_PORT, + 'TEST': { + 'NAME': 'river' + str(uuid4()), + }, + 'OPTIONS': { + 'driver': DB_DRIVER + }, + } +} + +INSTALLED_APPS += ( + 'river.tests', +) + +if django.get_version() >= '1.9.0': + MIGRATION_MODULES = DisableMigrations() diff --git a/setup.py b/setup.py index 6fa410a..be623da 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,6 @@ import os import sys -__author__ = 'ahmetdal' - from setuptools import setup, find_packages readme_file = os.path.join(os.path.dirname(__file__), 'README.rst') @@ -15,7 +13,7 @@ setup( name='django-river', - version='3.1.4', + version='3.2.0', author='Ahmet DAL', author_email='ceahmetdal@gmail.com', packages=find_packages(), diff --git a/test_urls.py b/test_urls.py index d125365..ede2ec9 100644 --- a/test_urls.py +++ b/test_urls.py @@ -1,4 +1,3 @@ -__author__ = 'ahmetdal' from django.conf.urls import url from django.contrib import admin diff --git a/tox.ini b/tox.ini index a255641..a99a0ce 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = {py27,py34}-{dj1.11}-{sqlite3}, {py36}-{dj1.11,dj2.0,dj2.1,dj2.2,dj3.0}-{sqlite3}, {py36}-{dj2.2}-{postgresql9,postgresql10,postgresql11,postgresql12}, {py36}-{dj2.2}-{mysql8.0}, + {py36}-{dj2.2}-{msqsql17,mssql19}, cov, [testenv] @@ -12,7 +13,9 @@ docker = postgresql10: postgres:10-alpine postgresql11: postgres:11-alpine postgresql12: postgres:12-alpine - mysql8.0: mysql:8.0 + mysql8.0: mysql:8.0.18 + mssql17: mcr.microsoft.com/mssql/server:2017-latest + mssql19: mcr.microsoft.com/mssql/server:2019-latest dockerenv = POSTGRES_USER=river POSTGRES_PASSWORD=river @@ -21,10 +24,14 @@ dockerenv = MYSQL_USER=river MYSQL_PASSWORD=river MYSQL_DATABASE=river + MSSQL_PID=Express + SA_PASSWORD=River@Credentials + ACCEPT_EULA=y setenv = sqlite3: DJANGO_SETTINGS_MODULE=settings.with_sqlite3 postgresql9,postgresql10,postgresql11,postgresql12: DJANGO_SETTINGS_MODULE=settings.with_postgresql mysql8.0: DJANGO_SETTINGS_MODULE=settings.with_mysql + mssql17,mssql19: DJANGO_SETTINGS_MODULE=settings.with_mssql deps = pytest-django>3.1.2 pytest-cov @@ -36,6 +43,7 @@ deps = dj3.0: Django>=3.0,<3.1.0 postgresql9,postgresql10,postgresql11,postgresql12: psycopg2 mysql8.0: mysqlclient + mssql17,mssql19: django-mssql-backend commands = py.test --junitxml=../junit-{envname}.xml python manage.py behave @@ -50,9 +58,23 @@ deps = commands = py.test --ds='settings.with_sqlite3' --cov ./ --cov-report term-missing -[docker:mysql:8.0] +[docker:mysql:8.0.18] healthcheck_cmd = 'mysqladmin ping --silent -u root --password=river' healthcheck_interval = 10 healthcheck_start_period = 5 healthcheck_retries = 30 -healthcheck_timeout = 10 \ No newline at end of file +healthcheck_timeout = 10 + +[docker:mcr.microsoft.com/mssql/server:2017-latest] +healthcheck_cmd = '/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P River@Credentials -Q "SELECT 1" || exit 1' +healthcheck_interval = 10 +healthcheck_start_period = 10 +healthcheck_retries = 10 +healthcheck_timeout = 3 + +[docker:mcr.microsoft.com/mssql/server:2019-latest] +healthcheck_cmd = '/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P River@Credentials -Q "SELECT 1" || exit 1' +healthcheck_interval = 10 +healthcheck_start_period = 10 +healthcheck_retries = 10 +healthcheck_timeout = 3 \ No newline at end of file