Skip to content

Commit

Permalink
event questions: restructure database to use a more relational layout
Browse files Browse the repository at this point in the history
  • Loading branch information
thomersch committed Feb 22, 2020
1 parent 612c401 commit 30e9b67
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -2,7 +2,7 @@

A simple calendar for tracking OpenStreetMap related activities, so we don't have to wrestle the wiki anymore.

This is a Django application, it uses [pipenv](https://docs.pipenv.org/en/latest/) for managing dependencies. See their manual for setup instructions.
This is a Django application, it uses [pipenv](https://pipenv.kennethreitz.org/en/latest/) for managing dependencies. See their manual for setup instructions.

## Principles

Expand Down
6 changes: 0 additions & 6 deletions osmcal/fixtures/demo.yaml
Expand Up @@ -46,7 +46,6 @@
name: "International OSM Day"
start: "2029-05-01 20:00:00"
end: "2029-05-01 22:00:00"
created_by: 1

- model: osmcal.event
pk: 2
Expand All @@ -58,7 +57,6 @@
city: Osaka
country: Japan
location: POINT(135.5023 34.6931)
created_by: 1

- model: osmcal.event
pk: 3
Expand Down Expand Up @@ -99,7 +97,6 @@
- Fahrrad: Parkplätze vor Ort vorhanden
- Auto: Parken kann man vor Ort oder in der Christian-Miesen-Straße.
created_by: 1
- model: osmcal.event
pk: 4
Expand All @@ -113,7 +110,6 @@
street: Alexanderstraße
city: Berlin
country: Germany
created_by: 1

- model: osmcal.event
pk: 5
Expand All @@ -126,15 +122,13 @@
street: Xinyi Rd
city: Taipei City
country: Taiwan
created_by: 1

- model: osmcal.event
pk: 6
fields:
name: Multiday with Times
start: "2029-08-20 17:00:00"
end: "2029-08-22 19:00:00"
created_by: 1

- model: osmcal.eventparticipation
pk: 1
Expand Down
18 changes: 12 additions & 6 deletions osmcal/forms.py
@@ -1,4 +1,5 @@
import json
from typing import Dict, Iterable

from django import forms
from django.contrib.postgres.forms import SimpleArrayField
Expand All @@ -13,29 +14,34 @@ class QuestionForm(forms.ModelForm):
choices = SimpleArrayField(forms.CharField())

def clean_choices(self):
return [{'text': x} for x in self.cleaned_data['choices'][0].splitlines()]
return [x for x in self.cleaned_data['choices'][0].splitlines()]

class Meta:
model = models.ParticipationQuestion
fields = ('question_text', 'answer_type', 'mandatory')


class QuestionnaireForm(forms.Form):
def __init__(self, questions, **kwargs):
self.fields = {}
def __init__(self, questions: Iterable[models.ParticipationQuestion], **kwargs) -> None:
self.fields: Dict[str, forms.Field] = {}
super().__init__(**kwargs)
for question in questions:
if question.answer_type == 'TEXT':
f = forms.CharField(label=question.question_text)
f = forms.CharField(label=question.question_text, required=question.mandatory)
elif question.answer_type == 'BOOL':
f = forms.BooleanField(label=question.question_text)
f = forms.BooleanField(label=question.question_text, required=question.mandatory)
elif question.answer_type == 'CHOI':
f = forms.ChoiceField(label=question.question_text, choices=[(x['text'], x['text']) for x in question.choices])
f = forms.ChoiceField(label=question.question_text, required=question.mandatory, choices=[(x.id, x.text) for x in question.choices.all()])
else:
raise ValueError("invalid answer_type: %s" % (question.answer_type))

self.fields[str(question.id)] = f

def clean(self, *args, **kwargs):
for k, v in self.cleaned_data.items():
self.cleaned_data[int(k)] = self.cleaned_data.pop(k)
return super().clean()


class EventForm(forms.ModelForm):
class Meta:
Expand Down
36 changes: 36 additions & 0 deletions osmcal/migrations/0016_auto_20200221_1643.py
@@ -0,0 +1,36 @@
# Generated by Django 3.0.2 on 2020-02-21 16:43

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('osmcal', '0015_auto_20200215_1726'),
]

operations = [
migrations.RemoveField(
model_name='participationquestion',
name='choices',
),
migrations.CreateModel(
name='ParticipationQuestionChoice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.CharField(max_length=200)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='osmcal.ParticipationQuestion')),
],
),
migrations.CreateModel(
name='ParticipationAnswers',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(max_length=200)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='osmcal.ParticipationQuestion')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
]
17 changes: 17 additions & 0 deletions osmcal/migrations/0017_remove_eventparticipation_answers.py
@@ -0,0 +1,17 @@
# Generated by Django 3.0.2 on 2020-02-21 16:49

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('osmcal', '0016_auto_20200221_1643'),
]

operations = [
migrations.RemoveField(
model_name='eventparticipation',
name='answers',
),
]
27 changes: 27 additions & 0 deletions osmcal/migrations/0018_auto_20200221_1810.py
@@ -0,0 +1,27 @@
# Generated by Django 3.0.2 on 2020-02-21 18:10

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('osmcal', '0017_remove_eventparticipation_answers'),
]

operations = [
migrations.CreateModel(
name='ParticipationAnswer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer', models.CharField(max_length=200)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='osmcal.ParticipationQuestion')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
),
migrations.DeleteModel(
name='ParticipationAnswers',
),
]
17 changes: 17 additions & 0 deletions osmcal/migrations/0019_auto_20200222_1056.py
@@ -0,0 +1,17 @@
# Generated by Django 3.0.2 on 2020-02-22 10:56

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('osmcal', '0018_auto_20200221_1810'),
]

operations = [
migrations.AddConstraint(
model_name='participationanswer',
constraint=models.UniqueConstraint(fields=('question', 'user'), name='unique_question_answer'),
),
]
16 changes: 14 additions & 2 deletions osmcal/models.py
Expand Up @@ -72,14 +72,26 @@ class ParticipationQuestion(models.Model):
answer_type = models.CharField(max_length=4, choices=[(x.name, x.value) for x in AnswerType])
mandatory = models.BooleanField(default=True)

choices = JSONField(blank=True, null=True)

class ParticipationQuestionChoice(models.Model):
question = models.ForeignKey(ParticipationQuestion, related_name='choices', on_delete=models.CASCADE)
text = models.CharField(max_length=200)


class EventParticipation(models.Model):
event = models.ForeignKey('Event', null=True, on_delete=models.SET_NULL, related_name='participation')
user = models.ForeignKey('User', null=True, on_delete=models.SET_NULL)

answers = JSONField(blank=True, null=True)

class ParticipationAnswer(models.Model):
question = models.ForeignKey(ParticipationQuestion, on_delete=models.CASCADE, related_name='answers')
user = models.ForeignKey('User', null=True, on_delete=models.SET_NULL)
answer = models.CharField(max_length=200)

class Meta:
constraints = (
models.UniqueConstraint(fields=('question', 'user'), name='unique_question_answer'),
)


class EventLog(models.Model):
Expand Down
18 changes: 13 additions & 5 deletions osmcal/views.py
Expand Up @@ -22,8 +22,8 @@

from . import forms
from .ical import encode_event, encode_events
from .models import (Event, EventLog, EventParticipation,
ParticipationQuestion, User)
from .models import (Event, EventLog, EventParticipation, ParticipationAnswer,
ParticipationQuestion, ParticipationQuestionChoice, User)


class EventListView(View):
Expand Down Expand Up @@ -141,16 +141,21 @@ def post(self, request, event_id):
})

ep = EventParticipation.objects.create(event=evt, user=request.user)
if answers:
ep.answers = answers
ep.save()
for qid, answer in answers.items():
ParticipationAnswer.objects.update_or_create(
question_id=qid,
user=request.user,
defaults={'answer': answer},
)
ep.save()
return redirect(reverse('event', kwargs={'event_id': event_id}))


class UnjoinEvent(View):
@method_decorator(login_required)
def post(self, request, event_id):
EventParticipation.objects.get(event__id=event_id, user=request.user).delete()
# TODO: Remove ParticipationAnswers
return redirect(reverse('event', kwargs={'event_id': event_id}))


Expand Down Expand Up @@ -197,8 +202,11 @@ def post(self, request, event_id=None):
EventLog.objects.create(created_by=request.user, event=evt, data=form.to_json())

for qd in questions_data:
choices = qd.pop('choices')
pq = ParticipationQuestion.objects.create(**qd)
pq.event = evt
for choice in choices:
ParticipationQuestionChoice.objects.create(text=choice, question=pq)
pq.save()
else:
form.save()
Expand Down

0 comments on commit 30e9b67

Please sign in to comment.