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

View File

@ -48,6 +48,17 @@ def is_course_session_expert(user, course_session_id: int):
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):
if user.is_superuser:
return True

View File

@ -1,31 +1,55 @@
# 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_extensions.db.fields
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0005_course_enable_circle_documents'),
('learning_mentor', '0001_initial'),
("course", "0005_course_enable_circle_documents"),
("learning_mentor", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='MentorInvitation',
name="MentorInvitation",
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')),
('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')),
(
"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"
),
),
(
"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={
'verbose_name': 'Mentor Invitation',
'verbose_name_plural': 'Mentor Invitations',
"verbose_name": "Mentor Invitation",
"verbose_name_plural": "Mentor Invitations",
},
),
]

View File

@ -4,18 +4,23 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('learning_mentor', '0002_mentorinvitation'),
("learning_mentor", "0002_mentorinvitation"),
]
operations = [
migrations.AlterModelOptions(
name='learningmentor',
options={'verbose_name': 'Lernbegleiter', 'verbose_name_plural': 'Lernbegleiter'},
name="learningmentor",
options={
"verbose_name": "Lernbegleiter",
"verbose_name_plural": "Lernbegleiter",
},
),
migrations.AlterModelOptions(
name='mentorinvitation',
options={'verbose_name': 'Lernbegleiter Einladung', 'verbose_name_plural': 'Lernbegleiter Einladungen'},
name="mentorinvitation",
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):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField()
participant = models.ForeignKey(
CourseSessionUser, on_delete=models.CASCADE
)
participant = models.ForeignKey(CourseSessionUser, on_delete=models.CASCADE)
def __str__(self):
return f"{self.email} ({self.participant})"
@ -43,3 +41,4 @@ class MentorInvitation(TimeStampedModel):
class Meta:
verbose_name = "Lernbegleiter Einladung"
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 rest_framework import permissions
from rest_framework.decorators import api_view, permission_classes
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
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 (
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.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"])
@permission_classes([IsAuthenticated])
def list_invitations(request, course_session_id: int):
@ -48,12 +57,23 @@ def list_invitations(request, course_session_id: int):
@api_view(["POST"])
@permission_classes([IsAuthenticated])
@permission_classes([IsAuthenticated, CourseSessionMember])
def create_invitation(request, course_session_id: int):
# Validate request
# Create invitation
# Send email
pass
course_session = get_object_or_404(CourseSession, id=course_session_id)
course_session_user = get_object_or_404(
CourseSessionUser, user=request.user, course_session=course_session
)
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"])