Skip to content

Commit

Permalink
add permissions and client-side subvertical loading
Browse files Browse the repository at this point in the history
  • Loading branch information
Ali-D-Akbar committed Jan 24, 2025
1 parent 0bc2e88 commit 30f3ec1
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 86 deletions.
21 changes: 21 additions & 0 deletions course_discovery/apps/tagging/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.http import HttpResponseForbidden
from django.conf import settings


class VerticalTaggingAdministratorPermissionRequiredMixin:
"""
A mixin to enforce permission on VERTICALS_MANAGEMENT_GROUPS for the class-based views.
"""

def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden("You need to be logged in to access this page.")

is_in_group = request.user.groups.filter(
name__in=settings.VERTICALS_MANAGEMENT_GROUPS
).exists()

if not request.user.is_superuser and not is_in_group:
return HttpResponseForbidden("You do not have permission to access this page.")

return super().dispatch(request, *args, **kwargs)
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<td>{{ forloop.counter }}</td>
<td>{{ course.key }}</td>
<td>
<a href="{% url 'tagging:course_detail' uuid=course.uuid %}">
<a href="{% url 'tagging:course_tagging_detail' uuid=course.uuid %}">
{{ course.title }}
</a>
</td>
Expand Down
17 changes: 12 additions & 5 deletions course_discovery/apps/tagging/templates/tagging/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,27 @@
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<div class="container">
<a class="navbar-brand" href="{% url 'tagging:course_list' %}">Tagging App</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'tagging:course_list' %}">Course List</a>
<a class="nav-link" href="{% url 'tagging:course_list' %}">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'tagging:vertical_list' %}">Vertical List</a>
<a class="nav-link" href="{% url 'tagging:vertical_list' %}">Verticals</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'tagging:sub_vertical_list' %}">Sub-Vertical List</a>
<a class="nav-link" href="{% url 'tagging:sub_vertical_list' %}">Sub-Verticals</a>
</li>
</ul>
<ul class="navbar-nav mr-10">
{% if user.is_authenticated %}
<li class="nav-item">
<a class="nav-link text-light" href="#">{{ user.username }}</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
Expand Down
50 changes: 0 additions & 50 deletions course_discovery/apps/tagging/templates/tagging/course_detail.html

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{% block content %}
<div class="container mt-5">
<h1 class="mb-4">Course List</h1>
<h1 class="mb-4">Courses</h1>

<form method="get" class="mb-4" hx-get="{% url 'tagging:course_list' %}" hx-target="#course-table" hx-trigger="keyup changed delay:500ms from:search">
<div class="input-group">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{% extends "tagging/base.html" %}

{% block content %}
<div class="container mt-5">
<h1 class="mb-4">Course: {{ course.title }}</h1>
<h2>Key: {{ course.key }}</h2>

<h3>Assign or Edit Vertical and Sub-Vertical</h3>
<form method="post" action=""
hx-post=""
hx-target="#message-container"
hx-swap="innerHTML">
{% csrf_token %}

<div class="form-group">
<label for="vertical">Vertical</label>
<select name="vertical" id="vertical" class="form-control"
hx-trigger="change"
hx-on="change: filterSubVerticals(event)">
<option value="">-- Select Vertical --</option>
{% for vertical in verticals %}
<option value="{{ vertical.slug }}"
{% if course.vertical and course.vertical.vertical.slug == vertical.slug %}selected{% endif %}>
{{ vertical.name }}
</option>
{% endfor %}
</select>
</div>

<div class="form-group">
<label for="sub_vertical">Sub-Vertical</label>
<select name="sub_vertical" id="sub_vertical" class="form-control">
<option value="">-- Select Sub-Vertical --</option>
{% for sub_vertical in all_sub_verticals %}
<option value="{{ sub_vertical.slug }}"
data-vertical="{{ sub_vertical.vertical.slug }}"
{% if course.vertical and course.vertical.sub_vertical and course.vertical.sub_vertical.slug == sub_vertical.slug %}selected{% endif %}
{% if course.vertical and not course.vertical.sub_vertical and sub_vertical.vertical.slug != course.vertical.vertical.slug %}style="display: none;"{% endif %}
{% if not course.vertical or sub_vertical.vertical.slug != course.vertical.vertical.slug %}style="display: none;"{% endif %}>
{{ sub_vertical.name }}
</option>
{% endfor %}
</select>
</div>

<button type="submit" class="btn btn-primary">Save</button>
</form>

<!-- Message container for success/error -->
<div id="message-container" class="mt-3"></div>
</div>

<script>
function filterSubVerticals(event) {
const selectedVertical = event.target.value;
const subVerticalSelect = document.getElementById('sub_vertical');
const options = subVerticalSelect.querySelectorAll('option[data-vertical]');

// Clear sub-vertical selection only when vertical is changed to no selection
if (!selectedVertical) {
subVerticalSelect.value = ""; // Reset sub-vertical selection
}

// Hide or show sub-vertical options based on selected vertical
options.forEach(option => {
if (selectedVertical && option.getAttribute('data-vertical') === selectedVertical) {
option.style.display = ""; // Show relevant sub-vertical
} else {
option.style.display = "none"; // Hide irrelevant sub-vertical
}
});

// Automatically clear sub-vertical selection if no matching options are visible
const selectedSubVertical = subVerticalSelect.value;
const matchingOption = Array.from(options).find(option => option.value === selectedSubVertical && option.style.display !== "none");
if (!matchingOption) {
subVerticalSelect.value = ""; // Clear selection if no valid options remain
}
}

document.addEventListener("DOMContentLoaded", function () {
const selectedVertical = document.getElementById('vertical');
filterSubVerticals({ target: selectedVertical });
});
</script>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2>Tagged Courses</h2>
<ul class="list-group">
{% for course in courses %}
<li class="list-group-item">
<a href="{% url 'tagging:course_detail' uuid=course.uuid %}">
<a href="{% url 'tagging:course_tagging_detail' uuid=course.uuid %}">
{{ course.title }}
</a>
</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ <h2>Tagged Courses</h2>
<ul class="list-group">
{% for course in courses %}
<li class="list-group-item">
<a href="{% url 'tagging:course_detail' uuid=course.uuid %}">
<a href="{% url 'tagging:course_tagging_detail' uuid=course.uuid %}">
{{ course.title }}
</a>
</li>
Expand Down
6 changes: 2 additions & 4 deletions course_discovery/apps/tagging/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from django.urls import path

from course_discovery.apps.tagging.views import (
CourseDetailView,
CourseTaggingDetailView,
CourseListView,
LoadSubVerticalsView,
SubVerticalDetailView,
SubVerticalListView,
VerticalDetailView,
Expand All @@ -13,11 +12,10 @@
app_name = 'tagging'

urlpatterns = [
path('courses/<uuid:uuid>/', CourseDetailView.as_view(), name='course_detail'),
path('courses/<uuid:uuid>/', CourseTaggingDetailView.as_view(), name='course_tagging_detail'),
path('verticals/<slug:slug>/', VerticalDetailView.as_view(), name='vertical_detail'),
path('sub_verticals/<slug:slug>/', SubVerticalDetailView.as_view(), name='sub_vertical_detail'),
path('courses/', CourseListView.as_view(), name='course_list'),
path('verticals/', VerticalListView.as_view(), name='vertical_list'),
path('sub_verticals/', SubVerticalListView.as_view(), name='sub_vertical_list'),
path('load-sub-verticals/', LoadSubVerticalsView.as_view(), name='load_sub_verticals'),
]
35 changes: 12 additions & 23 deletions course_discovery/apps/tagging/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@
from django.views.generic import DetailView, ListView

from course_discovery.apps.tagging.models import Course, CourseVertical, SubVertical, Vertical
from course_discovery.apps.tagging.mixins import VerticalTaggingAdministratorPermissionRequiredMixin


class CourseDetailView(View):
class CourseTaggingDetailView(VerticalTaggingAdministratorPermissionRequiredMixin, View):
"""
Handles displaying course details and assigning verticals and sub-verticals to a course.
Handles displaying course tagging details and assigning verticals and sub-verticals to a course.
"""

def get(self, request, uuid):
course = get_object_or_404(Course, uuid=uuid, draft=False)
verticals = Vertical.objects.all()
sub_vertical = SubVertical.objects.all()
return render(request, "tagging/course_detail.html", {
all_sub_verticals = SubVertical.objects.select_related('vertical')
return render(request, "tagging/course_tagging_detail.html", {
"course": course,
"verticals": verticals,
"sub_vertical": sub_vertical,
"all_sub_verticals": all_sub_verticals,
})

def post(self, request, uuid):
Expand All @@ -36,7 +37,7 @@ def post(self, request, uuid):
html = render_to_string("partials/message.html", {
"error": "Sub-vertical does not belong to the selected vertical."
}, request)
return JsonResponse({"html": html}, status=200)
return HttpResponse(html, status=200)

CourseVertical.objects.update_or_create(
course=course,
Expand All @@ -49,19 +50,7 @@ def post(self, request, uuid):
return HttpResponse(html, status=200)


class LoadSubVerticalsView(View):
"""
HTMX View to dynamically load sub-verticals based on selected vertical.
"""

def get(self, request):
vertical_slug = request.GET.get("vertical")
sub_verticals = SubVertical.objects.filter(vertical__slug=vertical_slug) if vertical_slug else SubVertical.objects.none()
html = render_to_string("partials/sub_vertical_options.html", {"sub_verticals": sub_verticals})
return JsonResponse({"html": html})


class CourseListView(ListView):
class CourseListView(VerticalTaggingAdministratorPermissionRequiredMixin, ListView):
"""
Renders a list of all Courses with search, sort, and pagination capabilities.
"""
Expand Down Expand Up @@ -112,7 +101,7 @@ def get_context_data(self, **kwargs):
return context


class VerticalListView(ListView):
class VerticalListView(VerticalTaggingAdministratorPermissionRequiredMixin, ListView):
"""
Renders a list of all Verticals with their assigned courses.
"""
Expand All @@ -136,7 +125,7 @@ def get_context_data(self, **kwargs):
return context


class SubVerticalListView(ListView):
class SubVerticalListView(VerticalTaggingAdministratorPermissionRequiredMixin, ListView):
"""
Renders a list of all SubVerticals with their parent Verticals and assigned courses.
"""
Expand All @@ -160,7 +149,7 @@ def get_context_data(self, **kwargs):
return context


class VerticalDetailView(DetailView):
class VerticalDetailView(VerticalTaggingAdministratorPermissionRequiredMixin, DetailView):
"""
Display details of a specific vertical and associated courses.
"""
Expand All @@ -174,7 +163,7 @@ def get_context_data(self, **kwargs):
return context


class SubVerticalDetailView(DetailView):
class SubVerticalDetailView(VerticalTaggingAdministratorPermissionRequiredMixin, DetailView):
"""
Display details of a specific sub-vertical and associated courses.
"""
Expand Down

0 comments on commit 30f3ec1

Please sign in to comment.