0003-manager-choose-time-period-weekday-indexes-45159.patch
chrono/agendas/models.py | ||
---|---|---|
29 | 29 |
from dateutil.rrule import DAILY, WEEKLY, rrule, rruleset |
30 | 30 |
from django.conf import settings |
31 | 31 |
from django.contrib.auth.models import Group |
32 |
from django.contrib.humanize.templatetags.humanize import ordinal |
|
32 | 33 |
from django.contrib.postgres.fields import ArrayField, JSONField |
33 | 34 |
from django.core.exceptions import FieldDoesNotExist, ValidationError |
34 | 35 |
from django.core.validators import MaxValueValidator, MinValueValidator |
... | ... | |
1185 | 1186 |
ordering = ['weekday', 'start_time'] |
1186 | 1187 | |
1187 | 1188 |
def __str__(self): |
1188 |
return '%s / %s → %s' % ( |
|
1189 |
force_text(WEEKDAYS[self.weekday]), |
|
1189 |
label = force_text(WEEKDAYS[self.weekday]) |
|
1190 |
if self.weekday_indexes: |
|
1191 |
label = _('%(weekday)s (%(ordinals)s of the month)') % { |
|
1192 |
'weekday': label, |
|
1193 |
'ordinals': ', '.join(ordinal(i) for i in self.weekday_indexes), |
|
1194 |
} |
|
1195 | ||
1196 |
label = '%s / %s → %s' % ( |
|
1197 |
label, |
|
1190 | 1198 |
date_format(self.start_time, 'TIME_FORMAT'), |
1191 | 1199 |
date_format(self.end_time, 'TIME_FORMAT'), |
1192 | 1200 |
) |
1201 |
return mark_safe(label) |
|
1193 | 1202 | |
1194 | 1203 |
def save(self, *args, **kwargs): |
1195 | 1204 |
if self.agenda: |
chrono/manager/forms.py | ||
---|---|---|
35 | 35 |
from django.utils.translation import ugettext_lazy as _ |
36 | 36 | |
37 | 37 |
from chrono.agendas.models import ( |
38 |
WEEK_CHOICES, |
|
38 | 39 |
WEEKDAYS_LIST, |
39 | 40 |
AbsenceReason, |
40 | 41 |
AbsenceReasonGroup, |
... | ... | |
694 | 695 |
) |
695 | 696 | |
696 | 697 | |
697 |
class TimePeriodAddForm(forms.Form): |
|
698 |
weekdays = forms.MultipleChoiceField( |
|
699 |
label=_('Days'), widget=forms.CheckboxSelectMultiple(), choices=WEEKDAYS_LIST |
|
698 |
class TimePeriodFormBase(forms.Form): |
|
699 |
repeat = forms.ChoiceField( |
|
700 |
label=_('Repeat'), |
|
701 |
widget=forms.RadioSelect, |
|
702 |
choices=( |
|
703 |
('every-week', _('Every week')), |
|
704 |
('custom', _('Custom')), |
|
705 |
), |
|
706 |
initial='every-week', |
|
707 |
) |
|
708 |
weekday_indexes = forms.TypedMultipleChoiceField( |
|
709 |
choices=WEEK_CHOICES, |
|
710 |
coerce=int, |
|
711 |
required=False, |
|
712 |
label='', |
|
700 | 713 |
) |
701 |
start_time = forms.TimeField(label=_('Start Time'), widget=widgets.TimeWidget()) |
|
702 |
end_time = forms.TimeField(label=_('End Time'), widget=widgets.TimeWidget()) |
|
703 | 714 | |
704 | 715 |
def clean_end_time(self): |
705 | 716 |
if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']: |
706 | 717 |
raise ValidationError(_('End time must come after start time.')) |
707 | 718 |
return self.cleaned_data['end_time'] |
708 | 719 | |
720 |
def clean_weekday_indexes(self): |
|
721 |
if self.cleaned_data['repeat'] == 'every-week': |
|
722 |
return None |
|
723 |
return self.cleaned_data['weekday_indexes'] |
|
724 | ||
709 | 725 | |
710 |
class TimePeriodForm(forms.ModelForm): |
|
726 |
class TimePeriodAddForm(TimePeriodFormBase): |
|
727 |
field_order = ['weekdays', 'start_time', 'end_time', 'repeat', 'weekday_indexes'] |
|
728 | ||
729 |
weekdays = forms.MultipleChoiceField( |
|
730 |
label=_('Days'), widget=forms.CheckboxSelectMultiple(), choices=WEEKDAYS_LIST |
|
731 |
) |
|
732 |
start_time = forms.TimeField(label=_('Start Time'), widget=widgets.TimeWidget()) |
|
733 |
end_time = forms.TimeField(label=_('End Time'), widget=widgets.TimeWidget()) |
|
734 | ||
735 | ||
736 |
class TimePeriodForm(TimePeriodFormBase, forms.ModelForm): |
|
711 | 737 |
class Meta: |
712 | 738 |
model = TimePeriod |
713 | 739 |
widgets = { |
714 | 740 |
'start_time': widgets.TimeWidget(), |
715 | 741 |
'end_time': widgets.TimeWidget(), |
716 | 742 |
} |
717 |
exclude = ['agenda', 'desk']
|
|
743 |
fields = ['weekday', 'start_time', 'end_time', 'repeat', 'weekday_indexes']
|
|
718 | 744 | |
719 | 745 |
def __init__(self, *args, **kwargs): |
720 | 746 |
super().__init__(*args, **kwargs) |
... | ... | |
722 | 748 |
self.old_start_time = self.instance.start_time |
723 | 749 |
self.old_end_time = self.instance.end_time |
724 | 750 | |
725 |
def clean_end_time(self): |
|
726 |
if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']: |
|
727 |
raise ValidationError(_('End time must come after start time.')) |
|
728 |
return self.cleaned_data['end_time'] |
|
751 |
if self.instance.weekday_indexes: |
|
752 |
self.fields['repeat'].initial = 'custom' |
|
729 | 753 | |
730 | 754 |
def save(self): |
731 | 755 |
super().save() |
chrono/manager/templates/chrono/manager_meetings_agenda_settings.html | ||
---|---|---|
88 | 88 |
</li> |
89 | 89 |
{% endif %} |
90 | 90 |
{% for time_period in desk.timeperiod_set.all %} |
91 |
<li><a rel="popup" href="{% url 'chrono-manager-time-period-edit' pk=time_period.id %}"> |
|
92 |
{{time_period.weekday_str}} / {{time_period.start_time}} → {{time_period.end_time}}</a> |
|
91 |
<li><a rel="popup" href="{% url 'chrono-manager-time-period-edit' pk=time_period.id %}">{{ time_period }}</a> |
|
93 | 92 |
<a rel="popup" class="delete" href="{% url 'chrono-manager-time-period-delete' pk=time_period.id %}">{% trans "remove" %}</a> |
94 | 93 | |
95 | 94 |
</li> |
chrono/manager/templates/chrono/manager_time_period_form.html | ||
---|---|---|
1 | 1 |
{% extends "chrono/manager_agenda_view.html" %} |
2 |
{% load i18n %} |
|
2 |
{% load i18n gadjo %}
|
|
3 | 3 | |
4 | 4 |
{% block extrascripts %} |
5 | 5 |
{{ block.super }} |
... | ... | |
30 | 30 | |
31 | 31 |
<form method="post" enctype="multipart/form-data"> |
32 | 32 |
{% csrf_token %} |
33 |
{{ form.as_p }}
|
|
33 |
{{ form|with_template }}
|
|
34 | 34 |
<div class="buttons"> |
35 | 35 |
<button class="submit-button">{% trans "Save" %}</button> |
36 | 36 |
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a> |
37 | 37 |
</div> |
38 | ||
39 |
<script> |
|
40 |
$('input[type=radio][name=repeat]').change(function() { |
|
41 |
if(!this.checked) |
|
42 |
return; |
|
43 |
if(this.value == 'every-week') { |
|
44 |
$('select#id_weekday_indexes').hide(); |
|
45 |
} else { |
|
46 |
$('select#id_weekday_indexes').show(); |
|
47 |
} |
|
48 |
}).change(); |
|
49 |
</script> |
|
38 | 50 |
</form> |
39 | 51 |
{% endblock %} |
chrono/manager/templates/chrono/manager_virtual_agenda_settings.html | ||
---|---|---|
66 | 66 |
<div> |
67 | 67 |
<ul class="objects-list single-links"> |
68 | 68 |
{% for time_period in agenda.excluded_timeperiods.all %} |
69 |
<li><a rel="popup" href="{% url 'chrono-manager-time-period-edit' pk=time_period.id %}"> |
|
70 |
{{time_period.weekday_str}} / {{time_period.start_time}} → {{time_period.end_time}}</a> |
|
69 |
<li><a rel="popup" href="{% url 'chrono-manager-time-period-edit' pk=time_period.id %}">{{ time_period }}</a> |
|
71 | 70 |
<a rel="popup" class="delete" href="{% url 'chrono-manager-time-period-delete' pk=time_period.id %}">{% trans "remove" %}</a> |
72 | 71 |
</li> |
73 | 72 |
{% endfor %} |
chrono/manager/views.py | ||
---|---|---|
2445 | 2445 |
weekday=weekday, |
2446 | 2446 |
start_time=form.cleaned_data['start_time'], |
2447 | 2447 |
end_time=form.cleaned_data['end_time'], |
2448 |
weekday_indexes=form.cleaned_data['weekday_indexes'], |
|
2448 | 2449 |
) |
2449 | 2450 |
if desk: |
2450 | 2451 |
period.desk = desk |
chrono/settings.py | ||
---|---|---|
52 | 52 |
'django.contrib.sessions', |
53 | 53 |
'django.contrib.messages', |
54 | 54 |
'django.contrib.staticfiles', |
55 |
'django.contrib.humanize', |
|
55 | 56 |
'gadjo', |
56 | 57 |
'chrono.agendas', |
57 | 58 |
'chrono.api', |
tests/manager/test_timeperiod.py | ||
---|---|---|
25 | 25 |
assert TimePeriod.objects.get(desk=desk).start_time.minute == 0 |
26 | 26 |
assert TimePeriod.objects.get(desk=desk).end_time.hour == 17 |
27 | 27 |
assert TimePeriod.objects.get(desk=desk).end_time.minute == 0 |
28 |
assert TimePeriod.objects.get(desk=desk).weekday_indexes is None |
|
28 | 29 |
assert desk2.timeperiod_set.exists() is False |
29 | 30 |
resp = resp.follow() |
30 | 31 | |
... | ... | |
33 | 34 |
resp.form.get('weekdays', index=0).checked = True |
34 | 35 |
resp.form['start_time'] = '10:00' |
35 | 36 |
resp.form['end_time'] = '13:00' |
37 |
resp.form['repeat'] = 'custom' |
|
38 |
resp.form['weekday_indexes'] = [1, 3] |
|
36 | 39 |
resp = resp.form.submit() |
37 | 40 |
resp = resp.follow() |
38 |
assert 'Monday / 10 a.m. → 1 p.m.' in resp.text |
|
41 |
assert 'Monday (1st, 3rd of the month) / 10 a.m. → 1 p.m.' in resp.text
|
|
39 | 42 |
assert 'Wednesday / 10 a.m. → 5 p.m.' in resp.text |
40 | 43 |
assert resp.text.index('Monday') < resp.text.index('Wednesday') |
41 | 44 | |
... | ... | |
134 | 137 |
time_period2.refresh_from_db() |
135 | 138 |
assert time_period2.start_time.hour == 9 |
136 | 139 | |
140 |
resp = resp.click('Monday / 10 a.m. → noon') |
|
141 |
resp.form['repeat'] = 'custom' |
|
142 |
resp.form['weekday_indexes'] = [5] |
|
143 |
resp = resp.form.submit().follow() |
|
144 |
time_period.refresh_from_db() |
|
145 |
assert time_period.weekday_indexes == [5] |
|
146 | ||
147 |
resp = resp.click('Monday \\(5th of the month\\) / 10 a.m. → noon') |
|
148 |
resp.form['repeat'] = 'every-week' |
|
149 |
resp = resp.form.submit().follow() |
|
150 |
time_period.refresh_from_db() |
|
151 |
assert time_period.weekday_indexes is None |
|
152 | ||
137 | 153 |
# edit with inverted start/end |
138 | 154 |
resp2 = resp.click('Monday / 10 a.m. → noon') |
139 | 155 |
resp2.form['start_time'] = '18:00' |
140 |
- |