mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-22 19:06:41 +03:00
added form generate and process logic
This commit is contained in:
parent
2d8576ff9b
commit
da12a206be
0
akarpov/static/js/form_logic.js
Normal file
0
akarpov/static/js/form_logic.js
Normal file
|
@ -25,15 +25,9 @@
|
||||||
================================================== -->
|
================================================== -->
|
||||||
{# Placed at the top of the document so pages load faster with defer #}
|
{# Placed at the top of the document so pages load faster with defer #}
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<!-- Bootstrap JS -->
|
|
||||||
<script defer src="{% static 'js/bootstrap.bundle.min.js' %}" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
||||||
<!-- Your stuff: Third-party javascript libraries go here -->
|
|
||||||
|
|
||||||
<!-- place project specific Javascript in this file -->
|
|
||||||
<script defer src="{% static 'js/project.js' %}"></script>
|
|
||||||
|
|
||||||
{% endblock javascript %}
|
{% endblock javascript %}
|
||||||
|
<script defer src="{% static 'js/bootstrap.bundle.min.js' %}" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script defer src="{% static 'js/project.js' %}"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
10
akarpov/templates/test_platform/admin.html
Normal file
10
akarpov/templates/test_platform/admin.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -5,22 +5,63 @@
|
||||||
{% block title %}Creating new Form on akarpov{% endblock %}
|
{% block title %}Creating new Form on akarpov{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form class="pt-2" enctype="multipart/form-data" method="POST" id="designer-form">
|
<form class="pt-2 needs-validation" enctype="multipart/form-data" method="POST" id="designer-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{{ field|as_crispy_field }}
|
{{ field|as_crispy_field }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="mt-4 flex justify-end space-x-4">
|
|
||||||
<button class="btn btn-secondary" type="submit" id="submit">
|
|
||||||
Save Changes
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% for question, question_form in questions.items %}
|
|
||||||
{{ question }}
|
|
||||||
{% for field in question_form %}
|
|
||||||
{{ field|as_crispy_field }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</form>
|
</form>
|
||||||
|
<div class="row d-flex justify-content-center">
|
||||||
|
<div class="m-3 col-md-2 col-sm-auto" id="questions">
|
||||||
|
<div id="new_btn" class="input-group mb-3">
|
||||||
|
<button type="button" id="create_question_button" class="btn btn-outline-primary"><i class="bi bi-plus-circle"></i></button>
|
||||||
|
<select class="form-select" id="inputGroupSelect" aria-label="select with addon">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 flex justify-end space-x-4">
|
||||||
|
<input class="btn btn-success" type="submit" form="designer-form" value="Create"/>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block inline_javascript %}
|
||||||
|
<script>
|
||||||
|
let questionTypes = []
|
||||||
|
let questionTypesDescription = []
|
||||||
|
let questions = []
|
||||||
|
let q_index = 1;
|
||||||
|
|
||||||
|
const button = document.getElementById("create_question_button");
|
||||||
|
const form = document.getElementById("designer-form");
|
||||||
|
const select = document.getElementById('inputGroupSelect')
|
||||||
|
|
||||||
|
{% for question, question_form in questions.items %}
|
||||||
|
type = '{{ question.type }}'
|
||||||
|
description = '{{ question.type_plural }}'
|
||||||
|
|
||||||
|
questionTypes.push(type)
|
||||||
|
questionTypesDescription.push(description)
|
||||||
|
select.innerHTML += `<option value="${type}">${description}</option>`
|
||||||
|
|
||||||
|
questionPrototype = `
|
||||||
|
<div class="border p-4 mt-5 rounded-2">
|
||||||
|
<input type="hidden" value="${type}" name="type" />
|
||||||
|
{% for field in question_form %}
|
||||||
|
{{ field|as_crispy_field }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
questions.push(questionPrototype)
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
button.onclick = function () {
|
||||||
|
console.log(q_index)
|
||||||
|
let index = select.selectedIndex;
|
||||||
|
let div = questions[index];
|
||||||
|
form.innerHTML += div;
|
||||||
|
q_index += 1;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -12,8 +12,12 @@
|
||||||
|
|
||||||
|
|
||||||
class FormFormClass(forms.ModelForm):
|
class FormFormClass(forms.ModelForm):
|
||||||
time_since = forms.DateField(widget=forms.TextInput(attrs={"type": "date"}))
|
time_since = forms.DateField(
|
||||||
time_till = forms.DateField(widget=forms.TextInput(attrs={"type": "date"}))
|
widget=forms.TextInput(attrs={"type": "date"}), required=False
|
||||||
|
)
|
||||||
|
time_till = forms.DateField(
|
||||||
|
widget=forms.TextInput(attrs={"type": "date"}), required=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Form
|
model = Form
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 4.1.7 on 2023-02-25 11:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("test_platform", "0002_form_time_since_selectanswerquestion_question"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="numberrangequestion",
|
||||||
|
name="number_range_max",
|
||||||
|
field=models.IntegerField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="numberrangequestion",
|
||||||
|
name="number_range_min",
|
||||||
|
field=models.IntegerField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="selectquestion",
|
||||||
|
name="max_required_answers",
|
||||||
|
field=models.IntegerField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="selectquestion",
|
||||||
|
name="min_required_answers",
|
||||||
|
field=models.IntegerField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="textquestion",
|
||||||
|
name="answer_should_contain",
|
||||||
|
field=models.CharField(blank=True, max_length=250),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="textquestion",
|
||||||
|
name="answer_should_not_contain",
|
||||||
|
field=models.CharField(blank=True, max_length=250),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="textquestion",
|
||||||
|
name="correct_answer",
|
||||||
|
field=models.CharField(blank=True, max_length=250),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
@ -38,6 +39,10 @@ def available(self) -> bool:
|
||||||
else True
|
else True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
# TODO change to admin
|
||||||
|
return reverse("test_platform:create")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"form: {self.name}"
|
return f"form: {self.name}"
|
||||||
|
|
||||||
|
@ -59,29 +64,29 @@ def __str__(self):
|
||||||
class TextQuestion(BaseQuestion):
|
class TextQuestion(BaseQuestion):
|
||||||
type = "text"
|
type = "text"
|
||||||
type_plural = _("Text question")
|
type_plural = _("Text question")
|
||||||
correct_answer = models.CharField(max_length=250, blank=False)
|
correct_answer = models.CharField(max_length=250, blank=True)
|
||||||
answer_should_contain = models.CharField(max_length=250, blank=False)
|
answer_should_contain = models.CharField(max_length=250, blank=True)
|
||||||
answer_should_not_contain = models.CharField(max_length=250, blank=False)
|
answer_should_not_contain = models.CharField(max_length=250, blank=True)
|
||||||
|
|
||||||
|
|
||||||
class NumberQuestion(BaseQuestion):
|
class NumberQuestion(BaseQuestion):
|
||||||
type = "number"
|
type = "number"
|
||||||
type_plural = _("Number question")
|
type_plural = _("Number question")
|
||||||
correct_answer = models.IntegerField()
|
correct_answer = models.IntegerField(blank=True)
|
||||||
|
|
||||||
|
|
||||||
class NumberRangeQuestion(BaseQuestion):
|
class NumberRangeQuestion(BaseQuestion):
|
||||||
type = "range"
|
type = "range"
|
||||||
type_plural = _("Number question")
|
type_plural = _("Number range question")
|
||||||
number_range_min = models.IntegerField(blank=False)
|
number_range_min = models.IntegerField(blank=True)
|
||||||
number_range_max = models.IntegerField(blank=False)
|
number_range_max = models.IntegerField(blank=True)
|
||||||
|
|
||||||
|
|
||||||
class SelectQuestion(BaseQuestion):
|
class SelectQuestion(BaseQuestion):
|
||||||
type = "select"
|
type = "select"
|
||||||
type_plural = _("Select question")
|
type_plural = _("Select question")
|
||||||
min_required_answers = models.IntegerField(blank=False)
|
min_required_answers = models.IntegerField(blank=True)
|
||||||
max_required_answers = models.IntegerField(blank=False)
|
max_required_answers = models.IntegerField(blank=True)
|
||||||
|
|
||||||
|
|
||||||
class SelectAnswerQuestion(models.Model):
|
class SelectAnswerQuestion(models.Model):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from akarpov.test_platform.forms import (
|
from akarpov.test_platform.forms import (
|
||||||
|
BaseQuestionForm,
|
||||||
NumberQuestionForm,
|
NumberQuestionForm,
|
||||||
NumberRangeQuestionForm,
|
NumberRangeQuestionForm,
|
||||||
SelectQuestionForm,
|
SelectQuestionForm,
|
||||||
|
@ -20,9 +21,35 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_question_types():
|
def _get_fields_from_type(type: str):
|
||||||
|
for question in BaseQuestion.get_subclasses()[::-1]:
|
||||||
|
if question.type == type:
|
||||||
|
form = question_forms[question]
|
||||||
|
return form.Meta.fields
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
|
def get_question_types() -> dict[BaseQuestion, BaseQuestionForm]:
|
||||||
res = {}
|
res = {}
|
||||||
questions = BaseQuestion.get_subclasses()
|
questions = BaseQuestion.get_subclasses()[::-1]
|
||||||
for question in questions:
|
for question in questions:
|
||||||
res[question.type_plural] = question_forms[question]
|
res[question] = question_forms[question]
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def parse_form_create(values) -> list[dict[str, str]]:
|
||||||
|
offset: dict[str, int] = {}
|
||||||
|
res: list[dict[str, str]] = []
|
||||||
|
question_amount = len(values.getlist("type"))
|
||||||
|
for i in range(question_amount):
|
||||||
|
type = values.getlist("type")[i]
|
||||||
|
res.append({"type": type})
|
||||||
|
fields = _get_fields_from_type(type)
|
||||||
|
for field in fields:
|
||||||
|
if field in offset:
|
||||||
|
offset[field] += 1
|
||||||
|
else:
|
||||||
|
offset[field] = 0
|
||||||
|
value = values.getlist(field)[offset[field]]
|
||||||
|
res[i][field] = value
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.views.generic import CreateView
|
from django.views.generic import CreateView
|
||||||
|
|
||||||
from akarpov.test_platform.forms import FormFormClass
|
from akarpov.test_platform.forms import FormFormClass
|
||||||
from akarpov.test_platform.models import Form
|
from akarpov.test_platform.models import Form
|
||||||
from akarpov.test_platform.services.forms import get_question_types
|
from akarpov.test_platform.services.forms import get_question_types, parse_form_create
|
||||||
|
|
||||||
|
|
||||||
class FromCreateView(LoginRequiredMixin, CreateView):
|
class FromCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||||
model = Form
|
model = Form
|
||||||
form_class = FormFormClass
|
form_class = FormFormClass
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ def get_context_data(self, **kwargs):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.creator = self.request.user
|
form.instance.creator = self.request.user
|
||||||
|
print(parse_form_create(self.request.POST))
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
|
20
poetry.lock
generated
20
poetry.lock
generated
|
@ -988,14 +988,14 @@ Pillow = ">=9.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-cors-headers"
|
name = "django-cors-headers"
|
||||||
version = "3.13.0"
|
version = "3.14.0"
|
||||||
description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)."
|
description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "django-cors-headers-3.13.0.tar.gz", hash = "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"},
|
{file = "django_cors_headers-3.14.0-py3-none-any.whl", hash = "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e"},
|
||||||
{file = "django_cors_headers-3.13.0-py3-none-any.whl", hash = "sha256:37e42883b5f1f2295df6b4bba96eb2417a14a03270cb24b2a07f021cd4487cf4"},
|
{file = "django_cors_headers-3.14.0.tar.gz", hash = "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1492,14 +1492,14 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "faker"
|
name = "faker"
|
||||||
version = "17.1.0"
|
version = "17.3.0"
|
||||||
description = "Faker is a Python package that generates fake data for you."
|
description = "Faker is a Python package that generates fake data for you."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "Faker-17.1.0-py3-none-any.whl", hash = "sha256:abc383d8e02403fe2aa3b5369283dd468494a450533b513ab6d23637f3f25396"},
|
{file = "Faker-17.3.0-py3-none-any.whl", hash = "sha256:1dfffa43b4492b899a839619f2056060b585ba0bc76f329525194ca04dde0988"},
|
||||||
{file = "Faker-17.1.0.tar.gz", hash = "sha256:b94dc47512234fc5fff6f0752aaddd5e6ffea550f6ba31f4865b48d2b35429a1"},
|
{file = "Faker-17.3.0.tar.gz", hash = "sha256:26b2864a5332094f2c7f3968deebabce69be39ed5db4dbf22b4fa0ba3d1acdae"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -2051,7 +2051,6 @@ category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"},
|
|
||||||
{file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
|
{file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3679,6 +3678,7 @@ category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
|
{file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
|
||||||
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
|
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3703,14 +3703,14 @@ watchdog = ["watchdog"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whitenoise"
|
name = "whitenoise"
|
||||||
version = "6.3.0"
|
version = "6.4.0"
|
||||||
description = "Radically simplified static file serving for WSGI applications"
|
description = "Radically simplified static file serving for WSGI applications"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "whitenoise-6.3.0-py3-none-any.whl", hash = "sha256:cf8ecf56d86ba1c734fdb5ef6127312e39e92ad5947fef9033dc9e43ba2777d9"},
|
{file = "whitenoise-6.4.0-py3-none-any.whl", hash = "sha256:599dc6ca57e48929dfeffb2e8e187879bfe2aed0d49ca419577005b7f2cc930b"},
|
||||||
{file = "whitenoise-6.3.0.tar.gz", hash = "sha256:fe0af31504ab08faa1ec7fc02845432096e40cc1b27e6a7747263d7b30fb51fa"},
|
{file = "whitenoise-6.4.0.tar.gz", hash = "sha256:a02d6660ad161ff17e3042653c8e3f5ecbb2a2481a006bde125b9efb9a30113a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user