From 46319def76c7df02a037fccfd0c6edc2f139646e Mon Sep 17 00:00:00 2001
From: Dmitry Belan '
+ for row in data:
+ html += f'
'
+ return format_html(html)
+ except Exception:
+ return obj.text_answer or "Нет ответа"
else:
if obj.file_answer:
return format_html('Скачать файл', obj.file_answer.url)
diff --git a/lmsege/lms/forms.py b/lmsege/lms/forms.py
index 97a68b5..bb358db 100644
--- a/lmsege/lms/forms.py
+++ b/lmsege/lms/forms.py
@@ -14,6 +14,16 @@ class AnswersForm(forms.Form):
'placeholder': 'Введите ваш ответ здесь...'
})
)
+ elif q.question_type == 'TABLE_AUTO':
+ rows = getattr(q, 'table_rows', 2) or 2
+ for i in range(1, rows + 1):
+ for j in range(1, 3):
+ self.fields[f'answer_{q.id}_r{i}_c{j}'] = forms.CharField(
+ required=False,
+ widget=forms.TextInput(attrs={
+ 'class': 'w-full px-2 py-1 border rounded focus:outline-none focus:ring-1 focus:ring-blue-500',
+ })
+ )
else:
self.fields[f'file_{q.id}'] = forms.FileField(
label='Прикрепить файл:',
diff --git a/lmsege/lms/migrations/0010_question_table_rows_alter_question_correct_answer_and_more.py b/lmsege/lms/migrations/0010_question_table_rows_alter_question_correct_answer_and_more.py
new file mode 100644
index 0000000..4be638d
--- /dev/null
+++ b/lmsege/lms/migrations/0010_question_table_rows_alter_question_correct_answer_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 5.1.1 on 2026-09-19 09:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('lms', '0009_remove_assignment_deadline'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='question',
+ name='table_rows',
+ field=models.PositiveIntegerField(blank=True, default=2, help_text='Укажите количество строк. Столбцов всегда 2.', null=True, verbose_name='Количество строк в таблице'),
+ ),
+ migrations.AlterField(
+ model_name='question',
+ name='correct_answer',
+ field=models.CharField(blank=True, help_text='Оставьте пустым для ручной проверки. Для таблицы вводите ответы в формате "Ответ1|Ответ2;Ответ3|Ответ4" (строки через точку с запятой, столбцы через прямую черту).', max_length=255, null=True, verbose_name='Правильный ответ (для автопроверки)'),
+ ),
+ migrations.AlterField(
+ model_name='question',
+ name='question_type',
+ field=models.CharField(choices=[('TEXT_AUTO', 'Краткий ответ (автопроверка)'), ('FILE_MANUAL', 'Файл/Развернутый ответ (ручная проверка)'), ('TABLE_AUTO', 'Таблица (2 столбца, автопроверка)')], default='TEXT_AUTO', max_length=20, verbose_name='Тип ответа'),
+ ),
+ ]
diff --git a/lmsege/lms/models.py b/lmsege/lms/models.py
index 748d2eb..afddaf7 100644
--- a/lmsege/lms/models.py
+++ b/lmsege/lms/models.py
@@ -31,12 +31,14 @@ class Question(models.Model):
TYPE_CHOICES = (
('TEXT_AUTO', 'Краткий ответ (автопроверка)'),
('FILE_MANUAL', 'Файл/Развернутый ответ (ручная проверка)'),
+ ('TABLE_AUTO', 'Таблица (2 столбца, автопроверка)'),
)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE, related_name='questions', verbose_name='Задание')
text = RichTextUploadingField('Текст задачи')
attached_file = models.FileField('Файл (опционально)', upload_to='questions/', blank=True, null=True, validators=[validate_file_extension])
question_type = models.CharField('Тип ответа', max_length=20, choices=TYPE_CHOICES, default='TEXT_AUTO')
- correct_answer = models.CharField('Правильный ответ (для автопроверки)', max_length=255, blank=True, null=True, help_text='Оставьте пустым, если проверка ручная')
+ table_rows = models.PositiveIntegerField('Количество строк в таблице', default=2, blank=True, null=True, help_text='Укажите количество строк. Столбцов всегда 2.')
+ correct_answer = models.CharField('Правильный ответ (для автопроверки)', max_length=255, blank=True, null=True, help_text='Оставьте пустым для ручной проверки. Для таблицы вводите ответы в формате "Ответ1|Ответ2;Ответ3|Ответ4" (строки через точку с запятой, столбцы через прямую черту).')
order = models.PositiveIntegerField('Порядок', default=0)
class Meta:
diff --git a/lmsege/lms/static/js/admin_assignment.js b/lmsege/lms/static/js/admin_assignment.js
index 2583be5..ef9bb2e 100644
--- a/lmsege/lms/static/js/admin_assignment.js
+++ b/lmsege/lms/static/js/admin_assignment.js
@@ -2,19 +2,21 @@ document.addEventListener('DOMContentLoaded', function() {
function toggleCorrectAnswer(row) {
const typeSelect = row.querySelector('select[name$="-question_type"]');
const correctAnswerInput = row.querySelector('[name$="-correct_answer"]');
+ const tableRowsInput = row.querySelector('[name$="-table_rows"]');
- if (typeSelect && correctAnswerInput) {
- let wrapper = row.querySelector('.field-correct_answer');
- if (!wrapper) {
- // Find a suitable parent div to hide. Unfold puts fields in divs, often with mb-4 or similar.
- // We go up to the closest field group container.
- wrapper = correctAnswerInput.closest('.form-row') || correctAnswerInput.parentElement.parentElement;
+ if (typeSelect) {
+ if (correctAnswerInput) {
+ let wrapper = row.querySelector('.field-correct_answer');
+ if (!wrapper) wrapper = correctAnswerInput.closest('.form-row') || correctAnswerInput.parentElement.parentElement;
+ if (wrapper) {
+ wrapper.style.display = typeSelect.value === 'FILE_MANUAL' ? 'none' : '';
+ }
}
- if (wrapper) {
- if (typeSelect.value === 'FILE_MANUAL') {
- wrapper.style.display = 'none';
- } else {
- wrapper.style.display = '';
+ if (tableRowsInput) {
+ let wrapper = row.querySelector('.field-table_rows');
+ if (!wrapper) wrapper = tableRowsInput.closest('.form-row') || tableRowsInput.parentElement.parentElement;
+ if (wrapper) {
+ wrapper.style.display = typeSelect.value === 'TABLE_AUTO' ? '' : 'none';
}
}
}
diff --git a/lmsege/lms/templates/lms/assignment_detail.html b/lmsege/lms/templates/lms/assignment_detail.html
index a8f5814..c4991aa 100644
--- a/lmsege/lms/templates/lms/assignment_detail.html
+++ b/lmsege/lms/templates/lms/assignment_detail.html
@@ -51,6 +51,32 @@
Ожидает проверки
{% endif %}
'
+ html += '{row[0]} {row[1]}
Ваш ответ:
+ {% load lms_tags %} + {% with table_data=answer.text_answer|parse_json %} + {% if table_data %} +| {{ row.0 }} | +{{ row.1 }} | +
Нет ответа
+ {% endif %} + {% endwith %} ++ {% if answer.is_correct %} + Верно + {% elif answer.is_correct == False %} + Неверно + {% else %} + Ожидает проверки + {% endif %} +
{% else %} {% if answer.file_answer %}Прикрепленный файл: Скачать
@@ -72,7 +98,7 @@