diff --git a/Dockerfile b/Dockerfile index 888f8e778d2b3e87ff07359a72a9d10030e51232..366bebc2564c2ed5ae6741e1287dd03872b14958 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM registry.gitlab.com/arkindex/backend/base:python-3.7 as build +FROM registry.gitlab.com/arkindex/backend/base:django-3.1 as build RUN mkdir build ADD . build RUN cd build && python3 setup.py sdist -FROM registry.gitlab.com/arkindex/backend/base:latest +FROM registry.gitlab.com/arkindex/backend/base:django-3.1 ARG COMMON_BRANCH=master ARG COMMON_ID=9855787 diff --git a/Dockerfile.binary b/Dockerfile.binary index 3f38d8ee5a57c9b4ce4a4d59be44d22a9abc18f8..e072406b890fd96d28f2cc8ad6b437c1d7c1194b 100644 --- a/Dockerfile.binary +++ b/Dockerfile.binary @@ -57,7 +57,7 @@ RUN python -m nuitka \ arkindex/manage.py # Start over from a clean setup -FROM registry.gitlab.com/arkindex/backend/base:python-3.7 as build +FROM registry.gitlab.com/arkindex/backend/base:django-3.1 as build # Import files from compilation RUN mkdir /usr/share/arkindex diff --git a/arkindex/dataimport/migrations/0016_new_jsonfield.py b/arkindex/dataimport/migrations/0016_new_jsonfield.py new file mode 100644 index 0000000000000000000000000000000000000000..d5a2ac741ae5275476071a8dfcb4411868a7ee8a --- /dev/null +++ b/arkindex/dataimport/migrations/0016_new_jsonfield.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-08-10 14:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dataimport', '0015_clear_payload'), + ] + + operations = [ + migrations.AlterField( + model_name='workerversion', + name='configuration', + field=models.JSONField(), + ), + ] diff --git a/arkindex/dataimport/models.py b/arkindex/dataimport/models.py index 7509378e1f6851362f5e1a549afe7c29c45de842..7111c0a6cb7cd40a20a4f536218c9265369bf40d 100644 --- a/arkindex/dataimport/models.py +++ b/arkindex/dataimport/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.contrib.postgres.fields import JSONField from django.conf import settings from django.utils.functional import cached_property from rest_framework.exceptions import ValidationError @@ -392,7 +391,7 @@ class WorkerVersion(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) worker = models.ForeignKey('dataimport.Worker', on_delete=models.CASCADE) revision = models.ForeignKey('dataimport.Revision', on_delete=models.CASCADE, related_name='versions') - configuration = JSONField() + configuration = models.JSONField() docker_image = models.ForeignKey(Artifact, on_delete=models.CASCADE, null=True) state = EnumField(WorkerVersionState, default=WorkerVersionState.Created) diff --git a/arkindex/dataimport/serializers/workers.py b/arkindex/dataimport/serializers/workers.py index 32035d80b2833b37633ab6c3154ee178978ecd9e..cc5eb30dd5cc7f1f34aba8d5249f80de31cd9e73 100644 --- a/arkindex/dataimport/serializers/workers.py +++ b/arkindex/dataimport/serializers/workers.py @@ -27,6 +27,9 @@ class WorkerVersionSerializer(serializers.ModelSerializer): """ state = EnumField(WorkerVersionState, required=False) worker = WorkerSerializer(read_only=True) + # ModelSerializer does not yet support Django 3.1's JSONField + # https://github.com/encode/django-rest-framework/pull/7467 + configuration = serializers.JSONField() class Meta: model = WorkerVersion diff --git a/arkindex/documents/api/entities.py b/arkindex/documents/api/entities.py index 89377c11df86083c43254fe3b0db05390836c24e..508d451bdb05adf7f8c739b5a0e512723d921c36 100644 --- a/arkindex/documents/api/entities.py +++ b/arkindex/documents/api/entities.py @@ -107,15 +107,17 @@ class EntityElements(ListAPIView): .filter( corpus__in=Corpus.objects.readable(self.request.user), metadatas__entity_id=pk - ).select_related('type') + ) \ + .select_related('type') \ + .prefetch_related('metadatas__entity', 'metadatas__revision', 'corpus') transcription_elements = Element.objects \ .filter( corpus__in=Corpus.objects.readable(self.request.user), transcriptions__transcription_entities__entity_id=pk - ).select_related('type') - return metadata_elements.union(transcription_elements) \ - .order_by('name', 'type') \ + ).select_related('type') \ .prefetch_related('metadatas__entity', 'metadatas__revision', 'corpus') + return metadata_elements.union(transcription_elements) \ + .order_by('name', 'type') class EntityCreate(CreateAPIView): diff --git a/arkindex/documents/api/ml.py b/arkindex/documents/api/ml.py index 9879819b3bf98e31c700cf17b2a0a3589aad7a98..dad743131f84445b85659cbb209ad52b5b43eb58 100644 --- a/arkindex/documents/api/ml.py +++ b/arkindex/documents/api/ml.py @@ -381,7 +381,8 @@ class CorpusMLClassList(CorpusACLMixin, ListAPIView): .annotate(nb_best=Count( 'classifications', filter=best_classification_filter, - )) + )) \ + .order_by('name') class MLClassList(ListAPIView): diff --git a/arkindex/documents/tests/consumers/test_ml_results_consumer.py b/arkindex/documents/tests/consumers/test_ml_results_consumer.py index 25feab0a4a254746c3a8a6dd622590a90ac3deb3..35a0844c1beb6953069e8ce4f97d30ca18f86dff 100644 --- a/arkindex/documents/tests/consumers/test_ml_results_consumer.py +++ b/arkindex/documents/tests/consumers/test_ml_results_consumer.py @@ -94,7 +94,7 @@ class TestMLResultsConsumer(FixtureTestCase): self.assertEqual(self.page1.metadatas.count(), 2) self.assertEqual(self.page2.metadatas.count(), 2) - with self.assertNumQueries(14): + with self.assertNumQueries(13): MLResultsConsumer({}).ml_results_delete({'corpus_id': str(self.corpus.id)}) for queryset in querysets: @@ -144,7 +144,7 @@ class TestMLResultsConsumer(FixtureTestCase): for queryset in folder2_querysets: self.assertTrue(queryset.exists()) - with self.assertNumQueries(15): + with self.assertNumQueries(14): MLResultsConsumer({}).ml_results_delete({'element_id': str(self.folder1.id)}) for queryset in folder1_querysets: diff --git a/arkindex/project/config.py b/arkindex/project/config.py index 39f05889d9814dfa036d440b66108071954e0b4f..d09d1fe2e2a6c0317e0d49945f4d2c74e588d9b5 100644 --- a/arkindex/project/config.py +++ b/arkindex/project/config.py @@ -12,11 +12,19 @@ class CacheType(Enum): class CookieSameSiteOption(Enum): + """ + Options for the SameSite flag on a cookie. Django accepts Lax, Strict, None and False. + None is a 'None' string, not Python's None, and disables the SameSite protection. + This can cause warnings when the Secure flag is active. + False removes the flag entirely from the cookie, leaving the decision up to the browser, which can cause warnings. + + https://docs.djangoproject.com/en/3.1/ref/settings/#std:setting-SESSION_COOKIE_SAMESITE + """ Lax = 'lax' Strict = 'strict' # Cannot redefine Python's None! - # Django needs a real None here to disable the check - None_ = None + None_ = 'none' + Disabled = False def get_settings_parser(base_dir): @@ -86,9 +94,8 @@ def get_settings_parser(base_dir): cors_parser = parser.add_subparser('cors', default={}) cors_parser.add_option('origin_whitelist', type=str, many=True, default=[ - 'universalviewer.io', # TODO: Remove this one? - 'localhost:8080', - '127.0.0.1:8080', + 'http://localhost:8080', + 'http://127.0.0.1:8080', ]) cors_parser.add_option('suffixes', type=str, many=True, default=[]) diff --git a/arkindex/project/polygon.py b/arkindex/project/polygon.py index 4e13efb422be3caa2409291f8a9202f734b1c4cc..89b6a332b29a8453eba62c3fc25465b06af83775 100644 --- a/arkindex/project/polygon.py +++ b/arkindex/project/polygon.py @@ -14,7 +14,10 @@ class Point(namedtuple('Point', ['x', 'y'])): """ __slots__ = () - def __new__(cls, x, y): + def __new__(cls, x, y=None): + # Allow both Point(1, 2) and Point(tuple(1, 2)) for Django 3 compatibility + if isinstance(x, Iterable): + x, y = x return super().__new__(cls, int(x), int(y)) def __str__(self): diff --git a/arkindex/project/tests/config_samples/defaults.yaml b/arkindex/project/tests/config_samples/defaults.yaml index 1c698d6d91d2f7c623557487bcfbacbe1c4b69a5..31bb76bfe6624259011e0a26652af00b5d1f9433 100644 --- a/arkindex/project/tests/config_samples/defaults.yaml +++ b/arkindex/project/tests/config_samples/defaults.yaml @@ -9,9 +9,8 @@ cache: url: null cors: origin_whitelist: - - universalviewer.io - - localhost:8080 - - 127.0.0.1:8080 + - http://localhost:8080 + - http://127.0.0.1:8080 suffixes: [] csrf: cookie_domain: null diff --git a/arkindex/project/tests/config_samples/override.yaml b/arkindex/project/tests/config_samples/override.yaml index de8575efe8d362fe67b8f2e6f4c9994505fa4a79..96d3482c2b54de763121ae1d54f99495f2417448 100644 --- a/arkindex/project/tests/config_samples/override.yaml +++ b/arkindex/project/tests/config_samples/override.yaml @@ -81,7 +81,7 @@ sentry: session: cookie_domain: cookie-dolmen cookie_name: stonehenge - cookie_samesite: null + cookie_samesite: false static: cdn_assets_url: http://cdn.teklia.horse/ frontend_version: 1.2.3-alpha4 diff --git a/arkindex/project/tests/test_config.py b/arkindex/project/tests/test_config.py index b6f816c9d8d37af1cfce41903c53a659a9115f62..355f507446c50c7916b3e00ab8c10fec4d511013 100644 --- a/arkindex/project/tests/test_config.py +++ b/arkindex/project/tests/test_config.py @@ -22,11 +22,11 @@ class TestConfig(TestCase): def str_representer(self, data): if isinstance(data, Enum): data = data.value - else: - data = str(data) if data is None: return self.represent_none(data) - return self.represent_str(data) + elif isinstance(data, (bool, int, float, bytes, str)): + return self.represent_data(data) + return self.represent_str(str(data)) dumper.add_representer(None, str_representer) dumper.ignore_aliases = lambda *args: True diff --git a/base/requirements.txt b/base/requirements.txt index 8f79c4baeebbf30252a8184d52ba660f8632160b..fbbd84f820f8e112e7fe5f061f4cc14bd343f103 100644 --- a/base/requirements.txt +++ b/base/requirements.txt @@ -1,6 +1,6 @@ boto3==1.9 cryptography>=2.8 -Django==2.2.13 +Django==3.1 elasticsearch==6.2.0 hiredis==1.0.0 ijson==2.3 diff --git a/requirements.txt b/requirements.txt index b2807536cb3a5926cae4fc961abd801e1b0054a8..0dd9cae66ef4e8959bceae20f0ffcd200629cef3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,12 +8,12 @@ certifi==2017.7.27.1 channels==2.3.1 channels-redis==2.4.1 chardet==3.0.4 -django-admin-hstore-widget==1.0.1 +django-admin-hstore-widget==1.1.0 django-cachalot==2.2.2 -django-cors-headers==2.4.0 -django-enumfields==1.0.0 +django-cors-headers==3.4.0 +django-enumfields==2.0.0 django-redis==4.12.1 -djangorestframework==3.11.0 +djangorestframework==3.11.1 elasticsearch-dsl>=6.0.0,<7.0.0 gitpython==3.0.8 idna==2.6