Skip to content

Commit

Permalink
Merge pull request #585 from dzhuang/event_enhance
Browse files Browse the repository at this point in the history
Select/text widget for event forms, non-negative duration and count
  • Loading branch information
dzhuang authored May 25, 2018
2 parents 7fae9c0 + 18be12e commit e2a0a01
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 80 deletions.
186 changes: 132 additions & 54 deletions course/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
ugettext_lazy as _, pgettext_lazy)
from django.contrib.auth.decorators import login_required
from course.utils import course_view, render_course_page
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.db import transaction, IntegrityError
from django.core.exceptions import (
PermissionDenied, ObjectDoesNotExist, ValidationError)
from django.db import transaction
from django.contrib import messages # noqa
import django.forms as forms

Expand All @@ -48,6 +49,25 @@
from course.models import Event


class ListTextWidget(forms.TextInput):
# Widget which allow free text and choices for CharField
def __init__(self, data_list, name, *args, **kwargs):
super(ListTextWidget, self).__init__(*args, **kwargs)
self._name = name
self._list = data_list
self.attrs.update({'list': 'list__%s' % self._name})

def render(self, name, value, attrs=None, renderer=None):
text_html = super(ListTextWidget, self).render(
name, value, attrs=attrs, renderer=renderer)
data_list = '<datalist id="list__%s">' % self._name
for item in self._list:
data_list += '<option value="%s">%s</option>' % (item[0], item[1])
data_list += '</datalist>'

return (text_html + data_list)


# {{{ creation

class RecurringEventForm(StyledForm):
Expand All @@ -60,6 +80,7 @@ class RecurringEventForm(StyledForm):
options={"format": "YYYY-MM-DD HH:mm", "sideBySide": True}),
label=pgettext_lazy("Starting time of event", "Starting time"))
duration_in_minutes = forms.FloatField(required=False,
min_value=0,
label=_("Duration in minutes"))
all_day = forms.BooleanField(
required=False,
Expand All @@ -81,10 +102,19 @@ class RecurringEventForm(StyledForm):
label=pgettext_lazy(
"Starting ordinal of recurring events", "Starting ordinal"))
count = forms.IntegerField(required=True,
min_value=0,
label=pgettext_lazy("Count of recurring events", "Count"))

def __init__(self, *args, **kwargs):
def __init__(self, course_identifier, *args, **kwargs):
super(RecurringEventForm, self).__init__(*args, **kwargs)
self.course_identifier = course_identifier

exist_event_choices = [(choice, choice) for choice in set(
Event.objects.filter(
course__identifier=course_identifier)
.values_list("kind", flat=True))]
self.fields['kind'].widget = ListTextWidget(data_list=exist_event_choices,
name="event_choices")

self.helper.add_input(
Submit("submit", _("Create")))
Expand Down Expand Up @@ -115,10 +145,12 @@ def _create_recurring_events_backend(course, time, kind, starting_ordinal, inter
minutes=duration_in_minutes)
try:
evt.save()
except IntegrityError:
raise EventAlreadyExists(
_("'%(event_kind)s %(event_ordinal)d' already exists") %
{'event_kind': kind, 'event_ordinal': ordinal})
except Exception as e:
if isinstance(e, ValidationError) and "already exists" in str(e):
raise EventAlreadyExists(
_("'%(exist_event)s' already exists")
% {'exist_event': evt})
raise e

date = time.date()
if interval == "weekly":
Expand All @@ -143,9 +175,12 @@ def create_recurring_events(pctx):
raise PermissionDenied(_("may not edit events"))

request = pctx.request
message = None
message_level = None

if request.method == "POST":
form = RecurringEventForm(request.POST, request.FILES)
form = RecurringEventForm(
pctx.course.identifier, request.POST, request.FILES)
if form.is_valid():
if form.cleaned_data["starting_ordinal"] is not None:
starting_ordinal = form.cleaned_data["starting_ordinal"]
Expand All @@ -169,52 +204,79 @@ def create_recurring_events(pctx):
shown_in_calendar=(
form.cleaned_data["shown_in_calendar"])
)
message = _("Events created.")
message_level = messages.SUCCESS
except EventAlreadyExists as e:
if starting_ordinal_specified:
messages.add_message(request, messages.ERROR,
message = (
string_concat(
"%(err_type)s: %(err_str)s. ",
_("No events created."))
% {
"err_type": type(e).__name__,
"err_str": str(e)})
message_level = messages.ERROR
else:
starting_ordinal += 10
continue

except Exception as e:
messages.add_message(request, messages.ERROR,
string_concat(
"%(err_type)s: %(err_str)s. ",
_("No events created."))
% {
"err_type": type(e).__name__,
"err_str": str(e)})
else:
messages.add_message(request, messages.SUCCESS,
_("Events created."))

if isinstance(e, ValidationError):
for field, error in e.error_dict.items():
try:
form.add_error(field, error)
except ValueError:
# This happens when ValidationError were
# raised for fields which don't exist in
# RecurringEventForm
form.add_error(
"__all__", "'%s': %s" % (field, error))
else:
message = (
string_concat(
"%(err_type)s: %(err_str)s. ",
_("No events created."))
% {
"err_type": type(e).__name__,
"err_str": str(e)})
message_level = messages.ERROR
break
else:
form = RecurringEventForm()
form = RecurringEventForm(pctx.course.identifier)

if message and message_level:
messages.add_message(request, message_level, message)
return render_course_page(pctx, "course/generic-course-form.html", {
"form": form,
"form_description": _("Create recurring events"),
})


class RenumberEventsForm(StyledForm):
kind = forms.CharField(required=True,
kind = forms.ChoiceField(required=True,
help_text=_("Should be lower_case_with_underscores, no spaces "
"allowed."),
label=pgettext_lazy("Kind of event", "Kind of event"))
starting_ordinal = forms.IntegerField(required=True, initial=1,
help_text=_("The starting ordinal of this kind of events"),
label=pgettext_lazy(
"Starting ordinal of recurring events", "Starting ordinal"))
preserve_ordinal_order = forms.BooleanField(
required=False,
initial=False,
help_text=_("Tick to preserve the order of ordinals of "
"existing events."),
label=_("Preserve ordinal order"))

def __init__(self, *args, **kwargs):
def __init__(self, course_identifier, *args, **kwargs):
super(RenumberEventsForm, self).__init__(*args, **kwargs)
self.course_identifier = course_identifier

renumberable_event_kinds = set(Event.objects.filter(
course__identifier=self.course_identifier,
ordinal__isnull=False).values_list("kind", flat=True))
self.fields['kind'].choices = tuple(
(kind, kind) for kind in renumberable_event_kinds)

self.helper.add_input(
Submit("submit", _("Renumber")))
Expand All @@ -229,42 +291,58 @@ def renumber_events(pctx):

request = pctx.request

message = None
message_level = None

if request.method == "POST":
form = RenumberEventsForm(request.POST, request.FILES)
form = RenumberEventsForm(
pctx.course.identifier, request.POST, request.FILES)
if form.is_valid():
events = list(Event.objects
.filter(course=pctx.course, kind=form.cleaned_data["kind"])
.order_by('time'))

if events:
queryset = (Event.objects
.filter(course=pctx.course, kind=form.cleaned_data["kind"]))

queryset.delete()

ordinal = form.cleaned_data["starting_ordinal"]
for event in events:
new_event = Event()
new_event.course = pctx.course
new_event.kind = form.cleaned_data["kind"]
new_event.ordinal = ordinal
new_event.time = event.time
new_event.end_time = event.end_time
new_event.all_day = event.all_day
new_event.shown_in_calendar = event.shown_in_calendar
new_event.save()

ordinal += 1

messages.add_message(request, messages.SUCCESS,
_("Events renumbered."))
else:
messages.add_message(request, messages.ERROR,
_("No events found."))
kind = form.cleaned_data["kind"]
order_field = "time"
if form.cleaned_data["preserve_ordinal_order"]:
order_field = "ordinal"
events = list(
Event.objects.filter(
course=pctx.course, kind=kind,

# there might be event with the same kind but no ordinal,
# we don't renumber that
ordinal__isnull=False)
.order_by(order_field))

assert events
queryset = (Event.objects.filter(
course=pctx.course, kind=kind,

# there might be event with the same kind but no ordinal,
# we don't renumber that
ordinal__isnull=False))

queryset.delete()

ordinal = form.cleaned_data["starting_ordinal"]
for event in events:
new_event = Event()
new_event.course = pctx.course
new_event.kind = kind
new_event.ordinal = ordinal
new_event.time = event.time
new_event.end_time = event.end_time
new_event.all_day = event.all_day
new_event.shown_in_calendar = event.shown_in_calendar
new_event.save()

ordinal += 1

message = _("Events renumbered.")
message_level = messages.SUCCESS

else:
form = RenumberEventsForm()
form = RenumberEventsForm(pctx.course.identifier)

if messages and message_level:
messages.add_message(request, message_level, message)
return render_course_page(pctx, "course/generic-course-form.html", {
"form": form,
"form_description": _("Renumber events"),
Expand Down
26 changes: 25 additions & 1 deletion course/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

import six

from django.db import models
from django.db import models, IntegrityError
from django.utils.timezone import now
from django.urls import reverse
from django.core.exceptions import ValidationError, ObjectDoesNotExist
Expand Down Expand Up @@ -321,6 +321,30 @@ def __unicode__(self):
else:
return self.kind

def clean(self):
super(Event, self).clean()

if self.end_time:
if self.end_time < self.time:
raise ValidationError(
{"end_time":
_("End time must not be ahead of start time.")})

def save(self, *args, **kwargs):
self.full_clean()

if self.ordinal is None:
null_ordinal_qset = Event.objects.filter(
kind=self.kind, ordinal__isnull=True)

if self.pk:
null_ordinal_qset = null_ordinal_qset.exclude(id=self.pk)

if null_ordinal_qset.exists():
raise IntegrityError()

super(Event, self).save(*args, **kwargs)

if six.PY3:
__str__ = __unicode__

Expand Down
Loading

0 comments on commit e2a0a01

Please sign in to comment.