diff --git a/services/backend/apps/task/admin.py b/services/backend/apps/task/admin.py index 7b6fbfa..10f3c67 100644 --- a/services/backend/apps/task/admin.py +++ b/services/backend/apps/task/admin.py @@ -4,6 +4,7 @@ from apps.task.models import ( CompetitionTask, CompetitionTaskAttachment, CompetitionTaskSubmission, + CompetitionTaskCriteria ) @@ -12,12 +13,17 @@ class CompletionAttachmentInline(admin.StackedInline): extra = 0 +class CompetitionCriteriaInline(admin.StackedInline): + model = CompetitionTaskCriteria + extra = 0 + + @admin.register(CompetitionTask) class CompetitionTaskAdmin(admin.ModelAdmin): list_display = ("title", "type", "points") filter_horizontal = ("reviewers",) list_filter = ("type",) - inlines = (CompletionAttachmentInline,) + inlines = (CompletionAttachmentInline, CompetitionCriteriaInline,) @admin.register(CompetitionTaskSubmission) diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index 0c77556..ef0016d 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -2,7 +2,7 @@ from uuid import uuid4 from django.db import models from django.db.models import Count, Q -from martor.models import MartorField +from mdeditor.fields import MDTextField from apps.competition.models import Competition from apps.core.models import BaseModel @@ -19,11 +19,15 @@ class CompetitionTask(BaseModel): def answer_file_upload_to(instance, filename) -> str: return f"tasks/{instance.id}/answer/{uuid4()}/{filename}" - in_competition_position = models.PositiveSmallIntegerField() - competition = models.ForeignKey(Competition, on_delete=models.CASCADE) + in_competition_position = models.PositiveSmallIntegerField( + verbose_name="позиция в соревновании" + ) + competition = models.ForeignKey(Competition, on_delete=models.CASCADE, + verbose_name="привязанное соревнование") title = models.CharField(verbose_name="заголовок", max_length=50) - description = MartorField(verbose_name="описание") - max_attempts = models.PositiveSmallIntegerField(null=True, blank=True) + description = MDTextField(verbose_name="описание") + max_attempts = models.PositiveSmallIntegerField(null=True, blank=True, + verbose_name="максимальное кол-во попыток") type = models.CharField( choices=CompetitionTaskType, max_length=8, verbose_name="тип проверки" ) @@ -56,7 +60,7 @@ class CompetitionTask(BaseModel): help_text="Справа отображаются действующие проверяющие, слева - доступные для выбора. Для перемещения можно кликнуть 2 раза по проверяющему", ) submission_reviewers_count = models.PositiveSmallIntegerField( - default=1, null=True, blank=True + default=1, null=True, blank=True, verbose_name="кол-во проверяющих для зачета задачи" ) def __str__(self): @@ -72,10 +76,25 @@ class CompetitionTaskCriteria(BaseModel): CompetitionTask, on_delete=models.CASCADE, related_name="criteries" ) - name = models.TextField() - slug = models.SlugField() - description = models.TextField() - max_value = models.PositiveSmallIntegerField() + name = models.TextField( + verbose_name="название" + ) + slug = models.SlugField( + verbose_name="техническое название" + ) + description = models.TextField( + verbose_name="описание критерии" + ) + max_value = models.PositiveSmallIntegerField( + verbose_name="максимальное кол-во баллов" + ) + + def __str__(self): + return self.name + + class Meta: + verbose_name = "критерий" + verbose_name_plural = "критерии" class CompetitionTaskAttachment(BaseModel): diff --git a/services/backend/apps/user/models.py b/services/backend/apps/user/models.py index aaa0ec0..598abb6 100644 --- a/services/backend/apps/user/models.py +++ b/services/backend/apps/user/models.py @@ -5,9 +5,9 @@ from apps.achievement.models import Achievement from apps.core.models import BaseModel -class UserRole(models.Choices): - STUDENT = "student" - METODIST = "metodist" +class UserRole(models.TextChoices): + STUDENT = "student", "Участник соревнований" + METODIST = "metodist", "Методист (составитель заданий)" class User(BaseModel): @@ -15,7 +15,7 @@ class User(BaseModel): username = models.SlugField(unique=True, verbose_name="юзернейм") password = models.TextField(verbose_name="пароль") - created_at = models.DateTimeField(auto_now=True) + created_at = models.DateTimeField(auto_now=True, verbose_name="дата создания") achievements = models.ManyToManyField( Achievement, blank=True, verbose_name="ачивки пользователя" @@ -29,7 +29,8 @@ class User(BaseModel): return check_password(self.password, password) status = models.CharField( - max_length=10, choices=UserRole, default="student" + max_length=10, choices=UserRole.choices, default="student", + verbose_name="роль участника" ) def __str__(self) -> str: diff --git a/services/backend/config/settings.py b/services/backend/config/settings.py index 3987361..cb0d18e 100644 --- a/services/backend/config/settings.py +++ b/services/backend/config/settings.py @@ -271,6 +271,8 @@ DEFAULT_CHARSET = "utf-8" FORCE_SCRIPT_NAME = None +X_FRAME_OPTIONS = "SAMEORIGIN" + INTERNAL_IPS = env( "DJANGO_INTERNAL_IPS", list, @@ -438,8 +440,7 @@ INSTALLED_APPS = [ "django_guid", "ninja", "minio_storage", - "tinymce", - "martor", + "mdeditor", # Internal apps "apps.core", "apps.user", @@ -450,65 +451,6 @@ INSTALLED_APPS = [ "apps.achievement", ] -# tinymce -TINYMCE_DEFAULT_CONFIG = { - "theme": "silver", - "height": 500, - "menubar": False, - "plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor," - "searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste," - "code,help,wordcount,markdown", - "toolbar": "undo redo | formatselect | " - "bold italic backcolor | alignleft aligncenter " - "alignright alignjustify | bullist numlist outdent indent | " - "removeformat | help", - "skin": "oxide-dark", - "content_css": "dark", - "textpattern_patterns": [ - {"start": "*", "end": "*", "format": "italic"}, - {"start": "**", "end": "**", "format": "bold"}, - {"start": "#", "format": "h1"}, - {"start": "##", "format": "h2"}, - {"start": "###", "format": "h3"}, - {"start": "####", "format": "h4"}, - {"start": "#####", "format": "h5"}, - {"start": "######", "format": "h6"}, - {"start": "1. ", "cmd": "InsertOrderedList"}, - {"start": "* ", "cmd": "InsertUnorderedList"}, - {"start": "- ", "cmd": "InsertUnorderedList"}, - ], -} - -# martor - -MARTOR_THEME = "bootstrap" - -MARTOR_ENABLE_CONFIGS = { - "emoji": "true", # to enable/disable emoji icons. - "imgur": "true", # to enable/disable imgur/custom uploader. - "mention": "false", # to enable/disable mention - "jquery": "true", # to include/revoke jquery (require for admin default django) - "living": "false", # to enable/disable live updates in preview - "spellcheck": "false", # to enable/disable spellcheck in form textareas - "hljs": "true", # to enable/disable hljs highlighting in preview -} - -MARTOR_TOOLBAR_BUTTONS = [ - "bold", - "italic", - "horizontal", - "heading", - "pre-code", - "blockquote", - "unordered-list", - "ordered-list", - "link", - "emoji", - "direct-mention", - "toggle-maximize", - "help", -] - # GUID DJANGO_GUID = { diff --git a/services/backend/config/urls.py b/services/backend/config/urls.py index 6fe96c2..0fb5044 100644 --- a/services/backend/config/urls.py +++ b/services/backend/config/urls.py @@ -16,6 +16,8 @@ urlpatterns = [ path("tinymce/", include("tinymce.urls")), # martor path("martor/", include("martor.urls")), + # mdeditor + path(r'mdeditor/', include('mdeditor.urls')), # Admin urls path("admin/", admin.site.urls), # API urls diff --git a/services/backend/pyproject.toml b/services/backend/pyproject.toml index d3b2371..23fefd0 100644 --- a/services/backend/pyproject.toml +++ b/services/backend/pyproject.toml @@ -12,14 +12,13 @@ dependencies = [ "django-extensions>=3.2.3", "django-guid>=3.5.0", "django-health-check>=3.18.3", + "django-mdeditor>=0.1.20", "django-minio-storage>=0.5.7", "django-ninja>=1.3.0", "django-pagedown>=2.2.1", "django-stubs-ext>=5.1.3", - "django-tinymce>=4.1.0", "gunicorn>=23.0.0", "httpx>=0.28.1", - "martor>=1.6.45", "pillow>=11.1.0", "psycopg2-binary>=2.9.10", "pydantic>=2.10.5",