From 878158aa09943b3c2865abc0b734f7fbf7cd6bad Mon Sep 17 00:00:00 2001 From: Alexander-D-Karpov Date: Sat, 25 Feb 2023 00:05:22 +0300 Subject: [PATCH] added test app forms --- akarpov/test_platform/apps.py | 6 ++ akarpov/test_platform/forms.py | 79 +++++++++++++++++++ ...ime_since_selectanswerquestion_question.py | 30 +++++++ akarpov/test_platform/models.py | 21 +++-- akarpov/test_platform/signals.py | 0 akarpov/utils/base.py | 19 +++++ poetry.lock | 8 +- 7 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 akarpov/test_platform/forms.py create mode 100644 akarpov/test_platform/migrations/0002_form_time_since_selectanswerquestion_question.py create mode 100644 akarpov/test_platform/signals.py create mode 100644 akarpov/utils/base.py diff --git a/akarpov/test_platform/apps.py b/akarpov/test_platform/apps.py index 0848549..3e691ec 100644 --- a/akarpov/test_platform/apps.py +++ b/akarpov/test_platform/apps.py @@ -5,3 +5,9 @@ class TestPlatformConfig(AppConfig): name = "akarpov.test_platform" verbose_name = _("Test platform") + + def ready(self): + try: + import akarpov.test_platform.signals # noqa F401 + except ImportError: + pass diff --git a/akarpov/test_platform/forms.py b/akarpov/test_platform/forms.py new file mode 100644 index 0000000..9c67ad1 --- /dev/null +++ b/akarpov/test_platform/forms.py @@ -0,0 +1,79 @@ +from abc import ABC + +from django import forms + +from akarpov.test_platform.models import ( + BaseQuestion, + Form, + NumberQuestion, + NumberRangeQuestion, + SelectAnswerQuestion, + SelectQuestion, + TextQuestion, +) + + +class FormFormClass(forms.ModelForm): + class Meta: + model = Form + fields = ["name", "description", "public", "image", "time_since", "time_till"] + + +class BaseQuestionForm(forms.ModelForm, ABC): + class Meta: + model = BaseQuestion + fields = ["question", "help", "required"] + + +class TextQuestionForm(BaseQuestionForm): + def __init__(self, *args, **kwargs): + super(BaseQuestionForm, self).__init__(*args, **kwargs) + + class Meta(BaseQuestionForm.Meta): + model = TextQuestion + fields = BaseQuestionForm.Meta.fields + [ + "correct_answer", + "answer_should_contain", + "answer_should_not_contain", + ] + + +class NumberQuestionForm(BaseQuestionForm): + def __init__(self, *args, **kwargs): + super(BaseQuestionForm, self).__init__(*args, **kwargs) + + class Meta(BaseQuestionForm.Meta): + model = NumberQuestion + fields = BaseQuestionForm.Meta.fields + [ + "correct_answer", + ] + + +class NumberRangeQuestionForm(BaseQuestionForm): + def __init__(self, *args, **kwargs): + super(BaseQuestionForm, self).__init__(*args, **kwargs) + + class Meta(BaseQuestionForm.Meta): + model = NumberRangeQuestion + fields = BaseQuestionForm.Meta.fields + [ + "number_range_min", + "number_range_max", + ] + + +class SelectQuestionForm(BaseQuestionForm): + def __init__(self, *args, **kwargs): + super(BaseQuestionForm, self).__init__(*args, **kwargs) + + class Meta(BaseQuestionForm.Meta): + model = SelectQuestion + fields = BaseQuestionForm.Meta.fields + [ + "min_required_answers", + "max_required_answers", + ] + + +class SelectAnswerQuestionForm(forms.ModelForm): + class Meta: + model = SelectAnswerQuestion + fields = ["value", "correct"] diff --git a/akarpov/test_platform/migrations/0002_form_time_since_selectanswerquestion_question.py b/akarpov/test_platform/migrations/0002_form_time_since_selectanswerquestion_question.py new file mode 100644 index 0000000..46c1b3f --- /dev/null +++ b/akarpov/test_platform/migrations/0002_form_time_since_selectanswerquestion_question.py @@ -0,0 +1,30 @@ +# Generated by Django 4.1.7 on 2023-02-24 20:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("test_platform", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="form", + name="time_since", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="selectanswerquestion", + name="question", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="answers", + to="test_platform.selectquestion", + ), + preserve_default=False, + ), + ] diff --git a/akarpov/test_platform/models.py b/akarpov/test_platform/models.py index 2858696..ab4e806 100644 --- a/akarpov/test_platform/models.py +++ b/akarpov/test_platform/models.py @@ -6,6 +6,7 @@ from polymorphic.models import PolymorphicModel from akarpov.users.models import User +from akarpov.utils.base import SubclassesMixin from akarpov.utils.files import user_file_upload_mixin @@ -26,6 +27,7 @@ class Form(models.Model): image_cropped = models.ImageField(upload_to="cropped/", blank=True) passed = models.IntegerField(default=0) + time_since = models.DateTimeField(null=True, blank=True) time_till = models.DateTimeField(null=True, blank=True) @property @@ -40,8 +42,9 @@ def __str__(self): return f"form: {self.name}" -class BaseQuestion(PolymorphicModel): - type = _("No type") +class BaseQuestion(PolymorphicModel, SubclassesMixin): + type = "no_type" + type_plural = _("No type") form: Form = models.ForeignKey( "test_platform.Form", related_name="fields", on_delete=models.CASCADE ) @@ -54,24 +57,29 @@ def __str__(self): class TextQuestion(BaseQuestion): - type = _("Text question") + type = "text" + type_plural = _("Text question") correct_answer = models.CharField(max_length=250, blank=False) answer_should_contain = models.CharField(max_length=250, blank=False) answer_should_not_contain = models.CharField(max_length=250, blank=False) class NumberQuestion(BaseQuestion): - type = _("Number question") + type = "number" + type_plural = _("Number question") correct_answer = models.IntegerField() class NumberRangeQuestion(BaseQuestion): - type = _("Number question") + type = "range" + type_plural = _("Number question") number_range_min = models.IntegerField(blank=False) number_range_max = models.IntegerField(blank=False) class SelectQuestion(BaseQuestion): + type = "select" + type_plural = _("Select question") min_required_answers = models.IntegerField(blank=False) max_required_answers = models.IntegerField(blank=False) @@ -80,6 +88,9 @@ class SelectAnswerQuestion(models.Model): id: uuid.UUID = models.UUIDField( primary_key=True, default=uuid.uuid4, editable=False ) + question = models.ForeignKey( + "test_platform.SelectQuestion", related_name="answers", on_delete=models.CASCADE + ) value = models.CharField(max_length=150) correct = models.BooleanField(null=True) diff --git a/akarpov/test_platform/signals.py b/akarpov/test_platform/signals.py new file mode 100644 index 0000000..e69de29 diff --git a/akarpov/utils/base.py b/akarpov/utils/base.py new file mode 100644 index 0000000..26443b0 --- /dev/null +++ b/akarpov/utils/base.py @@ -0,0 +1,19 @@ +from django.contrib.contenttypes.models import ContentType + + +def all_subclasses(cls): + return set(cls.__subclasses__()).union( + [s for c in cls.__subclasses__() for s in all_subclasses(c)] + ) + + +class SubclassesMixin: + @classmethod + def get_subclasses(cls): + content_types = ContentType.objects.filter(app_label=cls._meta.app_label) + models = [ct.model_class() for ct in content_types] + return [ + model + for model in models + if (model is not None and issubclass(model, cls) and model is not cls) + ] diff --git a/poetry.lock b/poetry.lock index 19e7aeb..ed13e35 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1492,14 +1492,14 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "17.0.0" +version = "17.1.0" description = "Faker is a Python package that generates fake data for you." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "Faker-17.0.0-py3-none-any.whl", hash = "sha256:21c3c6c45183308151c14f62afe59bf54ace68f663e0180973698ba2a9a3b2c4"}, - {file = "Faker-17.0.0.tar.gz", hash = "sha256:17cf85aeb0363a3384ccd4c1f52b52ec8f414c7afaab74ae1f4c3e09a06e14de"}, + {file = "Faker-17.1.0-py3-none-any.whl", hash = "sha256:abc383d8e02403fe2aa3b5369283dd468494a450533b513ab6d23637f3f25396"}, + {file = "Faker-17.1.0.tar.gz", hash = "sha256:b94dc47512234fc5fff6f0752aaddd5e6ffea550f6ba31f4865b48d2b35429a1"}, ] [package.dependencies] @@ -2051,6 +2051,7 @@ category = "main" optional = false python-versions = "*" files = [ + {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] @@ -3678,7 +3679,6 @@ category = "main" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, ]