0003-manager-choose-time-period-weekday-indexes-45159.patch
chrono/agendas/models.py | ||
---|---|---|
30 | 30 |
from dateutil.rrule import DAILY, WEEKLY, rrule, rruleset |
31 | 31 |
from django.conf import settings |
32 | 32 |
from django.contrib.auth.models import Group |
33 |
from django.contrib.humanize.templatetags.humanize import ordinal |
|
33 | 34 |
from django.contrib.postgres.fields import ArrayField, JSONField |
34 | 35 |
from django.core.exceptions import FieldDoesNotExist, ValidationError |
35 | 36 |
from django.core.validators import MaxValueValidator, MinValueValidator |
... | ... | |
1218 | 1219 |
ordering = ['weekday', 'start_time'] |
1219 | 1220 | |
1220 | 1221 |
def __str__(self): |
1221 |
return '%s / %s → %s' % ( |
|
1222 |
force_text(WEEKDAYS[self.weekday]), |
|
1222 |
label = force_text(WEEKDAYS[self.weekday]) |
|
1223 |
if self.weekday_indexes: |
|
1224 |
label = _('%(weekday)s (%(ordinals)s of the month)') % { |
|
1225 |
'weekday': label, |
|
1226 |
'ordinals': ', '.join(ordinal(i) for i in self.weekday_indexes), |
|
1227 |
} |
|
1228 | ||
1229 |
label = '%s / %s → %s' % ( |
|
1230 |
label, |
|
1223 | 1231 |
date_format(self.start_time, 'TIME_FORMAT'), |
1224 | 1232 |
date_format(self.end_time, 'TIME_FORMAT'), |
1225 | 1233 |
) |
1234 |
return mark_safe(label) |
|
1226 | 1235 | |
1227 | 1236 |
def save(self, *args, **kwargs): |
1228 | 1237 |
if self.agenda: |
chrono/manager/forms.py | ||
---|---|---|
36 | 36 |
from django.utils.translation import ugettext_lazy as _ |
37 | 37 | |
38 | 38 |
from chrono.agendas.models import ( |
39 |
WEEK_CHOICES, |
|
39 | 40 |
WEEKDAY_CHOICES, |
40 | 41 |
WEEKDAYS_LIST, |
41 | 42 |
AbsenceReason, |
... | ... | |
699 | 700 |
) |
700 | 701 | |
701 | 702 | |
702 |
class TimePeriodAddForm(forms.Form): |
|
703 |
class TimePeriodFormBase(forms.Form): |
|
704 |
repeat = forms.ChoiceField( |
|
705 |
label=_('Repeat'), |
|
706 |
widget=forms.RadioSelect, |
|
707 |
choices=( |
|
708 |
('every-week', _('Every week')), |
|
709 |
('custom', _('Custom')), |
|
710 |
), |
|
711 |
initial='every-week', |
|
712 |
) |
|
713 |
weekday_indexes = forms.TypedMultipleChoiceField( |
|
714 |
choices=WEEK_CHOICES, |
|
715 |
coerce=int, |
|
716 |
required=False, |
|
717 |
label='', |
|
718 |
) |
|
719 | ||
720 |
def clean(self): |
|
721 |
cleaned_data = super().clean() |
|
722 | ||
723 |
if cleaned_data['end_time'] <= cleaned_data['start_time']: |
|
724 |
raise ValidationError(_('End time must come after start time.')) |
|
725 | ||
726 |
if cleaned_data['repeat'] == 'every-week': |
|
727 |
cleaned_data['weekday_indexes'] = None |
|
728 | ||
729 |
return cleaned_data |
|
730 | ||
731 | ||
732 |
class TimePeriodAddForm(TimePeriodFormBase): |
|
733 |
field_order = ['weekdays', 'start_time', 'end_time', 'repeat', 'weekday_indexes'] |
|
734 | ||
703 | 735 |
weekdays = forms.MultipleChoiceField( |
704 | 736 |
label=_('Days'), widget=forms.CheckboxSelectMultiple(), choices=WEEKDAYS_LIST |
705 | 737 |
) |
706 | 738 |
start_time = forms.TimeField(label=_('Start Time'), widget=widgets.TimeWidget()) |
707 | 739 |
end_time = forms.TimeField(label=_('End Time'), widget=widgets.TimeWidget()) |
708 | 740 | |
709 |
def clean_end_time(self): |
|
710 |
if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']: |
|
711 |
raise ValidationError(_('End time must come after start time.')) |
|
712 |
return self.cleaned_data['end_time'] |
|
713 | 741 | |
714 | ||
715 |
class TimePeriodForm(forms.ModelForm): |
|
742 |
class TimePeriodForm(TimePeriodFormBase, forms.ModelForm): |
|
716 | 743 |
class Meta: |
717 | 744 |
model = TimePeriod |
718 | 745 |
widgets = { |
719 | 746 |
'start_time': widgets.TimeWidget(), |
720 | 747 |
'end_time': widgets.TimeWidget(), |
721 | 748 |
} |
722 |
exclude = ['agenda', 'desk']
|
|
749 |
fields = ['weekday', 'start_time', 'end_time', 'repeat', 'weekday_indexes']
|
|
723 | 750 | |
724 | 751 |
def __init__(self, *args, **kwargs): |
725 | 752 |
super().__init__(*args, **kwargs) |
... | ... | |
727 | 754 |
self.old_start_time = self.instance.start_time |
728 | 755 |
self.old_end_time = self.instance.end_time |
729 | 756 | |
730 |
def clean_end_time(self): |
|
731 |
if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']: |
|
732 |
raise ValidationError(_('End time must come after start time.')) |
|
733 |
return self.cleaned_data['end_time'] |
|
757 |
if self.instance.weekday_indexes: |
|
758 |
self.fields['repeat'].initial = 'custom' |
|
734 | 759 | |
735 | 760 |
def save(self): |
736 | 761 |
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 | ||
---|---|---|
2494 | 2494 |
weekday=weekday, |
2495 | 2495 |
start_time=form.cleaned_data['start_time'], |
2496 | 2496 |
end_time=form.cleaned_data['end_time'], |
2497 |
weekday_indexes=form.cleaned_data['weekday_indexes'], |
|
2497 | 2498 |
) |
2498 | 2499 |
if desk: |
2499 | 2500 |
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 |
- |