feat: mentor invitation URLs

This commit is contained in:
Reto Aebersold 2023-12-11 10:01:24 +01:00
parent 3205eac33f
commit 16a6334802
9 changed files with 164 additions and 33 deletions

View File

@ -53,7 +53,6 @@ from vbv_lernwelt.importer.views import (
coursesessions_trainers_import, coursesessions_trainers_import,
t2l_sync, t2l_sync,
) )
from vbv_lernwelt.learning_mentor.views import mentor_summary
from vbv_lernwelt.notify.views import email_notification_settings from vbv_lernwelt.notify.views import email_notification_settings
from wagtail import urls as wagtail_urls from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls from wagtail.admin import urls as wagtailadmin_urls
@ -126,9 +125,7 @@ urlpatterns = [
request_course_completion_for_user, request_course_completion_for_user,
name="request_course_completion_for_user"), name="request_course_completion_for_user"),
path(r"api/mentor/<signed_int:course_session_id>/", path("api/mentor/<signed_int:course_session_id>/", include("vbv_lernwelt.learning_mentor.urls")),
mentor_summary,
name="mentor_summary"),
# assignment # assignment
path( path(

View File

@ -48,6 +48,17 @@ def is_course_session_expert(user, course_session_id: int):
return is_supervisor or is_expert return is_supervisor or is_expert
def is_course_session_member(user, course_session_id: int | None = None):
if course_session_id is None:
return False
return CourseSessionUser.objects.filter(
course_session_id=course_session_id,
user=user,
role=CourseSessionUser.Role.MEMBER,
).exists()
def can_evaluate_assignments(user, course_session_id: int): def can_evaluate_assignments(user, course_session_id: int):
if user.is_superuser: if user.is_superuser:
return True return True

View File

@ -1,31 +1,55 @@
# Generated by Django 3.2.20 on 2023-12-07 13:46 # Generated by Django 3.2.20 on 2023-12-07 13:46
from django.db import migrations, models import uuid
import django.db.models.deletion import django.db.models.deletion
import django_extensions.db.fields import django_extensions.db.fields
import uuid from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('course', '0005_course_enable_circle_documents'), ("course", "0005_course_enable_circle_documents"),
('learning_mentor', '0001_initial'), ("learning_mentor", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='MentorInvitation', name="MentorInvitation",
fields=[ fields=[
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), (
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), "created",
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), django_extensions.db.fields.CreationDateTimeField(
('email', models.EmailField(max_length=254)), auto_now_add=True, verbose_name="created"
('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.coursesessionuser')), ),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("email", models.EmailField(max_length=254)),
(
"participant",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="course.coursesessionuser",
),
),
], ],
options={ options={
'verbose_name': 'Mentor Invitation', "verbose_name": "Mentor Invitation",
'verbose_name_plural': 'Mentor Invitations', "verbose_name_plural": "Mentor Invitations",
}, },
), ),
] ]

View File

@ -4,18 +4,23 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('learning_mentor', '0002_mentorinvitation'), ("learning_mentor", "0002_mentorinvitation"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='learningmentor', name="learningmentor",
options={'verbose_name': 'Lernbegleiter', 'verbose_name_plural': 'Lernbegleiter'}, options={
"verbose_name": "Lernbegleiter",
"verbose_name_plural": "Lernbegleiter",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='mentorinvitation', name="mentorinvitation",
options={'verbose_name': 'Lernbegleiter Einladung', 'verbose_name_plural': 'Lernbegleiter Einladungen'}, options={
"verbose_name": "Lernbegleiter Einladung",
"verbose_name_plural": "Lernbegleiter Einladungen",
},
), ),
] ]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.20 on 2023-12-11 09:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("course", "0005_course_enable_circle_documents"),
("learning_mentor", "0003_auto_20231207_1448"),
]
operations = [
migrations.AlterUniqueTogether(
name="mentorinvitation",
unique_together={("email", "participant")},
),
]

View File

@ -33,9 +33,7 @@ class LearningMentor(models.Model):
class MentorInvitation(TimeStampedModel): class MentorInvitation(TimeStampedModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField() email = models.EmailField()
participant = models.ForeignKey( participant = models.ForeignKey(CourseSessionUser, on_delete=models.CASCADE)
CourseSessionUser, on_delete=models.CASCADE
)
def __str__(self): def __str__(self):
return f"{self.email} ({self.participant})" return f"{self.email} ({self.participant})"
@ -43,3 +41,4 @@ class MentorInvitation(TimeStampedModel):
class Meta: class Meta:
verbose_name = "Lernbegleiter Einladung" verbose_name = "Lernbegleiter Einladung"
verbose_name_plural = "Lernbegleiter Einladungen" verbose_name_plural = "Lernbegleiter Einladungen"
unique_together = [["email", "participant"]]

View File

@ -0,0 +1,50 @@
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from vbv_lernwelt.course.creators.test_utils import (
add_course_session_user,
create_course,
create_course_session,
create_user,
)
from vbv_lernwelt.course.models import CourseSessionUser
class LearningMentorInvitationTest(APITestCase):
def setUp(self) -> None:
self.course, self.course_page = create_course("Test Course")
self.course_session = create_course_session(course=self.course, title="Test VV")
self.participant = create_user("participant")
def test_create_invitation_not_member(self) -> None:
# GIVEN
self.client.force_login(self.participant)
invite_url = reverse(
"create_invitation", kwargs={"course_session_id": self.course_session.id}
)
# WHEN
response = self.client.post(invite_url)
# THEN
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_create_invitation(self) -> None:
# GIVEN
self.client.force_login(self.participant)
add_course_session_user(
self.course_session,
self.participant,
role=CourseSessionUser.Role.MEMBER,
)
invite_url = reverse(
"create_invitation", kwargs={"course_session_id": self.course_session.id}
)
# WHEN
response = self.client.post(invite_url, data={"email": "test@example.com"})
# THEN
self.assertEqual(response.status_code, status.HTTP_200_OK)

View File

@ -0,0 +1,8 @@
from django.urls import path
from . import views
urlpatterns = [
path("summary", views.mentor_summary, name="mentor_summary"),
path("invite", views.create_invitation, name="create_invitation"),
]

View File

@ -1,15 +1,17 @@
from django.shortcuts import redirect from django.shortcuts import redirect
from rest_framework import permissions
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from vbv_lernwelt.core.serializers import UserSerializer from vbv_lernwelt.core.serializers import UserSerializer
from vbv_lernwelt.course.models import CourseSession from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.iam.permissions import is_course_session_member
from vbv_lernwelt.learning_mentor.content.praxis_assignment import ( from vbv_lernwelt.learning_mentor.content.praxis_assignment import (
get_praxis_assignments, get_praxis_assignments,
) )
from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation
from vbv_lernwelt.learning_mentor.serializers import PraxisAssignmentStatusSerializer from vbv_lernwelt.learning_mentor.serializers import PraxisAssignmentStatusSerializer
from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.learnpath.models import Circle
@ -41,6 +43,13 @@ def mentor_summary(request, course_session_id: int):
) )
class CourseSessionMember(permissions.BasePermission):
def has_permission(self, request, view):
return is_course_session_member(
request.user, view.kwargs.get("course_session_id")
)
@api_view(["GET"]) @api_view(["GET"])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def list_invitations(request, course_session_id: int): def list_invitations(request, course_session_id: int):
@ -48,12 +57,23 @@ def list_invitations(request, course_session_id: int):
@api_view(["POST"]) @api_view(["POST"])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated, CourseSessionMember])
def create_invitation(request, course_session_id: int): def create_invitation(request, course_session_id: int):
# Validate request course_session = get_object_or_404(CourseSession, id=course_session_id)
# Create invitation course_session_user = get_object_or_404(
# Send email CourseSessionUser, user=request.user, course_session=course_session
pass )
email = request.data.get("email")
if not email:
return Response({"error": "email is required"}, status=400)
invitation = MentorInvitation.objects.get_or_create(
email=email,
participant=course_session_user,
)
return Response({})
@api_view(["GET"]) @api_view(["GET"])