Skip to content

Commit b1123f7

Browse files
authored
Merge pull request #56 from City-of-Helsinki/release/0.2.0
Release/0.2.0
2 parents ce50769 + 9aad19f commit b1123f7

31 files changed

Lines changed: 1453 additions & 213 deletions

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## 0.1.0 - 29 Jan 2020
2+
### Added
3+
- API for signup/login and query my profile
4+
- Send notifications when signed up successfully
5+
- API to query, add, update and remove children
6+
- API to query, add, update and remove events
7+
- API to query, add, update and remove occurrences
8+
- API to query, add, update and remove venues
9+
10+
## [0.2.0] - 17 Feb 2020
11+
### Added
12+
- Add enrolment API for child to enrol event occurrences
13+
- Add support to update event image
14+
- Add event capacity validation to event
15+
- Add publish events API
16+
- Add Django Admin publish event action
17+
- Send notifications to guardians when an event published
18+
### Updated
19+
- Update API to support nested fields update/delete
20+
### Fixed
21+
- Fix API queries to use RelationshipTypeEnum like mutations do
22+
23+
24+
25+
26+
[Unreleased]: https://github.com/City-of-Helsinki/kukkuu/compare/v0.2.0...HEAD
27+
[0.2.0]: https://github.com/City-of-Helsinki/kukkuu/compare/v0.1.0...v0.2.0
28+

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ We follow the basic config, without any modifications. Basic `black` commands:
7676
Or you can use [`pre-commit`](https://pre-commit.com/) to quickly format your code before committing.
7777

7878

79-
1. Install `pre-commit` (there are many ways to do but let's use pip as an example:
79+
1. Install `pre-commit` (there are many ways to do but let's use pip as an example):
8080
* `pip install pre-commit`
8181
2. Set up git hooks from `.pre-commit-config.yaml`, run this command from project root:
8282
* `pre-commit install`

children/admin.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django.contrib import admin
22

3+
from events.models import Enrolment
4+
35
from .models import Child, Relationship
46

57

@@ -13,6 +15,12 @@ def has_change_permission(self, request, obj=None):
1315
return False
1416

1517

18+
class EnrolmentInline(admin.TabularInline):
19+
model = Enrolment
20+
extra = 1
21+
readonly_fields = ("created_at",)
22+
23+
1624
@admin.register(Child)
1725
class ChildAdmin(admin.ModelAdmin):
1826
list_display = (
@@ -25,4 +33,4 @@ class ChildAdmin(admin.ModelAdmin):
2533
"updated_at",
2634
)
2735
fields = ("first_name", "last_name", "birthdate", "postal_code")
28-
inlines = (RelationshipInline,)
36+
inlines = (RelationshipInline, EnrolmentInline)

children/schema.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,20 @@ def get_node(cls, info, id):
4141
return None
4242

4343

44+
class RelationshipTypeEnum(graphene.Enum):
45+
PARENT = "parent"
46+
OTHER_GUARDIAN = "other_guardian"
47+
OTHER_RELATION = "other_relation"
48+
ADVOCATE = "advocate"
49+
50+
4451
class RelationshipNode(DjangoObjectType):
4552
class Meta:
4653
model = Relationship
4754
interfaces = (relay.Node,)
4855
fields = ("type", "child", "guardian")
4956

50-
51-
class RelationshipTypeEnum(graphene.Enum):
52-
PARENT = "parent"
53-
OTHER_GUARDIAN = "other_guardian"
54-
OTHER_RELATION = "other_relation"
55-
ADVOCATE = "advocate"
57+
type = graphene.Field(RelationshipTypeEnum)
5658

5759

5860
class RelationshipInput(graphene.InputObjectType):
@@ -167,7 +169,6 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
167169
raise KukkuuGraphQLError(
168170
'You need to use "SubmitChildrenAndGuardianMutation" first.'
169171
)
170-
171172
if (
172173
user.guardian.children.count()
173174
>= settings.KUKKUU_MAX_NUM_OF_CHILDREN_PER_GUARDIAN

children/tests/test_notifications.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,20 @@
22

33
import pytest
44
from django.core import mail
5-
from django_ilmoitin.models import NotificationTemplate
6-
from parler.utils.context import switch_language
75

86
from children.tests.test_api import (
97
SUBMIT_CHILDREN_AND_GUARDIAN_MUTATION,
108
SUBMIT_CHILDREN_AND_GUARDIAN_VARIABLES,
119
)
10+
from common.tests.utils import (
11+
assert_mails_match_snapshot,
12+
create_notification_template_in_language,
13+
)
1214
from users.factories import GuardianFactory
1315

1416
from ..notifications import NotificationType
1517

1618

17-
def assert_mails_match_snapshot(snapshot):
18-
snapshot.assert_match(
19-
[f"{m.from_email}|{m.to}|{m.subject}|{m.body}" for m in mail.outbox]
20-
)
21-
22-
23-
def create_notification_template_in_language(
24-
notification_type, language, **translations
25-
):
26-
try:
27-
template = NotificationTemplate.objects.get(type=notification_type)
28-
except NotificationTemplate.DoesNotExist:
29-
template = NotificationTemplate(type=notification_type)
30-
with switch_language(template, language):
31-
for field, value in translations.items():
32-
setattr(template, field, value)
33-
template.save()
34-
35-
return template
36-
37-
3819
@pytest.fixture
3920
def notification_template_signup_fi():
4021
return create_notification_template_in_language(

common/models.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import uuid
22

3+
from django.conf import settings
34
from django.db import models, transaction
45
from django.utils.translation import ugettext_lazy as _
56
from parler.managers import TranslatableQuerySet as ParlerTranslatableQuerySet
67
from parler.models import TranslatableModel as ParlerTranslatableModel
8+
from parler.utils.context import switch_language
9+
10+
from common.utils import update_object
711

812

913
class TimestampedModel(models.Model):
@@ -28,9 +32,7 @@ class TranslatableQuerySet(ParlerTranslatableQuerySet):
2832
def create_translatable_object(self, **kwargs):
2933
translations = kwargs.pop("translations")
3034
obj = self.create(**kwargs)
31-
for translation in translations:
32-
language_code = translation.pop("language_code")
33-
obj.create_translation(language_code=language_code, **translation)
35+
obj.create_or_update_translations(translations)
3436
return obj
3537

3638

@@ -39,3 +41,25 @@ class TranslatableModel(ParlerTranslatableModel):
3941

4042
class Meta:
4143
abstract = True
44+
45+
@transaction.atomic
46+
def delete_translations(self, language_codes):
47+
for code in language_codes:
48+
self.delete_translation(code)
49+
50+
@transaction.atomic
51+
def create_or_update_translations(
52+
self, translations=None, translations_to_delete=None
53+
):
54+
for translation in translations:
55+
language_code = translation.pop("language_code")
56+
if language_code not in settings.PARLER_SUPPORTED_LANGUAGE_CODES:
57+
continue
58+
if self.has_translation(language_code):
59+
with switch_language(self, language_code):
60+
update_object(self, translation)
61+
else:
62+
self.create_translation(language_code=language_code, **translation)
63+
64+
if translations_to_delete:
65+
self.delete_translations(translations_to_delete)

common/tests/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1+
import shutil
2+
13
import factory.random
24
import pytest
35
from django.contrib.auth.models import AnonymousUser
46
from django.test import RequestFactory
7+
from django.utils import timezone
58
from freezegun import freeze_time
69
from graphene.test import Client
710

11+
from events.factories import EventFactory, OccurrenceFactory
812
from kukkuu.schema import schema
913
from users.factories import GuardianFactory, UserFactory
14+
from venues.factories import VenueFactory
1015

1116

1217
@pytest.fixture(autouse=True)
1318
def setup_test_environment(settings):
1419
factory.random.reseed_random("777")
1520
settings.DEFAULT_FROM_EMAIL = "kukkuu@example.com"
1621
settings.ILMOITIN_TRANSLATED_FROM_EMAIL = {}
22+
settings.MEDIA_ROOT = "test_media"
1723
with freeze_time("2020-12-12"):
1824
yield
25+
shutil.rmtree("test_media", ignore_errors=True)
1926

2027

2128
@pytest.fixture
@@ -43,6 +50,21 @@ def guardian_api_client():
4350
return _create_api_client_with_user(UserFactory(guardian=GuardianFactory()))
4451

4552

53+
@pytest.fixture
54+
def event():
55+
return EventFactory()
56+
57+
58+
@pytest.fixture
59+
def venue():
60+
return VenueFactory()
61+
62+
63+
@pytest.fixture
64+
def occurrence():
65+
return OccurrenceFactory(time=timezone.now())
66+
67+
4668
def _create_api_client_with_user(user):
4769
request = RequestFactory().post("/graphql")
4870
request.user = user

common/tests/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
1+
from django.core import mail
2+
from django_ilmoitin.models import NotificationTemplate
3+
from parler.utils.context import switch_language
4+
5+
16
def assert_permission_denied(response):
27
assert (
38
response["errors"][0]["message"]
49
== "You do not have permission to perform this action"
510
)
11+
12+
13+
def assert_mails_match_snapshot(snapshot):
14+
snapshot.assert_match(
15+
[f"{m.from_email}|{m.to}|{m.subject}|{m.body}" for m in mail.outbox]
16+
)
17+
18+
19+
def create_notification_template_in_language(
20+
notification_type, language, **translations
21+
):
22+
try:
23+
template = NotificationTemplate.objects.get(type=notification_type)
24+
except NotificationTemplate.DoesNotExist:
25+
template = NotificationTemplate(type=notification_type)
26+
with switch_language(template, language):
27+
for field, value in translations.items():
28+
setattr(template, field, value)
29+
template.save()
30+
31+
return template

common/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1+
from django.db import transaction
2+
3+
14
def update_object(obj, data):
25
if not data:
36
return
47
for k, v in data.items():
58
setattr(obj, k, v)
69
obj.save()
10+
11+
12+
@transaction.atomic
13+
def update_object_with_translations(model, model_data):
14+
translations_input = model_data.pop("translations", None)
15+
delete_translations_input = model_data.pop("delete_translations", None)
16+
17+
if translations_input or delete_translations_input:
18+
model.create_or_update_translations(
19+
translations_input, delete_translations_input
20+
)
21+
update_object(model, model_data)

docker-entrypoint.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ fi
1515
# Apply database migrations
1616
if [[ "$APPLY_MIGRATIONS" = "1" ]]; then
1717
echo "Applying database migrations..."
18-
./manage.py makemigrations
1918
./manage.py migrate --noinput
2019
fi
2120

0 commit comments

Comments
 (0)