diff --git a/Dockerfile b/Dockerfile index e41b7d5843d5a31d0aa47ed4afde4e7423b0b82f..34714ff57a0213640ac70e9f06f18eb5c9bb3795 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,12 +24,6 @@ COPY . . VOLUME ["/app"] -RUN adduser --home /app --no-create-home --system --uid ${AURA_UID} --group app -RUN mkdir -p /app/logs -RUN chown -R app:app /app - -USER app - # run with Django's development server CMD ["run.dev"] @@ -37,6 +31,12 @@ FROM base AS prod COPY . . +RUN adduser --home /app --no-create-home --system --uid ${AURA_UID} --group app +RUN mkdir -p /app/logs +RUN mkdir -p /app/static/admin +RUN chown -R app:app /app + +USER app # run with gunicorn CMD ["run.prod"] diff --git a/Makefile b/Makefile index 307b334030973fb64e019e7fe830b0db69e5b303..e17ab421c64212a4d72515f93b83e867c141fbae 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ create_oidc_client.dashboard: create_oidc_client.tank: $(POETRY_RUN_MANAGE) create_oidc_client tank confidential --client-id ${TANK_OIDC_CLIENT_ID} --client-secret ${TANK_OIDC_CLIENT_SECRET} -r "code" -u ${TANK_CALLBACK_BASE_URL}/tank/auth/oidc/callback -initialize: migrate loaddata.program create_oidc_client.dashboard create_oidc_client.tank +initialize: migrate collectstatic loaddata.program create_oidc_client.dashboard create_oidc_client.tank $(POETRY_RUN_MANAGE) createsuperuser --no-input $(POETRY_RUN_MANAGE) creatersakey diff --git a/program/serializers.py b/program/serializers.py index ea70b69b4e178382c129e0b673a45e99eda6d25f..a5383bff21b2e531d97da4425282db95b7d60cfa 100644 --- a/program/serializers.py +++ b/program/serializers.py @@ -548,22 +548,23 @@ class ShowSerializer(serializers.HyperlinkedModelSerializer): instance.slug = validated_data.get("slug", instance.slug) instance.type = validated_data.get("type_id", instance.type) - if category := validated_data.get("category"): + # optional many-to-many in PATCH requests + if (category := validated_data.get("category")) is not None: instance.category.set(category) - if hosts := validated_data.get("hosts"): + if (hosts := validated_data.get("hosts")) is not None: instance.hosts.set(hosts) - if language := validated_data.get("language"): + if (language := validated_data.get("language")) is not None: instance.language.set(language) - if music_focus := validated_data.get("music_focus"): + if (music_focus := validated_data.get("music_focus")) is not None: instance.music_focus.set(music_focus) - if owners := validated_data.get("owners"): + if (owners := validated_data.get("owners")) is not None: instance.owners.set(owners) - if topic := validated_data.get("topic"): + if (topic := validated_data.get("topic")) is not None: instance.topic.set(topic) if links_data := validated_data.get("links"): @@ -779,7 +780,6 @@ class TimeSlotSerializer(serializers.ModelSerializer): instance.repetition_of = validated_data.get("repetition_of_id", instance.repetition_of) instance.playlist_id = validated_data.get("playlist_id", instance.playlist_id) - instance.save() return instance @@ -790,17 +790,15 @@ class NoteLinkSerializer(serializers.ModelSerializer): fields = ("type", "url") -tags_json_schema = { - "type": "array", - "items": { - "type": "string" - } -} +tags_json_schema = {"type": "array", "items": {"type": "string"}} class NoteSerializer(serializers.ModelSerializer): contributor_ids = serializers.PrimaryKeyRelatedField( - many=True, queryset=Host.objects.all(), required=False, source="contributors", + many=True, + queryset=Host.objects.all(), + required=False, + source="contributors", ) image_id = serializers.PrimaryKeyRelatedField( queryset=Image.objects.all(), required=False, allow_null=True @@ -864,6 +862,7 @@ class NoteSerializer(serializers.ModelSerializer): show = validated_data["timeslot"].schedule.show + # we derive `contributors`, `language` and `topic` from the Show's values if not set contributors = validated_data.pop("contributors", show.owners.values_list("id", flat=True)) language = validated_data.pop("language", show.language.values_list("id", flat=True)) topic = validated_data.pop("topic", show.topic.values_list("id", flat=True)) @@ -905,9 +904,16 @@ class NoteSerializer(serializers.ModelSerializer): instance.tags = validated_data.get("tags", instance.tags) instance.title = validated_data.get("title", instance.title) - if contributors := validated_data.get("contributors", []): + # optional many-to-many in PATCH requests + if (contributors := validated_data.get("contributors")) is not None: instance.contributors.set(contributors) + if (language := validated_data.get("language")) is not None: + instance.language.set(language) + + if (topic := validated_data.get("topic")) is not None: + instance.topic.set(topic) + if cba_id := validated_data.get("cba_id"): if audio_url := get_audio_url(cba_id): NoteLink.objects.create(note=instance, type="CBA", url=audio_url) @@ -918,12 +924,6 @@ class NoteSerializer(serializers.ModelSerializer): for link_data in links_data: NoteLink.objects.create(note=instance, **link_data) - if language := validated_data.get("language", []): - instance.language.set(language) - - if topic := validated_data.get("topic", []): - instance.topic.set(topic) - instance.updated_by = self.context.get("request").user.username instance.save() diff --git a/program/views.py b/program/views.py index 3b382ce1ca4dd1628c28900ffff18d16f5f21222..ca61dc3bf60c58e2741c345ef621054502903591 100644 --- a/program/views.py +++ b/program/views.py @@ -33,7 +33,7 @@ from rest_framework.pagination import LimitOffsetPagination from rest_framework.response import Response from django.contrib.auth.models import User -from django.http import Http404, HttpResponse +from django.http import Http404, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.translation import gettext as _ @@ -457,6 +457,7 @@ class APIRRuleViewSet(viewsets.ModelViewSet): (`one-solution-per-conflict`). Only one solution is allowed per conflict, so you either offered too many or not enough solutions for any reported conflicts. + * The referenced recurrence rule does not exist. """ ), ), @@ -561,6 +562,10 @@ class APIScheduleViewSet( try: resolution = resolve_conflicts(request.data, pk, show_pk) + # FIXME: Find a better way to do this. + # The exception is thrown by the instantiate_upcoming_schedule function. + except RRule.DoesNotExist as exc: + return JsonResponse({"rruleId": str(exc)}, status=status.HTTP_400_BAD_REQUEST) except ScheduleConflictError as exc: return Response(exc.conflicts, status.HTTP_409_CONFLICT)