-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add django templates for tagging app UI (#4547)
- Loading branch information
1 parent
63a1ecd
commit 245fa13
Showing
23 changed files
with
929 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
course_discovery/apps/tagging/migrations/0003_alter_coursevertical_course.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Generated by Django 4.2.11 on 2025-01-28 09:29 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('tagging', '0002_updatecourseverticalsconfig'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='coursevertical', | ||
name='course', | ||
field=models.OneToOneField(limit_choices_to={'draft': False}, on_delete=django.db.models.deletion.CASCADE, related_name='product_vertical', to='course_metadata.course'), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from django.conf import settings | ||
from django.contrib.auth.mixins import LoginRequiredMixin | ||
from django.core.exceptions import PermissionDenied | ||
|
||
|
||
class VerticalTaggingAdministratorPermissionRequiredMixin(LoginRequiredMixin): | ||
""" | ||
A mixin to enforce permission on VERTICALS_MANAGEMENT_GROUPS for class-based views. | ||
""" | ||
|
||
def dispatch(self, request, *args, **kwargs): | ||
response = super().dispatch(request, *args, **kwargs) | ||
if response.status_code == 403: | ||
return response | ||
|
||
in_vertical_management_group = request.user.groups.filter( | ||
name__in=settings.VERTICALS_MANAGEMENT_GROUPS | ||
).exists() | ||
|
||
if not request.user.is_superuser and not in_vertical_management_group: | ||
raise PermissionDenied("You do not have permission to access this page.") | ||
|
||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
course_discovery/apps/tagging/templates/partials/course_table.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<table class="table table-bordered table-hover"> | ||
<thead> | ||
<tr> | ||
<th>#</th> | ||
<th> | ||
<a href="?sort=key&direction={% if current_sort == 'key' and current_direction == 'asc' %}desc{% else %}asc{% endif %}"> | ||
Course Key | ||
{% if current_sort == 'key' %} | ||
<span>{% if current_direction == 'asc' %}▲{% else %}▼{% endif %}</span> | ||
{% endif %} | ||
</a> | ||
</th> | ||
<th> | ||
<a href="?sort=title&direction={% if current_sort == 'title' and current_direction == 'asc' %}desc{% else %}asc{% endif %}"> | ||
Course Title | ||
{% if current_sort == 'title' %} | ||
<span>{% if current_direction == 'asc' %}▲{% else %}▼{% endif %}</span> | ||
{% endif %} | ||
</a> | ||
</th> | ||
<th> | ||
<a href="?sort=vertical&direction={% if current_sort == 'vertical' and current_direction == 'asc' %}desc{% else %}asc{% endif %}"> | ||
Vertical | ||
{% if current_sort == 'vertical' %} | ||
<span>{% if current_direction == 'asc' %}▲{% else %}▼{% endif %}</span> | ||
{% endif %} | ||
</a> | ||
</th> | ||
<th> | ||
<a href="?sort=sub_vertical&direction={% if current_sort == 'sub_vertical' and current_direction == 'asc' %}desc{% else %}asc{% endif %}"> | ||
Sub-Vertical | ||
{% if current_sort == 'sub_vertical' %} | ||
<span>{% if current_direction == 'asc' %}▲{% else %}▼{% endif %}</span> | ||
{% endif %} | ||
</a> | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{% for course in courses %} | ||
<tr> | ||
<td>{{ forloop.counter }}</td> | ||
<td>{{ course.key }}</td> | ||
<td> | ||
<a href="{% url 'tagging:course_tagging_detail' uuid=course.uuid %}"> | ||
{{ course.title }} | ||
</a> | ||
</td> | ||
<td> | ||
{% if course.product_vertical and course.product_vertical.vertical %} | ||
<a href="{% url 'tagging:vertical_detail' slug=course.product_vertical.vertical.slug %}"> | ||
{{ course.product_vertical.vertical.name }} | ||
</a> | ||
{% else %} | ||
<span class="text-muted">None</span> | ||
{% endif %} | ||
</td> | ||
<td> | ||
{% if course.product_vertical and course.product_vertical.sub_vertical %} | ||
<a href="{% url 'tagging:sub_vertical_detail' slug=course.product_vertical.sub_vertical.slug %}"> | ||
{{ course.product_vertical.sub_vertical.name }} | ||
</a> | ||
{% else %} | ||
<span class="text-muted">None</span> | ||
{% endif %} | ||
</td> | ||
</tr> | ||
{% empty %} | ||
<tr> | ||
<td colspan="5" class="text-center text-muted">No courses found.</td> | ||
</tr> | ||
{% endfor %} | ||
</tbody> | ||
</table> | ||
|
||
{% if is_paginated %} | ||
<nav> | ||
<ul class="pagination"> | ||
{% if page_obj.has_previous %} | ||
<li class="page-item"> | ||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}&search={{ request.GET.search|default:'' }}&sort={{ current_sort }}&direction={{ current_direction }}" | ||
hx-get="?page={{ page_obj.previous_page_number }}&search={{ request.GET.search|default:'' }}&sort={{ current_sort }}&direction={{ current_direction }}" | ||
hx-target="#course-table"> | ||
« | ||
</a> | ||
</li> | ||
{% endif %} | ||
{% for page_num in paginator.page_range %} | ||
<li class="page-item {% if page_obj.number == page_num %}active{% endif %}"> | ||
<a class="page-link" href="?page={{ page_num }}&search={{ request.GET.search|default:'' }}&sort={{ current_sort }}&direction={{ current_direction }}" | ||
hx-get="?page={{ page_num }}&search={{ request.GET.search|default:'' }}&sort={{ current_sort }}&direction={{ current_direction }}" | ||
hx-target="#course-table"> | ||
{{ page_num }} | ||
</a> | ||
</li> | ||
{% endfor %} | ||
{% if page_obj.has_next %} | ||
<li class="page-item"> | ||
<a class="page-link" href="?page={{ page_obj.next_page_number }}&search={{ request.GET.search|default:'' }}&sort={{ current_sort }}&direction={{ current_direction }}" | ||
hx-get="?page={{ page_obj.next_page_number }}&search={{ request.GET.search|default:'' }}&sort={{ current_sort }}&direction={{ current_direction }}" | ||
hx-target="#course-table"> | ||
» | ||
</a> | ||
</li> | ||
{% endif %} | ||
</ul> | ||
</nav> | ||
{% endif %} | ||
|
6 changes: 6 additions & 0 deletions
6
course_discovery/apps/tagging/templates/partials/message.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{% if success %} | ||
<div class="alert alert-success">{{ success }}</div> | ||
{% endif %} | ||
{% if error %} | ||
<div class="alert alert-danger">{{ error }}</div> | ||
{% endif %} |
4 changes: 4 additions & 0 deletions
4
course_discovery/apps/tagging/templates/partials/sub_vertical_options.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<option value="">-- Select Sub-Vertical --</option> | ||
{% for sub_vertical in sub_verticals %} | ||
<option value={{ sub_vertical.slug }} >{{ sub_vertical.name }}</option> | ||
{% endfor %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>{% block title %}Course Tagging{% endblock %}</title> | ||
<!-- Bootstrap CSS --> | ||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" | ||
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous"> | ||
<script src="https://unpkg.com/htmx.org"></script> | ||
</head> | ||
<body> | ||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> | ||
<div class="container"> | ||
<a class="navbar-brand" href="{% url 'tagging:course_list' %}">Course Tagging</a> | ||
<div class="collapse navbar-collapse" id="navbarNav"> | ||
<ul class="navbar-nav me-auto"> | ||
<li class="nav-item"> | ||
<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-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> | ||
|
||
<main class="py-4"> | ||
{% block content %} | ||
{% endblock %} | ||
</main> | ||
</body> | ||
</html> |
18 changes: 18 additions & 0 deletions
18
course_discovery/apps/tagging/templates/tagging/course_list.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{% extends "tagging/base.html" %} | ||
|
||
{% block content %} | ||
<div class="container mt-5"> | ||
<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"> | ||
<input type="text" name="search" id="search" class="form-control" placeholder="Search courses..." value="{{ request.GET.search|default:'' }}"> | ||
<button class="btn btn-primary" type="submit">Search</button> | ||
</div> | ||
</form> | ||
|
||
<div id="course-table"> | ||
{% include "partials/course_table.html" %} | ||
</div> | ||
</div> | ||
{% endblock %} |
90 changes: 90 additions & 0 deletions
90
course_discovery/apps/tagging/templates/tagging/course_tagging_detail.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
{% 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.product_vertical and course.product_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.product_vertical and course.product_vertical.sub_vertical and course.product_vertical.sub_vertical.slug == sub_vertical.slug %} | ||
selected | ||
{% elif course.product_vertical and not course.product_vertical.sub_vertical and sub_vertical.vertical.slug != course.product_vertical.vertical.slug %} | ||
style="display: none;" | ||
{% elif not course.product_vertical or sub_vertical.vertical.slug != course.product_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 %} |
Oops, something went wrong.