diff --git a/THIRD_PARTY_LICENSES_PYTHON.md b/THIRD_PARTY_LICENSES_PYTHON.md new file mode 100644 index 0000000..3f46094 --- /dev/null +++ b/THIRD_PARTY_LICENSES_PYTHON.md @@ -0,0 +1,45 @@ +| Name | Version | License | +|---------------------------|--------------|---------------------------------------------------------| +| Automat | 25.4.16 | MIT License | +| Django | 5.1.13 | BSD License | +| Twisted | 25.5.0 | MIT License | +| asgiref | 3.10.0 | BSD License | +| attrs | 25.4.0 | UNKNOWN | +| autobahn | 25.10.1 | UNKNOWN | +| certifi | 2025.10.5 | Mozilla Public License 2.0 (MPL 2.0) | +| cffi | 2.0.0 | UNKNOWN | +| channels | 4.2.2 | BSD License | +| channels_redis | 4.3.0 | BSD | +| chardet | 5.2.0 | GNU Lesser General Public License v2 or later (LGPLv2+) | +| charset-normalizer | 3.4.4 | MIT | +| colorama | 0.4.6 | BSD License | +| constantly | 23.10.4 | MIT License | +| cryptography | 46.0.3 | UNKNOWN | +| daphne | 4.1.2 | BSD License | +| django-autocomplete-light | 3.12.1 | MIT License | +| django-cleanup | 9.0.0 | MIT License | +| geographiclib | 2.1 | MIT | +| geopy | 2.4.1 | MIT License | +| hyperlink | 21.0.0 | MIT License | +| idna | 3.11 | UNKNOWN | +| incremental | 24.7.2 | MIT License | +| msgpack | 1.1.2 | UNKNOWN | +| pillow | 11.1.0 | CMU License (MIT-CMU) | +| pyOpenSSL | 25.3.0 | Apache Software License | +| pyasn1 | 0.6.1 | BSD License | +| pyasn1_modules | 0.4.2 | BSD License | +| pycparser | 2.23 | BSD License | +| pypng | 0.20220715.0 | MIT License | +| python-dotenv | 1.1.1 | BSD License | +| qrcode | 7.4.2 | BSD License | +| redis | 6.4.0 | MIT License | +| reportlab | 4.2.5 | BSD License | +| requests | 2.32.5 | Apache Software License | +| service-identity | 24.2.0 | MIT License | +| sqlparse | 0.5.3 | BSD License | +| txaio | 25.9.2 | UNKNOWN | +| typing_extensions | 4.15.0 | UNKNOWN | +| tzdata | 2025.2 | Apache Software License | +| urllib3 | 2.5.0 | UNKNOWN | +| whitenoise | 6.6.0 | MIT License | +| zope.interface | 8.0.1 | Zope Public License | diff --git a/django/THIRD_PARTY_LICENSES_PYTHON.md b/django/THIRD_PARTY_LICENSES_PYTHON.md deleted file mode 100644 index e8696aa..0000000 --- a/django/THIRD_PARTY_LICENSES_PYTHON.md +++ /dev/null @@ -1,44 +0,0 @@ -| Name | Version | License | -|--------------------|--------------|---------------------------------------------------------| -| Automat | 25.4.16 | MIT License | -| Django | 5.1.13 | BSD License | -| Twisted | 25.5.0 | MIT License | -| asgiref | 3.10.0 | BSD License | -| attrs | 25.4.0 | UNKNOWN | -| autobahn | 25.10.1 | UNKNOWN | -| certifi | 2025.10.5 | Mozilla Public License 2.0 (MPL 2.0) | -| cffi | 2.0.0 | UNKNOWN | -| channels | 4.2.2 | BSD License | -| channels_redis | 4.3.0 | BSD | -| chardet | 5.2.0 | GNU Lesser General Public License v2 or later (LGPLv2+) | -| charset-normalizer | 3.4.4 | MIT | -| colorama | 0.4.6 | BSD License | -| constantly | 23.10.4 | MIT License | -| cryptography | 46.0.3 | UNKNOWN | -| daphne | 4.1.2 | BSD License | -| django-cleanup | 9.0.0 | MIT License | -| geographiclib | 2.1 | MIT | -| geopy | 2.4.1 | MIT License | -| hyperlink | 21.0.0 | MIT License | -| idna | 3.11 | UNKNOWN | -| incremental | 24.7.2 | MIT License | -| msgpack | 1.1.2 | UNKNOWN | -| pillow | 11.1.0 | CMU License (MIT-CMU) | -| pyOpenSSL | 25.3.0 | Apache Software License | -| pyasn1 | 0.6.1 | BSD License | -| pyasn1_modules | 0.4.2 | BSD License | -| pycparser | 2.23 | BSD License | -| pypng | 0.20220715.0 | MIT License | -| python-dotenv | 1.1.1 | BSD License | -| qrcode | 7.4.2 | BSD License | -| redis | 6.4.0 | MIT License | -| reportlab | 4.2.5 | BSD License | -| requests | 2.32.5 | Apache Software License | -| service-identity | 24.2.0 | MIT License | -| sqlparse | 0.5.3 | BSD License | -| txaio | 25.9.2 | UNKNOWN | -| typing_extensions | 4.15.0 | UNKNOWN | -| tzdata | 2025.2 | Apache Software License | -| urllib3 | 2.5.0 | UNKNOWN | -| whitenoise | 6.6.0 | MIT License | -| zope.interface | 8.0.1 | Zope Public License | diff --git a/django/core/settings.py b/django/core/settings.py index 7506dee..b0f6ceb 100644 --- a/django/core/settings.py +++ b/django/core/settings.py @@ -57,6 +57,8 @@ INSTALLED_APPS = [ 'channels', 'django_cleanup', 'django.contrib.humanize', + 'dal', + 'dal_select2', ] MIDDLEWARE = [ diff --git a/django/library/admin.py b/django/library/admin.py index 72eabab..0b11c31 100644 --- a/django/library/admin.py +++ b/django/library/admin.py @@ -6,6 +6,12 @@ class QivipQuizAdmin(admin.ModelAdmin): list_display = ('name','published_at', 'user_id', 'status', 'category', 'creation_date', 'update_date') # Welche Felder sollen in der Übersicht angezeigt werden search_fields = ('name', 'user_id__username', 'category__name') # Suchfelder list_filter = ('status', 'category') # Filteroptionen + autocomplete_fields = ("reference_quizzes",) + + + + + # Für das QivipQuestion Modell class QivipQuestionAdmin(admin.ModelAdmin): list_display = ('id', 'quiz_id', 'data', 'creation_date', 'update_date', 'credits') diff --git a/django/library/forms.py b/django/library/forms.py index 95b3c40..67a8718 100644 --- a/django/library/forms.py +++ b/django/library/forms.py @@ -1,17 +1,31 @@ from django import forms from .models import QivipQuiz, QivipQuestion, QuizCategory +from dal import autocomplete class QuizForm(forms.ModelForm): class Meta: model = QivipQuiz - fields = ['name', 'description', 'status', 'category','difficulty','credits'] + fields = ['name', 'description', 'status', 'category','difficulty','reference_quizzes','credits'] + widgets = { + 'reference_quizzes': autocomplete.ModelSelect2Multiple( + url='library:quiz-autocomplete', + forward=['pk'] + + ) + } def __init__(self, *args, **kwargs): super(QuizForm, self).__init__(*args, **kwargs) for field in self.fields.values(): field.widget.attrs.update({ - 'class': 'dark:bg-[#2a2f3a]! dark:text-white!' + }) + self.fields["reference_quizzes"].queryset = QivipQuiz.objects.filter( + status="öffentlich").exclude(pk=self.instance.pk) + self.fields["reference_quizzes"].label_from_instance = lambda obj: f"{obj.name} (ID: {obj.pk}) (User: {obj.user_id})" + + + diff --git a/django/library/migrations/0060_qivipquiz_similar_quizzes.py b/django/library/migrations/0060_qivipquiz_similar_quizzes.py new file mode 100644 index 0000000..4b46c0b --- /dev/null +++ b/django/library/migrations/0060_qivipquiz_similar_quizzes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.7 on 2025-11-27 18:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('library', '0059_remove_qivipquiz_single_player_or_multiple'), + ] + + operations = [ + migrations.AddField( + model_name='qivipquiz', + name='similar_quizzes', + field=models.ManyToManyField(blank=True, related_name='similar_to', to='library.qivipquiz'), + ), + ] diff --git a/django/library/migrations/0061_alter_qivipquiz_similar_quizzes.py b/django/library/migrations/0061_alter_qivipquiz_similar_quizzes.py new file mode 100644 index 0000000..68900a2 --- /dev/null +++ b/django/library/migrations/0061_alter_qivipquiz_similar_quizzes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.7 on 2025-11-27 18:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('library', '0060_qivipquiz_similar_quizzes'), + ] + + operations = [ + migrations.AlterField( + model_name='qivipquiz', + name='similar_quizzes', + field=models.ManyToManyField(blank=True, to='library.qivipquiz'), + ), + ] diff --git a/django/library/migrations/0062_alter_qivipquiz_similar_quizzes.py b/django/library/migrations/0062_alter_qivipquiz_similar_quizzes.py new file mode 100644 index 0000000..ef47dae --- /dev/null +++ b/django/library/migrations/0062_alter_qivipquiz_similar_quizzes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.7 on 2025-11-27 19:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('library', '0061_alter_qivipquiz_similar_quizzes'), + ] + + operations = [ + migrations.AlterField( + model_name='qivipquiz', + name='similar_quizzes', + field=models.ManyToManyField(blank=True, to='library.qivipquiz', verbose_name='Verweis auf andere Quizze'), + ), + ] diff --git a/django/library/migrations/0063_rename_similar_quizzes_qivipquiz_reference_quizzes.py b/django/library/migrations/0063_rename_similar_quizzes_qivipquiz_reference_quizzes.py new file mode 100644 index 0000000..5fbc609 --- /dev/null +++ b/django/library/migrations/0063_rename_similar_quizzes_qivipquiz_reference_quizzes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.7 on 2025-12-03 11:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('library', '0062_alter_qivipquiz_similar_quizzes'), + ] + + operations = [ + migrations.RenameField( + model_name='qivipquiz', + old_name='similar_quizzes', + new_name='reference_quizzes', + ), + ] diff --git a/django/library/models.py b/django/library/models.py index e7291a9..fc9878b 100644 --- a/django/library/models.py +++ b/django/library/models.py @@ -43,6 +43,12 @@ class QivipQuiz(models.Model): on_delete=models.PROTECT, related_name="quizzes" ) + reference_quizzes = models.ManyToManyField( + 'self', + blank=True, + symmetrical=False, + verbose_name="Verweis auf andere Quizze" + ) quiz_played_Lernmodus = models.IntegerField(null=True, blank=True, default=0) diff --git a/django/library/urls.py b/django/library/urls.py index 1d2c621..fde2131 100644 --- a/django/library/urls.py +++ b/django/library/urls.py @@ -3,6 +3,7 @@ from django.urls import path from django.conf import settings from . import views from django.conf.urls.static import static +from .views import QuizAutocomplete app_name = 'library' urlpatterns = [ @@ -20,7 +21,11 @@ urlpatterns = [ path('detail/copy//', views.copy_quiz, name='copy_quiz'), path('favorite_quiz//', views.favorite_quiz, name='favorite_quiz'), path("quiz-names-json/", views.quiz_names_json, name="quiz_names_json"), - #path('quiz//pdf/', views.pdf_solution, name='quiz_solution'), - #path('quiz//pdf/task', views.pdf_task, name='quiz_task'), + path( + "autocomplete/quiz", + QuizAutocomplete.as_view(), + name="quiz-autocomplete" + ), + ] diff --git a/django/library/views.py b/django/library/views.py index 832b076..b7fe058 100644 --- a/django/library/views.py +++ b/django/library/views.py @@ -40,6 +40,37 @@ from PIL import Image as PILImage, ImageOps from django.conf import settings import os from django.utils.html import escape +from .models import QivipQuiz +from django.db.models import Value, CharField +from django.db.models.functions import Concat +# views.py +from dal import autocomplete +from .models import QivipQuiz + +class QuizAutocomplete(autocomplete.Select2QuerySetView): + + def get_queryset(self): + qs = QivipQuiz.objects.filter(status="öffentlich") + + pk = self.forwarded.get('pk', None) + if pk: + qs = qs.exclude(pk=pk) + + if self.q: + qs = qs.annotate( + search_text=Concat( + 'name', + Value(' (ID: '), 'pk', Value(') (User: '), 'user_id', Value(')'), + output_field=CharField() + ) + ).filter(search_text__icontains=self.q) + return qs + + def get_result_label(self, result): + # z.B. Name + ID + User + return f"{result.name} (ID: {result.pk}) (User: {result.user_id})" + + """ def pdf_solution(request, pk): quiz = get_object_or_404(QivipQuiz, pk=pk) diff --git a/django/static/css/t-input.css b/django/static/css/t-input.css index e69bf20..646eced 100644 --- a/django/static/css/t-input.css +++ b/django/static/css/t-input.css @@ -1,5 +1,11 @@ @import 'tailwindcss'; +.select2-container { + @apply font-black!; +} + + + qp-container { @apply max-w-xl w-full mx-auto; } diff --git a/django/templates/library/detail_quiz.html b/django/templates/library/detail_quiz.html index d590f97..903470f 100644 --- a/django/templates/library/detail_quiz.html +++ b/django/templates/library/detail_quiz.html @@ -319,65 +319,61 @@ {% endif %} -
+ + + + + +
+

+ Quiz-Infos +

+
{% if quiz.base_quiz %} -

Bearbeitung: {{ quiz.user_id }}

+

Bearbeitung: {{ quiz.user_id }}

{% else %} -

Autor: {{ quiz.user_id }}

+

Autor: {{ quiz.user_id }}

{% endif %}
-
+
{% if quiz.base_quiz %} -

Kopie von: {{ quiz.base_quiz}}

+

Kopie von: {{ quiz.base_quiz}}

{% endif %}
-
+
{% if quiz.base_quiz %} -

Autor: {{ quiz.base_quiz.user_id }}

+

Autor: {{ quiz.base_quiz.user_id }}

{% endif %}
- + + +{% if quiz.reference_quizzes.all %} +
+

Verweis auf andere Quizze: + + {% for reference in quiz.reference_quizzes.all %} + {{ reference.name}}{% if not forloop.last %}, {% endif %} + {% empty %} + + {% endfor %} +

+
+{% endif %} +
+
- - - -

Credits diff --git a/django/templates/library/form.html b/django/templates/library/form.html index f3173be..00dcd04 100644 --- a/django/templates/library/form.html +++ b/django/templates/library/form.html @@ -1,43 +1,97 @@ -{% extends 'base.html' %} +{% extends 'base.html' %} {% block content %} + +{{ form.media }} + + + + +

+
+
+ {% csrf_token %} {{ form.as_p }} {% if form.instance.image %} + Bild der Frage + + {% endif %} +
+ + +
+ +
+ +
+ +
+ + Abbrechen + + +
+
-
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/requirements.txt b/requirements.txt index be6814f..9d10996 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ pip-licenses ~= 5.0.0 requests requests geopy +django-autocomplete-light