diff --git a/client/src/pages/onboarding/AccountProfile.vue b/client/src/pages/onboarding/AccountProfile.vue index d482882b..e3cf2e9c 100644 --- a/client/src/pages/onboarding/AccountProfile.vue +++ b/client/src/pages/onboarding/AccountProfile.vue @@ -14,18 +14,30 @@ import { profileNextRoute } from "@/services/onboarding"; const { t } = useTranslation(); type Organisation = { - id: string; - name: string; + organisation_id: number; + name_de: string; + name_fr: string; + name_it: string; }; const user = useUserStore(); const route = useRoute(); const fetchResult = useFetch("/api/core/organisations/"); -const organisations: Ref = fetchResult.data; +const organisationData: Ref = fetchResult.data; + +const organisations = computed(() => { + if (organisationData.value) { + return organisationData.value.map((c) => ({ + id: c.organisation_id, + name: c[`name_${user.language}`], + })); + } + return []; +}); const selectedOrganisation = ref({ - id: "0", + id: 0, name: t("a.Auswählen"), }); @@ -58,7 +70,6 @@ watch(avatarFileInfo, (info) => { }); watch(selectedOrganisation, (organisation) => { - console.log("organisation changed", organisation); itPut("/api/core/me/", { organisation: organisation.id, }); @@ -82,7 +93,7 @@ const nextRoute = computed(() => {

diff --git a/client/src/stores/user.ts b/client/src/stores/user.ts index 7b32430a..4053220a 100644 --- a/client/src/stores/user.ts +++ b/client/src/stores/user.ts @@ -25,7 +25,7 @@ export type UserState = { email: string; username: string; avatar_url: string; - organisation: string; + organisation: number; is_superuser: boolean; course_session_experts: string[]; loggedIn: boolean; @@ -58,7 +58,7 @@ const initialUserState: UserState = { username: "", avatar_url: "", is_superuser: false, - organisation: "", + organisation: 0, course_session_experts: [], loggedIn: false, language: defaultLanguage, diff --git a/server/config/urls.py b/server/config/urls.py index 50fddead..b4d338e6 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -13,6 +13,7 @@ from graphene_django.views import GraphQLView from wagtail import urls as wagtail_urls from wagtail.admin import urls as wagtailadmin_urls +from vbv_lernwelt.api.user import list_organisations, me_user_view from vbv_lernwelt.assignment.views import request_assignment_completion_status from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt from vbv_lernwelt.core.schema import schema @@ -20,8 +21,6 @@ from vbv_lernwelt.core.views import ( check_rate_limit, cypress_reset_view, generate_web_component_icons, - list_organisations, - me_user_view, permission_denied_view, rate_limit_exceeded_view, vue_home, diff --git a/server/vbv_lernwelt/api/__init__.py b/server/vbv_lernwelt/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/api/tests/__init__.py b/server/vbv_lernwelt/api/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/core/tests/test_me_api.py b/server/vbv_lernwelt/api/tests/test_me_api.py similarity index 89% rename from server/vbv_lernwelt/core/tests/test_me_api.py rename to server/vbv_lernwelt/api/tests/test_me_api.py index 44981678..36631f03 100644 --- a/server/vbv_lernwelt/core/tests/test_me_api.py +++ b/server/vbv_lernwelt/api/tests/test_me_api.py @@ -30,10 +30,10 @@ class MeUserViewTest(APITestCase): url = reverse("me_user_view") # replace with your actual URL name # WHEN - response = self.client.put(url, {"organisation": "6"}) + response = self.client.put(url, {"organisation": 6}) # THEN self.assertEqual(response.status_code, status.HTTP_200_OK) updated_user = User.objects.get(username="testuser") - self.assertEquals(updated_user.organisation, "6") + self.assertEquals(updated_user.organisation.organisation_id, 6) diff --git a/server/vbv_lernwelt/core/tests/test_organisation_api.py b/server/vbv_lernwelt/api/tests/test_organisation_api.py similarity index 71% rename from server/vbv_lernwelt/core/tests/test_organisation_api.py rename to server/vbv_lernwelt/api/tests/test_organisation_api.py index ec34a0b2..a8022ff6 100644 --- a/server/vbv_lernwelt/core/tests/test_organisation_api.py +++ b/server/vbv_lernwelt/api/tests/test_organisation_api.py @@ -3,7 +3,6 @@ from rest_framework import status from rest_framework.test import APITestCase from vbv_lernwelt.core.models import User -from vbv_lernwelt.core.organisations import ORGANISATIONS class OrganisationViewTest(APITestCase): @@ -23,5 +22,12 @@ class OrganisationViewTest(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_200_OK) - _id, name = ORGANISATIONS[0] - self.assertEqual(response.data[0], {"id": _id, "name": name}) + self.assertEqual( + response.data[0], + { + "organisation_id": 1, + "name_de": "andere Broker", + "name_fr": "autres Broker", + "name_it": "altre Broker", + }, + ) diff --git a/server/vbv_lernwelt/api/user.py b/server/vbv_lernwelt/api/user.py new file mode 100644 index 00000000..c83aacb9 --- /dev/null +++ b/server/vbv_lernwelt/api/user.py @@ -0,0 +1,35 @@ +from rest_framework.decorators import api_view +from rest_framework.response import Response + +from vbv_lernwelt.core.models import Organisation +from vbv_lernwelt.core.serializers import OrganisationSerializer, UserSerializer + + +@api_view(["GET"]) +def list_organisations(request): + if not request.user.is_authenticated: + return Response(status=403) + + serializer = OrganisationSerializer(Organisation.objects.all(), many=True) + return Response(serializer.data) + + +@api_view(["GET", "PUT"]) +def me_user_view(request): + if not request.user.is_authenticated: + return Response(status=403) + + if request.method == "GET": + return Response(UserSerializer(request.user).data) + + if request.method == "PUT": + serializer = UserSerializer( + request.user, + data=request.data, + partial=True, + ) + if serializer.is_valid(): + serializer.save() + return Response(UserSerializer(request.user).data) + + return Response(status=400) diff --git a/server/vbv_lernwelt/core/admin.py b/server/vbv_lernwelt/core/admin.py index f2a65d3b..17b7e636 100644 --- a/server/vbv_lernwelt/core/admin.py +++ b/server/vbv_lernwelt/core/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from django.contrib.auth import admin as auth_admin, get_user_model from django.utils.translation import gettext_lazy as _ -from vbv_lernwelt.core.models import JobLog +from vbv_lernwelt.core.models import JobLog, Organisation from vbv_lernwelt.core.utils import pretty_print_json User = get_user_model() @@ -78,3 +78,13 @@ class JobLogAdmin(LogAdmin): if obj.ended: return (obj.ended - obj.started).seconds // 60 return None + + +@admin.register(Organisation) +class OrganisationAdmin(admin.ModelAdmin): + list_display = ( + "organisation_id", + "name_de", + "name_fr", + "name_it", + ) diff --git a/server/vbv_lernwelt/core/migrations/0003_organisations.py b/server/vbv_lernwelt/core/migrations/0003_organisations.py new file mode 100644 index 00000000..9a99d4d0 --- /dev/null +++ b/server/vbv_lernwelt/core/migrations/0003_organisations.py @@ -0,0 +1,145 @@ +import django.db.models.deletion +from django.db import migrations, models + +orgs = { + 1: {"de": "andere Broker", "fr": "autres Broker", "it": "altre Broker"}, + 2: { + "de": "andere Krankenversicherer", + "fr": "autres assureurs santé", + "it": "altre assicurazioni sanitarie", + }, + 3: { + "de": "andere Privatversicherer", + "fr": "autres Assurance privée", + "it": "altre Assicurazione privato", + }, + 4: {"de": "Allianz Suisse", "fr": "Allianz Suisse", "it": "Allianz Suisse"}, + 5: {"de": "AON", "fr": "AON", "it": "AON"}, + 6: { + "de": "AXA Winterthur", + "fr": "AXA Assurances SA", + "it": "AXA Assicurazioni SA", + }, + 7: {"de": "Baloise", "fr": "Baloise", "it": "Baloise"}, + 8: { + "de": "CAP Rechtsschutz", + "fr": "CAP Protection juridique", + "it": "CAP Protezione giuridica", + }, + 9: { + "de": "Coop Rechtsschutz", + "fr": "Coop Protection juridique", + "it": "Coop Protezione giuridica", + }, + 10: {"de": "CSS", "fr": "CSS", "it": "CSS"}, + 11: {"de": "Die Mobiliar", "fr": "La Mobilière", "it": "La Mobiliare"}, + 12: { + "de": "Emmental Versicherung", + "fr": "Emmental Assurance", + "it": "Emmental Assicurazione", + }, + 13: { + "de": "GENERALI Versicherungen", + "fr": "Generali Assurances", + "it": "Generali Assicurazioni", + }, + 14: {"de": "Groupe Mutuel", "fr": "GROUPE MUTUEL", "it": "GROUPE MUTUEL"}, + 15: {"de": "Helsana", "fr": "Helsana", "it": "Helsana"}, + 16: {"de": "Helvetia", "fr": "Helvetia", "it": "Helvetia"}, + 17: {"de": "Kessler & Co AG", "fr": "Kessler & Co AG", "it": "Kessler & Co AG"}, + 18: { + "de": "Orion Rechtsschutz Versicherung", + "fr": "Orion Protection juridique", + "it": "Orion Protezione giuridica", + }, + 19: {"de": "PAX", "fr": "PAX", "it": "PAX"}, + 20: {"de": "Sanitas", "fr": "Sanitas", "it": "Sanitas"}, + 21: {"de": "SUVA", "fr": "SUVA", "it": "SUVA"}, + 22: {"de": "Swica", "fr": "Swica", "it": "Swica"}, + 23: {"de": "Swiss Life", "fr": "Swiss Life", "it": "Swiss Life"}, + 24: {"de": "Swiss Re", "fr": "Swiss Re", "it": "Swiss Re"}, + 25: { + "de": "Visana Services AG", + "fr": "Visana Services SA", + "it": "Visana Services SA", + }, + 26: { + "de": "VZ VermögensZentrum AG", + "fr": "VZ VermögensZentrum AG", + "it": "VZ VermögensZentrum AG", + }, + 27: { + "de": "Würth Financial Services AG", + "fr": "Würth Financial Services SA", + "it": "Würth Financial Services SA", + }, + 28: {"de": "Zürich", "fr": "Zurich", "it": "Zurigo"}, + 29: {"de": "VBV", "fr": "AFA", "it": "AFA"}, + 30: {"de": "Vaudoise", "fr": "Vaudoise", "it": "Vaudoise"}, + 31: { + "de": "Keine Firmenzugehörigkeit", + "fr": "Pas d'appartenance à une entreprise", + "it": "Nessuna affiliazione aziendale", + }, +} + + +def add_organisations(apps, schema_editor): + Organisation = apps.get_model("core", "Organisation") + + for org_id, org_data in orgs.items(): + Organisation.objects.create( + organisation_id=org_id, + name_de=org_data["de"], + name_fr=org_data["fr"], + name_it=org_data["it"], + ) + + +def remove_organisations(apps, schema_editor): + Organisation = apps.get_model("core", "Organisation") + + for org_id, org_data in orgs.items(): + Organisation.objects.filter( + organisation_id=org_id, + name_de=org_data["de"], + name_fr=org_data["fr"], + name_it=org_data["it"], + ).delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0002_joblog"), + ] + + operations = [ + migrations.CreateModel( + name="Organisation", + fields=[ + ( + "organisation_id", + models.IntegerField(primary_key=True, serialize=False), + ), + ("name_de", models.CharField(max_length=255)), + ("name_fr", models.CharField(max_length=255)), + ("name_it", models.CharField(max_length=255)), + ], + options={ + "verbose_name": "Organisation", + "verbose_name_plural": "Organisations", + "ordering": ["organisation_id"], + }, + ), + migrations.AddField( + model_name="user", + name="organisation", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="core.organisation", + ), + ), + migrations.RunPython(add_organisations, remove_organisations), + ] diff --git a/server/vbv_lernwelt/core/migrations/0003_user_organisation.py b/server/vbv_lernwelt/core/migrations/0003_user_organisation.py deleted file mode 100644 index fdf3922c..00000000 --- a/server/vbv_lernwelt/core/migrations/0003_user_organisation.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 3.2.20 on 2023-11-13 13:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("core", "0002_joblog"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="organisation", - field=models.CharField( - blank=True, - choices=[ - ("1", "Andere Broker"), - ("2", "Andere Krankenversicherer"), - ("3", "Andere Privatversicherer"), - ("4", "Allianz Suisse"), - ("5", "AON"), - ("6", "AXA Winterthur"), - ("7", "Baloise"), - ("8", "CAP Rechtsschutz"), - ("9", "Coop Rechtsschutz"), - ("10", "CSS"), - ("11", "Die Mobiliar"), - ("12", "Emmental Versicherung"), - ("13", "GENERALI Versicherungen"), - ("14", "Groupe Mutuel"), - ("15", "Helsana"), - ("16", "Helvetia"), - ("17", "Kessler & Co AG"), - ("18", "Orion Rechtsschutz Versicherung"), - ("19", "PAX"), - ("20", "Sanitas"), - ("21", "SUVA"), - ("22", "Swica"), - ("23", "Swiss Life"), - ("24", "Swiss Re"), - ("25", "Visana Services AG"), - ("26", "VZ VermögensZentrum AG"), - ("27", "Würth Financial Services AG"), - ("28", "Zürich"), - ("29", "VBV"), - ("30", "Vaudoise"), - ("31", "Keine Firmenzugehörigkeit"), - ], - max_length=255, - ), - ), - ] diff --git a/server/vbv_lernwelt/core/models.py b/server/vbv_lernwelt/core/models.py index 842a4a4f..4895d2d3 100644 --- a/server/vbv_lernwelt/core/models.py +++ b/server/vbv_lernwelt/core/models.py @@ -4,7 +4,20 @@ from django.contrib.auth.models import AbstractUser from django.db import models from django.db.models import JSONField -from vbv_lernwelt.core.organisations import ORGANISATIONS + +class Organisation(models.Model): + organisation_id = models.IntegerField(primary_key=True) + name_de = models.CharField(max_length=255) + name_fr = models.CharField(max_length=255) + name_it = models.CharField(max_length=255) + + def __str__(self): + return f"{self.name_de} ({self.organisation_id})" + + class Meta: + verbose_name = "Organisation" + verbose_name_plural = "Organisations" + ordering = ["organisation_id"] class User(AbstractUser): @@ -31,10 +44,8 @@ class User(AbstractUser): additional_json_data = JSONField(default=dict, blank=True) language = models.CharField(max_length=2, choices=LANGUAGE_CHOICES, default="de") - organisation = models.CharField( - max_length=255, - choices=ORGANISATIONS, - blank=True, + organisation = models.ForeignKey( + Organisation, on_delete=models.SET_NULL, null=True, blank=True ) diff --git a/server/vbv_lernwelt/core/organisations.py b/server/vbv_lernwelt/core/organisations.py deleted file mode 100644 index 6bc80d38..00000000 --- a/server/vbv_lernwelt/core/organisations.py +++ /dev/null @@ -1,33 +0,0 @@ -ORGANISATIONS = [ - ("1", "Andere Broker"), - ("2", "Andere Krankenversicherer"), - ("3", "Andere Privatversicherer"), - ("4", "Allianz Suisse"), - ("5", "AON"), - ("6", "AXA Winterthur"), - ("7", "Baloise"), - ("8", "CAP Rechtsschutz"), - ("9", "Coop Rechtsschutz"), - ("10", "CSS"), - ("11", "Die Mobiliar"), - ("12", "Emmental Versicherung"), - ("13", "GENERALI Versicherungen"), - ("14", "Groupe Mutuel"), - ("15", "Helsana"), - ("16", "Helvetia"), - ("17", "Kessler & Co AG"), - ("18", "Orion Rechtsschutz Versicherung"), - ("19", "PAX"), - ("20", "Sanitas"), - ("21", "SUVA"), - ("22", "Swica"), - ("23", "Swiss Life"), - ("24", "Swiss Re"), - ("25", "Visana Services AG"), - ("26", "VZ VermögensZentrum AG"), - ("27", "Würth Financial Services AG"), - ("28", "Zürich"), - ("29", "VBV"), - ("30", "Vaudoise"), - ("31", "Keine Firmenzugehörigkeit"), -] diff --git a/server/vbv_lernwelt/core/serializers.py b/server/vbv_lernwelt/core/serializers.py index 3e910b80..31483733 100644 --- a/server/vbv_lernwelt/core/serializers.py +++ b/server/vbv_lernwelt/core/serializers.py @@ -3,7 +3,7 @@ from typing import List from rest_framework import serializers from rest_framework.renderers import JSONRenderer -from vbv_lernwelt.core.models import User +from vbv_lernwelt.core.models import Organisation, User from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course_session_group.models import CourseSessionGroup @@ -53,3 +53,9 @@ class UserSerializer(serializers.ModelSerializer): ) return [str(_id) for _id in (supervisor_in_session_ids | expert_in_session_ids)] + + +class OrganisationSerializer(serializers.ModelSerializer): + class Meta: + model = Organisation + fields = "__all__" diff --git a/server/vbv_lernwelt/core/views.py b/server/vbv_lernwelt/core/views.py index f9b990af..aeb6825e 100644 --- a/server/vbv_lernwelt/core/views.py +++ b/server/vbv_lernwelt/core/views.py @@ -27,7 +27,6 @@ from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt -from vbv_lernwelt.core.organisations import ORGANISATIONS from vbv_lernwelt.core.serializers import UserSerializer logger = structlog.get_logger(__name__) @@ -91,35 +90,6 @@ def vue_login(request): ) -@api_view(["GET"]) -def list_organisations(request): - if not request.user.is_authenticated: - return Response(status=403) - - return Response([{"id": _id, "name": name} for _id, name in ORGANISATIONS]) - - -@api_view(["GET", "PUT"]) -def me_user_view(request): - if not request.user.is_authenticated: - return Response(status=403) - - if request.method == "GET": - return Response(UserSerializer(request.user).data) - - if request.method == "PUT": - serializer = UserSerializer( - request.user, - data=request.data, - partial=True, - ) - if serializer.is_valid(): - serializer.save() - return Response(UserSerializer(request.user).data) - - return Response(status=400) - - @api_view(["POST"]) def vue_logout(request): logout(request)