diff --git a/backend/settings.py b/backend/settings.py index 05ea5cd..71169f9 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -80,6 +80,7 @@ "djangocms_frontend.contrib.tabs", "djangocms_frontend.contrib.utilities", "zotero_publications_app", + "hydrolearn_modules_app", ] MIDDLEWARE = [ diff --git a/backend/urls.py b/backend/urls.py index f3c66ec..db6a26a 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -7,6 +7,7 @@ urlpatterns = [ path("admin/", admin.site.urls), path("zotero_publications_app/", include("zotero_publications_app.urls")), + path("hydrolearn_modules_app/", include("hydrolearn_modules_app.urls")), ] # if settings.DEBUG: diff --git a/hydrolearn_modules_app/__init__.py b/hydrolearn_modules_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hydrolearn_modules_app/admin.py b/hydrolearn_modules_app/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/hydrolearn_modules_app/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/hydrolearn_modules_app/apps.py b/hydrolearn_modules_app/apps.py new file mode 100644 index 0000000..1c42ac3 --- /dev/null +++ b/hydrolearn_modules_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HydrolearnModulesAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'hydrolearn_modules_app' diff --git a/hydrolearn_modules_app/cms_plugins.py b/hydrolearn_modules_app/cms_plugins.py new file mode 100644 index 0000000..ce04794 --- /dev/null +++ b/hydrolearn_modules_app/cms_plugins.py @@ -0,0 +1,22 @@ +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool +from django.utils.translation import gettext_lazy as _ +from .models import ( + HydroLearnModulesList, +) + +import logging + +logger = logging.getLogger(__name__) + + +@plugin_pool.register_plugin +class HydroLearnModulesPlugin(CMSPluginBase): + model = HydroLearnModulesList + name = _("HydroLearn Modules Plugin") + render_template = "hydrolearn-modules.html" + cache = False + + def render(self, context, instance, placeholder): + context = super().render(context, instance, placeholder) + return context diff --git a/hydrolearn_modules_app/migrations/0001_initial.py b/hydrolearn_modules_app/migrations/0001_initial.py new file mode 100644 index 0000000..652c629 --- /dev/null +++ b/hydrolearn_modules_app/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2 on 2024-03-19 18:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('cms', '0022_auto_20180620_1551'), + ] + + operations = [ + migrations.CreateModel( + name='HydroLearnModulesList', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='hydrolearn_modules_app_hydrolearnmoduleslist', serialize=False, to='cms.cmsplugin')), + ('organization', models.CharField(blank=True, default='', max_length=200)), + ('placeholder_image', models.CharField(default='https://placehold.co/200', max_length=200)), + ('modules', models.JSONField(default=dict, editable=False)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/hydrolearn_modules_app/migrations/__init__.py b/hydrolearn_modules_app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hydrolearn_modules_app/models.py b/hydrolearn_modules_app/models.py new file mode 100644 index 0000000..012f372 --- /dev/null +++ b/hydrolearn_modules_app/models.py @@ -0,0 +1,13 @@ +from cms.models.pluginmodel import CMSPlugin +from django.db import models +import logging + +logger = logging.getLogger(__name__) + + +class HydroLearnModulesList(CMSPlugin): + organization = models.CharField(max_length=200, default="", blank=True) + placeholder_image = models.CharField( + max_length=200, default="https://placehold.co/200" + ) + modules = models.JSONField(editable=False, default=dict) diff --git a/hydrolearn_modules_app/static/css/hydrolearn-modules.css b/hydrolearn_modules_app/static/css/hydrolearn-modules.css new file mode 100644 index 0000000..3bda552 --- /dev/null +++ b/hydrolearn_modules_app/static/css/hydrolearn-modules.css @@ -0,0 +1,103 @@ + +.img-tile{ + object-fit: cover; +} + +.name{ + height: 150px; + cursor: pointer; +} + + /* Hide scrollbar for Chrome, Safari and Opera */ +.name::-webkit-scrollbar { + display: none; +} + +/* Hide scrollbar for IE, Edge and Firefox */ +.name { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + + +.hydrolearn_module{ + box-shadow: 0 1px 10px 0 rgba(0,0,0,0.125), inset 0 0 0 1px rgba(255,255,255,0.75); + border-color: white; + border-bottom: 3px solid #255f9c; +} +.hydrolearn_module:hover, +.hydrolearn_module:focus { + background: #f5f5f5; + border-color: #bebebe; + box-shadow: 0 1px 4px 0 rgba(0,117,180,0.4); +} +.cover-image { + position: relative; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + cursor: pointer; +} + +.cover-image img { + display: block; + width: 100%; + transition: filter 0.5s ease; /* Smooth transition for the filter */ +} + +.learn-more { + position: absolute; + color: white; + padding: 5px 10px; + visibility: hidden; + transition: visibility 0s, opacity 0.5s linear; /* Smooth transition for visibility and opacity */ + opacity: 0; + border-color: #255f9c; + border-radius: 3px; + background: #255f9c; +} + +.cover-image:hover .learn-more { + visibility: visible; + opacity: 1; /* Make it fully opaque on hover */ +} + +.cover-image:hover img { + filter: brightness(50%); /* Darken the image */ +} +.learn-more > a { + color: inherit; + text-decoration: none; +} + +.hidden { + display: none; +} + + +@keyframes placeHolderShimmer{ + 0%{ + background-position: -800px 0 + } + 100%{ + background-position: 800px 0 + } +} + + + +.animated-background { + animation-duration: 7s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: placeHolderShimmer; + animation-timing-function: linear; +} +.titles-loading-background{ + background: #cbcbcb; + background: linear-gradient(to right, #cbcbcb 8%, #bbbbbb 18%, #cbcbcb 33%); +} +.descriptions-background{ + background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%); +} diff --git a/hydrolearn_modules_app/static/js/hydrolearn-modules.js b/hydrolearn_modules_app/static/js/hydrolearn-modules.js new file mode 100644 index 0000000..b638d67 --- /dev/null +++ b/hydrolearn_modules_app/static/js/hydrolearn-modules.js @@ -0,0 +1,153 @@ + + +const getCSRFToken = () => { + // Attempt to retrieve the CSRF token from the meta tag + const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + return token; +} + +const fetchLearningModules = () => { + // Assuming your Django server is running on localhost:8000 + // Update the URL if your setup is different or if you are using a production server + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), // Include the CSRF token in the request headers + }, + body: JSON.stringify(requestData), + }) + .then(response => { + console.log(response); + document.getElementById('placeholder-hydrolearn-modules').classList.add('hidden'); + document.getElementById('hydrolearn-modules-plugin').classList.remove('hidden'); + + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); // Assuming the response is JSON + }) + .then(data => { + console.log(data); + + let modulesHTML = `` + data['modules'].forEach(module => { + modulesHTML += ` +
+
+
` + if (module.course_url){ + modulesHTML += ` +
+
+ + ... + + + +
+
+
` + + } + else{ + modulesHTML += ` +
+
+ + ... + + + +
+
+
` + } + modulesHTML += ` +
+
+
+
${module.course_title}
+
+
+
+

` + if ( module.course_organization){ + modulesHTML +=`${module.course_organization}` + + } + modulesHTML += `

` + modulesHTML += ` +
+

` + if(module.course_code){ + modulesHTML += `${module.course_code }` + } + modulesHTML += `

` + + if (module.course_weekly_effort){ + modulesHTML +=`

Effort: ${module.course_weekly_effort}

` + } + modulesHTML +=` +
+

+ ${module.course_description_content} +

+
` + modulesHTML += `
` + + }); + + document.getElementById('hydrolearn-modules-plugin').innerHTML = modulesHTML; + + // For example, you could iterate over the data and append it to an element in your HTML + }) + .catch(error => { + console.error('There was a problem with the fetch operation:', error); + }); + } +const create_placeholders = () => { + let placeholdersHtmlElement = document.getElementById('placeholder-hydrolearn-modules'); + let htmlPlaceholders = ''; + for(var i = 0; i < 10; i++){ + htmlPlaceholders+=` +
+
+
+ +
+
+ + +
+
+ +
+
+
+
+
+
+ +

+ +
+

+

+
+
+
+
+
+ + ` + } + placeholdersHtmlElement.innerHTML = htmlPlaceholders; +} + +create_placeholders(); +fetchLearningModules(); diff --git a/hydrolearn_modules_app/templates/hydrolearn-modules-base.html b/hydrolearn_modules_app/templates/hydrolearn-modules-base.html new file mode 100644 index 0000000..bae0781 --- /dev/null +++ b/hydrolearn_modules_app/templates/hydrolearn-modules-base.html @@ -0,0 +1,16 @@ +{% extends CMS_TEMPLATE %} + +{% load sekizai_tags static cms_tags %} + +{% addtoblock "js" %} + +{% endaddtoblock %} + +{% addtoblock "css" %} + + +{% endaddtoblock %} + +{% block content %} + +{% endblock %} diff --git a/hydrolearn_modules_app/templates/hydrolearn-modules.html b/hydrolearn_modules_app/templates/hydrolearn-modules.html new file mode 100644 index 0000000..ab8a610 --- /dev/null +++ b/hydrolearn_modules_app/templates/hydrolearn-modules.html @@ -0,0 +1,31 @@ +{% load cms_tags sekizai_tags static %} + + {% csrf_token %} + + + +{% addtoblock "css" %} + + +{% endaddtoblock %} + +{% addtoblock "js" %} + + + +{% endaddtoblock %} + +{% block content %} + +
+ + +{% endblock %} + + \ No newline at end of file diff --git a/hydrolearn_modules_app/templates/single_hydrolearn_module.html b/hydrolearn_modules_app/templates/single_hydrolearn_module.html new file mode 100644 index 0000000..4630a98 --- /dev/null +++ b/hydrolearn_modules_app/templates/single_hydrolearn_module.html @@ -0,0 +1,74 @@ +{% load cms_tags sekizai_tags %} + +{% addtoblock "js" %} + +{% endaddtoblock %} + +
+
+
+ + {% if module.course_url %} +
+
+ + ... + + + +
+
+ {% else %} +
+
+ + ... + + + +
+
+ + {% endif %} +
+
+
+
+
{{ module.course_title }}
+
+
+
+

+ {% if module.course_organization %} + {{ module.course_organization }} + {% endif %} +

+
+
+

+ {% if module.course_code %} + {{ module.course_code }} + {% endif %} +

+
+
+ + {% if module.course_weekly_effort %} +

Effort: {{ module.course_weekly_effort }}

+ {% endif %} + +
+

+ {{ module.course_description_content }} +

+
+
+
+
+
+ + diff --git a/hydrolearn_modules_app/urls.py b/hydrolearn_modules_app/urls.py new file mode 100644 index 0000000..06a34a8 --- /dev/null +++ b/hydrolearn_modules_app/urls.py @@ -0,0 +1,13 @@ +from django.urls import re_path + + +from .views import base_view, hydrolearn_modules_view + +urlpatterns = [ + re_path(r"^$", base_view, name="base"), + re_path( + r"^hydrolearn-modules-api/$", + hydrolearn_modules_view, + name="hydrolearn-modules-api", + ), +] diff --git a/hydrolearn_modules_app/views.py b/hydrolearn_modules_app/views.py new file mode 100644 index 0000000..1f79e61 --- /dev/null +++ b/hydrolearn_modules_app/views.py @@ -0,0 +1,77 @@ +from django.shortcuts import render +from django.http import JsonResponse +import logging +import json +import requests + +logger = logging.getLogger(__name__) + + +def base_view(request): + + context = {} + return render(request, "hydrolearn-modules-base.html", context) + + +def hydrolearn_modules_view(request): + + # This dictionary can pass variables to the template. + logging.warning("hydrolearn_modules_view") + hl_modules = {} + body_unicode = request.body.decode("utf-8") + body = json.loads(body_unicode) + instance = { + "organization": body["organization"], + } + modules_list = [] + + try: + URL = "https://edx.hydrolearn.org" + client = requests.session() + client.get(URL) # sets cookie for CSRF + csrftoken = client.cookies.get("csrftoken", "") or client.cookies.get( + "csrf", "" + ) + courses_url = f"{URL}/search/course_discovery/" + + login_data = {"csrfmiddlewaretoken": csrftoken} + courses_response = client.post( + courses_url, data=login_data, headers={"Referer": courses_url} + ) + courses_list = courses_response.json()["results"] + + if instance["organization"]: + + def is_from_organization(course): + return course["data"]["org"] == instance["organization"] + + courses_list = filter(is_from_organization, courses_list) + for course in courses_list: + course_data = course["data"] + course_dict = { + "course_title": course_data["content"]["display_name"], + "course_url": f"{URL}/courses/{course_data.get('course')}/about", + "course_image_url": ( + f'{URL}{course_data.get("image_url")}' + if course_data.get("image_url", "") != "" + else "" + ), + "course_organization": course_data.get("org", ""), + "course_code": course_data.get("number", ""), + "course_weekly_effort": course_data.get("effort", ""), + "course_description_content": course_data.get("content").get( + "short_description", "" + ), + } + # logger.warning(course_dict) + + modules_list.append(course_dict) + + hl_modules["modules"] = modules_list + except Exception as e: + logger.warning(f"Error fetching HydroLearn modules: {e}") + hl_modules["modules"] = modules_list + + return JsonResponse(hl_modules) + # context = {instance: {"modules": modules_list}} + # return render(request, "hydrolearn-modules.html", context)