Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 86 additions & 2 deletions lunes_cms/cmsv2/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,96 @@

from __future__ import absolute_import, unicode_literals

from django.contrib import admin
from datetime import datetime
from typing import Any

from django.contrib import admin, messages
from django.contrib.auth.models import User
from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils.translation import gettext_lazy as _

from lunes_cms.core import settings

from .admins import FeedbackAdmin, JobAdmin, LunesUserAdmin, UnitAdmin, WordAdmin
from .models import Feedback, Job, Unit, Word
from .models import Feedback, Job, Review, Unit, Word
from .models.static import ReviewStatus, Roles

_default_index = admin.AdminSite.index


def index(
self: admin.AdminSite,
request: HttpRequest,
extra_context: dict[str, Any] | None = None,
) -> HttpResponse:
"""
Renders an empty dashboard for members of :attr:`Roles.EXPERT_GROUP`
instead of Jazzmin's default app-list dashboard.

:param self: A handle to the :class:`admin.AdminSite`
:param request: current user request
:param extra_context: additional context forwarded to the default dashboard

:return: the rendered dashboard response
"""
if request.user.groups.filter(name=Roles.EXPERT_GROUP).exists():
reviews = Review.objects.filter(
reviewer=request.user, completed_at__isnull=True
).order_by("assigned_at")
review_id = request.GET.get("review")
current_review = (
reviews.filter(pk=review_id).first() if review_id else None
) or reviews.first()
next_review = None
if current_review:
next_review = reviews.filter(
assigned_at__gt=current_review.assigned_at
).first()
context = {
**self.each_context(request),
"reviews": reviews,
"current_review": current_review,
"next_review": next_review,
"base_url": settings.MEDIA_URL,
**(extra_context or {}),
}

if current_review:
if request.POST.get("approve"):
current_review.review_status = ReviewStatus.APPROVED
current_review.completed_at = datetime.now()
current_review.save()
messages.success(
request,
(_("The word {} has been approved.").format(current_review.word)),
)
if next_review is not None:
return redirect(f"{request.path}?review={next_review.id}")
return TemplateResponse(request, "admin/index_empty.html", context)

if request.POST.get("reject"):
current_review.review_status = ReviewStatus.REJECTED
current_review.completed_at = datetime.now()
current_review.save()
if next_review is not None:
return redirect(f"{request.path}?review={next_review.id}")
return TemplateResponse(request, "admin/index_empty.html", context)

if request.POST.get("not_assessed"):
current_review.review_status = ReviewStatus.CANNOT_BE_ASSESSED
current_review.completed_at = datetime.now()
current_review.save()
if next_review is not None:
return redirect(f"{request.path}?review={next_review.id}")
return TemplateResponse(request, "admin/index_empty.html", context)

return TemplateResponse(request, "admin/index_empty.html", context)
return _default_index(self, request, extra_context)


admin.AdminSite.index = index # type: ignore[method-assign,assignment]
admin.site.register(Job, JobAdmin)
admin.site.register(Unit, UnitAdmin)
admin.site.register(Word, WordAdmin)
Expand Down
1 change: 1 addition & 0 deletions lunes_cms/cmsv2/models/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Roles:
DEFAULT_GROUP_NAME = None
ADMIN_GROUP = "Lunes"
REVIEWER_GROUP = _("Reviewer")
EXPERT_GROUP = "Lunes Expert:innen (v2)"


class Permissions:
Expand Down
66 changes: 66 additions & 0 deletions lunes_cms/cmsv2/templates/admin/index_empty.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block content %}
<div class="mx-auto my-2 mw-48">
<h4>{% translate "Good day" %}, {{ user.get_full_name }}</h4>
<p>{{ reviews|length }} {% translate "open reviews" %}</p>
{% if reviews %}
<h5>Aktuell zu prüfende Vokabel</h5>
<div class="bg-white p-4 rounded-4">
<div class="ps-1"><span class="fs-2 fw-bold">{{ current_review.word.word }}</span>, <span class="fs-4">{{ current_review.word.singular_article_as_text }}</span></div>
<div class="ps-1"><span>{% if current_review.word.word_type %}{{ current_review.word.get_word_type_display }} {% else %} <i>{% translate "At the moment there is no word type for this word." %}</i></span> &bull;{% endif %}
{% if current_review.word.units %}
<span class="fw-bold">{% translate "Units" %}:</span>
{% for unit in current_review.word.units.all %}
<span>{{ unit }}</span>
{% endfor %}
{% else %}
<i>{% translate "This word hasn't been assigned to any units yet." %}</i>
{% endif %}
</div>
<div class="row">
<div class="ps-3 col-6">
{% if current_review.word.image %}
<div class="my-2 p-3 rounded-3 bg-body-tertiary">
<img class="w-100" src="{{ base_url }}{{ current_review.word.image }}">
</div>
{% else %}
<i>{% translate "There is no image for this word yet." %}</i>
{% endif %}
</div>
<div class="pe-3 col-6">
{% if current_review.word.audio %}
<div class="m-2 p-3 rounded-3 bg-body-tertiary">
<img class="w-100"
src="{{ current_review.word.waveform_image_url }}"
alt="">
<audio controls
src="{{ base_url }}{{ current_review.word.audio }}"
class="w-100 mt-2"></audio>
</div>
{% else %}
<i>{% translate "There is no audio for this word yet." %}</i>
{% endif %}
</div>
</div>
<form method="post" action="">
{% csrf_token %}
<div class="row my-2">
<input type="submit" name="approve" value="{% translate 'Approve' %}" class="col mx-3 py-2 rounded-3 alert-success border-green">
</div>
<div class="row">
<button class="col btn btn-outline border-primary ms-3 me-1 py-2 bg-transparent rounded-3 text-primary">{% translate "Request change" %}</button>
<input type="submit" name="reject" value="{% translate 'Reject' %}" class="col btn btn-outline border-danger mx-1 py-2 bg-transparent rounded-3 text-danger">
<input type="submit" name="not_assessed" value="{% translate 'Cannot be assessed' %}" class="col btn btn-outline border-secondary me-3 ms-1 py-2 bg-transparent rounded-3 text-secondary">
</div>
</form>
</div>
{% if next_review %}
<a href="?review={{ next_review.pk }}"
class="mt-4 btn btn-outline border-dark">{% translate "Show next word" %}</a>
{% endif %}
{% else %}
<p>{% translate "Currently there are no words to check" %}</p>
{% endif %}
</div>
{% endblock %}
14 changes: 14 additions & 0 deletions lunes_cms/core/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ def probe_ffmpeg(path: str) -> None:
run_ffmpeg("-v", "error", "-i", path, "-map", "0:a:0", "-f", "null", "-")


def generate_waveform_image(audio_path: str, output_path: str) -> None:
"""Render a static waveform preview of an audio file to a PNG via ffmpeg."""
run_ffmpeg(
"-y",
"-i",
audio_path,
"-filter_complex",
"showwavespic=s=600x120:colors=0d6efd",
"-frames:v",
"1",
output_path,
)


#: True-peak ceiling and loudness range used alongside the integrated
#: loudness target (EBU R128 defaults, they rarely need tuning)
_LOUDNORM_TRUE_PEAK = -1.5
Expand Down
57 changes: 57 additions & 0 deletions lunes_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,7 @@ msgstr "Plural"

#: cmsv2/admins/word_export_resource.py cmsv2/models/unit.py
#: cmsv2/templates/admin/cmsv2/assign_units_to_user.html
#: cmsv2/templates/admin/index_empty.html
msgid "Units"
msgstr "Einheit"

Expand Down Expand Up @@ -1242,6 +1243,62 @@ msgstr "Import starten"
msgid "Generate Audio"
msgstr "Audio generieren"

#: cmsv2/templates/admin/index_empty.html
msgid "Good day"
msgstr ""

#: cmsv2/templates/admin/index_empty.html
#, fuzzy
#| msgid "Image Preview"
msgid "open reviews"
msgstr "Bild Vorschau"

#: cmsv2/templates/admin/index_empty.html
msgid "At the moment there is no word type for this word."
msgstr ""

#: cmsv2/templates/admin/index_empty.html
#, fuzzy
#| msgid "This word is assigned to no training set."
msgid "This word hasn't been assigned to any units yet."
msgstr "Das word ist zu keinem Modul zugeordnet."

#: cmsv2/templates/admin/index_empty.html
#, fuzzy
#| msgid "No definition is provided for this word."
msgid "There is no image for this word yet."
msgstr "Für dieses Wort ist keine Definition hinterlegt."

#: cmsv2/templates/admin/index_empty.html
msgid "There is no audio for this word yet."
msgstr ""

#: cmsv2/templates/admin/index_empty.html
#, fuzzy
#| msgid "Approved"
msgid "Approve"
msgstr "Genehmigt"

#: cmsv2/templates/admin/index_empty.html
msgid "Request change"
msgstr ""

#: cmsv2/templates/admin/index_empty.html
msgid "Reject"
msgstr ""

#: cmsv2/templates/admin/index_empty.html
msgid "Can't be assessed"
msgstr ""

#: cmsv2/templates/admin/index_empty.html
msgid "Show next word"
msgstr ""

#: cmsv2/templates/admin/index_empty.html
msgid "Currently there are no words to check"
msgstr ""

#: cmsv2/utils.py
msgid "Generate example sentence"
msgstr "Beispielsatz generieren"
Expand Down
12 changes: 12 additions & 0 deletions lunes_cms/static/css/corporate_identity.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
:root{
--success: #0adb75;
}

/*primary buttons*/
.btn-primary {
background-color: #000e38;
Expand Down Expand Up @@ -257,6 +261,14 @@ fieldset.module {
width: 100%;
}

.mw-48 {
max-width: 48rem;
}

.border-green {
border-color: #0adb75;
}

.related-widget-wrapper {
display: flex;
align-items: stretch;
Expand Down