Commit f71ea9c0 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Extended the events registration to support fields

parent 2aec63a9
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django import forms from activemembers.models import Committee
from django.urls import reverse
from django.contrib import admin from django.contrib import admin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.http import is_safe_url
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.http import is_safe_url
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from utils.translation import TranslatedModelAdmin
from activemembers.models import Committee
from members.models import Member from members.models import Member
from utils.translation import TranslatedModelAdmin
from . import forms
from . import models from . import models
...@@ -32,17 +32,8 @@ class DoNextModelAdmin(TranslatedModelAdmin): ...@@ -32,17 +32,8 @@ class DoNextModelAdmin(TranslatedModelAdmin):
return _do_next(request, res) return _do_next(request, res)
class RegistrationInformationFieldForm(forms.ModelForm):
order = forms.IntegerField(label=_('order'), initial=0)
class Meta:
fields = '__all__'
model = models.RegistrationInformationField
class RegistrationInformationFieldInline(admin.StackedInline): class RegistrationInformationFieldInline(admin.StackedInline):
form = RegistrationInformationFieldForm form = forms.RegistrationInformationFieldForm
extra = 0 extra = 0
model = models.RegistrationInformationField model = models.RegistrationInformationField
ordering = ('_order',) ordering = ('_order',)
......
from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import RegistrationInformationField
class RegistrationInformationFieldForm(forms.ModelForm):
order = forms.IntegerField(label=_('order'), initial=0)
class Meta:
fields = '__all__'
model = RegistrationInformationField
widgets = {
'type': forms.Select,
}
class FieldsForm(forms.Form):
def __init__(self, *args, **kwargs):
registration = kwargs.pop('registration')
super(FieldsForm, self).__init__(*args, **kwargs)
self.information_fields = registration.registration_information()
for information_field in self.information_fields:
field = information_field['field']
key = "info_field_{}".format(field.id)
if field.type == RegistrationInformationField.BOOLEAN_FIELD:
self.fields[key] = forms.BooleanField(
required=False
)
elif field.type == RegistrationInformationField.INTEGER_FIELD:
self.fields[key] = forms.IntegerField()
elif field.type == RegistrationInformationField.TEXT_FIELD:
self.fields[key] = forms.CharField()
self.fields[key].label = field.name
self.fields[key].help_text = field.description
self.fields[key].initial = information_field['value']
if not field.type == RegistrationInformationField.BOOLEAN_FIELD:
self.fields[key].required = field.required
def field_values(self):
for information_field in self.information_fields:
key = "info_field_{}".format(information_field['field'].id)
field = {
"field": information_field["field"],
"value": self.cleaned_data[key]
}
yield field
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-10-09 21:52
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0006_auto_20160921_2201'),
]
operations = [
migrations.AddField(
model_name='registrationinformationfield',
name='required',
field=models.BooleanField(default=True, verbose_name='required'),
preserve_default=False,
),
migrations.AlterField(
model_name='registrationinformationfield',
name='type',
field=models.CharField(choices=[('boolean', 'Checkbox'), ('text', 'Text'), ('integer', 'Integer')], max_length=10, verbose_name='field type'),
),
]
...@@ -2,9 +2,9 @@ from django.core import validators ...@@ -2,9 +2,9 @@ from django.core import validators
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, string_concat from django.utils.translation import ugettext_lazy as _, string_concat
from django.urls import reverse
from utils.translation import MultilingualField, ModelTranslateMeta from utils.translation import MultilingualField, ModelTranslateMeta
...@@ -120,6 +120,7 @@ class Event(models.Model, metaclass=ModelTranslateMeta): ...@@ -120,6 +120,7 @@ class Event(models.Model, metaclass=ModelTranslateMeta):
def reached_participants_limit(self): def reached_participants_limit(self):
return self.max_participants <= self.registration_set.count() return self.max_participants <= self.registration_set.count()
@property
def status(self): def status(self):
now = timezone.now() now = timezone.now()
if bool(self.registration_start) or bool(self.registration_end): if bool(self.registration_start) or bool(self.registration_end):
...@@ -188,9 +189,13 @@ class Event(models.Model, metaclass=ModelTranslateMeta): ...@@ -188,9 +189,13 @@ class Event(models.Model, metaclass=ModelTranslateMeta):
class RegistrationInformationField(models.Model): class RegistrationInformationField(models.Model):
"""Field description to ask for when registering""" """Field description to ask for when registering"""
FIELD_TYPES = (('checkbox', _('checkbox')), BOOLEAN_FIELD = 'boolean'
('charfield', _('text field')), INTEGER_FIELD = 'integer'
('intfield', _('integer field'))) TEXT_FIELD = 'text'
FIELD_TYPES = ((BOOLEAN_FIELD, _('Checkbox')),
(TEXT_FIELD, _('Text')),
(INTEGER_FIELD, _('Integer')),)
event = models.ForeignKey(Event, models.CASCADE) event = models.ForeignKey(Event, models.CASCADE)
...@@ -211,13 +216,18 @@ class RegistrationInformationField(models.Model): ...@@ -211,13 +216,18 @@ class RegistrationInformationField(models.Model):
blank=True, blank=True,
) )
required = models.BooleanField(
_('required'),
)
def get_value_for(self, registration): def get_value_for(self, registration):
if self.type == 'charfield': if self.type == self.TEXT_FIELD:
value_set = self.textregistrationinformation_set value_set = self.textregistrationinformation_set
elif self.type == 'checkbox': elif self.type == self.BOOLEAN_FIELD:
value_set = self.booleanregistrationinformation_set value_set = self.booleanregistrationinformation_set
elif self.type == 'intfield': elif self.type == self.INTEGER_FIELD:
value_set = self.integerregistrationinformation_set value_set = self.integerregistrationinformation_set
try: try:
return value_set.get(registration=registration).value return value_set.get(registration=registration).value
except (TextRegistrationInformation.DoesNotExist, except (TextRegistrationInformation.DoesNotExist,
...@@ -225,6 +235,31 @@ class RegistrationInformationField(models.Model): ...@@ -225,6 +235,31 @@ class RegistrationInformationField(models.Model):
IntegerRegistrationInformation.DoesNotExist): IntegerRegistrationInformation.DoesNotExist):
return None return None
def set_value_for(self, registration, value):
if self.type == self.TEXT_FIELD:
value_set = self.textregistrationinformation_set
elif self.type == self.BOOLEAN_FIELD:
value_set = self.booleanregistrationinformation_set
elif self.type == self.INTEGER_FIELD:
value_set = self.integerregistrationinformation_set
try:
field_value = value_set.get(registration=registration)
except BooleanRegistrationInformation.DoesNotExist:
field_value = BooleanRegistrationInformation()
except TextRegistrationInformation.DoesNotExist:
field_value = TextRegistrationInformation()
except IntegerRegistrationInformation.DoesNotExist:
field_value = IntegerRegistrationInformation()
field_value.registration = registration
field_value.field = self
field_value.value = value
field_value.save()
def __str__(self):
return "{} ({})".format(self.name, dict(self.FIELD_TYPES)[self.type])
class Meta: class Meta:
order_with_respect_to = 'event' order_with_respect_to = 'event'
...@@ -278,8 +313,8 @@ class Registration(models.Model): ...@@ -278,8 +313,8 @@ class Registration(models.Model):
self.event.registration_set.filter( self.event.registration_set.filter(
(Q(date_cancelled__gte=self.date_cancelled) | (Q(date_cancelled__gte=self.date_cancelled) |
Q(date_cancelled=None)) & Q(date_cancelled=None)) &
Q(date_lte=self.date) Q(date__lte=self.date)
) < self.event.max_participants) ).count() < self.event.max_participants)
def is_registered(self): def is_registered(self):
return self.date_cancelled is None return self.date_cancelled is None
......
...@@ -10,10 +10,14 @@ ...@@ -10,10 +10,14 @@
{% endfor %} {% endfor %}
<th>{% trans "present"|capfirst %}</th> <th>{% trans "present"|capfirst %}</th>
<th>{% trans "paid"|capfirst %}</th> <th>{% trans "paid"|capfirst %}</th>
{% if registrations|length > 0 and registrations.0.date_cancelled is not None %}
<th>{% trans "late"|capfirst %}</th>
{% endif %}
<th> <th>
{% if addlink != 0 %} {% if addlink != 0 %}
<a class="addlink" href="{% url 'admin:events_registration_add' %}?event_pk={{ event.pk }}&next={{ request.get_full_path | urlencode }}">{% trans "add"|capfirst %}</a></th> <a class="addlink" href="{% url 'admin:events_registration_add' %}?event_pk={{ event.pk }}&next={{ request.get_full_path | urlencode }}">{% trans "add"|capfirst %}</a>
{% endif %} {% endif %}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -36,6 +40,9 @@ ...@@ -36,6 +40,9 @@
{% endfor %} {% endfor %}
<td>{{ registration.present|yesno }}</td> <td>{{ registration.present|yesno }}</td>
<td>{{ registration.paid|yesno }}</td> <td>{{ registration.paid|yesno }}</td>
{% if registration.date_cancelled is not None %}
<td>{{ registration.is_late_cancellation|yesno }}</td>
{% endif %}
<td><a class="changelink" href="{% url 'admin:events_registration_change' registration.pk %}?next={{ request.get_full_path|urlencode }}">{% trans "change" %}</a></td> <td><a class="changelink" href="{% url 'admin:events_registration_change' registration.pk %}?next={{ request.get_full_path|urlencode }}">{% trans "change" %}</a></td>
</tr> </tr>
{% empty %} {% empty %}
......
...@@ -128,9 +128,15 @@ ...@@ -128,9 +128,15 @@
<td></td> <td></td>
<td> <td>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
{% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %} {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %}
{% if registration is not None and registration.date_cancelled is None and event.has_fields %}
<a class="btn btn-style1" href="{% url 'events:registration' event.id 'update' %}">{% trans "Update registration" %}</a>
<br /><br />
{% endif %}
{% endif %}
{% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY or event.status == event.REGISTRATION_OPEN_NO_CANCEL %}
{% if registration is None or registration.date_cancelled is not None %} {% if registration is None or registration.date_cancelled is not None %}
{% if event.status == event.REGISTRATION_OPEN %} {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %}
<a class="btn btn-style1" href="{% url 'events:registration' event.id 'register' %}"> <a class="btn btn-style1" href="{% url 'events:registration' event.id 'register' %}">
{% if event.reached_participants_limit %} {% if event.reached_participants_limit %}
{% trans "Put me on the waiting list" %} {% trans "Put me on the waiting list" %}
...@@ -144,11 +150,6 @@ ...@@ -144,11 +150,6 @@
<a class="btn btn-style1" href="{% url 'events:registration' event.id 'cancel' %}" onclick="return confirm('{% trans "Are you sure you want to cancel your registration?" %}');">{% trans "Cancel registration" %}</a> <a class="btn btn-style1" href="{% url 'events:registration' event.id 'cancel' %}" onclick="return confirm('{% trans "Are you sure you want to cancel your registration?" %}');">{% trans "Cancel registration" %}</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %}
{% if registration is not None and registration.date_cancelled is None and event.has_fields %}
<a class="btn btn-style1" href="{% url 'events:registration' event.id 'update' %}">{% trans "Update information" %}</a>
{% endif %}
{% endif %}
{% else %} {% else %}
<a class="btn btn-style1" href="{% url 'login' %}?next={{ request.path }}">{% trans "Login" %}</a> <a class="btn btn-style1" href="{% url 'login' %}?next={{ request.path }}">{% trans "Login" %}</a>
{% endif %} {% endif %}
...@@ -163,8 +164,10 @@ ...@@ -163,8 +164,10 @@
{% trans "You have to log in before you can register for this event." %} {% trans "You have to log in before you can register for this event." %}
{% elif event.status == event.REGISTRATION_NOT_YET_OPEN %} {% elif event.status == event.REGISTRATION_NOT_YET_OPEN %}
{% blocktrans with datetime=event.registration_start %}Registration will open {{ datetime }}{% endblocktrans %} {% blocktrans with datetime=event.registration_start %}Registration will open {{ datetime }}{% endblocktrans %}
{% elif event.status == event.REGISTRATION_OPEN_NO_CANCEL %} {% elif event.status == event.REGISTRATION_OPEN_NO_CANCEL and registration.date_cancelled is None %}
{% blocktrans with costs=event.costs %}Cancellation isn't possible anymore without having to pay the full costs of {{ costs }}{% endblocktrans %} {% if event.costs > 0 %}
{% blocktrans with costs=event.costs %}Cancellation isn't possible anymore without having to pay the full costs of {{ costs }}{% endblocktrans %}
{% endif %}
{% elif event.status == event.REGISTRATION_CLOSED or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %} {% elif event.status == event.REGISTRATION_CLOSED or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %}
{% blocktrans %}Registration is not possible anymore.{% endblocktrans %} {% blocktrans %}Registration is not possible anymore.{% endblocktrans %}
{% elif status == event.REGISTRATION_NOT_NEEDED %} {% elif status == event.REGISTRATION_NOT_NEEDED %}
......
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n static %} {% load i18n static fieldtype %}
{% block title %}{{ event.title }} — {% trans "Calendar"|capfirst %} — {{ block.super }}{% endblock %} {% block title %}{% if action == 'update' %}{% trans "Update registration" %}{% else %}{% trans "Complete registration" %}{% endif %} — {{ event.title }} — {% trans "Calendar" %} — {{ block.super }}{% endblock %}
{% block body %} {% block body %}
<h1>{{ event.title }}</h1> {% if action == 'update' %}
<h1>{% blocktrans with title=event.title %}Update registration for {{ title }}{% endblocktrans %}</h1>
{% else %}
<h1>{% blocktrans with title=event.title %}Completing registration for {{ title }}{% endblocktrans %}</h1>
{% endif %}
<form method="post" class="form-horizontal span8 offset2">
{% csrf_token %}
{% for field in form %}
<div class="control-group row {% if field.errors %}error{% endif %}">
<label class="control-label" for="{{ field.name }}">{{ field.label_tag }}</label>
<div class="controls">
{{ field }}
{% for error in field.errors %}
<span class="help-block">{{ error|escape }}</span>
{% endfor %}
<span class="help-block">{{ field.help_text|safe }}</span>
</div>
</div>
{% endfor %}
{% if action == 'update' %}
<input type="submit" value="{% trans "Update registration" %}" class="btn btn-style1 pull-right login" />
{% else %}
<input type="submit" value="{% trans "Complete registration" %}" class="btn btn-style1 pull-right login" />
{% endif %}
<a href="{% url "events:event" event.id %}" class="forgot btn pull-right btn-style2">{% trans 'Cancel' %}</a>
</form>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -11,6 +11,7 @@ from django.utils.text import slugify ...@@ -11,6 +11,7 @@ from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .models import Event, Registration from .models import Event, Registration
from .forms import FieldsForm
@staff_member_required @staff_member_required
...@@ -106,7 +107,8 @@ def event(request, event_id): ...@@ -106,7 +107,8 @@ def event(request, event_id):
Event.objects.filter(published=True), Event.objects.filter(published=True),
pk=event_id pk=event_id
) )
registrations = event.registration_set.filter(date_cancelled=None) registrations = event.registration_set.filter(
date_cancelled=None)[:event.max_participants]
context = { context = {
'event': event, 'event': event,
...@@ -137,73 +139,82 @@ def registration(request, event_id, action=None): ...@@ -137,73 +139,82 @@ def registration(request, event_id, action=None):
pk=event_id pk=event_id
) )
if event.registration_required(): if (event.registration_required() and
event.status != Event.REGISTRATION_NOT_NEEDED):
try: try:
registration = Registration.objects.get( obj = Registration.objects.get(
event=event, event=event,
member=request.user.member member=request.user.member
) )
except Registration.DoesNotExist: except Registration.DoesNotExist:
registration = None obj = None
success_message = None success_message = None
error_message = None error_message = None
show_fields = False show_fields = False
if action == 'register': if action == 'register' and (
event.status == Event.REGISTRATION_OPEN or
event.status == Event.REGISTRATION_OPEN_NO_CANCEL
):
if event.has_fields(): if event.has_fields():
show_fields = True show_fields = True
if registration is None: if obj is None:
registration = Registration() obj = Registration()
registration.event = event obj.event = event
registration.member = request.user.member obj.member = request.user.member
elif registration.date_cancelled is not None: elif obj.date_cancelled is not None:
registration.date = timezone.now() if obj.is_late_cancellation():
registration.date_cancelled = None error_message = _("You cannot re-register anymore since "
"you've cancelled after the deadline.")
else:
obj.date = timezone.now()
obj.date_cancelled = None
else: else:
error_message = _("You were already registered.") error_message = _("You were already registered.")
if error_message is None: if error_message is None:
success_message = _("Registration successful") success_message = _("Registration successful")
elif (action == 'update' and event.has_fields() and elif (action == 'update'
registration is not None): and event.has_fields()
and obj is not None and
event.status == Event.REGISTRATION_OPEN or
event.status == Event.REGISTRATION_OPEN_NO_CANCEL):
show_fields = True show_fields = True
elif action == 'cancel': success_message = _("Registration successfully updated")
if (registration is not None and elif action == 'cancel' and (
registration.date_cancelled is None): event.status == Event.REGISTRATION_OPEN or
registration.date_cancelled = timezone.now() event.status == Event.REGISTRATION_CLOSED_CANCEL_ONLY
):
if (obj is not None and
obj.date_cancelled is None):
# Note that this doesn't remove the values for the
# information fields that the user entered upon registering.
# But this is regarded as a feature, not a bug. Especially
# since the values will still appear in the backend.
obj.date_cancelled = timezone.now()
success_message = _("Registration successfully cancelled") success_message = _("Registration successfully cancelled")
else: else:
error_message = _("You were not registered for this event.") error_message = _("You were not registered for this event.")
if show_fields: if show_fields:
# saved = False if request.POST:
# form = FieldsForm(request.POST, registration=obj)
# if request.POST: if form.is_valid():
# form = AddExamForm(request.POST, request.FILES) form_field_values = form.field_values()
# if form.is_valid(): for field in form_field_values:
# saved = True field['field'].set_value_for(obj,
# obj = form.save(commit=False) field['value'])
# obj.uploader = request.user obj.save()
# obj.uploader_date = datetime.now() else:
# obj.save() form = FieldsForm(registration=obj)
# context = {'event': event, 'form': form, 'action': action}
# form = AddExamForm() return render(request, 'events/event_fields.html', context)
# form.exam_date = datetime.now()
# else: if success_message is not None:
# obj = Exam() messages.success(request, success_message)
# if id is not None: elif error_message is not None:
# obj.course = Course.objects.get(id=id) messages.error(request, error_message)
# form = AddExamForm(instance=obj) obj.save()
# form.exam_date = datetime.now()
context = {'event': event}
return render(request, 'events/event_fields.html', context)
else:
if success_message is not None:
messages.success(request, success_message)
elif error_message is not None:
messages.error(request, error_message)
registration.save()
return redirect(event) return redirect(event)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment