From 437ffde8b1607c591beacdf3d31c31e314792f0f Mon Sep 17 00:00:00 2001
From: Reto Aebersold
Date: Tue, 14 Nov 2023 17:13:27 +0100
Subject: [PATCH] feat: add organisations as model
---
.../src/pages/onboarding/AccountProfile.vue | 23 ++-
client/src/stores/user.ts | 4 +-
server/config/urls.py | 3 +-
server/vbv_lernwelt/api/__init__.py | 0
server/vbv_lernwelt/api/tests/__init__.py | 0
.../{core => api}/tests/test_me_api.py | 4 +-
.../tests/test_organisation_api.py | 12 +-
server/vbv_lernwelt/api/user.py | 35 +++++
server/vbv_lernwelt/core/admin.py | 12 +-
.../core/migrations/0003_organisations.py | 145 ++++++++++++++++++
.../core/migrations/0003_user_organisation.py | 53 -------
server/vbv_lernwelt/core/models.py | 21 ++-
server/vbv_lernwelt/core/organisations.py | 33 ----
server/vbv_lernwelt/core/serializers.py | 8 +-
server/vbv_lernwelt/core/views.py | 30 ----
15 files changed, 245 insertions(+), 138 deletions(-)
create mode 100644 server/vbv_lernwelt/api/__init__.py
create mode 100644 server/vbv_lernwelt/api/tests/__init__.py
rename server/vbv_lernwelt/{core => api}/tests/test_me_api.py (89%)
rename server/vbv_lernwelt/{core => api}/tests/test_organisation_api.py (71%)
create mode 100644 server/vbv_lernwelt/api/user.py
create mode 100644 server/vbv_lernwelt/core/migrations/0003_organisations.py
delete mode 100644 server/vbv_lernwelt/core/migrations/0003_user_organisation.py
delete mode 100644 server/vbv_lernwelt/core/organisations.py
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)