From e50708cf2ca1f565ae671915372cec329adfe5ff Mon Sep 17 00:00:00 2001 From: "sascha.beele" Date: Sat, 23 Nov 2024 09:59:42 +0100 Subject: [PATCH] 531: List duplicated words on dashboard --- lunes_cms/api/utils.py | 2 +- lunes_cms/cms/admin.py | 40 ++- lunes_cms/cms/admin/__init__.py | 0 lunes_cms/cms/admin/discipline/__init__.py | 9 + .../discipline}/discipline_admin.py | 4 +- lunes_cms/cms/admin/discipline/form.py | 17 ++ lunes_cms/cms/admin/discipline/list_filter.py | 70 +++++ lunes_cms/cms/admin/document/__init__.py | 17 ++ .../document}/alternative_word_admin.py | 2 +- .../document}/document_admin.py | 6 +- .../document}/document_image_admin.py | 2 +- .../document}/document_resource.py | 4 +- .../cms/admin/document/duplicates/__init__.py | 5 + .../document/duplicates/get_duplicates.py | 79 ++++++ .../cms/{ => admin/document}/list_filter.py | 69 +---- lunes_cms/cms/admin/feedback/__init__.py | 7 + .../feedback}/feedback_admin.py | 4 +- .../feedback/filter.py} | 2 +- lunes_cms/cms/admin/group_api_key/__init__.py | 5 + .../group_api_key}/group_api_key_admin.py | 0 lunes_cms/cms/admin/sponsor/__init__.py | 5 + .../sponsor}/sponsor_admin.py | 2 +- lunes_cms/cms/admin/training_set/__init__.py | 7 + .../{forms.py => admin/training_set/form.py} | 21 +- .../training_set}/training_set_admin.py | 10 +- lunes_cms/cms/admins/__init__.py | 15 -- lunes_cms/cms/apps.py | 2 +- .../0016_alter_trainingset_options.py | 23 ++ .../0017_alter_trainingset_options.py | 23 ++ lunes_cms/cms/templates/admin/index.html | 55 ++++ lunes_cms/core/context_processors.py | 2 +- lunes_cms/locale/de/LC_MESSAGES/django.po | 243 ++++++++++-------- 32 files changed, 512 insertions(+), 240 deletions(-) create mode 100644 lunes_cms/cms/admin/__init__.py create mode 100644 lunes_cms/cms/admin/discipline/__init__.py rename lunes_cms/cms/{admins => admin/discipline}/discipline_admin.py (99%) create mode 100644 lunes_cms/cms/admin/discipline/form.py create mode 100644 lunes_cms/cms/admin/discipline/list_filter.py create mode 100644 lunes_cms/cms/admin/document/__init__.py rename lunes_cms/cms/{admins => admin/document}/alternative_word_admin.py (90%) rename lunes_cms/cms/{admins => admin/document}/document_admin.py (99%) rename lunes_cms/cms/{admins => admin/document}/document_image_admin.py (96%) rename lunes_cms/cms/{admins => admin/document}/document_resource.py (98%) create mode 100644 lunes_cms/cms/admin/document/duplicates/__init__.py create mode 100644 lunes_cms/cms/admin/document/duplicates/get_duplicates.py rename lunes_cms/cms/{ => admin/document}/list_filter.py (77%) create mode 100644 lunes_cms/cms/admin/feedback/__init__.py rename lunes_cms/cms/{admins => admin/feedback}/feedback_admin.py (96%) rename lunes_cms/cms/{feedback_filter.py => admin/feedback/filter.py} (96%) create mode 100644 lunes_cms/cms/admin/group_api_key/__init__.py rename lunes_cms/cms/{admins => admin/group_api_key}/group_api_key_admin.py (100%) create mode 100644 lunes_cms/cms/admin/sponsor/__init__.py rename lunes_cms/cms/{admins => admin/sponsor}/sponsor_admin.py (97%) create mode 100644 lunes_cms/cms/admin/training_set/__init__.py rename lunes_cms/cms/{forms.py => admin/training_set/form.py} (78%) rename lunes_cms/cms/{admins => admin/training_set}/training_set_admin.py (98%) delete mode 100644 lunes_cms/cms/admins/__init__.py create mode 100644 lunes_cms/cms/migrations/0016_alter_trainingset_options.py create mode 100644 lunes_cms/cms/migrations/0017_alter_trainingset_options.py create mode 100644 lunes_cms/cms/templates/admin/index.html diff --git a/lunes_cms/api/utils.py b/lunes_cms/api/utils.py index c410cf70..4b5b6d3d 100644 --- a/lunes_cms/api/utils.py +++ b/lunes_cms/api/utils.py @@ -186,7 +186,7 @@ def find_duplicates_for_word(request, word): if duplicate.definition else _("Definition: ") + _("No definition is provided for this word.") ), - "training_sets": _("Training sets: ") + training_sets_description, + "training_sets": _("Training sets") + ": " + training_sets_description, } return JsonResponse(result) diff --git a/lunes_cms/cms/admin.py b/lunes_cms/cms/admin.py index ad00be00..3d3f8a30 100644 --- a/lunes_cms/cms/admin.py +++ b/lunes_cms/cms/admin.py @@ -1,5 +1,5 @@ """ -Register models for Django's CRUD back end and +Register models for Django"s CRUD back end and specify autocomplete_fields, search_fields and nested modules """ from __future__ import absolute_import, unicode_literals @@ -7,17 +7,38 @@ from django.contrib import admin from django.utils.translation import gettext_lazy as _ -from .admins import ( - DisciplineAdmin, - DocumentAdmin, - FeedbackAdmin, - GroupAPIKeyAdmin, - SponsorAdmin, - TrainingSetAdmin, -) +from .admin.discipline import DisciplineAdmin +from .admin.document import DocumentAdmin +from .admin.document.duplicates import get_duplicate_vocabularies +from .admin.feedback import FeedbackAdmin +from .admin.group_api_key import GroupAPIKeyAdmin +from .admin.sponsor import SponsorAdmin +from .admin.training_set import TrainingSetAdmin from .models import Discipline, Document, Feedback, GroupAPIKey, Sponsor, TrainingSet +def each_context(self, request): + """ + Return a dictionary of variables to put in the template context for + *every* page in the admin site. + + For sites running on a subpath, use the SCRIPT_NAME value if site_url + hasn't been customized. + """ + script_name = request.META["SCRIPT_NAME"] + site_url = script_name if self.site_url == "/" and script_name else self.site_url + return { + "site_title": self.site_title, + "site_header": self.site_header, + "site_url": site_url, + "has_permission": self.has_permission(request), + "available_apps": self.get_app_list(request), + "is_popup": False, + "is_nav_sidebar_enabled": self.enable_nav_sidebar, + "duplicate_vocabularies": get_duplicate_vocabularies(), + } + + def get_app_list(self, request): """ Function that returns a sorted list of all the installed apps that have been @@ -54,6 +75,7 @@ def get_app_list(self, request): return app_list +admin.AdminSite.each_context = each_context admin.AdminSite.get_app_list = get_app_list admin.site.register(Discipline, DisciplineAdmin) admin.site.register(TrainingSet, TrainingSetAdmin) diff --git a/lunes_cms/cms/admin/__init__.py b/lunes_cms/cms/admin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lunes_cms/cms/admin/discipline/__init__.py b/lunes_cms/cms/admin/discipline/__init__.py new file mode 100644 index 00000000..af4cece7 --- /dev/null +++ b/lunes_cms/cms/admin/discipline/__init__.py @@ -0,0 +1,9 @@ +from .discipline_admin import DisciplineAdmin +from .form import DisciplineChoiceField +from .list_filter import DisciplineListFilter + +__all__ = [ + "DisciplineAdmin", + "DisciplineChoiceField", + "DisciplineListFilter", +] diff --git a/lunes_cms/cms/admins/discipline_admin.py b/lunes_cms/cms/admin/discipline/discipline_admin.py similarity index 99% rename from lunes_cms/cms/admins/discipline_admin.py rename to lunes_cms/cms/admin/discipline/discipline_admin.py index d50b4a79..a94ab73b 100644 --- a/lunes_cms/cms/admins/discipline_admin.py +++ b/lunes_cms/cms/admin/discipline/discipline_admin.py @@ -14,8 +14,8 @@ from mptt.admin import DraggableMPTTAdmin from tablib import Dataset -from ..models import Discipline, Document, Static -from .document_resource import DocumentResource +from ..document import DocumentResource +from ...models import Discipline, Document, Static class DisciplineAdmin(DraggableMPTTAdmin): diff --git a/lunes_cms/cms/admin/discipline/form.py b/lunes_cms/cms/admin/discipline/form.py new file mode 100644 index 00000000..9edc3bfd --- /dev/null +++ b/lunes_cms/cms/admin/discipline/form.py @@ -0,0 +1,17 @@ +from django import forms + + +class DisciplineChoiceField(forms.ModelMultipleChoiceField): + """ + Custom form field in order to include parent nodes in string representation. + Inherits from `forms.ModelMultipleChocieField`. + """ + + def label_from_instance(self, obj): + if obj.parent: + ancestors = [ + node.title for node in obj.parent.get_ancestors(include_self=True) + ] + ancestors.append(obj.title) + return " \u2794 ".join(ancestors) + return obj.title diff --git a/lunes_cms/cms/admin/discipline/list_filter.py b/lunes_cms/cms/admin/discipline/list_filter.py new file mode 100644 index 00000000..968b8153 --- /dev/null +++ b/lunes_cms/cms/admin/discipline/list_filter.py @@ -0,0 +1,70 @@ +from django.contrib import admin +from django.db.models import F +from django.utils.translation import gettext_lazy as _ + +from ...models import Discipline + + +class DisciplineListFilter(admin.SimpleListFilter): + """ + Generic Filter for models that have a direct relationship to disciplines. + Inherits from `admin.SimpleListFilter`. + """ + + title = _("disciplines") + + # Parameter for the filter that will be used in the URL query. + parameter_name = "disciplines" + + template = "admin/discipline_filter.html" + + def lookups(self, request, model_admin): + """ + Defining look up values that can be seen in the admin + interface. Returns tuples: the first element is a coded + value, whereas the second one is human-readable. + + :param request: current user request + :type request: django.http.request + :param model_admin: admin of current model + :type model_admin: ModelAdmin + :return: list of tuples containing id and title of each discipline + :rtype: list + """ + + # Verify that only disciplines are displayed that actually can contain training sets + queryset = Discipline.objects.filter(lft=F("rght") - 1) + + if "training set" in request.GET: + queryset = queryset.filter(training_sets=request.GET["training set"]) + + if request.user.is_superuser: + queryset = queryset.filter(creator_is_admin=True) + else: + queryset = queryset.filter(created_by__in=request.user.groups.all()) + + list_of_disciplines = [ + ( + str(discipline.id), + f"{discipline.parent} \u2794 {discipline}", + ) + for discipline in queryset + ] + return sorted(list_of_disciplines, key=lambda tp: tp[1]) + + def queryset(self, request, queryset): + """ + Returns the filtered queryset based on the value + provided in the query string and retrievable via + `self.value()`. + + :param request: current user request + :type request: django.http.request + :param queryset: current queryset + :type queryset: QuerySet + :return: filtered queryset based on the value provided in the query string + :rtype: QuerySet + """ + if self.value(): + return queryset.filter(discipline=self.value()).distinct() + return queryset diff --git a/lunes_cms/cms/admin/document/__init__.py b/lunes_cms/cms/admin/document/__init__.py new file mode 100644 index 00000000..078602b9 --- /dev/null +++ b/lunes_cms/cms/admin/document/__init__.py @@ -0,0 +1,17 @@ +from .document_admin import DocumentAdmin +from .document_resource import DocumentResource +from .list_filter import ( + ApprovedImageListFilter, + AssignedListFilter, + DocumentDisciplineListFilter, + DocumentTrainingSetListFilter, +) + +__all__ = [ + "ApprovedImageListFilter", + "AssignedListFilter", + "DocumentAdmin", + "DocumentResource", + "DocumentDisciplineListFilter", + "DocumentTrainingSetListFilter", +] diff --git a/lunes_cms/cms/admins/alternative_word_admin.py b/lunes_cms/cms/admin/document/alternative_word_admin.py similarity index 90% rename from lunes_cms/cms/admins/alternative_word_admin.py rename to lunes_cms/cms/admin/document/alternative_word_admin.py index 26361992..1f6c0b61 100644 --- a/lunes_cms/cms/admins/alternative_word_admin.py +++ b/lunes_cms/cms/admin/document/alternative_word_admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from ..models import AlternativeWord +from ...models import AlternativeWord class AlternativeWordAdmin(admin.StackedInline): diff --git a/lunes_cms/cms/admins/document_admin.py b/lunes_cms/cms/admin/document/document_admin.py similarity index 99% rename from lunes_cms/cms/admins/document_admin.py rename to lunes_cms/cms/admin/document/document_admin.py index 54ebab65..1eea387a 100644 --- a/lunes_cms/cms/admins/document_admin.py +++ b/lunes_cms/cms/admin/document/document_admin.py @@ -4,15 +4,15 @@ from django.db.models import Case, Exists, IntegerField, OuterRef, Value, When from django.utils.translation import gettext_lazy as _ -from ..list_filter import ( +from .list_filter import ( ApprovedImageListFilter, - AssignedListFilter, DocumentDisciplineListFilter, DocumentTrainingSetListFilter, + AssignedListFilter, ) -from ..models import DocumentImage, Static from .alternative_word_admin import AlternativeWordAdmin from .document_image_admin import DocumentImageAdmin +from ...models import DocumentImage, Static SUPERUSER_ONLY_LIST_FILTERS = [ApprovedImageListFilter] diff --git a/lunes_cms/cms/admins/document_image_admin.py b/lunes_cms/cms/admin/document/document_image_admin.py similarity index 96% rename from lunes_cms/cms/admins/document_image_admin.py rename to lunes_cms/cms/admin/document/document_image_admin.py index 05249cc6..edd60ec8 100644 --- a/lunes_cms/cms/admins/document_image_admin.py +++ b/lunes_cms/cms/admin/document/document_image_admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from ..models import DocumentImage +from ...models import DocumentImage class DocumentImageAdmin(admin.StackedInline): diff --git a/lunes_cms/cms/admins/document_resource.py b/lunes_cms/cms/admin/document/document_resource.py similarity index 98% rename from lunes_cms/cms/admins/document_resource.py rename to lunes_cms/cms/admin/document/document_resource.py index 847c3128..d8ffb4f2 100644 --- a/lunes_cms/cms/admins/document_resource.py +++ b/lunes_cms/cms/admin/document/document_resource.py @@ -2,8 +2,8 @@ from import_export import fields, resources from import_export.admin import ExportActionMixin -from ..models import Document -from ..models.static import Static +from ...models import Document +from ...models.static import Static class DocumentResource(resources.ModelResource): diff --git a/lunes_cms/cms/admin/document/duplicates/__init__.py b/lunes_cms/cms/admin/document/duplicates/__init__.py new file mode 100644 index 00000000..7ca30dfe --- /dev/null +++ b/lunes_cms/cms/admin/document/duplicates/__init__.py @@ -0,0 +1,5 @@ +from .get_duplicates import get_duplicate_vocabularies + +__all__ = [ + "get_duplicate_vocabularies", +] diff --git a/lunes_cms/cms/admin/document/duplicates/get_duplicates.py b/lunes_cms/cms/admin/document/duplicates/get_duplicates.py new file mode 100644 index 00000000..8d2b181a --- /dev/null +++ b/lunes_cms/cms/admin/document/duplicates/get_duplicates.py @@ -0,0 +1,79 @@ +from collections import defaultdict +from typing import TypedDict, List + +from django.db.models import Count +from django.db.models.functions import Lower + +from lunes_cms.cms.models import Document + + +class DuplicateVocabulary(TypedDict): + """ + Dictionary type representing a duplicate vocabulary group + """ + + word: str + word_type: str + documents: List[Document] + + +class DuplicateVocabularyDocument(TypedDict): + """ + Dictionary type representing a document with a duplicate vocabulary group + """ + + id: str + word: str + training_sets: List[str] + + +def map_to_duplicate_vocabulary_documents(documents) -> [DuplicateVocabularyDocument]: + """ + Maps the result of the get duplicate vocabularies query to DuplicateVocabularyDocument + """ + mapped = [] + for document in documents: + training_sets = [] + for training_set in document.get("_prefetched_objects_cache").get( + "training_sets", None + ): + training_sets.append(training_set.title) + mapped.append( + {"id": document.id, "word": document.word, "training_sets": training_sets} + ) + return mapped + + +def get_duplicate_vocabularies() -> List[DuplicateVocabulary]: + """ + Retrieves duplicate vocabularies from the database + """ + duplicate_groups = ( + Document.objects.annotate(lower_word=Lower("word")) + .values("lower_word", "word_type") + .annotate(count=Count("id")) + .filter(count__gt=1) + ) + duplicate_vocabularies = Document.objects.prefetch_related("training_sets").filter( + id__in=Document.objects.annotate(lower_word=Lower("word")) + .filter( + lower_word__in=[group["lower_word"] for group in duplicate_groups], + word_type__in=[group["word_type"] for group in duplicate_groups], + ) + .values_list("id", flat=True) + ) + + grouped_duplicate_vocabularies = defaultdict(list) + for v in duplicate_vocabularies: + grouped_duplicate_vocabularies[(v.word.lower(), v.word_type)].append(v) + + result = [] + for _, value in grouped_duplicate_vocabularies.items(): + result.append( + { + "word": value[0].word, + "word_type": value[0].word_type, + "documents": map_to_duplicate_vocabulary_documents(value), + } + ) + return result diff --git a/lunes_cms/cms/list_filter.py b/lunes_cms/cms/admin/document/list_filter.py similarity index 77% rename from lunes_cms/cms/list_filter.py rename to lunes_cms/cms/admin/document/list_filter.py index ed10bd60..0a00937b 100644 --- a/lunes_cms/cms/list_filter.py +++ b/lunes_cms/cms/admin/document/list_filter.py @@ -1,73 +1,8 @@ from django.contrib import admin -from django.db.models import F from django.utils.translation import gettext_lazy as _ -from .models import Discipline, TrainingSet - - -class DisciplineListFilter(admin.SimpleListFilter): - """ - Generic Filter for models that have a direct relationship to disciplines. - Inherits from `admin.SimpleListFilter`. - """ - - title = _("disciplines") - - # Parameter for the filter that will be used in the URL query. - parameter_name = "disciplines" - - template = "admin/discipline_filter.html" - - def lookups(self, request, model_admin): - """ - Defining look up values that can be seen in the admin - interface. Returns tuples: the first element is a coded - value, whereas the second one is human-readable. - - :param request: current user request - :type request: django.http.request - :param model_admin: admin of current model - :type model_admin: ModelAdmin - :return: list of tuples containing id and title of each discipline - :rtype: list - """ - - # Verify that only disciplines are displayed that actually can contain training sets - queryset = Discipline.objects.filter(lft=F("rght") - 1) - - if "training set" in request.GET: - queryset = queryset.filter(training_sets=request.GET["training set"]) - - if request.user.is_superuser: - queryset = queryset.filter(creator_is_admin=True) - else: - queryset = queryset.filter(created_by__in=request.user.groups.all()) - - list_of_disciplines = [ - ( - str(discipline.id), - f"{discipline.parent} \u2794 {discipline}", - ) - for discipline in queryset - ] - return sorted(list_of_disciplines, key=lambda tp: tp[1]) - - def queryset(self, request, queryset): - """ - Returns the filtered queryset based on the value - provided in the query string and retrievable via - `self.value()`. - - :param request: current user request - :type request: django.http.request - :param queryset: current queryset - :type queryset: QuerySet - :return: filtered queryset based on the value provided in the query string - :rtype: QuerySet - """ - if self.value(): - return queryset.filter(discipline=self.value()).distinct() - return queryset +from ..discipline.list_filter import DisciplineListFilter +from ...models import TrainingSet class DocumentDisciplineListFilter(DisciplineListFilter): diff --git a/lunes_cms/cms/admin/feedback/__init__.py b/lunes_cms/cms/admin/feedback/__init__.py new file mode 100644 index 00000000..687bb6f7 --- /dev/null +++ b/lunes_cms/cms/admin/feedback/__init__.py @@ -0,0 +1,7 @@ +from .feedback_admin import FeedbackAdmin +from .filter import filter_feedback_by_creator + +__all__ = [ + "FeedbackAdmin", + "filter_feedback_by_creator", +] diff --git a/lunes_cms/cms/admins/feedback_admin.py b/lunes_cms/cms/admin/feedback/feedback_admin.py similarity index 96% rename from lunes_cms/cms/admins/feedback_admin.py rename to lunes_cms/cms/admin/feedback/feedback_admin.py index de73b2e8..3ec85f0e 100644 --- a/lunes_cms/cms/admins/feedback_admin.py +++ b/lunes_cms/cms/admin/feedback/feedback_admin.py @@ -2,8 +2,8 @@ from django.utils.translation import gettext_lazy as _ -from ..feedback_filter import filter_feedback_by_creator -from ..models import Feedback +from .filter import filter_feedback_by_creator +from ...models import Feedback class FeedbackAdmin(admin.ModelAdmin): diff --git a/lunes_cms/cms/feedback_filter.py b/lunes_cms/cms/admin/feedback/filter.py similarity index 96% rename from lunes_cms/cms/feedback_filter.py rename to lunes_cms/cms/admin/feedback/filter.py index 3bc74209..1c71adc9 100644 --- a/lunes_cms/cms/feedback_filter.py +++ b/lunes_cms/cms/admin/feedback/filter.py @@ -1,7 +1,7 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import Q -from .models import Discipline, TrainingSet, Document +from ...models import Discipline, TrainingSet, Document def filter_feedback_by_creator(feedback_queryset, user): diff --git a/lunes_cms/cms/admin/group_api_key/__init__.py b/lunes_cms/cms/admin/group_api_key/__init__.py new file mode 100644 index 00000000..f5ddb918 --- /dev/null +++ b/lunes_cms/cms/admin/group_api_key/__init__.py @@ -0,0 +1,5 @@ +from .group_api_key_admin import GroupAPIKeyAdmin + +__all__ = [ + "GroupAPIKeyAdmin", +] diff --git a/lunes_cms/cms/admins/group_api_key_admin.py b/lunes_cms/cms/admin/group_api_key/group_api_key_admin.py similarity index 100% rename from lunes_cms/cms/admins/group_api_key_admin.py rename to lunes_cms/cms/admin/group_api_key/group_api_key_admin.py diff --git a/lunes_cms/cms/admin/sponsor/__init__.py b/lunes_cms/cms/admin/sponsor/__init__.py new file mode 100644 index 00000000..17bbedf2 --- /dev/null +++ b/lunes_cms/cms/admin/sponsor/__init__.py @@ -0,0 +1,5 @@ +from .sponsor_admin import SponsorAdmin + +__all__ = [ + "SponsorAdmin", +] diff --git a/lunes_cms/cms/admins/sponsor_admin.py b/lunes_cms/cms/admin/sponsor/sponsor_admin.py similarity index 97% rename from lunes_cms/cms/admins/sponsor_admin.py rename to lunes_cms/cms/admin/sponsor/sponsor_admin.py index 078a94da..e1a6d628 100644 --- a/lunes_cms/cms/admins/sponsor_admin.py +++ b/lunes_cms/cms/admin/sponsor/sponsor_admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.utils.translation import gettext_lazy as _ -from ..utils import get_image_tag +from ...utils import get_image_tag class SponsorAdmin(admin.ModelAdmin): diff --git a/lunes_cms/cms/admin/training_set/__init__.py b/lunes_cms/cms/admin/training_set/__init__.py new file mode 100644 index 00000000..7a67ef36 --- /dev/null +++ b/lunes_cms/cms/admin/training_set/__init__.py @@ -0,0 +1,7 @@ +from .form import TrainingSetForm +from .training_set_admin import TrainingSetAdmin + +__all__ = [ + "TrainingSetAdmin", + "TrainingSetForm", +] diff --git a/lunes_cms/cms/forms.py b/lunes_cms/cms/admin/training_set/form.py similarity index 78% rename from lunes_cms/cms/forms.py rename to lunes_cms/cms/admin/training_set/form.py index e6dba5b4..b8942ca7 100644 --- a/lunes_cms/cms/forms.py +++ b/lunes_cms/cms/admin/training_set/form.py @@ -4,24 +4,9 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from .models import Discipline, Document, TrainingSet -from .widgets import ManyToManyOverlay - - -class DisciplineChoiceField(forms.ModelMultipleChoiceField): - """ - Custom form field in order to include parent nodes in string representation. - Inherits from `forms.ModelMultipleChocieField`. - """ - - def label_from_instance(self, obj): - if obj.parent: - ancestors = [ - node.title for node in obj.parent.get_ancestors(include_self=True) - ] - ancestors.append(obj.title) - return " \u2794 ".join(ancestors) - return obj.title +from ..discipline import DisciplineChoiceField +from ...models import Discipline, Document, TrainingSet +from ...widgets import ManyToManyOverlay class TrainingSetForm(forms.ModelForm): diff --git a/lunes_cms/cms/admins/training_set_admin.py b/lunes_cms/cms/admin/training_set/training_set_admin.py similarity index 98% rename from lunes_cms/cms/admins/training_set_admin.py rename to lunes_cms/cms/admin/training_set/training_set_admin.py index 419a39c8..b8a6b3a1 100644 --- a/lunes_cms/cms/admins/training_set_admin.py +++ b/lunes_cms/cms/admin/training_set/training_set_admin.py @@ -5,14 +5,14 @@ from django.db.models import Count, F, Q from django.urls import reverse from django.utils.safestring import mark_safe -from django.utils.translation import ngettext from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext from mptt.admin import DraggableMPTTAdmin -from ..forms import TrainingSetForm -from ..list_filter import DisciplineListFilter -from ..models import Discipline, Document, Static -from ..utils import iter_to_string +from .form import TrainingSetForm +from ..discipline import DisciplineListFilter +from ...models import Static, Discipline, Document +from ...utils import iter_to_string class TrainingSetAdmin(DraggableMPTTAdmin): diff --git a/lunes_cms/cms/admins/__init__.py b/lunes_cms/cms/admins/__init__.py deleted file mode 100644 index 6a8d2de1..00000000 --- a/lunes_cms/cms/admins/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .discipline_admin import DisciplineAdmin -from .document_admin import DocumentAdmin -from .feedback_admin import FeedbackAdmin -from .group_api_key_admin import GroupAPIKeyAdmin -from .sponsor_admin import SponsorAdmin -from .training_set_admin import TrainingSetAdmin - -__all__ = [ - "DisciplineAdmin", - "TrainingSetAdmin", - "DocumentAdmin", - "GroupAPIKeyAdmin", - "FeedbackAdmin", - "SponsorAdmin", -] diff --git a/lunes_cms/cms/apps.py b/lunes_cms/cms/apps.py index bda3d6b3..b64db663 100644 --- a/lunes_cms/cms/apps.py +++ b/lunes_cms/cms/apps.py @@ -9,4 +9,4 @@ class CmsConfig(AppConfig): """ name = "lunes_cms.cms" - verbose_name = _("vocabulary management") + verbose_name = _("Vocabulary management") diff --git a/lunes_cms/cms/migrations/0016_alter_trainingset_options.py b/lunes_cms/cms/migrations/0016_alter_trainingset_options.py new file mode 100644 index 00000000..a3f5f2df --- /dev/null +++ b/lunes_cms/cms/migrations/0016_alter_trainingset_options.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.21 on 2024-11-23 08:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + """ + Migration file to change the verbose names of model trainingset + """ + + dependencies = [ + ("cms", "0015_add_grammatical_gender_fields"), + ] + + operations = [ + migrations.AlterModelOptions( + name="trainingset", + options={ + "verbose_name": "Training set", + "verbose_name_plural": "Training sets", + }, + ), + ] diff --git a/lunes_cms/cms/migrations/0017_alter_trainingset_options.py b/lunes_cms/cms/migrations/0017_alter_trainingset_options.py new file mode 100644 index 00000000..8337d919 --- /dev/null +++ b/lunes_cms/cms/migrations/0017_alter_trainingset_options.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.21 on 2024-11-23 08:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + """ + Migration file to change the verbose names of model trainingset + """ + + dependencies = [ + ("cms", "0016_alter_trainingset_options"), + ] + + operations = [ + migrations.AlterModelOptions( + name="trainingset", + options={ + "verbose_name": "training set", + "verbose_name_plural": "training sets", + }, + ), + ] diff --git a/lunes_cms/cms/templates/admin/index.html b/lunes_cms/cms/templates/admin/index.html new file mode 100644 index 00000000..34988f61 --- /dev/null +++ b/lunes_cms/cms/templates/admin/index.html @@ -0,0 +1,55 @@ +{% extends "admin/index.html" %} + +{% block content %} +
+
+
+
+ {{ _("Duplicate vocabularies") }} +
+
+
+ {% if duplicate_vocabularies %} + + + {% for v in duplicate_vocabularies %} + + + + + {% endfor %} + +
+ {{ v.word }} + ({{ v.word_type }}) + +
    + {% for document in v.documents %} +
  • + + {{ document.word }} ({{ document.id }}) + +
    + {{ _("Training sets") }}: + {% if document.training_sets %} + {% for ts in document.training_sets %} + {{ ts.title }} + {% if not forloop.last %}, {% endif %} + {% endfor %} + {% else %} + {{ _("None") }} + {% endif %} +
    +
  • + {% endfor %} +
+
+ {% else %} + {{ _("No duplicate vocabularies available") }}. + {% endif %} +
+
+
+ + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/lunes_cms/core/context_processors.py b/lunes_cms/core/context_processors.py index a06f0da9..e6e080ee 100644 --- a/lunes_cms/core/context_processors.py +++ b/lunes_cms/core/context_processors.py @@ -1,8 +1,8 @@ """ Context processors pass additional variables to templates (see :ref:`context-processors`). """ +from ..cms.admin.feedback import filter_feedback_by_creator # pylint: disable=no-name-in-module from ..cms.models import Feedback -from ..cms.feedback_filter import filter_feedback_by_creator def feedback_processor(request): diff --git a/lunes_cms/locale/de/LC_MESSAGES/django.po b/lunes_cms/locale/de/LC_MESSAGES/django.po index c201300e..526c1df6 100644 --- a/lunes_cms/locale/de/LC_MESSAGES/django.po +++ b/lunes_cms/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-27 22:23+0000\n" +"POT-Creation-Date: 2024-11-23 10:29+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -37,9 +37,9 @@ msgstr "Definition: " msgid "No definition is provided for this word." msgstr "Für dieses Wort ist keine Definition hinterlegt." -#: api/utils.py:189 -msgid "Training sets: " -msgstr "Module: " +#: api/utils.py:189 cms/templates/admin/index.html:29 +msgid "Training sets" +msgstr "Module " #: api/utils.py:193 msgid "This word is not yet registered in the system" @@ -56,143 +56,209 @@ msgstr "" msgid "{} with id {} does not exist." msgstr "{} mit der ID {} existiert nicht." -#: cms/admin.py:35 cms/admins/document_admin.py:162 -#: cms/admins/training_set_admin.py:361 cms/forms.py:46 cms/forms.py:48 -#: cms/list_filter.py:14 cms/models/discipline.py:101 +#: cms/admin.py:56 cms/admin/discipline/list_filter.py:14 +#: cms/admin/document/document_admin.py:157 cms/admin/training_set/form.py:31 +#: cms/admin/training_set/form.py:33 +#: cms/admin/training_set/training_set_admin.py:361 +#: cms/models/discipline.py:101 msgid "disciplines" msgstr "Modulgruppen" -#: cms/admin.py:36 cms/list_filter.py:103 cms/models/training_set.py:92 +#: cms/admin.py:57 cms/admin/document/list_filter.py:38 +#: cms/models/training_set.py:92 msgid "training sets" msgstr "Module" -#: cms/admin.py:37 cms/forms.py:52 cms/forms.py:53 cms/models/document.py:148 +#: cms/admin.py:58 cms/admin/training_set/form.py:37 +#: cms/admin/training_set/form.py:38 cms/models/document.py:148 #: cms/models/document.py:149 msgid "vocabulary" msgstr "Vokabeln" -#: cms/admin.py:38 +#: cms/admin.py:59 msgid "api keys" msgstr "API Keys" -#: cms/admin.py:39 cms/models/feedback.py:90 +#: cms/admin.py:60 cms/models/feedback.py:90 msgid "feedback" msgstr "Feedback" -#: cms/admins/discipline_admin.py:258 +#: cms/admin/discipline/discipline_admin.py:258 msgid "Release selected disciplines" msgstr "Ausgewählte Modulgruppen veröffentlichen" -#: cms/admins/discipline_admin.py:271 +#: cms/admin/discipline/discipline_admin.py:271 msgid "Unrelease selected disciplines" msgstr "Ausgewählte Modulgruppen zurückhalten" -#: cms/admins/discipline_admin.py:284 +#: cms/admin/discipline/discipline_admin.py:284 msgid "Export all vocabulary for this discipline to CSV" msgstr "Alle Vokabeln dieser Modulgruppe als CSV exportieren" -#: cms/admins/discipline_admin.py:324 +#: cms/admin/discipline/discipline_admin.py:324 msgid "released modules" msgstr "veröffentlichte Module" -#: cms/admins/discipline_admin.py:345 +#: cms/admin/discipline/discipline_admin.py:345 msgid "unreleased modules" msgstr "unveröffentlichte Module" -#: cms/admins/discipline_admin.py:366 +#: cms/admin/discipline/discipline_admin.py:366 msgid "published words in released modules" msgstr "veröffentlichte Vokabeln in veröffentlichten Modulen" -#: cms/admins/discipline_admin.py:387 cms/admins/document_admin.py:179 -#: cms/admins/training_set_admin.py:378 +#: cms/admin/discipline/discipline_admin.py:387 +#: cms/admin/document/document_admin.py:174 +#: cms/admin/training_set/training_set_admin.py:378 msgid "creator group" msgstr "Besitzergruppe" -#: cms/admins/document_admin.py:142 cms/models/training_set.py:25 +#: cms/admin/document/document_admin.py:137 cms/models/training_set.py:25 #: cms/models/training_set.py:91 msgid "training set" msgstr "Modul" -#: cms/admins/document_admin.py:196 cms/models/document.py:66 +#: cms/admin/document/document_admin.py:191 cms/models/document.py:66 msgid "audio" msgstr "Audio" -#: cms/admins/document_admin.py:216 cms/models/document_image.py:124 +#: cms/admin/document/document_admin.py:211 cms/models/document_image.py:124 msgid "image" msgstr "Bild" -#: cms/admins/document_admin.py:231 cms/models/alternative_word.py:24 +#: cms/admin/document/document_admin.py:226 cms/models/alternative_word.py:24 #: cms/models/document.py:43 msgid "singular article" msgstr "Singular-Artikel" -#: cms/admins/document_resource.py:24 +#: cms/admin/document/document_resource.py:24 msgid "Word" msgstr "Vokabel" -#: cms/admins/document_resource.py:26 +#: cms/admin/document/document_resource.py:26 msgid "Word type" msgstr "Wortart" -#: cms/admins/document_resource.py:29 +#: cms/admin/document/document_resource.py:29 msgid "Singular Article" msgstr "Singular Artikel" -#: cms/admins/document_resource.py:43 +#: cms/admin/document/document_resource.py:43 msgid "Plural Article" msgstr "Plural Artikel" -#: cms/admins/document_resource.py:56 +#: cms/admin/document/document_resource.py:56 msgid "Has audio?" msgstr "Audio" -#: cms/admins/document_resource.py:63 +#: cms/admin/document/document_resource.py:63 msgid "Yes" msgstr "Ja" -#: cms/admins/document_resource.py:64 +#: cms/admin/document/document_resource.py:64 msgid "No" msgstr "Nein" -#: cms/admins/document_resource.py:67 +#: cms/admin/document/document_resource.py:67 msgid "Example sentence" msgstr "Beispielsatz" -#: cms/admins/document_resource.py:71 +#: cms/admin/document/document_resource.py:71 msgid "Creation date" msgstr "Erstellt am" -#: cms/admins/document_resource.py:81 +#: cms/admin/document/document_resource.py:81 msgid "Training Sets" msgstr "Module" -#: cms/admins/feedback_admin.py:34 +#: cms/admin/document/list_filter.py:98 +msgid "Images" +msgstr "Bilder" + +#: cms/admin/document/list_filter.py:119 +msgid "At least one approved image" +msgstr "Mind. ein bestätigtes Bild" + +#: cms/admin/document/list_filter.py:120 +msgid "At least one pending image" +msgstr "Mind. ein unbestätigtes Bild" + +#: cms/admin/document/list_filter.py:121 +msgid "No approved images" +msgstr "Keine bestätigten Bilder" + +#: cms/admin/document/list_filter.py:122 +msgid "No images" +msgstr "Keine Bilder" + +#: cms/admin/document/list_filter.py:164 +msgid "Assignments" +msgstr "Zuweisungen" + +#: cms/admin/document/list_filter.py:185 +msgid "Not assigned to any module" +msgstr "Keinem Modul zugewiesen" + +#: cms/admin/document/list_filter.py:186 +msgid "Assigned to at least one module" +msgstr "Mind. einem Modul zugewiesen" + +#: cms/admin/document/list_filter.py:187 +msgid "Assigned to released modules" +msgstr "Veröffentlichten Modulen zugewiesen" + +#: cms/admin/document/list_filter.py:190 +msgid "Assigned to released modules in released disciplines" +msgstr "Veröffentlichten Modulen in veröffentlichten Modulgruppen zugewiesen" + +#: cms/admin/document/list_filter.py:192 +msgid "Assigned to unreleased modules" +msgstr "Unveröffentlichten Modulen zugewiesen" + +#: cms/admin/feedback/feedback_admin.py:34 msgid "Mark as read" msgstr "Als gelesen markieren" -#: cms/admins/feedback_admin.py:49 +#: cms/admin/feedback/feedback_admin.py:49 msgid "The selected feedback entries were successfully marked as read." msgstr "" "Die ausgewählten Feedback-Einträge wurden erfolgreich als gelesen markiert." -#: cms/admins/feedback_admin.py:53 +#: cms/admin/feedback/feedback_admin.py:53 msgid "Mark as unread" msgstr "Als ungelesen markieren" -#: cms/admins/feedback_admin.py:68 +#: cms/admin/feedback/feedback_admin.py:68 msgid "The selected feedback entries were successfully marked as unread." msgstr "" "Die ausgewählten Feedback-Einträge wurden erfolgreich als ungelesen markiert." -#: cms/admins/sponsor_admin.py:36 cms/models/sponsor.py:26 +#: cms/admin/sponsor/sponsor_admin.py:36 cms/models/sponsor.py:26 msgid "logo" msgstr "Logo" -#: cms/admins/training_set_admin.py:185 +#: cms/admin/training_set/form.py:40 +msgid "" +"Please select some vocabularies for your training set. To see a preview of " +"the corresponding image and audio files, press the alt key while selecting." +msgstr "" +"Bitte wähle ein paar Vokabeln für dein Modul aus. Um eine Vorschau der " +"Bilder und Audio-Dateien zu sehen, drücke die Alt-Taste, während du ein " +"Element auswählst." + +#: cms/admin/training_set/form.py:64 +msgid "" +"You can only release a training set that contains at least {} vocabulary " +"words with confirmed images." +msgstr "" +"Sie können nur Module veröffentlichen, die mindestens {} Vokabeln mit " +"bestätigten Bildern enthalten." + +#: cms/admin/training_set/training_set_admin.py:185 msgid "Release selected training sets" msgstr "Ausgewählte Module veröffentlichen" -#: cms/admins/training_set_admin.py:220 +#: cms/admin/training_set/training_set_admin.py:220 msgid "" "The training set {} could not be released because it contains less than {} " "vocabulary words with confirmed images." @@ -206,111 +272,50 @@ msgstr[1] "" "Die Module {} konnten nicht veröffentlicht werden, da sie weniger als {} " "Vokabeln mit bestätigten Bildern enthalten." -#: cms/admins/training_set_admin.py:235 +#: cms/admin/training_set/training_set_admin.py:235 msgid "The training set {} is already released." msgid_plural "The training sets {} are already released." msgstr[0] "Das Modul {} ist bereits veröffentlicht." msgstr[1] "Die Module {} sind bereits veröffentlicht." -#: cms/admins/training_set_admin.py:249 +#: cms/admin/training_set/training_set_admin.py:249 msgid "The training set {} was successfully released." msgid_plural "The training sets {} were successfully released." msgstr[0] "Das Modul {} wurde erfolgreich veröffentlicht." msgstr[1] "Die Module {} wurden erfolgreich veröffentlicht." -#: cms/admins/training_set_admin.py:255 +#: cms/admin/training_set/training_set_admin.py:255 msgid "Unrelease selected training sets" msgstr "Ausgewählte Module zurückhalten" -#: cms/admins/training_set_admin.py:279 +#: cms/admin/training_set/training_set_admin.py:279 msgid "The training set {} is already unreleased." msgid_plural "The training sets {} are already unreleased." msgstr[0] "Das Modul {} ist bereits zurückgehalten." msgstr[1] "Die Module {} sind bereits zurückgehalten." -#: cms/admins/training_set_admin.py:290 +#: cms/admin/training_set/training_set_admin.py:290 msgid "The training set {} was successfully unreleased." msgid_plural "The training sets {} were successfully unreleased." msgstr[0] "Das Modul {} wurde erfolgreich zurückgehalten." msgstr[1] "Die Module {} wurden erfolgreich zurückgehalten." -#: cms/admins/training_set_admin.py:297 +#: cms/admin/training_set/training_set_admin.py:297 msgid "words" msgstr "Vokabeln" -#: cms/admins/training_set_admin.py:315 +#: cms/admin/training_set/training_set_admin.py:315 msgid "published words" msgstr "veröffentlichte Vokabeln" -#: cms/admins/training_set_admin.py:333 +#: cms/admin/training_set/training_set_admin.py:333 msgid "unpublished words" msgstr "unveröffentlichte Vokabeln" #: cms/apps.py:12 -msgid "vocabulary management" +msgid "Vocabulary management" msgstr "Vokabelverwaltung" -#: cms/forms.py:55 -msgid "" -"Please select some vocabularies for your training set. To see a preview of " -"the corresponding image and audio files, press the alt key while selecting." -msgstr "" -"Bitte wähle ein paar Vokabeln für dein Modul aus. Um eine Vorschau der " -"Bilder und Audio-Dateien zu sehen, drücke die Alt-Taste, während du ein " -"Element auswählst." - -#: cms/forms.py:79 -msgid "" -"You can only release a training set that contains at least {} vocabulary " -"words with confirmed images." -msgstr "" -"Sie können nur Module veröffentlichen, die mindestens {} Vokabeln mit " -"bestätigten Bildern enthalten." - -#: cms/list_filter.py:163 -msgid "Images" -msgstr "Bilder" - -#: cms/list_filter.py:184 -msgid "At least one approved image" -msgstr "Mind. ein bestätigtes Bild" - -#: cms/list_filter.py:185 -msgid "At least one pending image" -msgstr "Mind. ein unbestätigtes Bild" - -#: cms/list_filter.py:186 -msgid "No approved images" -msgstr "Keine bestätigten Bilder" - -#: cms/list_filter.py:187 -msgid "No images" -msgstr "Keine Bilder" - -#: cms/list_filter.py:229 -msgid "Assignments" -msgstr "Zuweisungen" - -#: cms/list_filter.py:250 -msgid "Not assigned to any module" -msgstr "Keinem Modul zugewiesen" - -#: cms/list_filter.py:251 -msgid "Assigned to at least one module" -msgstr "Mind. einem Modul zugewiesen" - -#: cms/list_filter.py:252 -msgid "Assigned to released modules" -msgstr "Veröffentlichten Modulen zugewiesen" - -#: cms/list_filter.py:255 -msgid "Assigned to released modules in released disciplines" -msgstr "Veröffentlichten Modulen in veröffentlichten Modulgruppen zugewiesen" - -#: cms/list_filter.py:257 -msgid "Assigned to unreleased modules" -msgstr "Unveröffentlichten Modulen zugewiesen" - #: cms/models/alternative_word.py:13 cms/models/alternative_word.py:56 msgid "alternative word" msgstr "Alternatives Wort" @@ -615,6 +620,18 @@ msgstr "" "Löschen der ausgewählten %(objects_name)s würde die folgenden verwandten " "Objekte ebenfalls löschen, die allerdings geschützt sind:" +#: cms/templates/admin/index.html:8 +msgid "Duplicate vocabularies" +msgstr "Doppelte Vokabeln" + +#: cms/templates/admin/index.html:36 +msgid "None" +msgstr "Keine" + +#: cms/templates/admin/index.html:48 +msgid "No duplicate vocabularies available" +msgstr "Keine doppelten Vokabeln gefunden" + #: cms/templates/admin/object_delete_summary.html:2 msgid "Summary" msgstr "Zusammenfassung" @@ -659,6 +676,12 @@ msgstr "Willkommen bei der Vokabelverwaltung von Lunes!" msgid "help" msgstr "Hilfe" +#~ msgid "Training set" +#~ msgstr "Module: " + +#~ msgid "Training sets: " +#~ msgstr "Module: " + #~ msgid "Article" #~ msgstr "Arikel"