Limit page access to users which can access course
This commit is contained in:
parent
d8148158a1
commit
d8577c70d5
|
|
@ -515,7 +515,7 @@ if "django_redis.cache.RedisCache" in env("IT_DJANGO_CACHE_BACKEND", default="")
|
||||||
|
|
||||||
CACHES["api_page_cache"] = {
|
CACHES["api_page_cache"] = {
|
||||||
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
||||||
"LOCATION": "django_cache_learning_path",
|
"LOCATION": "django_cache_table_api_page",
|
||||||
}
|
}
|
||||||
|
|
||||||
# OAuth/OpenId Connect
|
# OAuth/OpenId Connect
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from vbv_lernwelt.core.views import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course.views import (
|
from vbv_lernwelt.course.views import (
|
||||||
mark_course_completion,
|
mark_course_completion,
|
||||||
page_api_view,
|
course_page_api_view,
|
||||||
request_course_completion,
|
request_course_completion,
|
||||||
)
|
)
|
||||||
from wagtail import urls as wagtail_urls
|
from wagtail import urls as wagtail_urls
|
||||||
|
|
@ -59,7 +59,8 @@ urlpatterns = [
|
||||||
name="generate_web_component_icons"),
|
name="generate_web_component_icons"),
|
||||||
|
|
||||||
# course
|
# course
|
||||||
path(r"api/course/page/<slug:slug>/", page_api_view, name="page_api_view"),
|
path(r"api/course/page/<slug:slug>/", course_page_api_view,
|
||||||
|
name="course_page_api_view"),
|
||||||
path(r"api/course/completion/mark/", mark_course_completion,
|
path(r"api/course/completion/mark/", mark_course_completion,
|
||||||
name="mark_course_completion"),
|
name="mark_course_completion"),
|
||||||
path(r"api/course/completion/<course_id>/", request_course_completion,
|
path(r"api/course/completion/<course_id>/", request_course_completion,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.cache import caches
|
||||||
from rest_framework.throttling import UserRateThrottle
|
from rest_framework.throttling import UserRateThrottle
|
||||||
from structlog.types import EventDict
|
from structlog.types import EventDict
|
||||||
|
|
||||||
|
|
@ -50,3 +51,16 @@ def first_true(iterable, default=False, pred=None):
|
||||||
# first_true([a,b,c], x) --> a or b or c or x
|
# first_true([a,b,c], x) --> a or b or c or x
|
||||||
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
|
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
|
||||||
return next(filter(pred, iterable), default)
|
return next(filter(pred, iterable), default)
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_page_cache():
|
||||||
|
return caches["api_page_cache"]
|
||||||
|
|
||||||
|
|
||||||
|
def api_page_cache_get_or_set(key, func, timeout=60 * 60 * 8):
|
||||||
|
cache = get_api_page_cache()
|
||||||
|
value = cache.get(key)
|
||||||
|
if value is None:
|
||||||
|
value = func()
|
||||||
|
cache.set(key, value, timeout=timeout)
|
||||||
|
return value
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
from rest_framework import permissions
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
|
|
||||||
|
|
||||||
class CourseAccessPermission(permissions.BasePermission):
|
def has_course_access(request, obj):
|
||||||
def has_object_permission(self, request, view, obj):
|
if request.user.is_superuser:
|
||||||
# Read permissions are allowed to any request,
|
return True
|
||||||
# so we'll always allow GET, HEAD or OPTIONS requests.
|
|
||||||
if request.method in permissions.SAFE_METHODS:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Instance must have an attribute named `owner`.
|
course = obj.specific.get_course()
|
||||||
return obj.owner == request.user
|
|
||||||
|
# attached to CourseSession
|
||||||
|
course_session = CourseSessionUser.objects.filter(
|
||||||
|
course_session__course_id=course.id, user=request.user
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
if course_session:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# TODO is trainer/expert of session
|
||||||
|
|
||||||
|
# TODO check school class access
|
||||||
|
return False
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
create_default_users()
|
create_default_users()
|
||||||
create_test_course()
|
create_test_course()
|
||||||
self.user = User.objects.get(username="student")
|
self.user = User.objects.get(username="admin")
|
||||||
self.client.login(username="student", password="test")
|
self.client.login(username="admin", password="test")
|
||||||
|
|
||||||
def test_completeLearningContent_works(self):
|
def test_completeLearningContent_works(self):
|
||||||
learning_content = LearningContent.objects.get(title="Fachcheck Fahrzeug")
|
learning_content = LearningContent.objects.get(title="Fachcheck Fahrzeug")
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import structlog
|
import structlog
|
||||||
from django.views.decorators.cache import cache_page
|
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
|
|
||||||
from vbv_lernwelt.course.models import CourseCompletion, CoursePage
|
from vbv_lernwelt.core.utils import api_page_cache_get_or_set
|
||||||
|
from vbv_lernwelt.course.models import CourseCompletion
|
||||||
|
from vbv_lernwelt.course.permissions import has_course_access
|
||||||
from vbv_lernwelt.course.serializers import CourseCompletionSerializer
|
from vbv_lernwelt.course.serializers import CourseCompletionSerializer
|
||||||
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
||||||
|
|
||||||
|
|
@ -12,12 +14,20 @@ logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
@cache_page(60 * 60 * 8, cache="api_page_cache")
|
def course_page_api_view(request, slug):
|
||||||
def page_api_view(request, slug):
|
|
||||||
try:
|
try:
|
||||||
page = Page.objects.get(slug=slug, locale__language_code="de-CH")
|
page = Page.objects.get(slug=slug, locale__language_code="de-CH")
|
||||||
serializer = page.specific.get_serializer_class()(page.specific)
|
if not has_course_access(request, page):
|
||||||
return Response(serializer.data)
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
data = api_page_cache_get_or_set(
|
||||||
|
key=request.get_full_path(),
|
||||||
|
func=lambda: page.specific.get_serializer_class()(page.specific).data,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(data)
|
||||||
|
except PermissionDenied as e:
|
||||||
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
return Response({"error": str(e)}, status=404)
|
return Response({"error": str(e)}, status=404)
|
||||||
|
|
@ -25,48 +35,63 @@ def page_api_view(request, slug):
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def request_course_completion(request, course_id):
|
def request_course_completion(request, course_id):
|
||||||
response_data = CourseCompletionSerializer(
|
try:
|
||||||
CourseCompletion.objects.filter(user=request.user, course_id=course_id),
|
response_data = CourseCompletionSerializer(
|
||||||
many=True,
|
CourseCompletion.objects.filter(user=request.user, course_id=course_id),
|
||||||
).data
|
many=True,
|
||||||
|
).data
|
||||||
|
|
||||||
return Response(status=200, data=response_data)
|
return Response(status=200, data=response_data)
|
||||||
|
except PermissionDenied as e:
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
return Response({"error": str(e)}, status=404)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["POST"])
|
@api_view(["POST"])
|
||||||
def mark_course_completion(request):
|
def mark_course_completion(request):
|
||||||
page_key = request.data.get("page_key")
|
try:
|
||||||
completion_status = request.data.get("completion_status", "success")
|
page_key = request.data.get("page_key")
|
||||||
|
completion_status = request.data.get("completion_status", "success")
|
||||||
|
|
||||||
page = Page.objects.get(translation_key=page_key, locale__language_code="de-CH")
|
page = Page.objects.get(translation_key=page_key, locale__language_code="de-CH")
|
||||||
page_type = get_wagtail_type(page.specific)
|
if not has_course_access(request, page):
|
||||||
course = CoursePage.objects.ancestor_of(page).first().specific.course
|
raise PermissionDenied()
|
||||||
|
|
||||||
cc, created = CourseCompletion.objects.get_or_create(
|
page_type = get_wagtail_type(page.specific)
|
||||||
user=request.user,
|
course = page.specific.get_course()
|
||||||
page_key=page_key,
|
|
||||||
course_id=course.id,
|
|
||||||
)
|
|
||||||
cc.page_slug = page.slug
|
|
||||||
cc.page_type = page_type
|
|
||||||
cc.completion_status = completion_status
|
|
||||||
cc.save()
|
|
||||||
|
|
||||||
response_data = CourseCompletionSerializer(
|
cc, created = CourseCompletion.objects.get_or_create(
|
||||||
CourseCompletion.objects.filter(user=request.user, course_id=course.id),
|
user=request.user,
|
||||||
many=True,
|
page_key=page_key,
|
||||||
).data
|
course_id=course.id,
|
||||||
|
)
|
||||||
|
cc.page_slug = page.slug
|
||||||
|
cc.page_type = page_type
|
||||||
|
cc.completion_status = completion_status
|
||||||
|
cc.save()
|
||||||
|
|
||||||
logger.debug(
|
response_data = CourseCompletionSerializer(
|
||||||
"mark_course_completion successful",
|
CourseCompletion.objects.filter(user=request.user, course_id=course.id),
|
||||||
label="completion_api",
|
many=True,
|
||||||
page_key=page_key,
|
).data
|
||||||
page_type=page_type,
|
|
||||||
page_slug=page.slug,
|
|
||||||
page_title=page.title,
|
|
||||||
user_id=request.user.id,
|
|
||||||
course_id=course.id,
|
|
||||||
completion_status=completion_status,
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response(status=200, data=response_data)
|
logger.debug(
|
||||||
|
"mark_course_completion successful",
|
||||||
|
label="completion_api",
|
||||||
|
page_key=page_key,
|
||||||
|
page_type=page_type,
|
||||||
|
page_slug=page.slug,
|
||||||
|
page_title=page.title,
|
||||||
|
user_id=request.user.id,
|
||||||
|
course_id=course.id,
|
||||||
|
completion_status=completion_status,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(status=200, data=response_data)
|
||||||
|
except PermissionDenied as e:
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
return Response({"error": str(e)}, status=404)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue