1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 from __future__
import unicode_literals
20 from datetime
import date
21 from datetime
import timedelta
22 from operator
import methodcaller
, attrgetter
24 from flask_pluginengine
import current_plugin
25 from wtforms
.ext
.dateutil
.fields
import DateField
26 from wtforms
.fields
.core
import BooleanField
, SelectField
27 from wtforms
.fields
.html5
import IntegerField
28 from wtforms
.fields
.simple
import StringField
, HiddenField
29 from wtforms
.validators
import DataRequired
, Length
, NumberRange
, Optional
, Regexp
, ValidationError
31 from indico
.modules
.vc
.models
import VCRoom
, VCRoomStatus
32 from indico
.modules
.vc
.util
import full_block_id
33 from indico
.util
.i18n
import _
34 from indico
.web
.flask
.util
import url_for
35 from indico
.web
.forms
.base
import IndicoForm
, generated_data
36 from indico
.web
.forms
.fields
import PrincipalListField
, IndicoRadioField
, EmailListField
37 from indico
.web
.forms
.validators
import UsedIf
, Exclusive
38 from indico
.web
.forms
.widgets
import JinjaWidget
, SwitchWidget
, TypeaheadWidget
40 ROOM_NAME_RE
= re
.compile(r
'[\w\-]+')
43 class VCRoomField(HiddenField
):
44 widget
= TypeaheadWidget(min_trigger_length
=3)
46 def process_formdata(self
, valuelist
):
47 if valuelist
and valuelist
[0].isdigit():
48 self
.data
= VCRoom
.get(valuelist
[0])
51 return self
.data
.id if self
.data
is not None else None
54 class LinkingWidget(JinjaWidget
):
55 """Renders a composite radio/select field"""
57 def __init__(self
, **context
):
58 super(LinkingWidget
, self
).__init
__('forms/linking_widget.html', **context
)
60 def __call__(self
, field
, **kwargs
):
62 has_error
= {subfield
.data
: (subfield
.data
in form
.conditional_fields
and form
[subfield
.data
].errors
)
63 for subfield
in field
}
64 return super(LinkingWidget
, self
).__call
__(field
, form
=form
, has_error
=has_error
, **kwargs
)
67 class VCPluginSettingsFormBase(IndicoForm
):
68 managers
= PrincipalListField(_('Managers'), groups
=True, serializable
=False,
69 description
=_('Service managers'))
70 acl
= PrincipalListField(_('ACL'), groups
=True, serializable
=False,
71 description
=_('Users and Groups authorised to create videoconference rooms'))
72 notification_emails
= EmailListField(_('Notification email addresses'),
73 description
=_('Notifications about videoconference rooms are sent to '
74 'these email addresses (one per line).'))
77 class VCRoomLinkFormBase(IndicoForm
):
78 conditional_fields
= {'contribution', 'block'}
80 linking
= IndicoRadioField(_("Link to"), [DataRequired()],
81 choices
=[('event', _("Event")),
82 ('contribution', _("Contribution")),
83 ('block', _("Session"))],
84 widget
=LinkingWidget())
85 contribution
= SelectField(_("Contribution"),
86 [UsedIf(lambda form
, field
: form
.linking
.data
== 'contribution'), DataRequired()])
87 block
= SelectField(_("Session block"),
88 [UsedIf(lambda form
, field
: form
.linking
.data
== 'block'), DataRequired()])
90 show
= BooleanField(_('Show room'),
91 widget
=SwitchWidget(),
92 description
=_('Display this room on the event page'))
94 def __init__(self
, *args
, **kwargs
):
95 self
.event
= kwargs
.pop('event')
96 super(VCRoomLinkFormBase
, self
).__init
__(*args
, **kwargs
)
97 contrib_choices
= [(contrib
.id, contrib
.title
) for contrib
in
98 sorted(self
.event
.getContributionList(), key
=attrgetter('title'))]
99 block_choices
= [(full_block_id(block
), block
.getFullTitle()) for block
in
100 sorted(self
.event
.getSessionSlotList(), key
=methodcaller('getFullTitle'))]
101 self
.contribution
.choices
= [('', _("Please select a contribution"))] + contrib_choices
102 self
.block
.choices
= [('', _("Please select a session block"))] + block_choices
103 self
.linking
._form
= self
106 class VCRoomAttachFormBase(VCRoomLinkFormBase
):
108 _("Room to link"), [DataRequired()],
109 description
=_("Please start writing the name of the room you would like to attach. "
110 "Indico will suggest existing rooms."))
112 def __init__(self
, *args
, **kwargs
):
113 super(VCRoomAttachFormBase
, self
).__init
__(*args
, **kwargs
)
114 self
.room
.widget
.search_url
= url_for('.manage_vc_rooms_search', self
.event
, service
=kwargs
.pop('service'))
117 class VCRoomFormBase(VCRoomLinkFormBase
):
118 advanced_fields
= {'show'}
119 skip_fields
= advanced_fields | VCRoomLinkFormBase
.conditional_fields
121 name
= StringField(_('Name'), [DataRequired(), Length(min=3, max=60), Regexp(ROOM_NAME_RE
)],
122 description
=_('The name of the room. It can contain only alphanumerical characters, underscores '
123 'and dashes. No spaces allowed.'))
125 def validate_name(self
, field
):
127 room
= VCRoom
.find_first(VCRoom
.name
== field
.data
, VCRoom
.status
!= VCRoomStatus
.deleted
,
128 VCRoom
.type == self
.service_name
)
129 if room
and room
!= self
.vc_room
:
130 raise ValidationError(_("There is already a room with this name"))
132 def __init__(self
, *args
, **kwargs
):
133 super(VCRoomFormBase
, self
).__init
__(*args
, **kwargs
)
134 self
.vc_room
= kwargs
.pop('vc_room')
135 self
.service_name
= current_plugin
.service_name
138 class VCRoomListFilterForm(IndicoForm
):
139 direction
= SelectField(_('Sort direction'), [DataRequired()],
140 choices
=[('asc', _('Ascending')), ('desc', _('Descending'))])
141 abs_start_date
= DateField(_('Start Date'), [Optional(), Exclusive('rel_start_date')],
142 parse_kwargs
={'dayfirst': True})
143 abs_end_date
= DateField(_('End Date'), [Optional(), Exclusive('rel_end_date')],
144 parse_kwargs
={'dayfirst': True})
145 rel_start_date
= IntegerField(_('Days in the past'), [Optional(), Exclusive('abs_start_date'), NumberRange(min=0)],
147 rel_end_date
= IntegerField(_('Days in the future'), [Optional(), Exclusive('abs_end_date'), NumberRange(min=0)],
151 def start_date(self
):
152 if self
.abs_start_date
.data
is None and self
.rel_start_date
.data
is None:
154 return self
.abs_start_date
.data
or (date
.today() - timedelta(days
=self
.rel_start_date
.data
))
158 if self
.abs_end_date
.data
is None and self
.rel_end_date
.data
is None:
160 return self
.abs_end_date
.data
or (date
.today() + timedelta(days
=self
.rel_end_date
.data
))