0001-wip.patch
chrono/agendas/migrations/0051_agendanotificationssettings.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2020-07-02 14:42 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 |
import django.db.models.deletion |
|
8 | ||
9 | ||
10 |
class Migration(migrations.Migration): |
|
11 | ||
12 |
dependencies = [ |
|
13 |
('auth', '0008_alter_user_username_max_length'), |
|
14 |
('agendas', '0050_event_slug'), |
|
15 |
] |
|
16 | ||
17 |
operations = [ |
|
18 |
migrations.CreateModel( |
|
19 |
name='AgendaNotificationsSettings', |
|
20 |
fields=[ |
|
21 |
( |
|
22 |
'id', |
|
23 |
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), |
|
24 |
), |
|
25 |
( |
|
26 |
'use_email_fields', |
|
27 |
models.BooleanField(default=False, verbose_name='Use email addresses instead of roles'), |
|
28 |
), |
|
29 |
( |
|
30 |
'almost_full_emails', |
|
31 |
django.contrib.postgres.fields.ArrayField( |
|
32 |
base_field=models.EmailField(max_length=254), |
|
33 |
blank=True, |
|
34 |
help_text='Enter a comma separated list of email addresses.', |
|
35 |
null=True, |
|
36 |
size=None, |
|
37 |
verbose_name='Almost full event (90%)', |
|
38 |
), |
|
39 |
), |
|
40 |
( |
|
41 |
'full_emails', |
|
42 |
django.contrib.postgres.fields.ArrayField( |
|
43 |
base_field=models.EmailField(max_length=254), |
|
44 |
blank=True, |
|
45 |
help_text='Enter a comma separated list of email addresses.', |
|
46 |
null=True, |
|
47 |
size=None, |
|
48 |
verbose_name='Full event', |
|
49 |
), |
|
50 |
), |
|
51 |
( |
|
52 |
'cancelled_emails', |
|
53 |
django.contrib.postgres.fields.ArrayField( |
|
54 |
base_field=models.EmailField(max_length=254), |
|
55 |
blank=True, |
|
56 |
help_text='Enter a comma separated list of email addresses.', |
|
57 |
null=True, |
|
58 |
size=None, |
|
59 |
verbose_name='Cancelled event', |
|
60 |
), |
|
61 |
), |
|
62 |
( |
|
63 |
'agenda', |
|
64 |
models.OneToOneField( |
|
65 |
on_delete=django.db.models.deletion.CASCADE, |
|
66 |
related_name='notifications_settings', |
|
67 |
to='agendas.Agenda', |
|
68 |
), |
|
69 |
), |
|
70 |
( |
|
71 |
'almost_full_role', |
|
72 |
models.ForeignKey( |
|
73 |
blank=True, |
|
74 |
default=None, |
|
75 |
null=True, |
|
76 |
on_delete=django.db.models.deletion.SET_NULL, |
|
77 |
related_name='+', |
|
78 |
to='auth.Group', |
|
79 |
verbose_name='Almost full event (90%)', |
|
80 |
), |
|
81 |
), |
|
82 |
( |
|
83 |
'cancelled_role', |
|
84 |
models.ForeignKey( |
|
85 |
blank=True, |
|
86 |
default=None, |
|
87 |
null=True, |
|
88 |
on_delete=django.db.models.deletion.SET_NULL, |
|
89 |
related_name='+', |
|
90 |
to='auth.Group', |
|
91 |
verbose_name='Cancelled event', |
|
92 |
), |
|
93 |
), |
|
94 |
( |
|
95 |
'full_role', |
|
96 |
models.ForeignKey( |
|
97 |
blank=True, |
|
98 |
default=None, |
|
99 |
null=True, |
|
100 |
on_delete=django.db.models.deletion.SET_NULL, |
|
101 |
related_name='+', |
|
102 |
to='auth.Group', |
|
103 |
verbose_name='Full event', |
|
104 |
), |
|
105 |
), |
|
106 |
], |
|
107 |
), |
|
108 |
] |
chrono/agendas/models.py | ||
---|---|---|
29 | 29 |
import django |
30 | 30 |
from django.conf import settings |
31 | 31 |
from django.contrib.auth.models import Group |
32 |
from django.contrib.postgres.fields import ArrayField |
|
32 | 33 |
from django.core.exceptions import FieldDoesNotExist |
33 | 34 |
from django.core.exceptions import ValidationError |
34 | 35 |
from django.core.validators import MaxValueValidator |
... | ... | |
1363 | 1364 |
def as_interval(self): |
1364 | 1365 |
'''Simplify insertion into IntervalSet''' |
1365 | 1366 |
return Interval(self.start_datetime, self.end_datetime) |
1367 | ||
1368 | ||
1369 |
class AgendaNotificationsSettings(models.Model): |
|
1370 |
agenda = models.OneToOneField(Agenda, on_delete=models.CASCADE, related_name='notifications_settings') |
|
1371 |
use_email_fields = models.BooleanField( |
|
1372 |
verbose_name=_('Use email addresses instead of roles'), default=False |
|
1373 |
) |
|
1374 | ||
1375 |
almost_full_role = models.ForeignKey( |
|
1376 |
Group, |
|
1377 |
blank=True, |
|
1378 |
null=True, |
|
1379 |
default=None, |
|
1380 |
related_name='+', |
|
1381 |
verbose_name=_('Almost full event (90%)'), |
|
1382 |
on_delete=models.SET_NULL, |
|
1383 |
) |
|
1384 |
almost_full_emails = ArrayField( |
|
1385 |
models.EmailField(), |
|
1386 |
blank=True, |
|
1387 |
null=True, |
|
1388 |
verbose_name=_('Almost full event (90%)'), |
|
1389 |
help_text=_('Enter a comma separated list of email addresses.'), |
|
1390 |
) |
|
1391 | ||
1392 |
full_role = models.ForeignKey( |
|
1393 |
Group, |
|
1394 |
blank=True, |
|
1395 |
null=True, |
|
1396 |
default=None, |
|
1397 |
related_name='+', |
|
1398 |
verbose_name=_('Full event'), |
|
1399 |
on_delete=models.SET_NULL, |
|
1400 |
) |
|
1401 |
full_emails = ArrayField( |
|
1402 |
models.EmailField(), |
|
1403 |
blank=True, |
|
1404 |
null=True, |
|
1405 |
verbose_name=_('Full event'), |
|
1406 |
help_text=_('Enter a comma separated list of email addresses.'), |
|
1407 |
) |
|
1408 | ||
1409 |
cancelled_role = models.ForeignKey( |
|
1410 |
Group, |
|
1411 |
blank=True, |
|
1412 |
null=True, |
|
1413 |
default=None, |
|
1414 |
related_name='+', |
|
1415 |
verbose_name=_('Cancelled event'), |
|
1416 |
on_delete=models.SET_NULL, |
|
1417 |
) |
|
1418 |
cancelled_emails = ArrayField( |
|
1419 |
models.EmailField(), |
|
1420 |
blank=True, |
|
1421 |
null=True, |
|
1422 |
verbose_name=_('Cancelled event'), |
|
1423 |
help_text=_('Enter a comma separated list of email addresses.'), |
|
1424 |
) |
|
1425 | ||
1426 |
def __iter__(self): |
|
1427 |
'''Yield enabled settings verbose names and values''' |
|
1428 |
if self.use_email_fields: |
|
1429 |
fields = ['almost_full_emails', 'full_emails', 'cancelled_emails'] |
|
1430 |
else: |
|
1431 |
fields = ['almost_full_role', 'full_role', 'cancelled_role'] |
|
1432 |
for field in fields: |
|
1433 |
value = getattr(self, field) |
|
1434 |
if value: |
|
1435 |
yield (self._meta.get_field(field).verbose_name, getattr(self, field)) |
chrono/manager/forms.py | ||
---|---|---|
37 | 37 |
TimePeriodExceptionSource, |
38 | 38 |
VirtualMember, |
39 | 39 |
Resource, |
40 |
AgendaNotificationsSettings, |
|
40 | 41 |
WEEKDAYS_LIST, |
41 | 42 |
) |
42 | 43 | |
... | ... | |
428 | 429 | |
429 | 430 |
class AgendaDuplicateForm(forms.Form): |
430 | 431 |
label = forms.CharField(label=_('New label'), max_length=150, required=False) |
432 | ||
433 | ||
434 |
class AgendaNotificationsForm(forms.ModelForm): |
|
435 |
role_fields = ['almost_full_role', 'full_role', 'cancelled_role'] |
|
436 |
email_fields = ['almost_full_emails', 'full_emails', 'cancelled_emails'] |
|
437 | ||
438 |
class Meta: |
|
439 |
model = AgendaNotificationsSettings |
|
440 |
fields = '__all__' |
|
441 |
widgets = { |
|
442 |
'agenda': forms.HiddenInput(), |
|
443 |
} |
|
444 | ||
445 |
def __init__(self, *args, **kwargs): |
|
446 |
super().__init__(*args, **kwargs) |
|
447 | ||
448 |
for field in self.email_fields: |
|
449 |
self.fields[field].widget.attrs['class'] = 'notification-email-field' |
|
450 |
self.fields[field].widget.attrs['size'] = 80 |
|
451 | ||
452 |
for field in self.role_fields: |
|
453 |
self.fields[field].widget.attrs['class'] = 'notification-role-field' |
|
454 | ||
455 |
# TODO restrict roles to view and edit, and only if email is present |
|
456 |
# or use sort on initial qs so view and edit are top |
chrono/manager/templates/chrono/manager_agenda_notifications_form.html | ||
---|---|---|
1 |
{% extends "chrono/manager_agenda_view.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="">{% trans "Notification settings" %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans "Notification settings" %}</h2> |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block content %} |
|
14 |
<form method="post" enctype="multipart/form-data"> |
|
15 |
{% csrf_token %} |
|
16 |
{{ form.as_p }} |
|
17 |
<div class="buttons"> |
|
18 |
<button class="submit-button">{% trans "Save" %}</button> |
|
19 |
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a> |
|
20 |
</div> |
|
21 |
</form> |
|
22 | ||
23 |
<script> |
|
24 |
function display_email_fields(show){ |
|
25 |
field_to_show = 'input.notification-email-field'; |
|
26 |
field_to_hide = 'select.notification-role-field'; |
|
27 | ||
28 |
if(!show) |
|
29 |
[field_to_show, field_to_hide] = [field_to_hide, field_to_show]; |
|
30 | ||
31 |
$(field_to_show).each(function(index, element){ |
|
32 |
console.log($(this).parent('p')); |
|
33 |
$(this).parent('p').show(); |
|
34 |
}); |
|
35 |
$(field_to_hide).each(function(index, element){ |
|
36 |
$(this).parent('p').hide(); |
|
37 |
}); |
|
38 |
} |
|
39 | ||
40 |
$('input#id_use_email_fields').change(function(){ |
|
41 |
if ($(this).is(':checked')) |
|
42 |
display_email_fields(true); |
|
43 |
else |
|
44 |
display_email_fields(false); |
|
45 |
}); |
|
46 |
$('input#id_use_email_fields').trigger('change'); |
|
47 |
</script> |
|
48 |
{% endblock %} |
chrono/manager/templates/chrono/manager_events_agenda_settings.html | ||
---|---|---|
62 | 62 |
</div> |
63 | 63 |
</div> |
64 | 64 | |
65 |
<div class="section"> |
|
66 |
<h3>{% trans "Notifications" %}</h3> |
|
67 |
<div> |
|
68 |
<ul> |
|
69 |
{% for setting, value in object.notifications_settings %} |
|
70 |
<li> |
|
71 |
{% blocktrans %} |
|
72 |
{{ setting }}: <i>{{ value }}</i> will receive a notification. |
|
73 |
{% endblocktrans %} |
|
74 |
</li> |
|
75 |
{% empty %} |
|
76 |
{% trans "Notifications are disabled for this agenda." %} |
|
77 |
{% endfor %} |
|
78 |
</ul> |
|
79 |
<a rel="popup" href="{% url 'chrono-manager-agenda-notifications-settings' pk=object.id %}">{% trans 'Configure' %}</a> |
|
80 |
</div> |
|
81 |
</div> |
|
82 | ||
65 | 83 |
{% endblock %} |
chrono/manager/urls.py | ||
---|---|---|
59 | 59 |
views.agenda_import_events, |
60 | 60 |
name='chrono-manager-agenda-import-events', |
61 | 61 |
), |
62 |
url( |
|
63 |
r'^agendas/(?P<pk>\d+)/notifications$', |
|
64 |
views.agenda_notifications_settings, |
|
65 |
name='chrono-manager-agenda-notifications-settings', |
|
66 |
), |
|
62 | 67 |
url( |
63 | 68 |
r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/$', |
64 | 69 |
views.event_view, |
chrono/manager/views.py | ||
---|---|---|
59 | 59 |
TimePeriodExceptionSource, |
60 | 60 |
VirtualMember, |
61 | 61 |
Resource, |
62 |
AgendaNotificationsSettings, |
|
62 | 63 |
) |
63 | 64 | |
64 | 65 |
from .forms import ( |
... | ... | |
82 | 83 |
ResourceEditForm, |
83 | 84 |
AgendaResourceForm, |
84 | 85 |
AgendaDuplicateForm, |
86 |
AgendaDuplicateForm, |
|
87 |
AgendaNotificationsForm, |
|
85 | 88 |
) |
86 | 89 |
from .utils import import_site |
87 | 90 | |
... | ... | |
1210 | 1213 |
agenda_import_events = AgendaImportEventsView.as_view() |
1211 | 1214 | |
1212 | 1215 | |
1216 |
class AgendaNotificationsSettingsView(ManagedAgendaMixin, UpdateView): |
|
1217 |
template_name = 'chrono/manager_agenda_notifications_form.html' |
|
1218 |
model = AgendaNotificationsSettings |
|
1219 |
form_class = AgendaNotificationsForm |
|
1220 | ||
1221 |
def get_object(self): |
|
1222 |
try: |
|
1223 |
return self.agenda.notifications_settings |
|
1224 |
except AgendaNotificationsSettings.DoesNotExist: |
|
1225 |
return AgendaNotificationsSettings.objects.create(agenda=self.agenda) |
|
1226 | ||
1227 | ||
1228 |
agenda_notifications_settings = AgendaNotificationsSettingsView.as_view() |
|
1229 | ||
1230 | ||
1213 | 1231 |
class EventDetailView(ViewableAgendaMixin, DetailView): |
1214 | 1232 |
model = Event |
1215 | 1233 |
pk_url_kwarg = 'event_pk' |
1216 |
- |