Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ApplicationSection and RecurringReservation Pindora GraphQL info #1610

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b18cfe0
Add `ReservationUnitAccessType` model
matti-lamppu Feb 17, 2025
053899b
Update admin panel to support new access type model
matti-lamppu Feb 18, 2025
2477a0a
Update GraphQL types to new access type model
matti-lamppu Feb 18, 2025
352bde3
Add new access types to test data
matti-lamppu Feb 20, 2025
ac3144b
Add tests for new access types
matti-lamppu Feb 20, 2025
aa1ff23
Update translations
matti-lamppu Feb 20, 2025
915afe0
Fix issues from PRs
matti-lamppu Feb 26, 2025
9fac0e9
Add `access_type` filter to reservation in admin panel
matti-lamppu Feb 12, 2025
6783de6
Show pindora info for series and seasonal booking reservations
matti-lamppu Feb 12, 2025
624d0de
Add `should_have_active_access_code` to series admin panel view
matti-lamppu Feb 12, 2025
2c7fefc
Add Pindora info to recurring reservation
matti-lamppu Feb 13, 2025
262c105
Add Pindora fields to reservation series GraphQL type
matti-lamppu Feb 14, 2025
c89b68b
Add Pindora info to application section GraphQL type
matti-lamppu Feb 14, 2025
5c46225
Add new Pindora data to section and series admin panel views
matti-lamppu Feb 14, 2025
612ef49
Fetch GQL info from series or section depending on relations
matti-lamppu Feb 14, 2025
3756bdd
Fixes to Pindora info in GraphQL APIs
matti-lamppu Feb 17, 2025
1dbdc5f
Create Pindora access codes on reservation series create
matti-lamppu Feb 17, 2025
928e3fb
Remove unnecessary test
matti-lamppu Feb 26, 2025
8fe66e7
Fix things after rebase to access typ model branch
matti-lamppu Feb 26, 2025
99abfd7
Fix Django Admin reservation page in case of Pindora error
ranta Feb 19, 2025
c4980b1
Fix Pindora caching when using methods with UUIDs instead of instances
ranta Feb 24, 2025
0f675ff
Fix expected status code for Pindora `change_*_access_code` methods
ranta Feb 24, 2025
a65bc57
Update Pindora Client to new API changes
matti-lamppu Feb 26, 2025
63d6f6c
Refactor Pindora client to per-object clients
matti-lamppu Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 205 additions & 67 deletions backend/locale/fi/LC_MESSAGES/django.po

Large diffs are not rendered by default.

261 changes: 200 additions & 61 deletions backend/locale/sv/LC_MESSAGES/django.po

Large diffs are not rendered by default.

583 changes: 292 additions & 291 deletions backend/poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ django-filter = "24.3"
django-health-check = "3.18.3"
django-helusers = "0.13.0"
django-jinja = "2.11.0"
django-lookup-property = "0.1.7"
django-lookup-property = "0.1.8"
django-modeltranslation = "0.19.12"
django-mptt = "0.16.0"
django-redis = "5.4.0"
Expand Down
2 changes: 2 additions & 0 deletions backend/tests/factories/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from .reservation_metadata_set import ReservationMetadataSetFactory
from .reservation_purpose import ReservationPurposeFactory
from .reservation_unit import ReservationUnitFactory
from .reservation_unit_access_type import ReservationUnitAccessTypeFactory
from .reservation_unit_cancellation_rule import ReservationUnitCancellationRuleFactory
from .reservation_unit_image import ReservationUnitImageFactory
from .reservation_unit_option import ReservationUnitOptionFactory
Expand Down Expand Up @@ -87,6 +88,7 @@
"ReservationMetadataFieldFactory",
"ReservationMetadataSetFactory",
"ReservationPurposeFactory",
"ReservationUnitAccessTypeFactory",
"ReservationUnitCancellationRuleFactory",
"ReservationUnitFactory",
"ReservationUnitImageFactory",
Expand Down
6 changes: 2 additions & 4 deletions backend/tests/factories/reservation_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from factory import LazyAttribute
from factory.fuzzy import FuzzyInteger

from tilavarauspalvelu.enums import AccessType, AuthenticationType, ReservationKind, ReservationStartInterval
from tilavarauspalvelu.enums import AuthenticationType, ReservationKind, ReservationStartInterval
from tilavarauspalvelu.models import ReservationUnit
from utils.date_utils import local_start_of_day
from utils.utils import as_p_tags
Expand Down Expand Up @@ -93,8 +93,6 @@ class Meta:
min_reservation_duration = None
buffer_time_before = factory.LazyFunction(datetime.timedelta)
buffer_time_after = factory.LazyFunction(datetime.timedelta)
access_type_start_date = None
access_type_end_date = None

# Booleans
is_draft = False
Expand All @@ -109,7 +107,6 @@ class Meta:
authentication = AuthenticationType.WEAK.value
reservation_start_interval = ReservationStartInterval.INTERVAL_15_MINUTES.value
reservation_kind = ReservationKind.DIRECT_AND_SEASON.value
access_type = AccessType.UNRESTRICTED.value

# Lists
search_terms = LazyAttribute(lambda i: [])
Expand Down Expand Up @@ -146,6 +143,7 @@ class Meta:
recurring_reservations = ReverseForeignKeyFactory("tests.factories.RecurringReservationFactory")
application_round_time_slots = ReverseForeignKeyFactory("tests.factories.ApplicationRoundTimeSlotFactory")
reservation_unit_options = ReverseForeignKeyFactory("tests.factories.ReservationUnitOptionFactory")
access_types = ReverseForeignKeyFactory("tests.factories.ReservationUnitAccessTypeFactory")

@classmethod
def create_reservable_now(cls, **kwargs: Any) -> ReservationUnit:
Expand Down
27 changes: 27 additions & 0 deletions backend/tests/factories/reservation_unit_access_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import annotations

import datetime

from tilavarauspalvelu.enums import AccessType
from tilavarauspalvelu.models import ReservationUnitAccessType, ReservationUnitPricing

from ._base import ForeignKeyFactory, GenericDjangoModelFactory, ModelFactoryBuilder

__all__ = [
"ReservationUnitAccessTypeBuilder",
"ReservationUnitAccessTypeFactory",
]


class ReservationUnitAccessTypeFactory(GenericDjangoModelFactory[ReservationUnitAccessType]):
class Meta:
model = ReservationUnitAccessType

begin_date = datetime.date(2021, 1, 1)
access_type = AccessType.UNRESTRICTED

reservation_unit = ForeignKeyFactory("tests.factories.ReservationUnitFactory")


class ReservationUnitAccessTypeBuilder(ModelFactoryBuilder[ReservationUnitPricing]):
factory = ReservationUnitAccessTypeFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from __future__ import annotations

import datetime

import freezegun
import pytest
from lookup_property import L

from tilavarauspalvelu.enums import AccessType
from utils.date_utils import local_datetime

from tests.factories import ReservationUnitAccessTypeFactory, ReservationUnitFactory

pytestmark = [
pytest.mark.django_db,
]


@freezegun.freeze_time("2025-01-01")
def test_reservation_unit__access_types__end_date():
now = local_datetime()

reservation_unit = ReservationUnitFactory.create()
ReservationUnitAccessTypeFactory.create(
reservation_unit=reservation_unit,
access_type=AccessType.UNRESTRICTED,
begin_date=now.date(),
)
ReservationUnitAccessTypeFactory.create(
reservation_unit=reservation_unit,
access_type=AccessType.ACCESS_CODE,
begin_date=now.date() + datetime.timedelta(days=7),
)

qs = reservation_unit.access_types.annotate(end_date=L("end_date")).order_by("begin_date").values("end_date")
assert list(qs) == [
{"end_date": datetime.date(2025, 1, 8)},
{"end_date": datetime.date.max},
]


@freezegun.freeze_time("2025-01-01")
def test_reservation_unit__access_types__active():
now = local_datetime()

reservation_unit = ReservationUnitFactory.create()
ReservationUnitAccessTypeFactory.create(
reservation_unit=reservation_unit,
access_type=AccessType.UNRESTRICTED,
begin_date=now.date(),
)
ReservationUnitAccessTypeFactory.create(
reservation_unit=reservation_unit,
access_type=AccessType.ACCESS_CODE,
begin_date=now.date() + datetime.timedelta(days=7),
)

qs = (
reservation_unit.access_types.active()
.annotate(end_date=L("end_date"))
.order_by("begin_date")
.values("access_type", "begin_date", "end_date")
)
assert list(qs) == [
{
"access_type": AccessType.UNRESTRICTED,
"begin_date": datetime.date(2025, 1, 1),
"end_date": datetime.date(2025, 1, 8),
},
]


@freezegun.freeze_time("2025-01-01")
@pytest.mark.parametrize(
"access_type",
[
AccessType.UNRESTRICTED,
AccessType.ACCESS_CODE,
AccessType.PHYSICAL_KEY,
],
)
def test_reservation_unit__access_type_at(access_type):
now = local_datetime()
past = now - datetime.timedelta(days=1)
future = now + datetime.timedelta(days=1)

reservation_unit = ReservationUnitFactory.create(
access_types__access_type=access_type,
access_types__begin_date=now.date(),
)

assert reservation_unit.actions.get_access_type_at(past) is None
assert reservation_unit.actions.get_access_type_at(now) == access_type
assert reservation_unit.actions.get_access_type_at(future) == access_type

assert reservation_unit.current_access_type == access_type


@freezegun.freeze_time("2025-01-01")
@pytest.mark.parametrize(
"access_type",
[
AccessType.UNRESTRICTED,
AccessType.ACCESS_CODE,
AccessType.PHYSICAL_KEY,
],
)
def test_reservation_unit__access_type_at__null(access_type):
now = local_datetime()
past = now - datetime.timedelta(days=1)
future = now + datetime.timedelta(days=1)

reservation_unit = ReservationUnitFactory.create(
access_types__access_type=access_type,
access_types__begin_date=future.date(),
)

assert reservation_unit.actions.get_access_type_at(past) is None
assert reservation_unit.actions.get_access_type_at(now) is None
assert reservation_unit.actions.get_access_type_at(future) == access_type

assert reservation_unit.current_access_type is None

This file was deleted.

9 changes: 9 additions & 0 deletions backend/tests/test_admin/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations

import pytest


@pytest.fixture(autouse=True)
def _language_fix(settings):
# Override languages, since TinyMCE can't handle lazy language name translations in tests ¯\_(ツ)_/¯
settings.LANGUAGES = [("fi", "Finnish"), ("en", "English"), ("sv", "Swedish")]
61 changes: 61 additions & 0 deletions backend/tests/test_admin/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from __future__ import annotations

from contextlib import contextmanager
from typing import TYPE_CHECKING, Any
from unittest.mock import patch

from django.template.response import TemplateResponse

if TYPE_CHECKING:
from collections.abc import Generator

from tilavarauspalvelu.models import ReservationUnit
from tilavarauspalvelu.typing import WSGIRequest


@contextmanager
def collect_admin_form_errors(errors: list[dict[str, Any]]) -> Generator[None, Any]:
"""Collect errors from the admin form to the given list of errors."""

def hook(request: WSGIRequest, template: list[str], context: dict[str, Any], *args, **kwargs) -> TemplateResponse:
errors.extend(context["errors"].get_json_data())
return TemplateResponse(request, template, context, *args, **kwargs)

path = "django.contrib.admin.options.TemplateResponse"
with patch(path, side_effect=hook):
yield


def management_form_data(name: str, *, total_forms: int = 0, initial_forms: int = 0) -> dict[str, Any]:
"""
Required formset "management form" data for inline forms.

:params name: Name of the one-to-many or many-to-many relationship the inline form is for.
:params total_forms: Number of forms in the formset after the form has been submitted.
:params initial_forms: Number of forms in the formset before the form has been submitted.
"""
return {
f"{name}-TOTAL_FORMS": total_forms,
f"{name}-INITIAL_FORMS": initial_forms,
f"{name}-MIN_NUM_FORMS": 0,
f"{name}-MAX_NUM_FORMS": 1000,
}


def required_reservation_unit_form_data(reservation_unit: ReservationUnit) -> dict[str, Any]:
"""Required fields for a new reservation unit."""
return {
#
# Required fields
"name": reservation_unit.name,
"name_fi": reservation_unit.name_fi,
"reservation_kind": reservation_unit.reservation_kind,
"authentication": reservation_unit.authentication,
"reservation_start_interval": reservation_unit.reservation_start_interval,
#
# Inline form metadata
**management_form_data("images"),
**management_form_data("pricings"),
**management_form_data("application_round_time_slots"),
**management_form_data("access_types"),
}
Loading