Initial commit
This commit is contained in:
1
Enquete/enquete/apps/vragenlijst/__init__.py
Normal file
1
Enquete/enquete/apps/vragenlijst/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
default_app_config = 'apps.vragenlijst.apps.VragenlijstConfig'
|
42
Enquete/enquete/apps/vragenlijst/admin.py
Normal file
42
Enquete/enquete/apps/vragenlijst/admin.py
Normal file
@ -0,0 +1,42 @@
|
||||
from django.contrib import admin
|
||||
from .models import Questionnaire, QuestionnaireTopic, QuestionnaireQuestion, QuestionnaireResponse, QuestionnaireStorage
|
||||
|
||||
# Register your models here.
|
||||
|
||||
|
||||
@admin.register(Questionnaire)
|
||||
class QuestionnaireAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'id',)
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
|
||||
@admin.register(QuestionnaireTopic)
|
||||
class QuestionnaireTopicAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'order', 'questionnaire',)
|
||||
ordering = ('order', )
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
|
||||
@admin.register(QuestionnaireQuestion)
|
||||
class QuestionnaireQuestionAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'order', 'type', 'topic', 'questionnaire')
|
||||
ordering = ('order', )
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
def questionnaire(self, item):
|
||||
return item.topic.questionnaire
|
||||
|
||||
|
||||
@admin.register(QuestionnaireResponse)
|
||||
class QuestionnaireResponseAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'created_at', 'questionnaire')
|
||||
ordering = ('-created_at', )
|
||||
readonly_fields = ('questionnaire','response','created_at', 'updated_at')
|
||||
|
||||
|
||||
@admin.register(QuestionnaireStorage)
|
||||
class QuestionnaireStorageAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'type', 'server')
|
||||
ordering = ('name', )
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
11
Enquete/enquete/apps/vragenlijst/apps.py
Normal file
11
Enquete/enquete/apps/vragenlijst/apps.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class VragenlijstConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.vragenlijst'
|
||||
|
||||
label = 'vragenlijst'
|
||||
verbose_name = _('Questionnaire')
|
||||
verbose_name_plural = _('Questionnaire')
|
67
Enquete/enquete/apps/vragenlijst/migrations/0001_initial.py
Normal file
67
Enquete/enquete/apps/vragenlijst/migrations/0001_initial.py
Normal file
@ -0,0 +1,67 @@
|
||||
# Generated by Django 4.0.2 on 2022-02-03 10:59
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Questionnaire',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Name of the questionnaire.', max_length=200, verbose_name='Name')),
|
||||
('description', models.TextField(blank=True, help_text='Enter a short description for this questionnaire.', null=True, verbose_name='Description')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this questionnaire has been created', verbose_name='Date created')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this questionnaire has been updated', verbose_name='Date updated')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Questionnaire',
|
||||
'verbose_name_plural': 'Questionnaires',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionnaireTopic',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Name of the questionnaire topic.', max_length=200, verbose_name='Name')),
|
||||
('description', models.TextField(blank=True, help_text='Enter a short description for this questionnaire topic.', null=True, verbose_name='Description')),
|
||||
('order', models.PositiveIntegerField(blank=True, verbose_name='Order')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this questionnaire topic has been created', verbose_name='Date created')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this questionnaire topic has been updated', verbose_name='Date updated')),
|
||||
('questionnaire', models.ForeignKey(help_text='The questionnaire topic for this questionnaire.', on_delete=django.db.models.deletion.CASCADE, to='vragenlijst.questionnaire', verbose_name='Questionnaire')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Questionnaire topic',
|
||||
'verbose_name_plural': 'Questionnaire topics',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionnaireQuestion',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Name of the questionnaire topic.', max_length=200, verbose_name='Name')),
|
||||
('type', models.CharField(choices=[('DATE', 'Date field'), ('NUMBER', 'Number field'), ('MULTIPLE', 'Multi options field'), ('SINGLE', 'Single option field'), ('TEXT', 'Single text line')], default='SINGLE', help_text='Question type', max_length=15, verbose_name='Type')),
|
||||
('description', models.TextField(blank=True, help_text='Enter a short description for this questionnaire topic.', null=True, verbose_name='Description')),
|
||||
('order', models.PositiveIntegerField(blank=True, verbose_name='Order')),
|
||||
('choices', models.TextField(blank=True, help_text='Enter a short description for this questionnaire topic.', null=True, verbose_name='Choices')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this questionnaire topic has been created', verbose_name='Date created')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this questionnaire topic has been updated', verbose_name='Date updated')),
|
||||
('topic', models.ForeignKey(help_text='The questionnaire topic for this questionnaire.', on_delete=django.db.models.deletion.CASCADE, to='vragenlijst.questionnairetopic', verbose_name='Questionnaire topic')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Questionnaire question',
|
||||
'verbose_name_plural': 'Questionnaire questions',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.0.2 on 2022-02-03 11:45
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('vragenlijst', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='questionnairetopic',
|
||||
name='questionnaire',
|
||||
field=models.ForeignKey(help_text='The questionnaire topic for this questionnaire.', on_delete=django.db.models.deletion.CASCADE, related_name='topics', to='vragenlijst.questionnaire', verbose_name='Questionnaire'),
|
||||
),
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.0.2 on 2022-02-03 12:21
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('vragenlijst', '0002_alter_questionnairetopic_questionnaire'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='questionnairequestion',
|
||||
name='topic',
|
||||
field=models.ForeignKey(help_text='The questionnaire topic for this questionnaire.', on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='vragenlijst.questionnairetopic', verbose_name='Questionnaire topic'),
|
||||
),
|
||||
]
|
@ -0,0 +1,49 @@
|
||||
# Generated by Django 4.0.2 on 2022-02-08 10:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('vragenlijst', '0003_alter_questionnairequestion_topic'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QuestionnaireStorage',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Name of the questionnaire storage.', max_length=200, verbose_name='Name')),
|
||||
('type', models.CharField(choices=[('WEBDAV', 'WebDAV')], default='WEBDAV', help_text='Storage type', max_length=15, verbose_name='Type')),
|
||||
('server', models.CharField(help_text='Server url', max_length=200, verbose_name='Server')),
|
||||
('username', models.CharField(help_text='Username', max_length=200, verbose_name='Username')),
|
||||
('password', models.CharField(help_text='Password', max_length=200, verbose_name='Password')),
|
||||
('path', models.CharField(help_text='Location on disk', max_length=200, verbose_name='Path')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this questionnaire storage has been created', verbose_name='Date created')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this questionnaire storage has been updated', verbose_name='Date updated')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Questionnaire storage',
|
||||
'verbose_name_plural': 'Questionnaire storages',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionnaireResponse',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('response', models.TextField(help_text='Questionaire response in CSV', verbose_name='Response')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this questionnaire response has been created', verbose_name='Date created')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this questionnaire response has been updated', verbose_name='Date updated')),
|
||||
('questionnaire', models.ForeignKey(help_text='The questionnaire for this response.', on_delete=django.db.models.deletion.CASCADE, to='vragenlijst.questionnaire', verbose_name='Questionnaire')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Questionnaire response',
|
||||
'verbose_name_plural': 'Questionnaire responses',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
134
Enquete/enquete/apps/vragenlijst/models.py
Normal file
134
Enquete/enquete/apps/vragenlijst/models.py
Normal file
@ -0,0 +1,134 @@
|
||||
from django.db import models
|
||||
from django.db.models import Max, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import uuid
|
||||
from encrypted_model_fields.fields import EncryptedCharField
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Questionnaire(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Questionnaire')
|
||||
verbose_name_plural = _('Questionnaires')
|
||||
ordering = ['name']
|
||||
|
||||
id = models.UUIDField(_('ID'), default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
name = models.CharField(_('Name'), max_length=200, help_text=_('Name of the questionnaire.'))
|
||||
description = models.TextField(_('Description'), blank=True, null=True, help_text=_('Enter a short description for this questionnaire.'))
|
||||
|
||||
created_at = models.DateTimeField(_('Date created'), auto_now_add=True, help_text=_('The date and time this questionnaire has been created'))
|
||||
updated_at = models.DateTimeField(_('Date updated'), auto_now=True, help_text=_('The date and time this questionnaire has been updated'))
|
||||
|
||||
|
||||
@property
|
||||
def allQuestions(self):
|
||||
return QuestionnaireQuestion.objects.filter(topic__questionnaire=self.id)
|
||||
|
||||
def __str__(self):
|
||||
"""str: Returns a readable string."""
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class QuestionnaireTopic(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Questionnaire topic')
|
||||
verbose_name_plural = _('Questionnaire topics')
|
||||
ordering = ['name']
|
||||
|
||||
id = models.UUIDField(_('ID'), default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
name = models.CharField(_('Name'), max_length=200, help_text=_('Name of the questionnaire topic.'))
|
||||
questionnaire = models.ForeignKey(Questionnaire, verbose_name=Questionnaire._meta.verbose_name, on_delete=models.CASCADE, help_text=_('The questionnaire topic for this questionnaire.'), related_name='topics')
|
||||
description = models.TextField(_('Description'), blank=True, null=True, help_text=_('Enter a short description for this questionnaire topic.'))
|
||||
|
||||
order = models.PositiveIntegerField(_('Order'), blank=True)
|
||||
|
||||
created_at = models.DateTimeField(_('Date created'), auto_now_add=True, help_text=_('The date and time this questionnaire topic has been created'))
|
||||
updated_at = models.DateTimeField(_('Date updated'), auto_now=True, help_text=_('The date and time this questionnaire topic has been updated'))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.order is None:
|
||||
self.order = QuestionnaireTopic.objects.filter(questionnaire=self.questionnaire).aggregate(neworder=Coalesce(Max('order'), Value(0)))['neworder'] + 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
"""str: Returns a readable string."""
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class QuestionnaireQuestionTypes(models.TextChoices):
|
||||
|
||||
DATE = ('DATE', _('Date field'))
|
||||
NUMBER = ('NUMBER', _('Number field'))
|
||||
MULTIPLE = ('MULTIPLE', _('Multi options field'))
|
||||
SINGLE = ('SINGLE', _('Single option field'))
|
||||
TEXT = ('TEXT', _('Single text line'))
|
||||
|
||||
|
||||
class QuestionnaireQuestion(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Questionnaire question')
|
||||
verbose_name_plural = _('Questionnaire questions')
|
||||
ordering = ['name']
|
||||
|
||||
id = models.UUIDField(_('ID'), default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
name = models.CharField(_('Name'), max_length=200, help_text=_('Name of the questionnaire topic.'))
|
||||
type = models.CharField(_('Type'), max_length=15, choices=QuestionnaireQuestionTypes.choices, default=QuestionnaireQuestionTypes.SINGLE, help_text=_('Question type'))
|
||||
description = models.TextField(_('Description'), blank=True, null=True, help_text=_('Enter a short description for this questionnaire topic.'))
|
||||
order = models.PositiveIntegerField(_('Order'), blank=True,)
|
||||
choices = models.TextField(_('Choices'), blank=True, null=True, help_text=_('Enter the choices 1 per line.<br />Use a format like \'= [Text]\' for an \'anders\' option.<br />Use format \'[Text]=[Value]\' for different value for each choice '))
|
||||
topic = models.ForeignKey(QuestionnaireTopic, verbose_name=QuestionnaireTopic._meta.verbose_name, on_delete=models.CASCADE, help_text=_('The questionnaire topic for this questionnaire.'), related_name='questions')
|
||||
|
||||
created_at = models.DateTimeField(_('Date created'), auto_now_add=True, help_text=_('The date and time this questionnaire topic has been created'))
|
||||
updated_at = models.DateTimeField(_('Date updated'), auto_now=True, help_text=_('The date and time this questionnaire topic has been updated'))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.order is None:
|
||||
self.order = QuestionnaireQuestion.objects.filter(topic__questionnaire=self.topic.questionnaire).aggregate(neworder=Coalesce(Max('order'), Value(0)))['neworder'] + 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def choices_list(self):
|
||||
return [choice.strip() for choice in self.choices.strip("\n").split('\n')]
|
||||
|
||||
def __str__(self):
|
||||
"""str: Returns a readable string."""
|
||||
return f'{self.name}'
|
||||
|
||||
|
||||
class QuestionnaireResponse(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _('Questionnaire response')
|
||||
verbose_name_plural = _('Questionnaire responses')
|
||||
ordering = ['-created_at']
|
||||
|
||||
id = models.UUIDField(_('ID'), default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
questionnaire = models.ForeignKey(Questionnaire, verbose_name=Questionnaire._meta.verbose_name, on_delete=models.CASCADE, help_text=_('The questionnaire for this response.'))
|
||||
response = models.TextField(_('Response'), help_text=_('Questionaire response in CSV'))
|
||||
|
||||
created_at = models.DateTimeField(_('Date created'), auto_now_add=True, help_text=_('The date and time this questionnaire response has been created'))
|
||||
updated_at = models.DateTimeField(_('Date updated'), auto_now=True, help_text=_('The date and time this questionnaire response has been updated'))
|
||||
|
||||
|
||||
class QuestionnaireStorageTypes(models.TextChoices):
|
||||
|
||||
WEBDAV = ('WEBDAV', _('WebDAV'))
|
||||
|
||||
class QuestionnaireStorage(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _('Questionnaire storage')
|
||||
verbose_name_plural = _('Questionnaire storages')
|
||||
ordering = ['name']
|
||||
|
||||
id = models.UUIDField(_('ID'), default=uuid.uuid4, editable=False, unique=True, primary_key=True)
|
||||
name = models.CharField(_('Name'), max_length=200, help_text=_('Name of the questionnaire storage.'))
|
||||
type = models.CharField(_('Type'), max_length=15, choices=QuestionnaireStorageTypes.choices, default=QuestionnaireStorageTypes.WEBDAV, help_text=_('Storage type'))
|
||||
server = models.CharField(_('Server'), max_length=200, help_text=_('Server url'))
|
||||
username = EncryptedCharField(_('Username'), max_length=200, help_text=_('Username'))
|
||||
password = EncryptedCharField(_('Password'), max_length=200, help_text=_('Password'))
|
||||
path = models.CharField(_('Path'), max_length=200, help_text=_('Location on disk'))
|
||||
|
||||
created_at = models.DateTimeField(_('Date created'), auto_now_add=True, help_text=_('The date and time this questionnaire storage has been created'))
|
||||
updated_at = models.DateTimeField(_('Date updated'), auto_now=True, help_text=_('The date and time this questionnaire storage has been updated'))
|
382
Enquete/enquete/apps/vragenlijst/templates/vragenlijst/form.html
Normal file
382
Enquete/enquete/apps/vragenlijst/templates/vragenlijst/form.html
Normal file
@ -0,0 +1,382 @@
|
||||
{% extends 'base.html' %}
|
||||
<!-- Add this for inheritance -->
|
||||
{% load replace %}
|
||||
{% load choice_value %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12">
|
||||
<div class="card px-0 pt-4 pb-0 mt-3 mb-3">
|
||||
<h2 id="heading">Fill out the forms</h2>
|
||||
<p>Fill all form field to go to next step</p>
|
||||
<form id="msform" method="POST">
|
||||
{% csrf_token %}
|
||||
<!-- progressbar -->
|
||||
<ul id="progressbar">
|
||||
{% for topic in questionnaire.topics.all|dictsort:"order" %}
|
||||
<li class="{% if forloop.first %} active {% endif %}"><strong><i class="fas fa-dot-circle"></i>{{ topic.name }}</strong></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<br> <!-- fieldsets -->
|
||||
{% for topic in questionnaire.topics.all|dictsort:"order" %}
|
||||
<fieldset>
|
||||
<div class="form-card">
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<h2 class="fs-title">{{topic.name}}:</h2>
|
||||
<p>{{topic.description}}</p>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<h2 class="steps text-right">Step {{forloop.counter}} - {{ questionnaire.topics.all.count }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
{% for question in topic.questions.all|dictsort:"order" %}
|
||||
<br /><br />
|
||||
<label class="fieldlabels" for="{{question.id}}">{{ question.description.strip }}</label><br />
|
||||
|
||||
{% if question.type == 'SINGLE' %}
|
||||
{% for choice in question.choices_list %}
|
||||
<input type="radio" id="{{question.id}}_{{ forloop.counter }}" name="{{question.id}}" value="{{choice.strip|choice_value}}" required="required">
|
||||
<label for="{{question.id}}_{{forloop.counter}}">{{choice.strip|replace:"/(^=|=.*)/" }}</label>
|
||||
|
||||
{% if choice.strip|slice:"0:1" == "=" %}
|
||||
<input type="text" name="{{question.id}}" id="{{question.id}}_{{ forloop.counter }}_anders" style="display:none" disabled="disabled">
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('input[type="radio"][name="{{question.id}}"]').on('change',(e) => {
|
||||
let choice = $(e.target)
|
||||
let andersTXT = $('input[type="text"][name="{{question.id}}"]')
|
||||
|
||||
if (choice.val().slice(0,1) == '=') {
|
||||
// Show
|
||||
andersTXT.show()
|
||||
andersTXT.prop('required',true)
|
||||
andersTXT.prop('disabled',false)
|
||||
} else {
|
||||
// Hide
|
||||
andersTXT.hide()
|
||||
andersTXT.prop('required',false)
|
||||
andersTXT.prop('disabled',true)
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{%endif%}
|
||||
{% endfor %}
|
||||
{%elif question.type == 'NUMBER' %}
|
||||
<input type="{{question.type|lower}}" id="{{question.id}}" name="{{question.id}}" value="" required="required">
|
||||
{%elif question.type == 'DATE' %}
|
||||
<input type="{{question.type|lower}}" id="{{question.id}}" name="{{question.id}}" value="" required="required">
|
||||
{%elif question.type == 'TEXT' %}
|
||||
<textarea rows="1" id="{{question.id}}" name="{{question.id}}" required="required"></textarea>
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if forloop.counter > 1 %}
|
||||
<input type="button" name="previous" class="previous action-button-previous" value="Previous" />
|
||||
{% endif %}
|
||||
{% if forloop.last %}
|
||||
<input type="submit" name="next" class="next action-button" value="Save" />
|
||||
{%else %}
|
||||
<input type="button" name="next" class="next action-button" value="Next" />
|
||||
|
||||
{%endif%}
|
||||
</fieldset>
|
||||
{% endfor %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#grad1 {
|
||||
background-color: #9C27B0;
|
||||
background-image: linear-gradient(120deg, #FF4081, #81D4FA);
|
||||
}
|
||||
|
||||
#msform {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin-top: 20px
|
||||
}
|
||||
|
||||
#msform fieldset .form-card {
|
||||
background: white;
|
||||
border: 0 none;
|
||||
border-radius: 0px;
|
||||
box-shadow: 0 2px 2px 2px rgba(0, 0, 0, 0.2);
|
||||
padding: 20px 40px 30px 40px;
|
||||
box-sizing: border-box;
|
||||
width: 94%;
|
||||
margin: 0 3% 20px 3%;
|
||||
position: relative
|
||||
}
|
||||
|
||||
#msform fieldset {
|
||||
background: white;
|
||||
border: 0 none;
|
||||
border-radius: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding-bottom: 20px;
|
||||
position: relative
|
||||
}
|
||||
|
||||
#msform fieldset:not(:first-of-type) {
|
||||
display: none
|
||||
}
|
||||
|
||||
#msform fieldset .form-card {
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#msform .action-button {
|
||||
width: 100px;
|
||||
background: skyblue;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
border: 0 none;
|
||||
border-radius: 0px;
|
||||
cursor: pointer;
|
||||
padding: 10px 5px;
|
||||
margin: 10px 5px
|
||||
}
|
||||
|
||||
#msform .action-button:hover,
|
||||
#msform .action-button:focus {
|
||||
box-shadow: 0 0 0 2px white, 0 0 0 3px skyblue
|
||||
}
|
||||
|
||||
#msform .action-button-previous {
|
||||
width: 100px;
|
||||
background: #616161;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
border: 0 none;
|
||||
border-radius: 0px;
|
||||
cursor: pointer;
|
||||
padding: 10px 5px;
|
||||
margin: 10px 5px
|
||||
}
|
||||
|
||||
#msform .action-button-previous:hover,
|
||||
#msform .action-button-previous:focus {
|
||||
box-shadow: 0 0 0 2px white, 0 0 0 3px #616161
|
||||
}
|
||||
|
||||
select.list-dt {
|
||||
border: none;
|
||||
outline: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 2px 5px 3px 5px;
|
||||
margin: 2px
|
||||
}
|
||||
|
||||
select.list-dt:focus {
|
||||
border-bottom: 2px solid skyblue
|
||||
}
|
||||
|
||||
.card {
|
||||
z-index: 0;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.fs-title {
|
||||
font-size: 25px;
|
||||
color: #2C3E50;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
#progressbar {
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
color: lightgrey
|
||||
}
|
||||
|
||||
#progressbar .active {
|
||||
color: #000000
|
||||
}
|
||||
|
||||
#progressbar li {
|
||||
list-style-type: none;
|
||||
font-size: 12px;
|
||||
width: {{menu_width}}%;
|
||||
float: left;
|
||||
position: relative
|
||||
}
|
||||
|
||||
#progressbar li:before {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
line-height: 45px;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
color: #ffffff;
|
||||
background: lightgray;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto 10px auto;
|
||||
padding: 2px
|
||||
}
|
||||
|
||||
#progressbar li:after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: lightgray;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 25px;
|
||||
z-index: -1
|
||||
}
|
||||
|
||||
#progressbar li.active:before,
|
||||
#progressbar li.active:after {
|
||||
background: skyblue
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
position: relative;
|
||||
margin-bottom: 25px
|
||||
}
|
||||
|
||||
.radio {
|
||||
display: inline-block;
|
||||
width: 204;
|
||||
height: 104;
|
||||
border-radius: 0;
|
||||
background: lightblue;
|
||||
box-shadow: 0 2px 2px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
margin: 8px 2px
|
||||
}
|
||||
|
||||
.radio:hover {
|
||||
box-shadow: 2px 2px 2px 2px rgba(0, 0, 0, 0.3)
|
||||
}
|
||||
|
||||
.radio.selected {
|
||||
box-shadow: 1px 1px 2px 2px rgba(0, 0, 0, 0.1)
|
||||
}
|
||||
|
||||
.fit-image {
|
||||
width: 100%;
|
||||
object-fit: cover
|
||||
}
|
||||
|
||||
.error {
|
||||
/* border: solid 1px red; */
|
||||
color:red;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
var current_fs, next_fs, previous_fs; //fieldsets
|
||||
var opacity;
|
||||
var current = 1;
|
||||
var steps = $("fieldset").length;
|
||||
|
||||
setProgressBar(current);
|
||||
|
||||
$(".next").click(function(){
|
||||
|
||||
current_fs = $(this).parent();
|
||||
next_fs = $(this).parent().next();
|
||||
|
||||
let errors = false
|
||||
|
||||
current_fs.find('input,textarea').each((counter, element) => {
|
||||
if (element.name && !element.disabled) {
|
||||
if (!element.checkValidity()) {
|
||||
jQuery('label[for=' + element.name + ']').addClass('error')
|
||||
errors = true;
|
||||
} else {
|
||||
jQuery('label[for=' + element.name + ']').removeClass('error')
|
||||
}
|
||||
}
|
||||
});
|
||||
if (errors) {
|
||||
return
|
||||
}
|
||||
|
||||
//Add Class Active
|
||||
$("#progressbar li").eq($("fieldset").index(next_fs)).addClass("active");
|
||||
|
||||
//show the next fieldset
|
||||
next_fs.show();
|
||||
//hide the current fieldset with style
|
||||
current_fs.animate({opacity: 0}, {
|
||||
step: function(now) {
|
||||
// for making fielset appear animation
|
||||
opacity = 1 - now;
|
||||
|
||||
current_fs.css({
|
||||
'display': 'none',
|
||||
'position': 'relative'
|
||||
});
|
||||
next_fs.css({'opacity': opacity});
|
||||
},
|
||||
duration: 500
|
||||
});
|
||||
setProgressBar(++current);
|
||||
});
|
||||
|
||||
$(".previous").click(function(){
|
||||
|
||||
current_fs = $(this).parent();
|
||||
previous_fs = $(this).parent().prev();
|
||||
|
||||
//Remove class active
|
||||
$("#progressbar li").eq($("fieldset").index(current_fs)).removeClass("active");
|
||||
|
||||
//show the previous fieldset
|
||||
previous_fs.show();
|
||||
|
||||
//hide the current fieldset with style
|
||||
current_fs.animate({opacity: 0}, {
|
||||
step: function(now) {
|
||||
// for making fielset appear animation
|
||||
opacity = 1 - now;
|
||||
|
||||
current_fs.css({
|
||||
'display': 'none',
|
||||
'position': 'relative'
|
||||
});
|
||||
previous_fs.css({'opacity': opacity});
|
||||
},
|
||||
duration: 500
|
||||
});
|
||||
setProgressBar(--current);
|
||||
});
|
||||
|
||||
function setProgressBar(curStep){
|
||||
var percent = parseFloat(100 / steps) * curStep;
|
||||
percent = percent.toFixed();
|
||||
$(".progress-bar")
|
||||
.css("width",percent+"%")
|
||||
}
|
||||
|
||||
// $(".submit").click(function(){
|
||||
// return false;
|
||||
// })
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,39 @@
|
||||
{% extends 'base.html' %}
|
||||
<!-- Add this for inheritance -->
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if messages %}
|
||||
<p>
|
||||
{% for message in messages %}
|
||||
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %}
|
||||
{{ message }} <br />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<p>Select the type of questionnaire you want to use</p>
|
||||
|
||||
|
||||
|
||||
{% if questionnaires %}
|
||||
|
||||
{% for questionnaire in questionnaires %}
|
||||
|
||||
<a href="{% url 'questionnaire' questionnaire.id %}" class="btn btn-primary btn-lg" role="button" aria-pressed="true">{{ questionnaire.name }}</a>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>No questionnaire are available.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,9 @@
|
||||
from django import template
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def choice_value ( string ):
|
||||
if string[0] != '=' and '=' in string:
|
||||
return string.split('=')[1].strip()
|
||||
|
||||
return string.strip()
|
11
Enquete/enquete/apps/vragenlijst/templatetags/replace.py
Normal file
11
Enquete/enquete/apps/vragenlijst/templatetags/replace.py
Normal file
@ -0,0 +1,11 @@
|
||||
import re
|
||||
|
||||
from django import template
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def replace ( string, args ):
|
||||
search = args.split(args[0])[1]
|
||||
replace = args.split(args[0])[2]
|
||||
|
||||
return re.sub( search, replace, string )
|
3
Enquete/enquete/apps/vragenlijst/tests.py
Normal file
3
Enquete/enquete/apps/vragenlijst/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
8
Enquete/enquete/apps/vragenlijst/urls.py
Normal file
8
Enquete/enquete/apps/vragenlijst/urls.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
path('<uuid:questionnaire_id>/', views.questionnaire, name='questionnaire'),
|
||||
]
|
69
Enquete/enquete/apps/vragenlijst/views.py
Normal file
69
Enquete/enquete/apps/vragenlijst/views.py
Normal file
@ -0,0 +1,69 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from .models import Questionnaire, QuestionnaireResponse, QuestionnaireStorage
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
|
||||
import csv
|
||||
from uuid import uuid4
|
||||
from pathlib import Path
|
||||
|
||||
from storage.storage import Storage
|
||||
# Create your views here.
|
||||
|
||||
|
||||
def index(request):
|
||||
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
|
||||
questionnaires = Questionnaire.objects.order_by('name')
|
||||
context = {'questionnaires': questionnaires}
|
||||
return render(request, 'vragenlijst/index.html', context)
|
||||
|
||||
|
||||
def questionnaire(request, questionnaire_id):
|
||||
if request.method == 'POST':
|
||||
|
||||
# As we should have a Django form, we could use te form validator. For now, everyhing is valid ;)
|
||||
questionnaire = Questionnaire.objects.get(pk=questionnaire_id)
|
||||
|
||||
# Get all the questions in one list with a single query
|
||||
allQuestions = {}
|
||||
for question in list(questionnaire.allQuestions.order_by('order').values('id','name')):
|
||||
allQuestions[str(question['id'])] = question['name']
|
||||
|
||||
# Store the response as a ';' seperated CSV file in order of the questionaire questions order
|
||||
try:
|
||||
csv_file = Path(f'{settings.HANZE_TEMP_CSV_STORAGE}/{questionnaire.name}-{uuid4()}.csv')
|
||||
with open(csv_file, 'w') as csvfile:
|
||||
filewriter = csv.writer(csvfile, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
||||
# Add headers
|
||||
filewriter.writerow(['vraag', 'antwoord'])
|
||||
|
||||
for question in allQuestions:
|
||||
if request.POST.get(question):
|
||||
filewriter.writerow([allQuestions[question], request.POST[question]])
|
||||
|
||||
# Store the data also in a database
|
||||
QuestionnaireResponse(questionnaire=questionnaire, response=csv_file.read_text()).save()
|
||||
storageSettings = QuestionnaireStorage.objects.first()
|
||||
# Move data to external storage
|
||||
webdav = Storage(
|
||||
storage_type=storageSettings.type,
|
||||
url=storageSettings.server,
|
||||
username=storageSettings.username,
|
||||
password=storageSettings.password
|
||||
)
|
||||
|
||||
webdav.upload_file(source=csv_file, destination=f'{storageSettings.path}/{csv_file.name}')
|
||||
|
||||
messages.success(request, 'Questionaire is saved to disk.')
|
||||
|
||||
except Exception as ex:
|
||||
messages.error(request, f'Could not save the questionaire. Error: {ex}')
|
||||
|
||||
|
||||
# Redirect to starting point
|
||||
return HttpResponseRedirect('/vragenlijst/')
|
||||
|
||||
questionnaire = Questionnaire.objects.get(pk=questionnaire_id)
|
||||
context = {'questionnaire': questionnaire, 'menu_width': 100 / questionnaire.topics.count()}
|
||||
return render(request, 'vragenlijst/form.html', context)
|
Reference in New Issue
Block a user