From f9a820066e16ba09a1939174d49df88211c19d75 Mon Sep 17 00:00:00 2001 From: Kari Salminen Date: Wed, 19 Jun 2024 17:57:14 +0300 Subject: [PATCH] feat: upgrade Django to 4.2 & upgrade all packages to latest done: - upgrade postgres from 11 to 12 - required, didn't work without this change - change import paths - graphql.execution.base.ResolveInfo -> graphql.type.GraphQLResolveInfo - add arbitrary primary keys to objects used in dummy notification contexts because factory's build doesn't create a primary key yet, and without it generating the URLs using the object's ID fails - remove QUERY_TOO_DEEP_ERROR and DepthAnalysisBackend, use graphene's depth_limit_validator instead which should give a GraphQLError like ` exceeds maximum operation depth of . - fix executed_graphql_request() has no `invalid` member - fix graphene enums to work similarly to graphene v2 -> using values - graphene v3 changed enums so they work differently than in graphene v2, forcing backward compatibility by converting enums to their values - fix using django-parler translations, map enums to their values - fix enums in TranslatableQuerySet objects, map them to their values refs KK-1108 --- .pre-commit-config.yaml | 6 +- Dockerfile | 6 +- README.md | 2 +- children/factories.py | 2 + children/schema.py | 15 +- children/tests/snapshots/snap_test_api.py | 14 +- children/tests/snapshots/snap_test_models.py | 68 ++--- children/tests/test_api.py | 24 +- common/mixins.py | 23 ++ common/models.py | 2 + .../snapshots/snap_test_qrcode_service.py | 36 +-- common/tests/test_utils.py | 254 ++++++++++++++++++ common/utils.py | 42 ++- docker-compose.yml | 2 +- events/factories.py | 9 +- events/notifications.py | 24 +- events/schema.py | 17 ++ events/tests/snapshots/snap_test_api.py | 51 ++-- .../snapshots/snap_test_notifications.py | 20 +- events/tests/test_api.py | 69 +++-- gdpr/service.py | 2 +- gdpr/tests/snapshots/snap_test_gdpr_api.py | 68 ++--- .../snap_test_notification_importer.py | 6 +- kukkuu/consts.py | 1 - kukkuu/exceptions.py | 4 - kukkuu/urls.py | 7 +- kukkuu/views.py | 111 ++------ messaging/factories.py | 1 + messaging/schema.py | 7 +- messaging/tests/snapshots/snap_test_api.py | 223 +-------------- messaging/tests/test_api.py | 45 +--- projects/factories.py | 1 + requirements-dev.txt | 117 ++++---- requirements-prod.txt | 6 +- requirements.in | 6 +- requirements.txt | 203 +++++++------- subscriptions/factories.py | 1 + subscriptions/notifications.py | 18 +- subscriptions/schema.py | 33 ++- .../tests/snapshots/snap_test_api.py | 4 +- subscriptions/tests/test_api.py | 1 - users/factories.py | 4 +- users/schema.py | 6 +- users/tests/snapshots/snap_test_api.py | 41 +-- users/tests/snapshots/snap_test_models.py | 78 +++--- .../snapshots/snap_test_notifications.py | 32 +-- users/tests/test_api.py | 2 +- users/tests/test_models.py | 3 - users/tests/test_services.py | 12 +- venues/factories.py | 1 + venues/schema.py | 4 + venues/tests/snapshots/snap_test_api.py | 16 +- verification_tokens/factories.py | 1 + 53 files changed, 922 insertions(+), 829 deletions(-) create mode 100644 common/mixins.py create mode 100644 common/tests/test_utils.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8be00e8a..374a5752 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,15 +2,15 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 24.4.2 hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.1.0 hooks: - id: flake8 args: ['--config=setup.cfg', '--ignore=W503,E741'] diff --git a/Dockerfile b/Dockerfile index acad208a..45121aae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # ============================== -FROM registry.access.redhat.com/ubi9/python-39 as appbase +FROM registry.access.redhat.com/ubi9/python-39 AS appbase # ============================== USER root @@ -20,7 +20,7 @@ COPY --chown=default:root docker-entrypoint.sh /entrypoint/docker-entrypoint.sh ENTRYPOINT ["/entrypoint/docker-entrypoint.sh"] # ============================== -FROM appbase as development +FROM appbase AS development # ============================== COPY --chown=default:root requirements-dev.txt /app/requirements-dev.txt @@ -34,7 +34,7 @@ USER default EXPOSE 8081/tcp # ============================== -FROM appbase as production +FROM appbase AS production # ============================== COPY --chown=default:root . /app/ diff --git a/README.md b/README.md index 02bf092f..cbb7d87e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The project is now running at [localhost:8081](http://localhost:8081) Prerequisites: -- PostgreSQL 11 +- PostgreSQL 12 - Python 3.9 ### Installing Python requirements diff --git a/children/factories.py b/children/factories.py index 4d720eed..21fecaee 100644 --- a/children/factories.py +++ b/children/factories.py @@ -24,6 +24,7 @@ class ChildFactory(factory.django.DjangoModelFactory): class Meta: model = Child + skip_postgeneration_save = True # Not needed after factory v4.0.0 class RelationshipFactory(factory.django.DjangoModelFactory): @@ -35,6 +36,7 @@ class RelationshipFactory(factory.django.DjangoModelFactory): class Meta: model = Relationship + skip_postgeneration_save = True # Not needed after factory v4.0.0 class ChildWithGuardianFactory(ChildFactory): diff --git a/children/schema.py b/children/schema.py index 579a3ffb..c8506004 100644 --- a/children/schema.py +++ b/children/schema.py @@ -1,6 +1,7 @@ import binascii +import datetime import logging -from datetime import datetime, timedelta +from datetime import timedelta import graphene from django.conf import settings @@ -19,7 +20,7 @@ from children.notifications import NotificationType from common.schema import set_obj_languages_spoken_at_home -from common.utils import login_required, update_object +from common.utils import login_required, map_enums_to_values_in_kwargs, update_object from events.models import Event, Occurrence from kukkuu.exceptions import ( ApiUsageError, @@ -267,7 +268,7 @@ def resolve_active_internal_and_ticket_system_enrolments(self, info, **kwargs): ) ) - datetime_max = make_aware(datetime.max, timezone=timezone.utc) + datetime_max = make_aware(datetime.datetime.max, timezone=datetime.timezone.utc) return sorted( ( *EnrolmentNode.get_queryset(internal_enrolments, info), @@ -391,6 +392,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user if hasattr(user, "guardian"): @@ -478,6 +480,7 @@ class Input(BaseNewChildInput): @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user if not hasattr(user, "guardian"): @@ -521,6 +524,7 @@ class Input(BaseUpdateChildInput): @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): validate_child_data(kwargs) user = info.context.user @@ -558,6 +562,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user @@ -586,6 +591,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user child_global_id = kwargs.pop("child_id") @@ -636,9 +642,10 @@ def resolve_child_notes(self, info, id): ) if id_type != "ChildNode": + formatted_id_type = "empty" if id_type == "" else id_type raise ApiUsageError( "childNotes must be queried using ChildNode type ID, " - + f"was queried with {id_type} type ID" + + f"was queried with {formatted_id_type} type ID" ) return ChildNotesNode.get_node(info, child_id) diff --git a/children/tests/snapshots/snap_test_api.py b/children/tests/snapshots/snap_test_api.py index 24be17f7..285a9bb1 100644 --- a/children/tests/snapshots/snap_test_api.py +++ b/children/tests/snapshots/snap_test_api.py @@ -107,7 +107,7 @@ "email": "michellewalker@example.net", "firstName": "Denise", "lastName": "Thompson", - "phoneNumber": "001-067-506-4976x3803", + "phoneNumber": "001-206-575-0649x7638", }, "type": "OTHER_RELATION", } @@ -132,7 +132,7 @@ "email": "blake64@example.net", "firstName": "Timothy", "lastName": "Baldwin", - "phoneNumber": "803.466.9727", + "phoneNumber": "480.934.6697", }, "type": "PARENT", } @@ -202,7 +202,7 @@ "email": "michellewalker@example.net", "firstName": "Denise", "lastName": "Thompson", - "phoneNumber": "001-067-506-4976x3803", + "phoneNumber": "001-206-575-0649x7638", }, "type": "OTHER_RELATION", } @@ -250,7 +250,7 @@ "email": "funderwood@example.com", "firstName": "Blake", "lastName": "Newton", - "phoneNumber": "976-380-3466x9727", + "phoneNumber": "497-963-8034x6697", }, "type": "PARENT", } @@ -281,7 +281,7 @@ "email": "anthonycross@example.com", "firstName": "Sarah", "lastName": "Larsen", - "phoneNumber": "8981710123", + "phoneNumber": "4895817101", }, "type": "OTHER_RELATION", } @@ -303,7 +303,7 @@ "email": "michellewalker@example.net", "firstName": "Michael", "lastName": "Patton", - "phoneNumber": "355.777.6712x406", + "phoneNumber": "235.857.7767x124", }, "type": "OTHER_GUARDIAN", } @@ -325,7 +325,7 @@ "email": "michellewalker@example.net", "firstName": "Michael", "lastName": "Patton", - "phoneNumber": "355.777.6712x406", + "phoneNumber": "235.857.7767x124", }, "type": "OTHER_GUARDIAN", } diff --git a/children/tests/snapshots/snap_test_models.py b/children/tests/snapshots/snap_test_models.py index dbf215fe..06b89ffe 100644 --- a/children/tests/snapshots/snap_test_models.py +++ b/children/tests/snapshots/snap_test_models.py @@ -37,7 +37,7 @@ {"key": "FIRST_NAME", "value": "Michael"}, {"key": "LAST_NAME", "value": "Patton"}, {"key": "EMAIL", "value": "michellewalker@example.net"}, - {"key": "PHONE_NUMBER", "value": "355.777.6712x406"}, + {"key": "PHONE_NUMBER", "value": "235.857.7767x124"}, {"key": "HAS_ACCEPTED_COMMUNICATION", "value": False}, { "children": [ @@ -58,7 +58,7 @@ "children": [ { "key": "TIME", - "value": "1988-02-22T23:02:01+00:00", + "value": "1983-07-25T04:16:22.559945+00:00", }, { "children": [ @@ -66,7 +66,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Increase thank certainly again thought summer.", + "fi": "Number lose least then top sing. Serious listen police shake.", "sv": "", }, }, @@ -113,7 +113,7 @@ "value": { "en": "", "fi": """779 Kevin Isle Suite 550 -Jonathanburgh, AZ 30120""", +Jonathanburgh, AK 06327""", "sv": "", }, }, @@ -136,7 +136,7 @@ "children": [ { "key": "TIME", - "value": "1974-05-30T15:30:48+00:00", + "value": "1973-04-18T19:54:40.016085+00:00", }, { "children": [ @@ -144,7 +144,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Include and individual effort indeed discuss challenge school.", + "fi": "Democratic focus significant kind various laugh.", "sv": "", }, }, @@ -182,7 +182,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Teach manager movie owner.", + "fi": "Medical check word style also. Question national throw three.", "sv": "", }, }, @@ -190,8 +190,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """40241 Alexander Fields -West Rubenbury, MS 61495""", + "fi": """Unit 7738 Box 4455 +DPO AE 93387""", "sv": "", }, }, @@ -214,7 +214,7 @@ "children": [ { "key": "TIME", - "value": "2011-01-05T08:35:11+00:00", + "value": "1983-12-06T02:14:55.080643+00:00", }, { "children": [ @@ -222,7 +222,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Enjoy when one wonder fund nor white.", + "fi": "Policy sport available.", "sv": "", }, }, @@ -260,7 +260,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Available policy physical heavy scientist.", + "fi": "Person material alone throughout investment check increase.", "sv": "", }, }, @@ -268,8 +268,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """37849 Alejandra Rapid Apt. 294 -South Mitchell, NC 30891""", + "fi": """18631 David Wells Apt. 018 +South Mikeburgh, NH 37426""", "sv": "", }, }, @@ -292,7 +292,7 @@ "children": [ { "key": "TIME", - "value": "1972-02-01T22:06:34+00:00", + "value": "1989-06-04T07:26:03.824456+00:00", }, { "children": [ @@ -300,7 +300,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Affect money school military statement.", + "fi": "Month score father middle brother station physical very.", "sv": "", }, }, @@ -338,7 +338,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Because treatment sense left.", + "fi": "Moment apply president unit positive.", "sv": "", }, }, @@ -346,8 +346,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """812 Russell Hollow Suite 792 -Jessicatown, MN 66988""", + "fi": """7921 Fischer Ranch +Port Timothy, MI 90086""", "sv": "", }, }, @@ -370,7 +370,7 @@ "children": [ { "key": "TIME", - "value": "2014-12-23T16:25:42+00:00", + "value": "1980-04-04T02:29:57.072751+00:00", }, { "children": [ @@ -378,7 +378,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Wife message focus between.", + "fi": "Suggest claim PM they. South blue reach ask.", "sv": "", }, }, @@ -416,7 +416,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Offer goal unit country call thus. By final design degree.", + "fi": "Process who catch bad. Seven process determine call.", "sv": "", }, }, @@ -424,8 +424,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """04194 Frederick Mill -Daniellechester, KS 04855""", + "fi": """1235 Eric Route Suite 932 +New Manuelshire, PR 28866""", "sv": "", }, }, @@ -446,14 +446,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "F@47o(Up&6"}, + {"key": "VALUE", "value": "jR1NWxHl(x"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Court cultural speak condition her station must.", + "fi": "Half state four hear trouble among face three.", "sv": "", }, }, @@ -495,14 +495,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "$jNvGUhi+8"}, + {"key": "VALUE", "value": ")1q1TpGzpE"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "I time bring third person. Resource certainly past pull.", + "fi": "Where official agree order just raise.", "sv": "", }, }, @@ -544,14 +544,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "0GX@55Bp(6"}, + {"key": "VALUE", "value": "+2(bkVx7(8"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Bed add should computer. Degree share across.", + "fi": "Many happy better agree concern often almost.", "sv": "", }, }, @@ -593,14 +593,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "PP$15MkwcE"}, + {"key": "VALUE", "value": "&KXlatF20E"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Yeah question he risk military result building.", + "fi": "Program song couple central each color.", "sv": "", }, }, @@ -642,14 +642,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "^4aK6a^8D8"}, + {"key": "VALUE", "value": "_O2oUJuK@V"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Support apply mind arrive wish.", + "fi": "Bit other she chair cover whether.", "sv": "", }, }, diff --git a/children/tests/test_api.py b/children/tests/test_api.py index e1a86d3b..061804cd 100644 --- a/children/tests/test_api.py +++ b/children/tests/test_api.py @@ -9,8 +9,7 @@ from django.utils.timezone import localtime, now from freezegun import freeze_time from graphene.utils.str_converters import to_snake_case -from graphql_relay import to_global_id -from graphql_relay.connection.arrayconnection import offset_to_cursor +from graphql_relay import offset_to_cursor, to_global_id from guardian.shortcuts import assign_perm from children.factories import ChildWithGuardianFactory @@ -810,7 +809,7 @@ def test_update_child_notes_mutation_no_extra_fields( assert_general_error(executed) assert_error_message( - executed, f'Cannot query field "{extra_field_name}" on type "ChildNotesNode".' + executed, f"Cannot query field '{extra_field_name}' on type 'ChildNotesNode'." ) @@ -871,17 +870,17 @@ def test_child_notes_query_with_incorrect_id_type( def test_child_notes_query_with_plain_id(user_api_client, child_with_random_guardian): """ - Test that ChildNotes query gives API usage error, if using plain UUID as ID. + Test that ChildNotes query gives general error, if using plain UUID as ID. """ variables = {"id": child_with_random_guardian.id} executed = user_api_client.execute(CHILD_NOTES_QUERY, variables=variables) - assert_match_error_code(executed, API_USAGE_ERROR) + assert_match_error_code(executed, GENERAL_ERROR) assert_error_message( executed, - "Unable to decode child ID in childNotes query, " - + 'please use "ChildNode:" encoded as base64.', + "Variable '$id' got invalid value ; " + + "ID cannot represent value: ", ) @@ -899,8 +898,8 @@ def test_child_notes_query_with_incorrect_id( assert_match_error_code(executed, API_USAGE_ERROR) assert_error_message( executed, - "Unable to decode child ID in childNotes query, " - + 'please use "ChildNode:" encoded as base64.', + "childNotes must be queried using ChildNode type ID, " + + "was queried with empty type ID", ) @@ -914,7 +913,7 @@ def test_child_notes_query_with_null_id(user_api_client, child_with_random_guard assert_general_error(executed) assert_error_message( - executed, 'Variable "$id" of required type "ID!" was not provided.' + executed, "Variable '$id' of non-null type 'ID!' must not be null." ) @@ -973,7 +972,7 @@ def test_child_notes_query_no_extra_fields( assert_general_error(executed) assert_error_message( - executed, f'Cannot query field "{extra_field_name}" on type "ChildNotesNode".' + executed, f"Cannot query field '{extra_field_name}' on type 'ChildNotesNode'." ) @@ -987,7 +986,8 @@ def test_child_notes_query_without_id_parameter_fails( assert_general_error(executed) assert_error_message( executed, - 'Field "childNotes" argument "id" of type "ID!" is required but not provided.', + "Field 'childNotes' argument 'id' of type 'ID!' is required, " + + "but it was not provided.", ) diff --git a/common/mixins.py b/common/mixins.py new file mode 100644 index 00000000..27153fa4 --- /dev/null +++ b/common/mixins.py @@ -0,0 +1,23 @@ +class SaveAfterPostGenerationMixin: + """ + Mixin for saving Django model instances after post-generation hooks. + + To use this derive the factory class that uses @factory.post_generation + decorator from factory.django.DjangoModelFactory as well as this, e.g. + class TestFactory(SaveAfterPostGenerationMixin, factory.django.DjangoModelFactory) + + NOTE: Needs to be before factory.django.DjangoModelFactory in the class + definition to work, because of how Python resolves method resolution order (MRO). + + Rationale: + - Because factory 3.3.0 has deprecated saving the instance after + post-generation hooks, and will remove the functionality in the + next major release. + """ + + @classmethod + def _after_postgeneration(cls, instance, create, results=None): + """Save again the instance if creating and at least one hook ran.""" + if create and results: + # Some post-generation hooks ran, and may have modified us. + instance.save() diff --git a/common/models.py b/common/models.py index b75d99a8..9770aa72 100644 --- a/common/models.py +++ b/common/models.py @@ -7,6 +7,7 @@ from parler.managers import TranslatableQuerySet as ParlerTranslatableQuerySet from parler.models import TranslatableModel as ParlerTranslatableModel +from common.utils import map_enums_to_values_in_kwargs from kukkuu.exceptions import MissingDefaultTranslationError @@ -29,6 +30,7 @@ class Meta: class TranslatableQuerySet(ParlerTranslatableQuerySet): @transaction.atomic + @map_enums_to_values_in_kwargs def create_translatable_object(self, **kwargs): translations = kwargs.pop("translations") obj = self.create(**kwargs) diff --git a/common/tests/snapshots/snap_test_qrcode_service.py b/common/tests/snapshots/snap_test_qrcode_service.py index 81669f4d..da432d36 100644 --- a/common/tests/snapshots/snap_test_qrcode_service.py +++ b/common/tests/snapshots/snap_test_qrcode_service.py @@ -6,26 +6,26 @@ snapshots = Snapshot() -snapshots[ - "test_qrcode_creation[QRCodeFileFormatEnum.PNG] 1" -] = 'b\'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x01\\x9a\\x00\\x00\\x01\\x9a\\x01\\x00\\x00\\x00\\x00\\x1e}\\xb8\\xce\\x00\\x00\\x02\\xfaIDATx\\x9c\\xed\\x9cQ\\x8e\\xa30\\x0c\\x86?/H}\\x0c\\xd2\\x1c`\\x8e\\x027[\\xcd\\xcd\\xe0(=\\xc0J\\xf0X)\\xc8\\xfb\\xe0$\\xb4\\x9d\\xa7\\xd9\\xee\\x90N1\\x0f-\\x85|\\x1aGX\\xb1\\xfd\\x9b\\x8c(_>\\xa6__g\\xc0!\\x87\\x1cr\\xc8!\\x87\\x1czMH\\xd2\\xd1\\xc2$"L\\x1d\\x88t\\xab]\\x85%\\x0f\\x18\\xaa\\x98\\xe7\\xd0\\x8ePk_\\xfd\\x08\\xb0\\xbc\\xd9/\\xe9\\xcf\\x82\\xb2\\x9cJ\\xda\\xd9D\\xbb\\xb1\\xb7y\\x0eU\\x83\\x96\\xb4\\x00\\xc8@\\xa3@\\xa32\\x84KZ7\\x00\\xb6\\xb3\\x1f3\\\'\\x87\\xfe\\x0b\\xd4\\xcf\\xab@\\xb8\\x88~t\\xa0:\\x83\\x0c\\xdf\\xf2\\x97\\x1czJ\\xa8\\xbd\\xbf0uM\\x04\\x1a\\x95~^\\x11\\xc2E\\x94\\xa5\\x96y\\x0eU\\x83\\x82\\xaa\\x8e\\x90\\xd7\\x08V\\x01\\x1a\\xd51\\\'\\x13\\xaa\\x1a\\xeb\\x99\\xe7\\xd0\\xde\\xd0$""\\x1d`)\\xc4rRXZ\\xe4\\xf7\\x0c2P\\xaa\\x8e:\\xe69\\xb4w\\xd4\\xb8\\x91\\xb2W\\x0b\\x13\\x16+\\xa6\\xaeA\\xef\\x06<\\xfb\\x9c\\x1cz\\x04BUU\\xe9g\\xc0\\xc2\\x04\\x8dBP\\xfbHG\\xb9\\xab\\xaa\\xaa\\xe3\\x93\\xcf\\xc9\\xa1G \\xd2C\\xce~`\\x0eBH^\\xa2\\xaa\\xd1\\n\\x0e\\x1b\\xd2\\xbbG\\xbc:\\x94\\xd6\\x88\\xed\\xb0g^\\xce\\xd2\\x1a\\xd1\\x14\\x07q\\x8f8\\x04\\xa4c\\x88@\\x88$\\xad\\xaa\\x9cm\\xda\\xf5\\xd4U3\\xcf\\xa1\\xfd\\xa3FZ\\x05\\x92\\\\\\x99\\xc2D\\x88\\xb9\\xe6\\xcc\\xfe\\xe2k\\xc4Q \\x19\\xc2\\xc5\\x94J\\xd3#\\xa6w\\xd5\\\\x\\x82\\xea\\xf9d\\x91D\\x86*\\xe69T\\xa1\\xfa\\\\:\\x94\\xe5M\\x05\\x9a(\\x84\\xb5UhT&iT\\x00\\xa1?\\xb7\\xb9\\x08}\\xf299\\xf48$\\xd25*\\xf2\\x9eTI\\x0b"\\x00\\x96c\\x92\\xbb_\\x95\\xccsh\\xff\\xccR\\xe7\\xd5V\\x81t,""\\xdd*[\\xe3\\x0bBD\\x86*\\xe69TA\\xa1\\xd2X\\xca\\xcd\\x88\\x8e\\xa6PE\\xe8]\\x8f8\\x18\\x94\\xf5\\x88\\x90\\xa4\\xc9+\\x91\\xf2\\xaa\\xf4\\xd0\\x88\\xeb\\x11\\x07\\x81n5\\xcbX\\x04\\xab\\xa2X\\x03\\xa9\\xfat\\x15\\xfb\\x10\\xd0\\xadf\\xd9\\x94\\x96x:K\\xab\\x85\\x89\\x12\\xeaQ\\xe3\\x00P\\xe9tm}\\x8d\\xf2\\xf4\\xe72dn\\xd4r\\x0b\\xf7\\x88\\x97\\x87\\xae2K[\\x14\\xb6\\xa4\\xd2\\x96\\x8c\\x90\\x14\\xcd\\xd4\\xf3r\\x8fxu\\xe8s\\xad1g\\xb7\\xe8o[`e\\xb0{\\xc4KCWQ#57\\x8aoX_#kU\\xd77\\x9e|N\\x0e=\\x02\\x95w\\xa8rCk\\xea\\xfe\\xa0,oQ\\x08 &J\\x8c I\\xe3\\xde\\xd7<\\x87*A\\xdb\\x9e.\\x1d\\xf3V\\x8d$I\\x9dE`i\\x01VI\\xc5\\xe8\\x8f\\x98\\x93C\\x0f\\xe6\\x11\\xb9%\\xbe\\xf50,\\x8f\\x08)Vl\\xd7\\xa6__g\\xc0!\\x87\\x1cr\\xc8!\\x87\\x1czMH\\xd2\\xd1\\xc2$"L\\x1d\\x88t\\xab]\\x85%\\x0f\\x18\\xaa\\x98\\xe7\\xd0\\x8ePk_\\xfd\\x08\\xb0\\xbc\\xd9/\\xe9\\xcf\\x82\\xb2\\x9cJ\\xda\\xd9D\\xbb\\xb1\\xb7y\\x0eU\\x83\\x96\\xb4\\x00\\xc8@\\xa3@\\xa32\\x84KZ7\\x00\\xb6\\xb3\\x1f3\\\'\\x87\\xfe\\x0b\\xd4\\xcf\\xab@\\xb8\\x88~t\\xa0:\\x83\\x0c\\xdf\\xf2\\x97\\x1czJ\\xa8\\xbd\\xbf0uM\\x04\\x1a\\x95~^\\x11\\xc2E\\x94\\xa5\\x96y\\x0eU\\x83\\x82\\xaa\\x8e\\x90\\xd7\\x08V\\x01\\x1a\\xd51\\\'\\x13\\xaa\\x1a\\xeb\\x99\\xe7\\xd0\\xde\\xd0$""\\x1d`)\\xc4rRXZ\\xe4\\xf7\\x0c2P\\xaa\\x8e:\\xe69\\xb4w\\xd4\\xb8\\x91\\xb2W\\x0b\\x13\\x16+\\xa6\\xaeA\\xef\\x06<\\xfb\\x9c\\x1cz\\x04BUU\\xe9g\\xc0\\xc2\\x04\\x8dBP\\xfbHG\\xb9\\xab\\xaa\\xaa\\xe3\\x93\\xcf\\xc9\\xa1G \\xd2C\\xce~`\\x0eBH^\\xa2\\xaa\\xd1\\n\\x0e\\x1b\\xd2\\xbbG\\xbc:\\x94\\xd6\\x88\\xed\\xb0g^\\xce\\xd2\\x1a\\xd1\\x14\\x07q\\x8f8\\x04\\xa4c\\x88@\\x88$\\xad\\xaa\\x9cm\\xda\\xf5\\xd4U3\\xcf\\xa1\\xfd\\xa3FZ\\x05\\x92\\\\\\x99\\xc2D\\x88\\xb9\\xe6\\xcc\\xfe\\xe2k\\xc4Q \\x19\\xc2\\xc5\\x94J\\xd3#\\xa6w\\xd5\\\\x\\x82\\xea\\xf9d\\x91D\\x86*\\xe69T\\xa1\\xfa\\\\:\\x94\\xe5M\\x05\\x9a(\\x84\\xb5UhT&iT\\x00\\xa1?\\xb7\\xb9\\x08}\\xf299\\xf48$\\xd25*\\xf2\\x9eTI\\x0b"\\x00\\x96c\\x92\\xbb_\\x95\\xccsh\\xff\\xccR\\xe7\\xd5V\\x81t,""\\xdd*[\\xe3\\x0bBD\\x86*\\xe69TA\\xa1\\xd2X\\xca\\xcd\\x88\\x8e\\xa6PE\\xe8]\\x8f8\\x18\\x94\\xf5\\x88\\x90\\xa4\\xc9+\\x91\\xf2\\xaa\\xf4\\xd0\\x88\\xeb\\x11\\x07\\x81n5\\xcbX\\x04\\xab\\xa2X\\x03\\xa9\\xfat\\x15\\xfb\\x10\\xd0\\xadf\\xd9\\x94\\x96x:K\\xab\\x85\\x89\\x12\\xeaQ\\xe3\\x00P\\xe9tm}\\x8d\\xf2\\xf4\\xe72dn\\xd4r\\x0b\\xf7\\x88\\x97\\x87\\xae2K[\\x14\\xb6\\xa4\\xd2\\x96\\x8c\\x90\\x14\\xcd\\xd4\\xf3r\\x8fxu\\xe8s\\xad1g\\xb7\\xe8o[`e\\xb0{\\xc4KCWQ#57\\x8aoX_#kU\\xd77\\x9e|N\\x0e=\\x02\\x95w\\xa8rCk\\xea\\xfe\\xa0,oQ\\x08 &J\\x8c I\\xe3\\xde\\xd7<\\x87*A\\xdb\\x9e.\\x1d\\xf3V\\x8d$I\\x9dE`i\\x01VI\\xc5\\xe8\\x8f\\x98\\x93C\\x0f\\xe6\\x11\\xb9%\\xbe\\xf50,\\x8f\\x08)Vl\\xd7T;]\\x8drkk\\x82\\xb5\\xaf\\x91\\x97L\\xb9\\xeb\\xe1\\x1aq|\\xe8N#r\\xbaY\\x83J\\xd6\\xeeW1\\x1e\\xee5\\x8e\\x0f\\xddM\\xd5Y\\xd0\\xd2Z\\xb8\\xb6Q\\xe3\\xf7F\\x16\\x06\\xd2T6\\xd4)\\xec\\xad\\xc5shs\\xe8N#\\xc4\\xdc\\x93\\xca\\x0ea\\x02\\x8d\\xfd{+\\xe6sL\\xda\\x10\\x86\\xa5\\xb54\\xc5\\xff\\xc9\\xf7\\xe4\\xd03P\\x9d\\xa1J\\xb1#6\\xf6\\xef\\xadFk\\xa2\\xd1\\x81\\xa5\\xa2\\xc4\\x00\\xc6|\\x96\\x9f\\xd7x\\x15h=\\xd3\\xa5a>\\xa5\\xa3\\x1a9#\\xbd\\x9a\\xc1\\xdc\\x02,\\x96\\x93\\xd1/\\xb1\\'\\x87\\x9e\\xaeP\\xad\\xdd\\xf0\\\\\\xb0\\x92\\x1ez^\\x9ek\\x1c\\x1e\\xba\\x9f\\xb3\\\\\\xdb\\x9e9\\xcd\\x88uf\\x02V\\xddp\\x8d84\\xf4\\x1fg\\xbaN9\\xcd\\x08\\xd76\\x9d\\xe0\\x81n\\xfa-\\xd9\\xf8\\xe4{r\\xe8\\x19\\xe8A#\\xc2\\xcfsI3\\x10\\x96B\\x88t\\xf2\\x0f\\xef\\x86\\xbf\\x00\\xf41\\x8e\\xc8\\xbe\\xe2\\xee\\xb6t=\\xbc\\xaf\\xf1\\x1aP\\xedk\\x00kP\\t\\xb9\\xfd]\\x8ej\\xd49}\\xd7\\x88\\xa3C\\xa6\\xff_\\xf3\\xf1\\xf2\\x7f&s\\xc8!\\x87\\x1cr\\xc8!\\x87\\x00~\\x019aww;\\xc8b#\\x00\\x00\\x00\\x00IEND\\xaeB`\\x82'" +snapshots["test_qrcode_creation[QRCodeFileFormatEnum.PNG] 2"] = ( + "b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x01\\x9a\\x00\\x00\\x01\\x9a\\x01\\x00\\x00\\x00\\x00\\x1e}\\xb8\\xce\\x00\\x00\\x03 IDATx\\x9c\\xed\\x9c[\\x8a\\xdbJ\\x10\\x86\\xbf\\x8a\\x04~\\x94\\xc0\\x0b\\x98\\xa5\\xb4w\\x90%eM\\xd9\\x81\\xb4\\x14/`@z4\\xb4\\xf8\\xf3\\xd0\\x179\\xe3\\x03\\x87\\xc4A\\x9a\\x91K/\\xb6\\xe4\\xfep5.\\xea\\xde6\\xf1\\xc7\\xd7\\xf8\\xed\\xcf\\x19p\\xc8!\\x87\\x1cr\\xc8!\\x87\\x8e\\tY\\xbeZ\\x18\\xcd\\x8c\\xb1\\x07\\xb3~IOa.\\x0b.\\xbb\\x88\\xe7\\xd0\\x86P\\x9b^\\xc2\\x000\\x9f!L=\\x06\\x86\\x98O5\\xecl\"\\x00\\xb6\\xb5x\\x0e\\xed\\x06\\xcd\\xd9\\x00\\x98\\xbdI\\x84\\xa9\\x91]\\xba[\\xb6\\x1b\\x00\\xeb\\xbb/\\xb3\\'\\x87\\xfe\\x06j\\x1f\\x9e\\xcc\\x86\\xe0f\\x1a\\xfbw\\xc4|\\xc6\\xc2\\xcf\\xbd\\xc4shs\\xe8Q#\\x00\\x0bC#\\x0b\\xd3\\x82\\xd1\\xddL\\xcc\\xff\\xe0\\x9b\\x1c\\xfaZP\\'i\\x00\\x08S#\\r,\\x064\\xd2P\\x82\\tIq?\\xf1\\x1c\\xda\\x1a\\x1a\\xcd\\xcc\\xac\\xcf\\x0f\\xed2\\x9f\\x04s\\x8b\\xfd\\x98\\xc0.\\xd4\\xacc\\x1f\\xf1\\x1c\\xda\\xdak<\\x96\\xb2\\x97\\xec+\\xc6\\xbeA\\x1f\\x17|\\xf2=9\\xf4\\x0c\\x84$\\x890\\x01\\xc9M\\xd0\\x08\\xbaH\\xf2$\\xe9\\xaa\\x9fJ\\x92\\x86O\\xbe\\'\\x87\\x9e\\x81\\xc8?2\\x8d4\\x14\\x15\\x80.k\\x89\\xa4\\x884AR\\x95\\xe0\\x1aqt\\xa8\\xe6\\x1a\\x8b\\x91+PM\\xd4x1\\xa0\\x13@#\\xa0\\x89\\x16\\xa6\\xde\\xb4\\xb5x\\x0em\\x0eQ\\x1cC\\x84\\xa0H\\xf6\\x1f]}\\x07)\\xff\\xb8[\\xec6\\xe2\\xc8P\\xd1\\x88\\t\\xa4\\xf4\\xc37%p\\xe8b\\xc99\\xbbXo]#^\\x022{\\x93\\xcc\\xden&\\xe9f\\x8co9\\x8e\\xb0\\x0b ]O\\xa9Za\\x97]\\xc4sh\\x1f\\xaf\\x91,C\\xf2\\x10]\\x84 %\\x93\\x91\\x16\\xe6@\\xd3m\\xc4K@f}ir\\xc1b\\xa5p\\t\\xa9\\x8eY\\xc2K\\xfd\\x06m\\'\\x9eC\\x9bC\\xd2\\xb5\\x85p\\xcd\\x99\\x87\\x86\\xb9M\\xf3\\x11\\x84i1\\xe8n\\x06]\\xc4.\\xbb\\x88\\xe7\\xd0\\x0e\\x15*e7\\xa1\\xa1\\x8bh\\xe8T]\\x87\\xd7#^\\x0b\\xaa\\x1aQKRSS\\x92\\xd15\\xf5(i\\xa9\\xc7\\x11\\xc7\\x87\\xeej\\x96\\xc5\\x1e\\x14S\\x90?\\x80\\x9c}z\\x15\\xfb%\\xa0l#r\\x15\\xa2\\xda\\x83\\xa2\\x0c9\\xffPns\\xb8\\xd78>T;]\\x8drkk\\x82\\xb5\\xaf\\x91\\x97L\\xb9\\xeb\\xe1\\x1aq|\\xe8N#r\\xbaY\\x83J\\xd6\\xeeW1\\x1e\\xee5\\x8e\\x0f\\xddM\\xd5Y\\xd0\\xd2Z\\xb8\\xb6Q\\xe3\\xf7F\\x16\\x06\\xd2T6\\xd4)\\xec\\xad\\xc5shs\\xe8N#\\xc4\\xdc\\x93\\xca\\x0ea\\x02\\x8d\\xfd{+\\xe6sL\\xda\\x10\\x86\\xa5\\xb54\\xc5\\xff\\xc9\\xf7\\xe4\\xd03P\\x9d\\xa1J\\xb1#6\\xf6\\xef\\xadFk\\xa2\\xd1\\x81\\xa5\\xa2\\xc4\\x00\\xc6|\\x96\\x9f\\xd7x\\x15h=\\xd3\\xa5a>\\xa5\\xa3\\x1a9#\\xbd\\x9a\\xc1\\xdc\\x02,\\x96\\x93\\xd1/\\xb1\\'\\x87\\x9e\\xaeP\\xad\\xdd\\xf0\\\\\\xb0\\x92\\x1ez^\\x9ek\\x1c\\x1e\\xba\\x9f\\xb3\\\\\\xdb\\x9e9\\xcd\\x88uf\\x02V\\xddp\\x8d84\\xf4\\x1fg\\xbaN9\\xcd\\x08\\xd76\\x9d\\xe0\\x81n\\xfa-\\xd9\\xf8\\xe4{r\\xe8\\x19\\xe8A#\\xc2\\xcfsI3\\x10\\x96B\\x88t\\xf2\\x0f\\xef\\x86\\xbf\\x00\\xf41\\x8e\\xc8\\xbe\\xe2\\xee\\xb6t=\\xbc\\xaf\\xf1\\x1aP\\xedk\\x00kP\\t\\xb9\\xfd]\\x8ej\\xd49}\\xd7\\x88\\xa3C\\xa6\\xff_\\xf3\\xf1\\xf2\\x7f&s\\xc8!\\x87\\x1cr\\xc8!\\x87\\x00~\\x019aww;\\xc8b#\\x00\\x00\\x00\\x00IEND\\xaeB`\\x82'" +) -snapshots[ - "test_qrcode_creation[QRCodeFileFormatEnum.PNG] 3" -] = "b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x01\\x9a\\x00\\x00\\x01\\x9a\\x01\\x00\\x00\\x00\\x00\\x1e}\\xb8\\xce\\x00\\x00\\x03\\x1fIDATx\\x9c\\xed\\x9cA\\x8e\\xdb8\\x10E_\\r\\x05xI\\x01}\\x80\\x1c\\x85\\xbe\\xc1\\x1c)\\xc8\\x91r\\x03\\xe9(}\\x80\\x01\\xc4e\\x03\\x12~\\x16$%\\xb53\\x8b\\t<\\x91\\x1c\\xbb\\xb40l\\x89\\x0f.\\xc2\\x05V\\xfd*\\xd2&~\\xf9\\x1a\\xff\\xfau\\x06\\x1cr\\xc8!\\x87\\x1cr\\xc8\\xa1\\xe7\\x84\\xac^\\x1d\\x90\\xcd\\xea\\xbd~)w!\\xb7\\x01\\xd7S\\xccs\\xe8x(I\\x92&\\x808\\x17g\\x90\\xde/\\x92$\\x01a}w\\x8ay\\x0e\\x1d\\x0f\\xe5\\xba\\x00\\x98\\xd9E\\xa4)\\xc8\\xae\\xf1\\xa3\\xad\\x1b\\xe5Aw\\x9ey\\x0e\\x9d\\x07\\xa5i1`1}\\xebA\\x9a\\xc0\\xae\\xbf\\xe5\\x9b\\x1czH\\xa8\\xbb\\xbd1\\xf6 \\x08\\xb24-\\x18\\xf1\\xc3D>\\xcb<\\x87N\\x83\\xa2\\xa4\\x01 M\\x00,\\xa6\\x81 \\r\\xf9R\\xb2\\x07I\\xf3y\\xe69t44\\x9a\\x99Y\\x0f@\\x90]\\xf3Ev\\xcd\\x1d\\xf6u\\x02\\xbb\\xb2\\xaa\\x8es\\xccs\\xe8\\xe8\\xa8qS\\xca\\x16\\xb9\\xbe0\\xf6\\x01\\xdd\\x0ex\\xf099t\\x0fDQ\\x955V\\x84\\x1a:\\x88R\\x89$\\xdadi\\x93\\xa0\\x1a\\x1e|N\\x0e\\xfd\\x0f\\xd0\\xd8\\x07A\\x9c\\xa9\\xce\\x00\\x90\\xde\\xad\\xa8\\x0e\\x004\\xb0\\xaa\\x8e?cN\\x0e\\xdd\\x155ro\\x90\\xadd\\x96\\x1a\\xaf\\x06D\\x01\\x04\\x01a\\xb64\\xf5\\xa6\\xa3\\xcds\\xe8p\\x88\\x16\\x18fH\\x9b\\x9a\\x88\\xf3\\xaa:\\x804\\x05\\xed\\x06{\\xd4xf\\xa8\\xfd\\xc8\\xd0~\\xf8\\xb8\\xafgW\\xcd\\x19\\xe7\\xf5\\xa3{\\xc4\\x93C\\x9f2\\xcb(\\x95\\x0e\\xc7P\\xde\\xd5uC\\x9a\\x824\\xc4\\xb9\\xf6?\\xdc#\\x9e\\x19Z\\xd5\\xe7\\xdc\\x89l\\x88\\xdc#X\\xbaR\\xb8\\x1c-\\xc8\\x00#\\xbdwx\\x1e\\xf1*\\x90\\x99]d\\xf6E\\xb2+A\\xf6uM\\x1c\\x862\\xac\\xa4\\x97\\xde\\xfb|\\x19H\\xd2GY\\x05(\\x05\\xeclf\\xd6/V\\x1b_\\xf1\\xc3J\\x9b\\xfcz\\x8ay\\x0e\\x9d\\xd3\\xe9\\x12\\xcc%~@\\x14\\x06`i\\x98;\\x91;4ZhR\\xe4\\xc1\\xe7\\xe4\\xd0=P\\x93\\x0f\\xad.U\\xfa[\\x9aX5gh\\xdatr\\xf5\\xf9\\n\\x10\\x9fvI\\xcd\\xc5#\\xd8U\\xac\\x81\\xaa>\\xbd\\x8a\\xfd\\x12\\xd0\\xaa>\\x83\\x8a3\\xa4\\x89\"2\\xeb\\xd3\\xf2\\xb1\\x95\\xb7]}>=\\xd4\\xd6\\x88\\xd8j\\x96\\xa9-\\x19[\\xcd\\xb2\\xd5#\\xdc#^\\x01jkD\\x89\\x10[\\xbb\\xab\\xc6\\x0f\\r\\xa5\\x84\\xd9b\\x8aG\\x8d\\xa7\\x87\\xf65\\xcb}}R\\x9f6_\\x7f\\x1a\\xec\\x1e\\xf1\\xd4\\xd0>\\x8f\\xd8r\\x86Z\\xca\\xae\\x0eR\\x06\\xee\\x1e<\\xf8\\x9c\\x1c\\xba\\x07\\xda\\xaa\\xd8lU\\x08\\x8d\\x16f#\\x82\\x95\\x04c\\x00#\\xbf\\xc9\\x8e6\\xcf\\xa1\\xc3\\xa1\\xe2\\x11m\\x03e\\x98E~\\x93%-\\xa6\\xf1\\xef K\\xdf\\r\\xc8o3\\xb0\\xf8\\xfe\\x88\\x17\\x80\\xf6\\x99\\xa5\\xa6\\xa0\\xbd\\xd6\\xd8\\x89\\x90\\xfd=\\x8f\\x1a/\\x01\\xe5\\xf5P\\'\\x8bI\\x13\\xe8[;\\xea\\xc7h\\xd6\\x9a\\x1b\\'\\x99\\xe7\\xd0a\\xd0\\xbf\\x9c\\xe0\\x99di\\x98\\xb1\\xf4\\xde\\x95\\x13<\\x10\\xa75\\xb0\\x1ck\\x9eC\\x87C?yD\\xfa\\xde\\xcd\\x1a\\xfb\\x7fZ\\xbb+\\x97\\xf3\\xe2\\x88m\\x8f\\xfe\\x83\\xcf\\xc9\\xa1{\\xa0\\xdb\\xcb<\\x87N\\x83\\xa2\\xa4\\x01 M\\x00,\\xa6\\x81 \\r\\xf9R\\xb2\\x07I\\xf3y\\xe69t44\\x9a\\x99Y\\x0f@\\x90]\\xf3Ev\\xcd\\x1d\\xf6u\\x02\\xbb\\xb2\\xaa\\x8es\\xccs\\xe8\\xe8\\xa8qS\\xca\\x16\\xb9\\xbe0\\xf6\\x01\\xdd\\x0ex\\xf099t\\x0fDQ\\x955V\\x84\\x1a:\\x88R\\x89$\\xdadi\\x93\\xa0\\x1a\\x1e|N\\x0e\\xfd\\x0f\\xd0\\xd8\\x07A\\x9c\\xa9\\xce\\x00\\x90\\xde\\xad\\xa8\\x0e\\x004\\xb0\\xaa\\x8e?cN\\x0e\\xdd\\x155ro\\x90\\xadd\\x96\\x1a\\xaf\\x06D\\x01\\x04\\x01a\\xb64\\xf5\\xa6\\xa3\\xcds\\xe8p\\x88\\x16\\x18fH\\x9b\\x9a\\x88\\xf3\\xaa:\\x804\\x05\\xed\\x06{\\xd4xf\\xa8\\xfd\\xc8\\xd0~\\xf8\\xb8\\xafgW\\xcd\\x19\\xe7\\xf5\\xa3{\\xc4\\x93C\\x9f2\\xcb(\\x95\\x0e\\xc7P\\xde\\xd5uC\\x9a\\x824\\xc4\\xb9\\xf6?\\xdc#\\x9e\\x19Z\\xd5\\xe7\\xdc\\x89l\\x88\\xdc#X\\xbaR\\xb8\\x1c-\\xc8\\x00#\\xbdwx\\x1e\\xf1*\\x90\\x99]d\\xf6E\\xb2+A\\xf6uM\\x1c\\x862\\xac\\xa4\\x97\\xde\\xfb|\\x19H\\xd2GY\\x05(\\x05\\xeclf\\xd6/V\\x1b_\\xf1\\xc3J\\x9b\\xfcz\\x8ay\\x0e\\x9d\\xd3\\xe9\\x12\\xcc%~@\\x14\\x06`i\\x98;\\x91;4ZhR\\xe4\\xc1\\xe7\\xe4\\xd0=P\\x93\\x0f\\xad.U\\xfa[\\x9aX5gh\\xdatr\\xf5\\xf9\\n\\x10\\x9fvI\\xcd\\xc5#\\xd8U\\xac\\x81\\xaa>\\xbd\\x8a\\xfd\\x12\\xd0\\xaa>\\x83\\x8a3\\xa4\\x89\"2\\xeb\\xd3\\xf2\\xb1\\x95\\xb7]}>=\\xd4\\xd6\\x88\\xd8j\\x96\\xa9-\\x19[\\xcd\\xb2\\xd5#\\xdc#^\\x01jkD\\x89\\x10[\\xbb\\xab\\xc6\\x0f\\r\\xa5\\x84\\xd9b\\x8aG\\x8d\\xa7\\x87\\xf65\\xcb}}R\\x9f6_\\x7f\\x1a\\xec\\x1e\\xf1\\xd4\\xd0>\\x8f\\xd8r\\x86Z\\xca\\xae\\x0eR\\x06\\xee\\x1e<\\xf8\\x9c\\x1c\\xba\\x07\\xda\\xaa\\xd8lU\\x08\\x8d\\x16f#\\x82\\x95\\x04c\\x00#\\xbf\\xc9\\x8e6\\xcf\\xa1\\xc3\\xa1\\xe2\\x11m\\x03e\\x98E~\\x93%-\\xa6\\xf1\\xef K\\xdf\\r\\xc8o3\\xb0\\xf8\\xfe\\x88\\x17\\x80\\xf6\\x99\\xa5\\xa6\\xa0\\xbd\\xd6\\xd8\\x89\\x90\\xfd=\\x8f\\x1a/\\x01\\xe5\\xf5P\\'\\x8bI\\x13\\xe8[;\\xea\\xc7h\\xd6\\x9a\\x1b\\'\\x99\\xe7\\xd0a\\xd0\\xbf\\x9c\\xe0\\x99di\\x98\\xb1\\xf4\\xde\\x95\\x13<\\x10\\xa75\\xb0\\x1ck\\x9eC\\x87C?yD\\xfa\\xde\\xcd\\x1a\\xfb\\x7fZ\\xbb+\\x97\\xf3\\xe2\\x88m\\x8f\\xfe\\x83\\xcf\\xc9\\xa1{\\xa0\\xdb\\n\'' +snapshots["test_qrcode_creation[QRCodeFileFormatEnum.SVG] 1"] = ( + 'b\'\\n\'' +) -snapshots[ - "test_qrcode_creation[QRCodeFileFormatEnum.SVG] 2" -] = 'b\'\\n\'' +snapshots["test_qrcode_creation[QRCodeFileFormatEnum.SVG] 2"] = ( + 'b\'\\n\'' +) -snapshots[ - "test_qrcode_creation[QRCodeFileFormatEnum.SVG] 3" -] = 'b\'\\n\'' +snapshots["test_qrcode_creation[QRCodeFileFormatEnum.SVG] 3"] = ( + 'b\'\\n\'' +) diff --git a/common/tests/test_utils.py b/common/tests/test_utils.py new file mode 100644 index 00000000..670a91be --- /dev/null +++ b/common/tests/test_utils.py @@ -0,0 +1,254 @@ +import enum +from enum import auto + +import graphene +import pytest + +from common.utils import ( + deepfix_enum_values, + is_enum_value, + map_enums_to_values_in_kwargs, +) + + +class _OrderEnum(graphene.Enum): + FIRST = 1 + SECOND = 2 + + +class _TestEnum(enum.Enum): + TEST = "test_1" + TEST_2 = 2 + + +class _TestGrapheneEnum(graphene.Enum): + FIRST_ENUM_NAME = "FIRST_ENUM_VALUE" + ENUM_NAME_1 = "ENUM_VALUE_1" + ENUM_NAME_2 = "ENUM_VALUE_2" + LAST_ENUM_NAME = "LAST_ENUM_VALUE" + + +class _TestGrapheneEnumAuto(graphene.Enum): + _123 = auto() + test = auto() + + +@pytest.mark.parametrize( + "value", + [ + _TestEnum.TEST, + _TestEnum.TEST_2, + _TestGrapheneEnum.FIRST_ENUM_NAME, + _TestGrapheneEnum.ENUM_NAME_1, + _TestGrapheneEnum.ENUM_NAME_2, + _TestGrapheneEnum.LAST_ENUM_NAME, + _TestGrapheneEnumAuto._123, + _TestGrapheneEnumAuto.test, + ], +) +def test_is_enum_value_true(value): + assert is_enum_value(value) is True + + +@pytest.mark.parametrize( + "value", + [ + None, + 0, + 1, + 2, + "0", + "1", + "2", + "FIRST_ENUM_VALUE", + "ENUM_VALUE_1", + "ENUM_VALUE_2", + "LAST_ENUM_VALUE", + ], +) +def test_is_enum_value_false(value): + assert is_enum_value(value) is False + + +@pytest.mark.parametrize( + "input", + [ + None, + 0, + 1, + 2, + "0", + "1", + "2", + "FIRST_ENUM_VALUE", + "ENUM_VALUE_1", + "ENUM_VALUE_2", + "LAST_ENUM_VALUE", + {1, 2, 3, "test", 2}, + (1, 2, 3, "test", 2), + [1, 2, 3, "test", 2], + (1, [2, {3: {4, (11, (12,), 13, None, "test")}}]), + ], +) +def test_deepfix_enum_values_unchanged(input): + assert deepfix_enum_values(input) == input + + +def test_deepfix_enum_values_changes_output_but_not_input(): + """ + Test that the input is not modified even when the output is. + """ + input = {_TestEnum.TEST: "test"} + assert deepfix_enum_values(input) == {"test_1": "test"} + assert input == {_TestEnum.TEST: "test"} + + +@pytest.mark.parametrize( + "input,expected_output", + [ + (_TestEnum.TEST, "test_1"), + (_TestEnum.TEST_2, 2), + (_TestGrapheneEnum.FIRST_ENUM_NAME, "FIRST_ENUM_VALUE"), + (_TestGrapheneEnum.ENUM_NAME_1, "ENUM_VALUE_1"), + (_TestGrapheneEnum.ENUM_NAME_2, "ENUM_VALUE_2"), + (_TestGrapheneEnum.LAST_ENUM_NAME, "LAST_ENUM_VALUE"), + (_TestGrapheneEnumAuto._123, 1), + (_TestGrapheneEnumAuto.test, 2), + ( + [_TestGrapheneEnumAuto.test, _TestEnum.TEST, 123, "TEST"], + [2, "test_1", 123, "TEST"], + ), + ({_TestEnum.TEST: "test"}, {"test_1": "test"}), + ( + { + _TestEnum.TEST: [ + "test", + "123", + 1234, + _TestGrapheneEnumAuto.test, + ( + _TestEnum.TEST_2, + _TestGrapheneEnum.ENUM_NAME_1, + _TestGrapheneEnumAuto.test, + ), + { + _TestGrapheneEnumAuto.test, + "not_enum", + _TestEnum.TEST, + _TestEnum.TEST_2, + }, + { + "not_enum_key": "not_enum_value", + _TestEnum.TEST: _TestGrapheneEnumAuto.test, + }, + (_TestGrapheneEnumAuto.test, _TestEnum.TEST, "not_enum"), + [_TestEnum.TEST, _TestGrapheneEnumAuto.test], + _TestGrapheneEnumAuto.test, + ], + _TestGrapheneEnum.ENUM_NAME_1: { + _TestEnum.TEST_2, + _TestGrapheneEnumAuto.test, + _TestGrapheneEnum.ENUM_NAME_1, + }, + }, + { + "test_1": [ + "test", + "123", + 1234, + 2, + (2, "ENUM_VALUE_1", 2), + {2, "not_enum", "test_1"}, + {"not_enum_key": "not_enum_value", "test_1": 2}, + (2, "test_1", "not_enum"), + ["test_1", 2], + 2, + ], + "ENUM_VALUE_1": { + 2, + 2, + "ENUM_VALUE_1", + }, + }, + ), + ( + ( + _TestEnum.TEST, + [ + _TestEnum.TEST_2, + { + _TestGrapheneEnum.FIRST_ENUM_NAME: { + _TestGrapheneEnum.ENUM_NAME_1, + (_TestGrapheneEnum.ENUM_NAME_2,), + _TestGrapheneEnum.LAST_ENUM_NAME, + } + }, + ], + ), + ( + "test_1", + [ + 2, + { + "FIRST_ENUM_VALUE": { + "ENUM_VALUE_1", + ("ENUM_VALUE_2",), + "LAST_ENUM_VALUE", + } + }, + ], + ), + ), + ], +) +def test_deepfix_enum_values_changed(input, expected_output): + assert deepfix_enum_values(input) == expected_output + + +@pytest.mark.parametrize("args", [(), ("testing", 1234, ["a", 1, 2, "b"])]) +@pytest.mark.parametrize( + "kwargs,expected_kwargs", + [ + ({"x": _OrderEnum.FIRST}, {"x": 1}), + ({"x": _OrderEnum.SECOND}, {"x": 2}), + ({"x": _OrderEnum.FIRST, "y": _OrderEnum.SECOND}, {"x": 1, "y": 2}), + ( + { + "order_priority_reverse_mapping": [ + { + 1: [_OrderEnum.SECOND, _OrderEnum.FIRST], + 2: [_OrderEnum.FIRST, _OrderEnum.SECOND], + }, + "test", + 1, + 2, + 3, + _OrderEnum.FIRST, + _OrderEnum.SECOND, + 3, + ] + }, + { + "order_priority_reverse_mapping": [ + { + 1: [2, 1], + 2: [1, 2], + }, + "test", + 1, + 2, + 3, + 1, + 2, + 3, + ] + }, + ), + ], +) +def test_map_enums_to_values_in_kwargs(args, kwargs, expected_kwargs): + @map_enums_to_values_in_kwargs + def method(*args, **kwargs): + return (args, kwargs) + + assert method(*args, **kwargs) == (args, expected_kwargs) diff --git a/common/utils.py b/common/utils.py index 87f81124..253191e8 100644 --- a/common/utils.py +++ b/common/utils.py @@ -1,4 +1,5 @@ import binascii +import enum from copy import deepcopy from functools import wraps @@ -6,7 +7,7 @@ from django.core.exceptions import PermissionDenied from django.db import transaction from graphene import Node -from graphql.execution.base import ResolveInfo +from graphql.type import GraphQLResolveInfo from graphql_relay import from_global_id, to_global_id from kukkuu import __version__ @@ -14,6 +15,41 @@ from kukkuu.settings import REVISION +def is_enum_value(value): + """ + Check if a value is an enum value, e.g. TestEnum.FI + where TestEnum derives from enum.Enum or graphene.Enum. + """ + # Works both for enum.Enum and graphene.Enum + return type(type(value)) == enum.EnumMeta + + +def deepfix_enum_values(data): + """ + Fix enum values recursively in/out of dictionaries, lists, sets, and tuples. + """ + if isinstance(data, dict): + return {deepfix_enum_values(k): deepfix_enum_values(v) for k, v in data.items()} + elif isinstance(data, (list, set, tuple)): + return type(data)(deepfix_enum_values(v) for v in data) + elif is_enum_value(data): + return data.value + else: + return data + + +def map_enums_to_values_in_kwargs(method): + """ + Decorator that maps enums to their values in keyword arguments. + """ + + def wrapper(*args, **kwargs): + fixed_kwargs = deepfix_enum_values(kwargs) + return method(*args, **fixed_kwargs) + + return wrapper + + def update_object(obj, data): if not data: return @@ -38,7 +74,7 @@ def get_api_version(): def get_global_id(obj): - return to_global_id(f"{obj.__class__.__name__}Node", obj.pk) + return to_global_id(f"{obj.__class__.__name__}Node", str(obj.pk)) def get_node_id_from_global_id(global_id, expected_node_name): @@ -83,7 +119,7 @@ def get_obj_if_user_can_administer(info, global_id, expected_obj_type): def context(f): def decorator(func): def wrapper(*args, **kwargs): - info = next(arg for arg in args if isinstance(arg, ResolveInfo)) + info = next(arg for arg in args if isinstance(arg, GraphQLResolveInfo)) return func(info.context, *args, **kwargs) return wrapper diff --git a/docker-compose.yml b/docker-compose.yml index 073c9d2d..3bb21cf2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.7' services: postgres: - image: postgres:11 + image: postgres:12 restart: on-failure environment: POSTGRES_USER: kukkuu diff --git a/events/factories.py b/events/factories.py index f6ef32ef..a0b9f43d 100644 --- a/events/factories.py +++ b/events/factories.py @@ -5,6 +5,7 @@ from django.utils import timezone from children.factories import ChildFactory +from common.mixins import SaveAfterPostGenerationMixin from events.models import Enrolment, Event, EventGroup, Occurrence, TicketSystemPassword from projects.models import Project from venues.factories import VenueFactory @@ -19,6 +20,7 @@ class EventGroupFactory(factory.django.DjangoModelFactory): class Meta: model = EventGroup + skip_postgeneration_save = True # Not needed after factory v4.0.0 class EventFactory(factory.django.DjangoModelFactory): @@ -37,6 +39,7 @@ class EventFactory(factory.django.DjangoModelFactory): class Meta: model = Event + skip_postgeneration_save = True # Not needed after factory v4.0.0 def get_external_ticket_system(): @@ -60,7 +63,9 @@ class LippupisteEventFactory(RandomExternalTicketSystemEventFactory): ticket_system = Event.LIPPUPISTE -class OccurrenceFactory(factory.django.DjangoModelFactory): +class OccurrenceFactory( + SaveAfterPostGenerationMixin, factory.django.DjangoModelFactory +): time = factory.Faker("date_time", tzinfo=pytz.timezone("Europe/Helsinki")) event = factory.SubFactory(EventFactory) venue = factory.SubFactory(VenueFactory) @@ -83,6 +88,7 @@ class EnrolmentFactory(factory.django.DjangoModelFactory): class Meta: model = Enrolment + skip_postgeneration_save = True # Not needed after factory v4.0.0 class TicketSystemPasswordFactory(factory.django.DjangoModelFactory): @@ -92,3 +98,4 @@ class TicketSystemPasswordFactory(factory.django.DjangoModelFactory): class Meta: model = TicketSystemPassword + skip_postgeneration_save = True # Not needed after factory v4.0.0 diff --git a/events/notifications.py b/events/notifications.py index dd328781..7cd39e6b 100644 --- a/events/notifications.py +++ b/events/notifications.py @@ -1,3 +1,5 @@ +from uuid import uuid4 + from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django_ilmoitin.dummy_context import dummy_context @@ -34,15 +36,19 @@ notifications.register(NotificationType.OCCURRENCE_CANCELLED, _("occurrence cancelled")) notifications.register(NotificationType.OCCURRENCE_REMINDER, _("occurrence reminder")) -project = ProjectFactory.build(year=2020) -event = EventFactory.build(project=project) -event_group = EventGroupFactory.build(project=project) -event_with_event_group = EventFactory.build(project=project, event_group=event_group) -venue = VenueFactory.build(project=project) -guardian = GuardianFactory.build() -child = ChildWithGuardianFactory.build(relationship__guardian=guardian, project=project) -occurrence = OccurrenceFactory.build(event=event, venue=venue) -enrolment = EnrolmentFactory.build(occurrence=occurrence, child=child) +project = ProjectFactory.build(pk=uuid4(), year=2020) +event = EventFactory.build(pk=uuid4(), project=project) +event_group = EventGroupFactory.build(pk=uuid4(), project=project) +event_with_event_group = EventFactory.build( + pk=uuid4(), project=project, event_group=event_group +) +venue = VenueFactory.build(pk=uuid4(), project=project) +guardian = GuardianFactory.build(pk=uuid4()) +child = ChildWithGuardianFactory.build( + pk=uuid4(), relationship__guardian=guardian, project=project +) +occurrence = OccurrenceFactory.build(pk=uuid4(), event=event, venue=venue) +enrolment = EnrolmentFactory.build(pk=uuid4(), occurrence=occurrence, child=child) unsubscribe_url = "https://kukkuu-ui-domain/fi/profile/subscriptions?authToken=abc123" common_event_context = { "event": event, diff --git a/events/schema.py b/events/schema.py index e625b13f..e80d91ae 100644 --- a/events/schema.py +++ b/events/schema.py @@ -23,6 +23,7 @@ get_node_id_from_global_id, get_obj_if_user_can_administer, login_required, + map_enums_to_values_in_kwargs, project_user_required, update_object, update_object_with_translations, @@ -643,6 +644,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): event_id = get_node_id_from_global_id(kwargs.get("event_id"), "EventNode") given_passwords = kwargs.get("passwords") @@ -730,6 +732,7 @@ class Input: @classmethod @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): event_id = get_node_id_from_global_id(kwargs.get("event_id"), "EventNode") child_id = get_node_id_from_global_id(kwargs.get("child_id"), "ChildNode") @@ -771,6 +774,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): original_kwargs = deepcopy(kwargs) @@ -839,6 +843,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): original_kwargs = deepcopy(kwargs) @@ -888,6 +893,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): event = get_obj_if_user_can_administer(info, kwargs["id"], Event) event.delete() @@ -907,6 +913,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): occurrence_id = from_global_id(kwargs["occurrence_id"])[1] child_id = from_global_id(kwargs["child_id"])[1] @@ -940,6 +947,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): occurrence_id = from_global_id(kwargs["occurrence_id"])[1] child_id = from_global_id(kwargs["child_id"])[1] @@ -980,6 +988,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): if "attended" not in kwargs: raise DataValidationError('"attended" is required.') @@ -1017,6 +1026,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): original_kwargs = deepcopy(kwargs) @@ -1060,6 +1070,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): original_kwargs = deepcopy(kwargs) occurrence = get_obj_if_user_can_administer(info, kwargs.pop("id"), Occurrence) @@ -1105,6 +1116,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): occurrence = get_obj_if_user_can_administer(info, kwargs["id"], Occurrence) log_text = ( @@ -1127,6 +1139,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): event = get_obj_if_user_can_administer(info, kwargs["id"], Event) user = info.context.user @@ -1170,6 +1183,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user @@ -1201,6 +1215,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): event_group = get_obj_if_user_can_administer(info, kwargs.pop("id"), EventGroup) user = info.context.user @@ -1230,6 +1245,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): event_group = get_obj_if_user_can_administer(info, kwargs["id"], EventGroup) user = info.context.user @@ -1252,6 +1268,7 @@ class Input: @classmethod @project_user_required + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): event_group = get_obj_if_user_can_administer(info, kwargs["id"], EventGroup) user = info.context.user diff --git a/events/tests/snapshots/snap_test_api.py b/events/tests/snapshots/snap_test_api.py index b31fcaed..c32d8915 100644 --- a/events/tests/snapshots/snap_test_api.py +++ b/events/tests/snapshots/snap_test_api.py @@ -230,14 +230,13 @@ "venue": { "translations": [ { - "accessibilityInfo": "Theory go home memory respond improve office. Near increase process truth list pressure. Capital city sing himself yard stuff. Option PM put matter benefit.", - "additionalInfo": """Policy data control as receive. -Teacher subject family around year. Space speak sense person the probably deep. -Social believe policy security score. Turn argue present throw spend prevent.""", + "accessibilityInfo": "Moment strong hand push book and interesting sit. Near increase process truth list pressure. Capital city sing himself yard stuff.", + "additionalInfo": """Sense person the probably. Simply state social believe policy. Score think turn argue present. +Prevent pressure point. Voice radio happen color scene.""", "address": """404 Figueroa Trace -Pollardview, RI 68038""", - "arrivalInstructions": """Significant land especially can quite industry relationship. Which president smile staff country actually generation. Age member whatever open effort clear. -Local challenge box myself last.""", +Pollardview, GA 81371""", + "arrivalInstructions": """Water those notice medical science sort. Front affect senior. Mission network who think significant land especially. +Staff read rule point leg within. Staff country actually generation five training.""", "description": """Page box child care any concern. Defense level church use. Never news behind. Beat at success decade either enter everything. Newspaper force newspaper business himself exist.""", "languageCode": "FI", @@ -399,7 +398,7 @@ "enrolmentCount": 0, "remainingCapacity": 35, "ticketSystem": {"type": "INTERNAL"}, - "time": "1971-04-30T08:38:26+00:00", + "time": "1970-12-29T14:27:50.629900+00:00", "venue": { "translations": [ { @@ -588,7 +587,7 @@ "enrolmentCount": 0, "remainingCapacity": 35, "ticketSystem": {"type": "INTERNAL"}, - "time": "1971-04-30T08:38:26+00:00", + "time": "1970-12-29T14:27:50.629900+00:00", "venue": { "translations": [ { @@ -646,7 +645,7 @@ "enrolmentCount": 0, "remainingCapacity": 35, "ticketSystem": {"type": "INTERNAL"}, - "time": "2014-01-28T14:12:00+00:00", + "time": "2004-11-06T11:51:21.341823+00:00", "venue": { "translations": [ { @@ -695,7 +694,7 @@ "enrolmentCount": 0, "remainingCapacity": 49, "ticketSystem": {"type": "INTERNAL"}, - "time": "2001-12-31T04:39:12+00:00", + "time": "1975-04-24T16:02:40.246613+00:00", "venue": { "translations": [ { @@ -795,11 +794,11 @@ "venue": { "translations": [ { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", @@ -939,11 +938,11 @@ "venue": { "translations": [ { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", @@ -991,8 +990,8 @@ "data": { "occurrences": { "edges": [ - {"node": {"time": "2005-09-07T17:47:05+00:00"}}, - {"node": {"time": "2016-04-25T18:13:39+00:00"}}, + {"node": {"time": "1988-06-02T19:08:36.943149+00:00"}}, + {"node": {"time": "1998-01-14T19:16:11.484399+00:00"}}, ] } } @@ -1064,9 +1063,9 @@ "data": { "occurrences": { "edges": [ - {"node": {"time": "1998-11-25T00:15:59+00:00"}}, - {"node": {"time": "2016-01-01T13:37:17+00:00"}}, - {"node": {"time": "2018-03-30T01:34:27+00:00"}}, + {"node": {"time": "1971-09-03T06:53:22.223652+00:00"}}, + {"node": {"time": "1981-10-20T21:01:52.047649+00:00"}}, + {"node": {"time": "2017-01-24T18:52:46.127687+00:00"}}, ] } } @@ -1100,11 +1099,11 @@ "venue": { "translations": [ { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", @@ -1149,11 +1148,11 @@ "venue": { "translations": [ { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", @@ -1190,11 +1189,11 @@ "venue": { "translations": [ { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", diff --git a/events/tests/snapshots/snap_test_notifications.py b/events/tests/snapshots/snap_test_notifications.py index ae61f7dd..6c64e87f 100644 --- a/events/tests/snapshots/snap_test_notifications.py +++ b/events/tests/snapshots/snap_test_notifications.py @@ -39,7 +39,7 @@ Unsubscribe: http://localhost:3000/fi/profile/subscriptions?authToken=a1b2c3d4e5f6g7h8 Obsoleted: False""", """kukkuu@example.com|['jennifer00@example.com']|Feedback FI| - Event FI: Difficult special respond. + Event FI: Special respond positive cold. Guardian FI: (30, 15) I Should Receive A Notification (jennifer00@example.com) Occurrence: 2020-12-11 23:30:00+00:00 Child: Jason Williams (2021) @@ -47,18 +47,18 @@ Unsubscribe: http://localhost:3000/fi/profile/subscriptions?authToken=a1b2c3d4e5f6g7h8 Obsoleted: False""", """kukkuu@example.com|['aaronlee@example.org']|Feedback FI| - Event FI: History ahead well herself consider fight. + Event FI: Bar wish find system woman why. Whose age feeling speech. Guardian FI: (135, None) I Should Receive A Notification (aaronlee@example.org) Occurrence: 2020-12-11 21:45:00+00:00 Child: Lindsey Baker (2022) Enrolment: 2020-12-11 21:45:00+00:00 Unsubscribe: http://localhost:3000/fi/profile/subscriptions?authToken=a1b2c3d4e5f6g7h8 Obsoleted: False""", - """kukkuu@example.com|['christine87@example.com']|Feedback FI| - Event FI: Sort clearly happy peace really brother. - Guardian FI: (10080, 15) I Should Receive A Notification (christine87@example.com) + """kukkuu@example.com|['dennis27@example.net']|Feedback FI| + Event FI: Other poor specific carry owner sense other. + Guardian FI: (10080, 15) I Should Receive A Notification (dennis27@example.net) Occurrence: 2020-12-05 00:00:00+00:00 - Child: Dana Smith (2023) + Child: Alexa Mcdonald (2018) Enrolment: 2020-12-05 00:00:00+00:00 Unsubscribe: http://localhost:3000/fi/profile/subscriptions?authToken=a1b2c3d4e5f6g7h8 Obsoleted: False""", @@ -96,7 +96,7 @@ snapshots["test_occurrence_cancelled_notification[False] 1"] = [ """kukkuu@example.com|['michellewalker@example.net']|Occurrence cancelled FI| - Event FI: Our very television beat at success decade. + Event FI: Decade either enter everything. Guardian FI: I Should Receive A Notification Valdez (michellewalker@example.net) Occurrence: 2020-12-12 01:00:00+00:00 Child: Richard Hayes (2019) @@ -106,7 +106,7 @@ snapshots["test_occurrence_cancelled_notification[True] 1"] = [ """kukkuu@example.com|['michellewalker@example.net']|Occurrence cancelled FI| - Event FI: Our very television beat at success decade. + Event FI: Decade either enter everything. Guardian FI: I Should Receive A Notification Valdez (michellewalker@example.net) Occurrence: 2020-12-12 01:00:00+00:00 Child: Richard Hayes (2019) @@ -148,7 +148,7 @@ Unsubscribe: http://localhost:3000/fi/profile/subscriptions?authToken=a1b2c3d4e5f6g7h8 Obsoleted: False""", """kukkuu@example.com|['jennifer00@example.com']|Occurrence reminder FI| - Event FI: Difficult special respond. + Event FI: Special respond positive cold. Guardian FI: I Should Receive A Notification (jennifer00@example.com) Occurrence: 2020-12-13 00:00:00+00:00 Child: Jason Williams (2021) @@ -169,7 +169,7 @@ Unsubscribe: http://localhost:3000/fi/profile/subscriptions?authToken=a1b2c3d4e5f6g7h8 Obsoleted: False""", """kukkuu@example.com|['jennifer00@example.com']|Occurrence reminder FI| - Event FI: Difficult special respond. + Event FI: Special respond positive cold. Guardian FI: I Should Receive A Notification (jennifer00@example.com) Occurrence: 2020-12-13 00:00:00+00:00 Child: Jason Williams (2021) diff --git a/events/tests/test_api.py b/events/tests/test_api.py index a8a07f99..6b2698b3 100644 --- a/events/tests/test_api.py +++ b/events/tests/test_api.py @@ -5,6 +5,7 @@ import pytest from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import RequestFactory from django.utils import timezone from django.utils.timezone import now from django.utils.translation import activate @@ -84,12 +85,12 @@ TICKET_SYSTEM_PASSWORD_NOTHING_TO_IMPORT_ERROR, TICKET_SYSTEM_URL_MISSING_ERROR, ) -from kukkuu.exceptions import EnrolmentReferenceIdDoesNotExist, QueryTooDeepError -from kukkuu.schema import schema -from kukkuu.views import DepthAnalysisBackend +from kukkuu.exceptions import EnrolmentReferenceIdDoesNotExist +from kukkuu.views import SentryGraphQLView from projects.factories import ProjectFactory from projects.models import Project from subscriptions.factories import FreeSpotNotificationSubscriptionFactory +from users.factories import GuardianFactory, UserFactory from venues.factories import VenueFactory @@ -917,9 +918,9 @@ def test_enrol_limit_reached( enrolled_amount + 1, time=timezone.now(), event__published_at=timezone.now(), - event__ticket_system=Event.TICKETMASTER - if use_ticket_system_passwords - else Event.INTERNAL, + event__ticket_system=( + Event.TICKETMASTER if use_ticket_system_passwords else Event.INTERNAL + ), ) for i in range(enrolled_amount): # Previous enrolments have been with TicketSystemPasswords @@ -1450,30 +1451,44 @@ def test_child_enrol_occurence_from_different_project( assert_match_error_code(executed, INELIGIBLE_OCCURRENCE_ENROLMENT) -def test_api_query_depth(snapshot, guardian_api_client, event): - # Depth 6 +@pytest.mark.parametrize( + "max_query_depth,expected_error_message", + [ + (3, "'Events' exceeds maximum operation depth of 3."), + (4, None), + ], +) +def test_api_query_depth(settings, event, max_query_depth, expected_error_message): + settings.KUKKUU_QUERY_MAX_DEPTH = max_query_depth + user = UserFactory(guardian=GuardianFactory()) + request = RequestFactory().post("/graphql") + request.user = user + query = """ query Events { events { edges { node { - project{ - events{ - name - } + project { + name } } } } } """ - backend = DepthAnalysisBackend(max_depth=5) - with pytest.raises(QueryTooDeepError): - backend.document_from_string(schema=schema, document_string=query) - backend = DepthAnalysisBackend(max_depth=6) - document = backend.document_from_string(schema=schema, document_string=query) - assert document is not None + view = SentryGraphQLView() + result = view.execute_graphql_request( + request=request, + data=None, + variables=None, + operation_name="Events", + query=query, + ) + + assert (result.errors is None) == (expected_error_message is None) + assert str(expected_error_message) in str(result.errors) @pytest.mark.parametrize("expected_attended", [True, None]) @@ -1670,19 +1685,21 @@ def test_events_and_event_groups_query_upcoming_filter( time=not_visible, event__name="Not visible", event__published_at=not_visible, - event__event_group=EventGroupFactory( - name="Not visible", published_at=not_visible - ) - if has_event_group - else None, + event__event_group=( + EventGroupFactory(name="Not visible", published_at=not_visible) + if has_event_group + else None + ), ) OccurrenceFactory.create( time=future, event__name="In the future", event__published_at=now(), - event__event_group=EventGroupFactory(name="In the future", published_at=now()) - if has_event_group - else None, + event__event_group=( + EventGroupFactory(name="In the future", published_at=now()) + if has_event_group + else None + ), ) executed = guardian_api_client.execute( diff --git a/gdpr/service.py b/gdpr/service.py index 199a78a0..5ebb01b6 100644 --- a/gdpr/service.py +++ b/gdpr/service.py @@ -63,7 +63,7 @@ def clear_data(user: "UserType", dry_run: bool) -> Optional[ErrorResponse]: FreeSpotNotificationSubscription.objects.filter(child=child).delete() except Guardian.DoesNotExist: - logger.warn( + logger.warning( "Could not call 'clear_gdpr_sensitive_data_fields' for a guardian object, " f"since there is no guardian linked to the user {user.uuid}." ) diff --git a/gdpr/tests/snapshots/snap_test_gdpr_api.py b/gdpr/tests/snapshots/snap_test_gdpr_api.py index 6d0d7b17..4c4ea5e8 100644 --- a/gdpr/tests/snapshots/snap_test_gdpr_api.py +++ b/gdpr/tests/snapshots/snap_test_gdpr_api.py @@ -23,7 +23,7 @@ {"key": "FIRST_NAME", "value": "Michael"}, {"key": "LAST_NAME", "value": "Patton"}, {"key": "EMAIL", "value": "michellewalker@example.net"}, - {"key": "PHONE_NUMBER", "value": "355.777.6712x406"}, + {"key": "PHONE_NUMBER", "value": "235.857.7767x124"}, {"key": "HAS_ACCEPTED_COMMUNICATION", "value": False}, { "children": [ @@ -44,7 +44,7 @@ "children": [ { "key": "TIME", - "value": "1986-01-08T08:26:19+00:00", + "value": "1981-12-29T15:28:25.080157+00:00", }, { "children": [ @@ -52,7 +52,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Hospital number lose least then. Beyond than trial western.", + "fi": "Increase thank certainly again thought summer.", "sv": "", }, }, @@ -99,7 +99,7 @@ "value": { "en": "", "fi": """779 Kevin Isle Suite 550 -Jonathanburgh, AZ 30120""", +Jonathanburgh, AK 06327""", "sv": "", }, }, @@ -122,7 +122,7 @@ "children": [ { "key": "TIME", - "value": "1974-05-30T15:30:48+00:00", + "value": "1973-04-20T19:20:51.908992+00:00", }, { "children": [ @@ -130,7 +130,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Include and individual effort indeed discuss challenge school.", + "fi": "Democratic focus significant kind various laugh.", "sv": "", }, }, @@ -168,7 +168,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Teach manager movie owner.", + "fi": "Medical check word style also. Question national throw three.", "sv": "", }, }, @@ -176,8 +176,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """40241 Alexander Fields -West Rubenbury, MS 61495""", + "fi": """Unit 7738 Box 4455 +DPO AE 93387""", "sv": "", }, }, @@ -200,7 +200,7 @@ "children": [ { "key": "TIME", - "value": "2011-01-05T08:35:11+00:00", + "value": "1983-12-14T10:41:44.139987+00:00", }, { "children": [ @@ -208,7 +208,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Enjoy when one wonder fund nor white.", + "fi": "Policy sport available.", "sv": "", }, }, @@ -246,7 +246,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Available policy physical heavy scientist.", + "fi": "Person material alone throughout investment check increase.", "sv": "", }, }, @@ -254,8 +254,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """37849 Alejandra Rapid Apt. 294 -South Mitchell, NC 30891""", + "fi": """18631 David Wells Apt. 018 +South Mikeburgh, NH 37426""", "sv": "", }, }, @@ -278,7 +278,7 @@ "children": [ { "key": "TIME", - "value": "1972-02-01T22:06:34+00:00", + "value": "1989-06-15T22:58:14.498478+00:00", }, { "children": [ @@ -286,7 +286,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Affect money school military statement.", + "fi": "Month score father middle brother station physical very.", "sv": "", }, }, @@ -324,7 +324,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Because treatment sense left.", + "fi": "Moment apply president unit positive.", "sv": "", }, }, @@ -332,8 +332,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """812 Russell Hollow Suite 792 -Jessicatown, MN 66988""", + "fi": """7921 Fischer Ranch +Port Timothy, MI 90086""", "sv": "", }, }, @@ -356,7 +356,7 @@ "children": [ { "key": "TIME", - "value": "2014-12-23T16:25:42+00:00", + "value": "1980-04-10T06:06:28.089342+00:00", }, { "children": [ @@ -364,7 +364,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Wife message focus between.", + "fi": "Suggest claim PM they. South blue reach ask.", "sv": "", }, }, @@ -402,7 +402,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Offer goal unit country call thus. By final design degree.", + "fi": "Process who catch bad. Seven process determine call.", "sv": "", }, }, @@ -410,8 +410,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """04194 Frederick Mill -Daniellechester, KS 04855""", + "fi": """1235 Eric Route Suite 932 +New Manuelshire, PR 28866""", "sv": "", }, }, @@ -432,14 +432,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "F@47o(Up&6"}, + {"key": "VALUE", "value": "jR1NWxHl(x"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Court cultural speak condition her station must.", + "fi": "Half state four hear trouble among face three.", "sv": "", }, }, @@ -481,14 +481,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "$jNvGUhi+8"}, + {"key": "VALUE", "value": ")1q1TpGzpE"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "I time bring third person. Resource certainly past pull.", + "fi": "Where official agree order just raise.", "sv": "", }, }, @@ -530,14 +530,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "0GX@55Bp(6"}, + {"key": "VALUE", "value": "+2(bkVx7(8"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Bed add should computer. Degree share across.", + "fi": "Many happy better agree concern often almost.", "sv": "", }, }, @@ -579,14 +579,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "PP$15MkwcE"}, + {"key": "VALUE", "value": "&KXlatF20E"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Yeah question he risk military result building.", + "fi": "Program song couple central each color.", "sv": "", }, }, @@ -628,14 +628,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "^4aK6a^8D8"}, + {"key": "VALUE", "value": "_O2oUJuK@V"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Support apply mind arrive wish.", + "fi": "Bit other she chair cover whether.", "sv": "", }, }, diff --git a/importers/tests/snapshots/snap_test_notification_importer.py b/importers/tests/snapshots/snap_test_notification_importer.py index 41bd0999..5b3b1903 100644 --- a/importers/tests/snapshots/snap_test_notification_importer.py +++ b/importers/tests/snapshots/snap_test_notification_importer.py @@ -16,6 +16,6 @@ ] = """event_published|event_published fi original subject|event_published en original subject|event_published sv original subject|event_published fi original body_text|event_published en original body_text|event_published sv original body_text||| occurrence_enrolment|occurrence_enrolment fi updated subject|occurrence_enrolment en updated subject|occurrence_enrolment sv updated subject|occurrence_enrolment fi updated body_text|occurrence_enrolment en updated body_text|occurrence_enrolment sv updated body_text|||""" -snapshots[ - "test_update_notifications 1" -] = "event_published|event_published fi updated subject|event_published en updated subject|event_published sv updated subject|event_published fi updated body_text|event_published en updated body_text|event_published sv updated body_text|||" +snapshots["test_update_notifications 1"] = ( + "event_published|event_published fi updated subject|event_published en updated subject|event_published sv updated subject|event_published fi updated body_text|event_published en updated body_text|event_published sv updated body_text|||" +) diff --git a/kukkuu/consts.py b/kukkuu/consts.py index e059f96c..17f8fb3d 100644 --- a/kukkuu/consts.py +++ b/kukkuu/consts.py @@ -8,7 +8,6 @@ OBJECT_DOES_NOT_EXIST_ERROR = "OBJECT_DOES_NOT_EXIST_ERROR" DATA_VALIDATION_ERROR = "DATA_VALIDATION_ERROR" API_USAGE_ERROR = "API_USAGE_ERROR" -QUERY_TOO_DEEP_ERROR = "QUERY_TOO_DEEP_ERROR" # Kukkuu specific errors MAX_NUMBER_OF_CHILDREN_PER_GUARDIAN_ERROR = "MAX_NUMBER_OF_CHILDREN_PER_GUARDIAN_ERROR" diff --git a/kukkuu/exceptions.py b/kukkuu/exceptions.py index b5a0f930..eedec93d 100644 --- a/kukkuu/exceptions.py +++ b/kukkuu/exceptions.py @@ -79,10 +79,6 @@ class IneligibleOccurrenceEnrolment(KukkuuGraphQLError): """Ineligible to enrol event""" -class QueryTooDeepError(KukkuuGraphQLError): - """Query depth exceeded settings.KUKKUU_QUERY_MAX_DEPTH""" - - class InvalidEmailFormatError(KukkuuGraphQLError): """Invalid email format""" diff --git a/kukkuu/urls.py b/kukkuu/urls.py index 7953d6e1..34f47e65 100644 --- a/kukkuu/urls.py +++ b/kukkuu/urls.py @@ -9,16 +9,13 @@ from rest_framework import routers from common.utils import get_api_version -from kukkuu.views import DepthAnalysisBackend, SentryGraphQLView +from kukkuu.views import SentryGraphQLView from reports.api import ChildViewSet admin.site.index_title = _("Kukkuu backend {api_version}").format( api_version=get_api_version() ) -gql_backend = DepthAnalysisBackend(max_depth=settings.KUKKUU_QUERY_MAX_DEPTH) - - router = routers.DefaultRouter() router.register(r"children", ChildViewSet) @@ -29,7 +26,7 @@ r"^graphql/?$", csrf_exempt( SentryGraphQLView.as_view( - graphiql=settings.ENABLE_GRAPHIQL or settings.DEBUG, backend=gql_backend + graphiql=settings.ENABLE_GRAPHIQL or settings.DEBUG ) ), ), diff --git a/kukkuu/views.py b/kukkuu/views.py index 294c4908..94a7c31b 100644 --- a/kukkuu/views.py +++ b/kukkuu/views.py @@ -1,14 +1,11 @@ +from typing import Optional + import sentry_sdk +from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from graphene.validation import depth_limit_validator from graphene_file_upload.django import FileUploadGraphQLView -from graphql.backend.core import GraphQLCoreBackend -from graphql.language.ast import ( - Field, - FragmentDefinition, - FragmentSpread, - InlineFragment, - OperationDefinition, -) +from graphql import ExecutionResult from helusers.oidc import AuthenticationError from kukkuu.consts import ( @@ -36,7 +33,6 @@ PAST_ENROLMENT_ERROR, PAST_OCCURRENCE_ERROR, PERMISSION_DENIED_ERROR, - QUERY_TOO_DEEP_ERROR, SINGLE_EVENTS_DISALLOWED_ERROR, TICKET_SYSTEM_PASSWORD_ALREADY_ASSIGNED_ERROR, TICKET_SYSTEM_PASSWORD_NOTHING_TO_IMPORT_ERROR, @@ -66,7 +62,6 @@ OccurrenceYearMismatchError, PastEnrolmentError, PastOccurrenceError, - QueryTooDeepError, SingleEventsDisallowedError, TicketSystemPasswordAlreadyAssignedError, TicketSystemPasswordNothingToImportError, @@ -75,12 +70,12 @@ ) error_codes_shared = { + type(None): GENERAL_ERROR, Exception: GENERAL_ERROR, ObjectDoesNotExistError: OBJECT_DOES_NOT_EXIST_ERROR, PermissionDenied: PERMISSION_DENIED_ERROR, ApiUsageError: API_USAGE_ERROR, DataValidationError: DATA_VALIDATION_ERROR, - QueryTooDeepError: QUERY_TOO_DEEP_ERROR, InvalidEmailFormatError: INVALID_EMAIL_FORMAT_ERROR, } @@ -123,89 +118,22 @@ error_codes = {**error_codes_shared, **error_codes_kukkuu} -def get_fragments(definitions): - return { - definition.name.value: definition - for definition in definitions - if isinstance(definition, FragmentDefinition) - } - - -def get_queries_and_mutations(definitions): - return [ - definition - for definition in definitions - if isinstance(definition, OperationDefinition) - ] - - -def measure_depth(node, fragments): - if isinstance(node, FragmentSpread): - fragment = fragments.get(node.name.value) - return measure_depth(node=fragment, fragments=fragments) - - elif isinstance(node, Field): - if node.name.value.lower() in ["__schema", "__introspection"]: - return 0 - - if not node.selection_set: - return 1 - - depths = [] - for selection in node.selection_set.selections: - depth = measure_depth(node=selection, fragments=fragments) - depths.append(depth) - return 1 + max(depths) - - elif ( - isinstance(node, FragmentDefinition) - or isinstance(node, OperationDefinition) - or isinstance(node, InlineFragment) - ): - depths = [] - for selection in node.selection_set.selections: - depth = measure_depth(node=selection, fragments=fragments) - depths.append(depth) - return max(depths) - else: - raise Exception("Unknown node") - - -def check_max_depth(max_depth, document): - fragments = get_fragments(document.definitions) - queries = get_queries_and_mutations(document.definitions) - - for query in queries: - depth = measure_depth(query, fragments) - if depth > max_depth: - raise QueryTooDeepError( - "Query is too deep - its depth is {} but the max depth is {}".format( - depth, max_depth - ) - ) - - -# Customize GraphQL Backend inspired by -# https://github.com/manesioz/secure-graphene/pull/1/files -class DepthAnalysisBackend(GraphQLCoreBackend): - def __init__(self, max_depth, executor=None): - super().__init__(executor=executor) - self.max_depth = max_depth - - def document_from_string(self, schema, document_string): - document = super().document_from_string(schema, document_string) - - check_max_depth(max_depth=self.max_depth, document=document.document_ast) - - return document - - class SentryGraphQLView(FileUploadGraphQLView): + def __init__(self, *args, **kwargs): + super().__init__( + validation_rules=[ + depth_limit_validator(max_depth=settings.KUKKUU_QUERY_MAX_DEPTH) + ], + *args, + **kwargs + ) + def execute_graphql_request(self, request, data, query, *args, **kwargs): """Extract any exceptions and send some of them to Sentry""" - result = super().execute_graphql_request(request, data, query, *args, **kwargs) - # If 'invalid' is set, it's a bad request - if result and result.errors and not result.invalid: + result: Optional[ExecutionResult] = super().execute_graphql_request( + request, data, query, *args, **kwargs + ) + if result and result.errors: errors_to_sentry = [ e for e in result.errors @@ -240,6 +168,7 @@ def get_error_code(exception): error_code = get_error_code(error.original_error.__class__) except AttributeError: error_code = GENERAL_ERROR + formatted_error = super(SentryGraphQLView, SentryGraphQLView).format_error( error ) diff --git a/messaging/factories.py b/messaging/factories.py index ba61c042..c024e9d0 100644 --- a/messaging/factories.py +++ b/messaging/factories.py @@ -13,3 +13,4 @@ class MessageFactory(factory.django.DjangoModelFactory): class Meta: model = Message + skip_postgeneration_save = True # Not needed after factory v4.0.0 diff --git a/messaging/schema.py b/messaging/schema.py index 13825556..0035001b 100644 --- a/messaging/schema.py +++ b/messaging/schema.py @@ -11,6 +11,7 @@ from common.schema import LanguageEnum from common.utils import ( get_obj_if_user_can_administer, + map_enums_to_values_in_kwargs, project_user_required, update_object_with_translations, ) @@ -68,7 +69,7 @@ class Meta: "recipient_count", "translations", ) - filter_fields = ("project_id", "protocol", "occurrences") + filter_fields = ("project_id",) @classmethod @project_user_required @@ -116,6 +117,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): send_directly = kwargs.pop("send_directly", False) data = deepcopy(kwargs) @@ -168,6 +170,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): data = deepcopy(kwargs) message = get_obj_if_user_can_administer(info, data.pop("id"), Message) @@ -247,6 +250,7 @@ class Input: @classmethod @project_user_required + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): message = get_obj_if_user_can_administer(info, kwargs["id"], Message) @@ -268,6 +272,7 @@ class Input: @classmethod @project_user_required + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): message = get_obj_if_user_can_administer(info, kwargs["id"], Message) if message.sent_at: diff --git a/messaging/tests/snapshots/snap_test_api.py b/messaging/tests/snapshots/snap_test_api.py index 17a05d75..61050414 100644 --- a/messaging/tests/snapshots/snap_test_api.py +++ b/messaging/tests/snapshots/snap_test_api.py @@ -59,9 +59,9 @@ "event": {"name": "Poor lawyer treat free heart significant."}, "occurrences": { "edges": [ - {"node": {"time": "1971-04-30T08:38:26+00:00"}}, - {"node": {"time": "1995-11-20T14:28:21+00:00"}}, - {"node": {"time": "2006-12-28T22:44:32+00:00"}}, + {"node": {"time": "1970-12-29T14:27:50.629900+00:00"}}, + {"node": {"time": "1977-02-25T23:14:59.889967+00:00"}}, + {"node": {"time": "1997-09-11T01:32:17.610651+00:00"}}, ] }, "project": {"year": 2020}, @@ -126,75 +126,6 @@ } } -snapshots["test_messages_query_occurrences_filter 1"] = { - "data": { - "messages": { - "edges": [ - { - "node": { - "bodyText": "Free heart significant machine try. President compare room hotel town south among. Fall long respond draw military dog. Increase thank certainly again thought summer. Beyond than trial western.", - "event": None, - "occurrences": { - "edges": [{"node": {"time": "1984-03-20T05:51:53+00:00"}}] - }, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Him question stay.", - } - }, - { - "node": { - "bodyText": "Child care any. Minute defense level church. Alone our very television beat at success.", - "event": None, - "occurrences": { - "edges": [{"node": {"time": "1984-03-20T05:51:53+00:00"}}] - }, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Business hot PM.", - } - }, - { - "node": { - "bodyText": "Focus significant kind. Laugh smile behavior whom gas. Significant minute rest. Special far magazine.", - "event": None, - "occurrences": { - "edges": [{"node": {"time": "2014-01-31T20:11:10+00:00"}}] - }, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Attention practice.", - } - }, - { - "node": { - "bodyText": "Conference carry factor front Mr amount conference thing. Positive cold start rest tonight including believe. Respond range bit college question. Stop treatment suggest. Sometimes growth check court.", - "event": None, - "occurrences": { - "edges": [{"node": {"time": "2014-01-31T20:11:10+00:00"}}] - }, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Past life thus.", - } - }, - ] - } - } -} - snapshots["test_messages_query_project_filter 1"] = { "data": { "messages": { @@ -217,154 +148,6 @@ } } -snapshots["test_messages_query_protocol_filter[email] 1"] = { - "data": { - "messages": { - "edges": [ - { - "node": { - "bodyText": "Which president smile staff country actually generation. Age member whatever open effort clear. Difficult look can. Care figure mention wrong when lead involve. Event lay yes policy data control as receive.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Partner exist true.", - } - }, - { - "node": { - "bodyText": "Girl middle same space speak. Person the probably deep center develop character situation. Score think turn argue present.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Decade every town.", - } - }, - { - "node": { - "bodyText": "Voice radio happen color scene. Create state rock only. Several behavior media career decide season mission TV. Work head table city central deep response. Through resource professional debate produce college able.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Walk fish teach.", - } - }, - { - "node": { - "bodyText": "Sea something western research. Candidate century network bar hear quite wonder. Up always sport return. Light a point charge stand store. Generation able take food share.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Product issue along.", - } - }, - { - "node": { - "bodyText": "Site chance of performance. Hand cause receive kitchen middle. Step able last in able local garden. Nearly gun two born land military first.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "EMAIL", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Discussion remain.", - } - }, - ] - } - } -} - -snapshots["test_messages_query_protocol_filter[sms] 1"] = { - "data": { - "messages": { - "edges": [ - { - "node": { - "bodyText": "Free heart significant machine try. President compare room hotel town south among. Fall long respond draw military dog. Increase thank certainly again thought summer. Beyond than trial western.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "SMS", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Him question stay.", - } - }, - { - "node": { - "bodyText": "Child care any. Minute defense level church. Alone our very television beat at success.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "SMS", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Business hot PM.", - } - }, - { - "node": { - "bodyText": "Enter everything history remember stay public high. Exist shoulder write century. Never skill down subject town. According hard enough watch condition like lay.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "SMS", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Detail.", - } - }, - { - "node": { - "bodyText": "From daughter order stay sign discover eight. Toward scientist service wonder everything. Middle moment strong hand push book and interesting. Near increase process truth list pressure. Capital city sing himself yard stuff.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "SMS", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Their tell.", - } - }, - { - "node": { - "bodyText": "Base may middle good father boy economy. Fly discussion huge get this success. Science sort already name. Senior number scene today friend maintain marriage.", - "event": None, - "occurrences": {"edges": []}, - "project": {"year": 2020}, - "protocol": "SMS", - "recipientCount": 0, - "recipientSelection": "ALL", - "sentAt": None, - "subject": "Even perhaps that.", - } - }, - ] - } - } -} - snapshots["test_send_message 1"] = { "data": { "sendMessage": { diff --git a/messaging/tests/test_api.py b/messaging/tests/test_api.py index 75a1ac0e..1439ae9d 100644 --- a/messaging/tests/test_api.py +++ b/messaging/tests/test_api.py @@ -20,8 +20,8 @@ from messaging.models import Message MESSAGES_QUERY = """ -query Messages($projectId: ID, $protocol: String, $occurrences: [ID]) { - messages(projectId: $projectId, protocol: $protocol, occurrences: $occurrences) { +query Messages($projectId: ID) { + messages(projectId: $projectId) { edges { node { project { @@ -76,47 +76,6 @@ def test_messages_query_project_filter( snapshot.assert_match(executed) -@pytest.mark.parametrize("protocol", [Message.SMS, Message.EMAIL]) -def test_messages_query_protocol_filter( - protocol, snapshot, project_user_api_client, project -): - MessageFactory.create_batch(5, protocol=Message.SMS, project=project) - MessageFactory.create_batch(5, protocol=Message.EMAIL, project=project) - - executed = project_user_api_client.execute( - MESSAGES_QUERY, variables={"protocol": protocol} - ) - assert all( - edge["node"]["protocol"] == protocol.upper() - for edge in executed["data"]["messages"]["edges"] - ) - assert len(executed["data"]["messages"]["edges"]) == 5 - snapshot.assert_match(executed) - - -def test_messages_query_occurrences_filter(snapshot, project_user_api_client, project): - occurrence1 = OccurrenceFactory( - messages=MessageFactory.create_batch(2, project=project) - ) - occurrence2 = OccurrenceFactory( - messages=MessageFactory.create_batch(2, project=project) - ) - OccurrenceFactory(messages=MessageFactory.create_batch(2, project=project)) - - assert Message.objects.count() == 6 - - executed = project_user_api_client.execute( - MESSAGES_QUERY, - variables={ - "occurrences": [ - get_global_id(occurrence) for occurrence in [occurrence1, occurrence2] - ] - }, - ) - assert len(executed["data"]["messages"]["edges"]) == 4 - snapshot.assert_match(executed) - - MESSAGE_QUERY = """ query Message($id: ID!) { message(id: $id){ diff --git a/projects/factories.py b/projects/factories.py index c3ac84d9..e7c2407a 100644 --- a/projects/factories.py +++ b/projects/factories.py @@ -8,3 +8,4 @@ class ProjectFactory(factory.django.DjangoModelFactory): class Meta: model = Project + skip_postgeneration_save = True # Not needed after factory v4.0.0 diff --git a/requirements-dev.txt b/requirements-dev.txt index 61ca8b09..e8c7c350 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,138 +4,141 @@ # # pip-compile requirements-dev.in # -appnope==0.1.2 - # via ipython -attrs==21.2.0 - # via - # -c requirements.txt - # pytest -backcall==0.2.0 - # via ipython -black==23.3.0 +asttokens==2.4.1 + # via stack-data +black==24.4.2 # via -r requirements-dev.in -certifi==2021.10.8 +certifi==2024.7.4 # via # -c requirements.txt # requests -charset-normalizer==2.0.6 +charset-normalizer==3.3.2 # via # -c requirements.txt # requests -click==8.1.3 +click==8.1.7 # via black -coverage[toml]==6.0.1 +coverage[toml]==7.6.0 # via pytest-cov -decorator==5.1.0 +decorator==5.1.1 # via ipython +exceptiongroup==1.2.2 + # via + # ipython + # pytest +executing==2.0.1 + # via stack-data fastdiff==0.3.0 # via snapshottest -flake8==6.1.0 +flake8==7.1.0 # via -r requirements-dev.in -freezegun==1.1.0 +freezegun==1.5.1 # via -r requirements-dev.in -idna==3.2 +idna==3.7 # via # -c requirements.txt # requests -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest -ipython==7.28.0 +ipython==8.18.1 # via -r requirements-dev.in -isort==5.12.0 +isort==5.13.2 # via -r requirements-dev.in -jedi==0.18.0 +jedi==0.19.1 # via ipython -matplotlib-inline==0.1.3 +matplotlib-inline==0.1.7 # via ipython mccabe==0.7.0 # via flake8 -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via black -packaging==24.0 +packaging==24.1 # via # -c requirements.txt # black # pytest -parso==0.8.2 +parso==0.8.4 # via jedi -pathspec==0.9.0 +pathspec==0.12.1 # via black -pexpect==4.8.0 - # via ipython -pickleshare==0.7.5 +pexpect==4.9.0 # via ipython -platformdirs==2.5.2 +platformdirs==4.2.2 # via black -pluggy==1.0.0 +pluggy==1.5.0 # via pytest -prompt-toolkit==3.0.20 +prompt-toolkit==3.0.47 # via ipython ptyprocess==0.7.0 # via pexpect -py==1.10.0 - # via pytest -pycodestyle==2.11.1 +pure-eval==0.2.2 + # via stack-data +pycodestyle==2.12.0 # via flake8 -pyflakes==3.1.0 +pyflakes==3.2.0 # via flake8 -pygments==2.10.0 +pygments==2.18.0 # via ipython -pytest==6.2.5 +pytest==8.2.2 # via # -r requirements-dev.in # pytest-cov # pytest-django -pytest-cov==3.0.0 +pytest-cov==5.0.0 # via -r requirements-dev.in -pytest-django==4.4.0 +pytest-django==4.8.0 # via -r requirements-dev.in -python-dateutil==2.8.2 +python-dateutil==2.9.0.post0 # via # -c requirements.txt # freezegun -requests==2.26.0 +pyyaml==6.0.1 + # via + # -c requirements.txt + # responses +requests==2.32.3 # via # -c requirements.txt # requests-mock # responses requests-mock==1.12.1 # via -r requirements-dev.in -responses==0.14.0 +responses==0.25.3 # via -r requirements-dev.in six==1.16.0 # via # -c requirements.txt + # asttokens # python-dateutil - # responses # snapshottest snapshottest==0.6.0 # via -r requirements-dev.in -termcolor==1.1.0 +stack-data==0.6.3 + # via ipython +termcolor==2.4.0 # via snapshottest -toml==0.10.2 - # via pytest tomli==2.0.1 # via # black # coverage -traitlets==5.1.0 + # pytest +traitlets==5.14.3 # via # ipython # matplotlib-inline -typing-extensions==4.10.0 - # via black -urllib3==1.26.7 +typing-extensions==4.12.2 + # via + # -c requirements.txt + # black + # ipython +urllib3==2.2.2 # via # -c requirements.txt # requests # responses -wasmer==1.0.0 +wasmer==1.1.0 # via fastdiff -wasmer-compiler-cranelift==1.0.0 +wasmer-compiler-cranelift==1.1.0 # via fastdiff -wcwidth==0.2.5 +wcwidth==0.2.13 # via prompt-toolkit - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements-prod.txt b/requirements-prod.txt index 1e9ea827..208ab27d 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile -# To update, run: +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: # # pip-compile requirements-prod.in # -uwsgi==2.0.18 +uwsgi==2.0.26 # via -r requirements-prod.in diff --git a/requirements.in b/requirements.in index f249e544..66ebb13c 100644 --- a/requirements.in +++ b/requirements.in @@ -1,8 +1,7 @@ -Django +Django~=4.2 django-cors-headers django-environ -# when helusers is upgraded, check if the expired token thingy in kukkuu.graphene.JWTMiddleware could be improved (and does it even still work) -django-helusers==0.11.0 +django-helusers django-ilmoitin django-parler django-cleanup @@ -15,6 +14,7 @@ psycopg2 sentry-sdk Pillow pycountry +pytz django-guardian requests djangorestframework diff --git a/requirements.txt b/requirements.txt index b87b2042..838cb4b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,39 +4,44 @@ # # pip-compile requirements.in # -aniso8601==7.0.0 +aniso8601==9.0.1 # via graphene -asgiref==3.4.1 - # via django -attrs==21.2.0 - # via jsonschema -authlib==1.3.0 +asgiref==3.8.1 + # via + # django + # django-cors-headers +attrs==23.2.0 + # via + # jsonschema + # referencing +authlib==1.3.1 # via drf-oidc-auth -azure-core==1.19.0 - # via azure-storage-blob -azure-storage-blob==12.9.0 +azure-core==1.30.2 + # via + # azure-storage-blob + # django-storages +azure-storage-blob==12.20.0 # via django-storages -cachetools==4.2.4 +cachetools==5.4.0 # via # django-helusers # google-auth -certifi==2021.10.8 +certifi==2024.7.4 # via - # msrest # requests # sentry-sdk -cffi==1.14.6 +cffi==1.16.0 # via cryptography -charset-normalizer==2.0.6 +charset-normalizer==3.3.2 # via requests -cryptography==35.0.0 +cryptography==42.0.8 # via # authlib # azure-storage-blob # drf-oidc-auth deprecation==2.1.0 # via django-helusers -django==3.2.11 +django==4.2.14 # via # -r requirements.in # django-anymail @@ -46,39 +51,40 @@ django==3.2.11 # django-helusers # django-ilmoitin # django-mailer + # django-parler # django-storages # djangorestframework # drf-oidc-auth # drf-spectacular # graphene-django # helsinki-profile-gdpr-api -django-anymail==8.4 +django-anymail==11.0.1 # via django-ilmoitin -django-cleanup==5.2.0 +django-cleanup==8.1.0 # via -r requirements.in -django-cors-headers==3.10.0 +django-cors-headers==4.4.0 # via -r requirements.in -django-environ==0.7.0 +django-environ==0.11.2 # via -r requirements.in -django-filter==21.1 +django-filter==24.2 # via -r requirements.in django-guardian==2.4.0 # via -r requirements.in -django-helusers==0.11.0 +django-helusers==0.13.0 # via # -r requirements.in # helsinki-profile-gdpr-api -django-ilmoitin==0.6.0 +django-ilmoitin==0.7.0 # via -r requirements.in -django-mailer==2.1 +django-mailer==2.3.2 # via django-ilmoitin -django-parler==2.2 +django-parler==2.3 # via # -r requirements.in # django-ilmoitin -django-storages[azure,google]==1.12.1 +django-storages[azure,google]==1.14.4 # via -r requirements.in -djangorestframework==3.12.4 +djangorestframework==3.15.2 # via # -r requirements.in # drf-oidc-auth @@ -86,112 +92,117 @@ djangorestframework==3.12.4 # helsinki-profile-gdpr-api drf-oidc-auth==3.0.0 # via helsinki-profile-gdpr-api -drf-spectacular==0.21.0 +drf-spectacular==0.27.2 # via -r requirements.in -ecdsa==0.17.0 +ecdsa==0.19.0 # via python-jose -factory-boy==3.2.0 +factory-boy==3.3.0 # via -r requirements.in -faker==9.3.1 +faker==26.0.0 # via factory-boy -google-api-core==2.1.0 +google-api-core==2.19.1 # via # google-cloud-core # google-cloud-storage -google-auth==2.3.0 +google-auth==2.32.0 # via # google-api-core # google-cloud-core # google-cloud-storage -google-cloud-core==2.1.0 +google-cloud-core==2.4.1 # via google-cloud-storage -google-cloud-storage==1.42.3 +google-cloud-storage==2.17.0 # via django-storages -google-crc32c==1.3.0 - # via google-resumable-media -google-resumable-media==2.0.3 +google-crc32c==1.5.0 + # via + # google-cloud-storage + # google-resumable-media +google-resumable-media==2.7.1 # via google-cloud-storage -googleapis-common-protos==1.53.0 +googleapis-common-protos==1.63.2 # via google-api-core -graphene==2.1.9 +graphene==3.3 # via graphene-django -graphene-django==2.15.0 +graphene-django==3.2.2 # via -r requirements.in graphene-file-upload==1.3.0 # via -r requirements.in -graphql-core==2.3.2 +graphql-core==3.2.3 # via # graphene # graphene-django # graphql-relay -graphql-relay==2.0.1 - # via graphene +graphql-relay==3.2.0 + # via + # graphene + # graphene-django hashids==1.3.1 # via -r requirements.in helsinki-profile-gdpr-api==0.2.0 # via -r requirements.in -idna==3.2 +idna==3.7 # via requests -importlib-metadata==7.1.0 +importlib-metadata==8.0.0 # via markdown inflection==0.5.1 # via drf-spectacular -isodate==0.6.0 - # via msrest -jinja2==3.0.2 +isodate==0.6.1 + # via azure-storage-blob +jinja2==3.1.4 # via django-ilmoitin -jsonschema==4.2.1 +jsonschema==4.23.0 # via drf-spectacular +jsonschema-specifications==2023.12.1 + # via jsonschema lockfile==0.12.2 # via django-mailer markdown==3.6 # via -r requirements.in -markupsafe==2.0.1 +markupsafe==2.1.5 # via jinja2 -msrest==0.6.21 - # via azure-storage-blob -oauthlib==3.1.1 - # via requests-oauthlib -packaging==24.0 +packaging==24.1 # via deprecation -pillow==8.3.2 +pillow==10.4.0 # via -r requirements.in promise==2.3 - # via - # graphene-django - # graphql-core - # graphql-relay -protobuf==3.18.1 + # via graphene-django +proto-plus==1.24.0 + # via google-api-core +protobuf==5.27.2 # via # google-api-core - # google-cloud-storage # googleapis-common-protos -psycopg2==2.9.1 + # proto-plus +psycopg2==2.9.9 # via -r requirements.in -pyasn1==0.4.8 +pyasn1==0.6.0 # via # pyasn1-modules # python-jose # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.4.0 # via google-auth -pycountry==20.7.3 +pycountry==24.6.1 # via -r requirements.in -pycparser==2.20 +pycparser==2.22 # via cffi -pyrsistent==0.18.0 - # via jsonschema -python-dateutil==2.8.2 +pypng==0.20220715.0 + # via qrcode +python-dateutil==2.9.0.post0 # via faker python-jose==3.3.0 # via django-helusers -pytz==2021.3 - # via django -pyyaml==6.0 +pytz==2024.1 + # via -r requirements.in +pyyaml==6.0.1 # via drf-spectacular -qrcode==7.3.1 +qrcode==7.4.2 # via -r requirements.in -requests==2.26.0 +referencing==0.35.1 + # via + # jsonschema + # jsonschema-specifications +requests==2.32.3 # via # -r requirements.in # azure-core @@ -200,49 +211,41 @@ requests==2.26.0 # drf-oidc-auth # google-api-core # google-cloud-storage - # msrest - # requests-oauthlib -requests-oauthlib==1.3.0 - # via msrest -rsa==4.7.2 +rpds-py==0.19.0 + # via + # jsonschema + # referencing +rsa==4.9 # via # google-auth # python-jose -rx==1.6.1 - # via graphql-core -sentry-sdk==1.4.3 +sentry-sdk==2.10.0 # via -r requirements.in -singledispatch==3.7.0 - # via graphene-django six==1.16.0 # via # azure-core - # django-mailer # ecdsa - # google-cloud-storage - # graphene - # graphene-django # graphene-file-upload - # graphql-core - # graphql-relay # isodate # promise # python-dateutil - # singledispatch -sqlparse==0.4.2 +sqlparse==0.5.1 # via django text-unidecode==1.3 + # via graphene-django +typing-extensions==4.12.2 # via - # faker - # graphene-django + # asgiref + # azure-core + # azure-storage-blob + # drf-spectacular + # qrcode uritemplate==4.1.1 # via drf-spectacular -urllib3==1.26.7 +urllib3==2.2.2 # via + # django-anymail # requests # sentry-sdk zipp==3.19.2 # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/subscriptions/factories.py b/subscriptions/factories.py index 9fa30002..6f31e3ba 100644 --- a/subscriptions/factories.py +++ b/subscriptions/factories.py @@ -11,3 +11,4 @@ class FreeSpotNotificationSubscriptionFactory(factory.django.DjangoModelFactory) class Meta: model = FreeSpotNotificationSubscription + skip_postgeneration_save = True # Not needed after factory v4.0.0 diff --git a/subscriptions/notifications.py b/subscriptions/notifications.py index 03bb53c3..a74df634 100644 --- a/subscriptions/notifications.py +++ b/subscriptions/notifications.py @@ -1,3 +1,5 @@ +from uuid import uuid4 + from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django_ilmoitin.dummy_context import dummy_context @@ -18,14 +20,16 @@ notifications.register(NotificationType.FREE_SPOT, _("free spot")) -project = ProjectFactory.build(year=2020) -event = EventFactory.build(project=project) -venue = VenueFactory.build(project=project) -guardian = GuardianFactory.build() -child = ChildWithGuardianFactory.build(relationship__guardian=guardian, project=project) -occurrence = OccurrenceFactory.build(event=event, venue=venue) +project = ProjectFactory.build(pk=uuid4(), year=2020) +event = EventFactory.build(pk=uuid4(), project=project) +venue = VenueFactory.build(pk=uuid4(), project=project) +guardian = GuardianFactory.build(pk=uuid4()) +child = ChildWithGuardianFactory.build( + pk=uuid4(), relationship__guardian=guardian, project=project +) +occurrence = OccurrenceFactory.build(pk=uuid4(), event=event, venue=venue) subscription = FreeSpotNotificationSubscriptionFactory.build( - child=child, occurrence=occurrence + pk=uuid4(), child=child, occurrence=occurrence ) unsubscribe_url = "https://kukkuu-ui-domain/fi/profile/subscriptions?authToken=abc123" dummy_context.update( diff --git a/subscriptions/schema.py b/subscriptions/schema.py index 724bb8f6..0fc1604a 100644 --- a/subscriptions/schema.py +++ b/subscriptions/schema.py @@ -7,7 +7,11 @@ from children.models import Child from children.schema import ChildNode -from common.utils import get_node_id_from_global_id, login_required +from common.utils import ( + get_node_id_from_global_id, + login_required, + map_enums_to_values_in_kwargs, +) from events.models import Occurrence from events.schema import OccurrenceNode from kukkuu.consts import OCCURRENCE_IS_NOT_FULL_ERROR @@ -24,10 +28,12 @@ class FreeSpotNotificationSubscriptionNode(DjangoObjectType): + occurrence = graphene.Field(OccurrenceNode) # WORKAROUND: See resolve_occurrence + class Meta: model = FreeSpotNotificationSubscription interfaces = (relay.Node,) - fields = ("id", "created_at", "child", "occurrence") + fields = ("id", "created_at", "child") filter_fields = ("child_id", "occurrence_id") @classmethod @@ -35,6 +41,26 @@ class Meta: def get_queryset(cls, queryset, info): return super().get_queryset(queryset, info).user_can_view(info.context.user) + def resolve_occurrence(self, info, **kwargs): + """ + Resolver for the occurrence field, which is a foreign key. + + WORKAROUND: Just putting "occurrence" into Meta.fields breaks + occurrence field resolving in test_child_free_spot_notifications_query + with graphene-django 3.2.2, but works with graphene-django 3.1.3. + + Probably caused by the changes done after 3.1.3: + - "fix: fk resolver permissions leak": + - https://github.com/graphql-python/graphene-django/pull/1411 + - "fix: foreign key nullable and custom resolver": + - https://github.com/graphql-python/graphene-django/pull/1446 + + Found a similar graphene-django issue #1482: + - "Vesion 3.1.5 returns null for an object pointed to non PK in referred table": + - https://github.com/graphql-python/graphene-django/issues/1482 + """ + return self.occurrence + def validate_free_spot_notification_subscription(child, occurrence): if child.free_spot_notification_subscriptions.filter( @@ -81,6 +107,7 @@ class Input: @classmethod @login_required + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user child, occurrence = _get_child_and_occurrence(info, **kwargs) @@ -111,6 +138,7 @@ class Input: @classmethod @login_required + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user child, occurrence = _get_child_and_occurrence(info, **kwargs) @@ -165,6 +193,7 @@ class Input: use_only_when_first_denied=True, ) @login_required + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user user.unsubscribe_all_notification_subscriptions() diff --git a/subscriptions/tests/snapshots/snap_test_api.py b/subscriptions/tests/snapshots/snap_test_api.py index 95e99b2f..ea6b2fc3 100644 --- a/subscriptions/tests/snapshots/snap_test_api.py +++ b/subscriptions/tests/snapshots/snap_test_api.py @@ -16,7 +16,7 @@ "child": {"name": "Subscriber"}, "createdAt": "2020-12-12T00:00:00+00:00", "id": "RnJlZVNwb3ROb3RpZmljYXRpb25TdWJzY3JpcHRpb25Ob2RlOjE=", - "occurrence": {"time": "1971-11-04T13:17:08+00:00"}, + "occurrence": {"time": "1993-10-11T08:58:57.954874+00:00"}, } } ] @@ -109,7 +109,7 @@ "data": { "unsubscribeFromFreeSpotNotification": { "child": {"name": "Subscriber"}, - "occurrence": {"time": "1971-11-04T13:17:08+00:00"}, + "occurrence": {"time": "1993-10-11T08:58:57.954874+00:00"}, } } } diff --git a/subscriptions/tests/test_api.py b/subscriptions/tests/test_api.py index c5c1163c..84990316 100644 --- a/subscriptions/tests/test_api.py +++ b/subscriptions/tests/test_api.py @@ -78,7 +78,6 @@ def test_child_free_spot_notifications_query( snapshot, guardian_api_client, guardian_child ): FreeSpotNotificationSubscriptionFactory(id=1, child=guardian_child) - executed = guardian_api_client.execute( CHILD_FREE_SPOT_NOTIFICATION_SUBSCRIPTIONS_QUERY, variables={"id": get_global_id(guardian_child)}, diff --git a/users/factories.py b/users/factories.py index c6fb1671..c984eabe 100644 --- a/users/factories.py +++ b/users/factories.py @@ -4,6 +4,7 @@ from django.contrib.auth import get_user_model from django.db.models import Q +from common.mixins import SaveAfterPostGenerationMixin from languages.models import Language from users.models import Guardian @@ -16,9 +17,10 @@ class UserFactory(factory.django.DjangoModelFactory): class Meta: model = get_user_model() + skip_postgeneration_save = True # Not needed after factory v4.0.0 -class GuardianFactory(factory.django.DjangoModelFactory): +class GuardianFactory(SaveAfterPostGenerationMixin, factory.django.DjangoModelFactory): id = factory.LazyFunction(lambda: uuid.uuid4()) user = factory.SubFactory(UserFactory) first_name = factory.Faker("first_name") diff --git a/users/schema.py b/users/schema.py index 2427243a..9b7c1974 100644 --- a/users/schema.py +++ b/users/schema.py @@ -6,7 +6,7 @@ from graphene_django.types import DjangoObjectType from common.schema import LanguageEnum, set_obj_languages_spoken_at_home -from common.utils import login_required, update_object +from common.utils import login_required, map_enums_to_values_in_kwargs, update_object from kukkuu.exceptions import ObjectDoesNotExistError from projects.schema import ProjectNode from verification_tokens.decorators import user_from_auth_verification_token @@ -97,6 +97,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user @@ -130,6 +131,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): user = info.context.user try: @@ -168,6 +170,7 @@ class Input: @classmethod @login_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): new_email = kwargs["email"] user = info.context.user @@ -209,6 +212,7 @@ class Input: use_only_when_first_denied=True, ) @login_required + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, has_accepted_communication, **kwargs): user = info.context.user try: diff --git a/users/tests/snapshots/snap_test_api.py b/users/tests/snapshots/snap_test_api.py index 1383005e..523e0c76 100644 --- a/users/tests/snapshots/snap_test_api.py +++ b/users/tests/snapshots/snap_test_api.py @@ -13,16 +13,16 @@ { "node": { "email": "michellewalker@example.net", - "firstName": "Steve", - "lastName": "Hutchinson", - "phoneNumber": "001-117-159-1023x20281", + "firstName": "Andrew", + "lastName": "Eaton", + "phoneNumber": "001-311-571-5910x23202", "relationships": { "edges": [ { "node": { "child": { "birthyear": 2023, - "name": "John Moore", + "name": "Nicholas Chavez", "project": {"year": 2020}, }, "type": "PARENT", @@ -46,7 +46,7 @@ "email": "debbie77@example.com", "firstName": "Guardian having children in own and another project", "lastName": "Should be visible 1/2", - "phoneNumber": "(712)406-7506x4976", + "phoneNumber": "(971)824-0675x0649", "relationships": { "edges": [ { @@ -68,7 +68,7 @@ "email": "michellewalker@example.net", "firstName": "Another project own guardian", "lastName": "Should be visible 2/2", - "phoneNumber": "202.813.0727", + "phoneNumber": "420.928.1307", "relationships": { "edges": [ { @@ -207,20 +207,20 @@ "data": { "myProfile": { "email": "michellewalker@example.net", - "firstName": "Timothy", + "firstName": "Robert", "hasAcceptedCommunication": False, "language": "FI", "languagesSpokenAtHome": {"edges": []}, - "lastName": "Baldwin", - "phoneNumber": "803.466.9727", + "lastName": "Crane", + "phoneNumber": "303.746.6972x70117", "relationships": { "edges": [ { "node": { "child": { - "birthyear": 2018, - "name": "Stephen Charles", - "postalCode": "71591", + "birthyear": 2019, + "name": "Ashley Hernandez", + "postalCode": "28130", }, "type": "OTHER_GUARDIAN", } @@ -277,39 +277,40 @@ snapshots[ "test_update_my_communication_subscriptions_returns_errors_without_required_args[variables0] 1" ] = { + "data": None, "errors": [ { "extensions": {"code": "GENERAL_ERROR"}, "locations": [{"column": 3, "line": 3}], - "message": """Variable "$input" got invalid value {}. -In field "hasAcceptedCommunication": Expected "Boolean!", found null.""", + "message": "Variable '$input' got invalid value {}; Field 'hasAcceptedCommunication' of required type 'Boolean!' was not provided.", } - ] + ], } snapshots[ "test_update_my_communication_subscriptions_returns_errors_without_required_args[variables1] 1" ] = { + "data": None, "errors": [ { "extensions": {"code": "GENERAL_ERROR"}, "locations": [{"column": 3, "line": 3}], - "message": 'Variable "$input" of required type "UpdateMyCommunicationSubscriptionsMutationInput!" was not provided.', + "message": "Variable '$input' of required type 'UpdateMyCommunicationSubscriptionsMutationInput!' was not provided.", } - ] + ], } snapshots[ "test_update_my_communication_subscriptions_returns_errors_without_required_args[variables2] 1" ] = { + "data": None, "errors": [ { "extensions": {"code": "GENERAL_ERROR"}, "locations": [{"column": 3, "line": 3}], - "message": """Variable "$input" got invalid value {"authToken": "what ever"}. -In field "hasAcceptedCommunication": Expected "Boolean!", found null.""", + "message": "Variable '$input' got invalid value {'authToken': 'what ever'}; Field 'hasAcceptedCommunication' of required type 'Boolean!' was not provided.", } - ] + ], } snapshots[ diff --git a/users/tests/snapshots/snap_test_models.py b/users/tests/snapshots/snap_test_models.py index ce6393a3..03b06f78 100644 --- a/users/tests/snapshots/snap_test_models.py +++ b/users/tests/snapshots/snap_test_models.py @@ -37,7 +37,7 @@ {"key": "FIRST_NAME", "value": "Michael"}, {"key": "LAST_NAME", "value": "Patton"}, {"key": "EMAIL", "value": "michellewalker@example.net"}, - {"key": "PHONE_NUMBER", "value": "355.777.6712x406"}, + {"key": "PHONE_NUMBER", "value": "235.857.7767x124"}, {"key": "HAS_ACCEPTED_COMMUNICATION", "value": False}, { "children": [ @@ -62,7 +62,7 @@ }, { "key": "OCCURRENCE", - "value": "2007-10-07 00:42:30+00:00 (101)", + "value": "1998-03-24 07:42:39.814032+00:00 (101)", }, ], "key": "FREESPOTNOTIFICATIONSUBSCRIPTION", @@ -95,7 +95,7 @@ "children": [ { "key": "TIME", - "value": "1978-01-13T09:04:50+00:00", + "value": "2014-12-15T00:10:52.106068+00:00", }, { "children": [ @@ -103,7 +103,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Rise item spend just imagine although dark.", + "fi": "Significant road including everybody star.", "sv": "", }, }, @@ -141,7 +141,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Medical price pick four moment body employee word.", + "fi": "Size hard pressure moment. Cover majority from song entire.", "sv": "", }, }, @@ -149,8 +149,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """49904 Roberts Island -Yorkview, OK 06572""", + "fi": """7259 Melissa Loaf Apt. 359 +Lake Kelly, NM 09613""", "sv": "", }, }, @@ -171,14 +171,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "$5Y3Eytpg5"}, + {"key": "VALUE", "value": "$kutEeNy44"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Thus need air shoulder it husband. More religious section.", + "fi": "Son doctor worry partner add.", "sv": "", }, }, @@ -234,7 +234,7 @@ }, { "key": "OCCURRENCE", - "value": "1974-05-30 15:30:48+00:00 (102)", + "value": "2006-08-11 23:28:41.941918+00:00 (102)", }, ], "key": "FREESPOTNOTIFICATIONSUBSCRIPTION", @@ -263,7 +263,7 @@ "children": [ { "key": "TIME", - "value": "2011-01-05T08:35:11+00:00", + "value": "2013-05-30T10:43:54.142655+00:00", }, { "children": [ @@ -271,7 +271,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Enjoy when one wonder fund nor white.", + "fi": "Management then some threat behavior see quite tax.", "sv": "", }, }, @@ -318,7 +318,7 @@ "value": { "en": "", "fi": """37849 Alejandra Rapid Apt. 294 -South Mitchell, NC 30891""", +South Mitchell, NC 69042""", "sv": "", }, }, @@ -341,7 +341,7 @@ "children": [ { "key": "TIME", - "value": "1972-02-01T22:06:34+00:00", + "value": "2020-03-29T03:04:22.011516+00:00", }, { "children": [ @@ -349,7 +349,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Affect money school military statement.", + "fi": "Federal minute paper third item future far power.", "sv": "", }, }, @@ -387,7 +387,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Because treatment sense left.", + "fi": "Service the politics money pressure. Across life author phone.", "sv": "", }, }, @@ -395,8 +395,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """812 Russell Hollow Suite 792 -Jessicatown, MN 66988""", + "fi": """662 Charles Orchard +Staceyville, PW 70051""", "sv": "", }, }, @@ -419,7 +419,7 @@ "children": [ { "key": "TIME", - "value": "2014-12-23T16:25:42+00:00", + "value": "2003-08-14T13:03:41.171262+00:00", }, { "children": [ @@ -427,7 +427,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Wife message focus between.", + "fi": "Message focus between picture under room.", "sv": "", }, }, @@ -474,7 +474,7 @@ "value": { "en": "", "fi": """04194 Frederick Mill -Daniellechester, KS 04855""", +Daniellechester, VI 33985""", "sv": "", }, }, @@ -497,7 +497,7 @@ "children": [ { "key": "TIME", - "value": "1977-12-17T06:50:34+00:00", + "value": "1975-12-13T23:27:49.073366+00:00", }, { "children": [ @@ -505,7 +505,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Political maintain there drive threat Mr.", + "fi": "Her set detail else surface break rate.", "sv": "", }, }, @@ -552,7 +552,7 @@ "value": { "en": "", "fi": """35141 Cruz Spring -Bellhaven, ME 48709""", +Bellhaven, NM 41103""", "sv": "", }, }, @@ -575,7 +575,7 @@ "children": [ { "key": "TIME", - "value": "1997-06-16T18:04:26+00:00", + "value": "1981-05-27T16:10:16.773219+00:00", }, { "children": [ @@ -583,7 +583,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Out treat tell these dinner. Walk choice reality nothing.", + "fi": "Whether page character.", "sv": "", }, }, @@ -621,7 +621,7 @@ "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Maintain reveal strong develop two send.", + "fi": "Whom inside appear research.", "sv": "", }, }, @@ -629,8 +629,8 @@ "key": "ADDRESS_WITH_TRANSLATIONS", "value": { "en": "", - "fi": """494 Sanders Estates Suite 075 -Jamestown, MT 39374""", + "fi": """571 Palmer Islands +Andrewburgh, RI 12639""", "sv": "", }, }, @@ -651,14 +651,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "G*Md1Co(!i"}, + {"key": "VALUE", "value": "!8GNa_b4ed"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Ahead center town least style above. And others along hotel.", + "fi": "Simply bed which you area body attention leg.", "sv": "", }, }, @@ -700,14 +700,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "+9eHg7Fy!d"}, + {"key": "VALUE", "value": "q!b9NM_p_p"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Determine girl the white statement authority.", + "fi": "Different unit pass show international remain sing.", "sv": "", }, }, @@ -749,14 +749,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "(AKx8MvU7s"}, + {"key": "VALUE", "value": "@@hObp%(8a"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Right financial cut director organization nothing rest.", + "fi": "Wife natural somebody student measure.", "sv": "", }, }, @@ -798,14 +798,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "^P+f743d6x"}, + {"key": "VALUE", "value": "Z3*d8fFmv0"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Meeting woman or walk.", + "fi": "Doctor stuff discover. Listen focus by bed.", "sv": "", }, }, @@ -847,14 +847,14 @@ { "children": [ {"key": "ASSIGNED_AT", "value": None}, - {"key": "VALUE", "value": "pU4KdAkN3)"}, + {"key": "VALUE", "value": ")#$43I2s^Q"}, { "children": [ { "key": "NAME_WITH_TRANSLATIONS", "value": { "en": "", - "fi": "Right science chair door suddenly paper. Drop street than.", + "fi": "Note say act friend data religious special.", "sv": "", }, }, diff --git a/users/tests/snapshots/snap_test_notifications.py b/users/tests/snapshots/snap_test_notifications.py index eb2b3c8f..46b097e5 100644 --- a/users/tests/snapshots/snap_test_notifications.py +++ b/users/tests/snapshots/snap_test_notifications.py @@ -42,46 +42,46 @@ Child name: Jason Berg -Event: Enjoy when one wonder fund nor white. -Occurrence: 2011-01-05 08:35:11+00:00 +Event: Policy sport available. +Occurrence: 1983-12-14 10:41:44.139987+00:00 -Event: Affect money school military statement. -Occurrence: 1972-02-01 22:06:34+00:00 +Event: Month score father middle brother station physical very. +Occurrence: 1989-06-15 22:58:14.498478+00:00 Child name: Katherine Gomez -Event: Data table TV minute. Agree room laugh prevent make. -Occurrence: 1971-04-02 05:11:11+00:00 +Event: Care any concern bed agree. Laugh prevent make never. +Occurrence: 1970-12-08 12:46:35.206162+00:00 -Event: Include and individual effort indeed discuss challenge school. -Occurrence: 1974-05-30 15:30:48+00:00 +Event: Democratic focus significant kind various laugh. +Occurrence: 1973-04-20 19:20:51.908992+00:00 Markdown: # Jason Berg -1. **Affect money school military statement.:** 2.2.1972 00:06 +1. **Policy sport available.:** 14.12.1983 12:41 - Peace mean education college daughter. + Region protect likely day. -2. **Enjoy when one wonder fund nor white.:** 5.1.2011 10:35 +2. **Month score father middle brother station physical very.:** 16.6.1989 01:58 - Sort deep phone such water price including. + Feel top huge civil certainly save western. # Katherine Gomez -1. **Data table TV minute. Agree room laugh prevent make.:** 2.4.1971 07:11 +1. **Care any concern bed agree. Laugh prevent make never.:** 8.12.1970 14:46 - Enter everything history remember stay public high. + Southern newspaper force newspaper business. -2. **Include and individual effort indeed discuss challenge school.:** 30.5.1974 17:30 +2. **Democratic focus significant kind various laugh.:** 20.4.1973 21:20 - Second know say former conference carry factor. + Rest two special far magazine on. """ ] diff --git a/users/tests/test_api.py b/users/tests/test_api.py index 81c7e312..4284825d 100644 --- a/users/tests/test_api.py +++ b/users/tests/test_api.py @@ -265,7 +265,7 @@ def test_update_my_email_mutation(snapshot, user_api_client, new_email, is_valid assert guardian.email == new_email elif new_email is None: assert len(executed["errors"]) == 1 - assert 'Variable "$input" got invalid value' in executed["errors"][0]["message"] + assert "Variable '$input' got invalid value" in executed["errors"][0]["message"] else: assert_match_error_code(executed, INVALID_EMAIL_FORMAT_ERROR) assert guardian.email == initial_email diff --git a/users/tests/test_models.py b/users/tests/test_models.py index cd4cd716..7529c4c3 100644 --- a/users/tests/test_models.py +++ b/users/tests/test_models.py @@ -339,7 +339,6 @@ def test_has_accepted_communication_for_notification_needs_acceptance( assert guardian_with_filtered_communication not in queryset_result -@pytest.mark.django_db @pytest.fixture def obsoleted_guardian_joined_yesterday(): return GuardianFactory( @@ -347,7 +346,6 @@ def obsoleted_guardian_joined_yesterday(): ) -@pytest.mark.django_db @pytest.fixture def guardian_joined_yesterday(): return GuardianFactory( @@ -355,7 +353,6 @@ def guardian_joined_yesterday(): ) -@pytest.mark.django_db @pytest.fixture def guardian_joined_today(): return GuardianFactory(user__date_joined=timezone.now(), user__is_obsolete=False) diff --git a/users/tests/test_services.py b/users/tests/test_services.py index 26d4ae6c..9d2ef595 100644 --- a/users/tests/test_services.py +++ b/users/tests/test_services.py @@ -27,16 +27,20 @@ def guardian_with_children_and_enrolments(): event4 = EventFactory(name="Event 4 for child2") occurrence1 = OccurrenceFactory( - event=event1, time=timezone.datetime(year=2024, month=1, day=1) + event=event1, + time=timezone.make_aware(timezone.datetime(year=2024, month=1, day=1)), ) occurrence2 = OccurrenceFactory( - event=event2, time=timezone.datetime(year=2023, month=12, day=24) + event=event2, + time=timezone.make_aware(timezone.datetime(year=2023, month=12, day=24)), ) occurrence3 = OccurrenceFactory( - event=event3, time=timezone.datetime(year=2023, month=6, day=16) + event=event3, + time=timezone.make_aware(timezone.datetime(year=2023, month=6, day=16)), ) occurrence4 = OccurrenceFactory( - event=event4, time=timezone.datetime(year=2023, month=12, day=6) + event=event4, + time=timezone.make_aware(timezone.datetime(year=2023, month=12, day=6)), ) EnrolmentFactory(child=child1, occurrence=occurrence1) diff --git a/venues/factories.py b/venues/factories.py index 387b7951..ae2ed46c 100644 --- a/venues/factories.py +++ b/venues/factories.py @@ -17,3 +17,4 @@ class VenueFactory(factory.django.DjangoModelFactory): class Meta: model = Venue + skip_postgeneration_save = True # Not needed after factory v4.0.0 diff --git a/venues/schema.py b/venues/schema.py index 0dada0ee..5d5d63fc 100644 --- a/venues/schema.py +++ b/venues/schema.py @@ -12,6 +12,7 @@ from common.utils import ( get_obj_if_user_can_administer, login_required, + map_enums_to_values_in_kwargs, project_user_required, update_object_with_translations, ) @@ -110,6 +111,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): kwargs["project_id"] = get_obj_if_user_can_administer( info, kwargs["project_id"], Project @@ -134,6 +136,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): project_global_id = kwargs.pop("project_id", None) if project_global_id: @@ -157,6 +160,7 @@ class Input: @classmethod @project_user_required @transaction.atomic + @map_enums_to_values_in_kwargs def mutate_and_get_payload(cls, root, info, **kwargs): venue = get_obj_if_user_can_administer(info, kwargs["id"], Venue) log_text = f"user {info.context.user.uuid} deleted venue {venue}" diff --git a/venues/tests/snapshots/snap_test_api.py b/venues/tests/snapshots/snap_test_api.py index 60a659dd..dbd58008 100644 --- a/venues/tests/snapshots/snap_test_api.py +++ b/venues/tests/snapshots/snap_test_api.py @@ -55,11 +55,11 @@ snapshots["test_venue_query_normal_user 1"] = { "data": { "venue": { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", @@ -68,11 +68,11 @@ "project": {"year": 2020}, "translations": [ { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", @@ -102,11 +102,11 @@ "edges": [ { "node": { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", @@ -114,11 +114,11 @@ "occurrences": {"edges": []}, "translations": [ { - "accessibilityInfo": """Sit enter stand himself from daughter order. Sign discover eight. + "accessibilityInfo": """Like lay still bar. From daughter order stay sign discover eight. Scientist service wonder everything pay. Moment strong hand push book and interesting sit.""", "additionalInfo": "Training thought price. Effort clear and local challenge box. Care figure mention wrong when lead involve.", "address": """04883 Mary Corner -Port Mikeview, IN 23956""", +Port Mikeview, NY 31053""", "arrivalInstructions": "Benefit treat final central. Past ready join enjoy. Huge get this success commercial recently from.", "description": """Together history perform. Respond draw military dog hospital number. Certainly again thought summer because serious listen. Page box child care any concern. Defense level church use.""", diff --git a/verification_tokens/factories.py b/verification_tokens/factories.py index a4563ed3..731fd2be 100644 --- a/verification_tokens/factories.py +++ b/verification_tokens/factories.py @@ -23,6 +23,7 @@ class Meta: model = VerificationToken exclude = ["content_object"] abstract = True + skip_postgeneration_save = True # Not needed after factory v4.0.0 class UserEmailVerificationTokenFactory(VerificationTokenFactory):