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
19 from hashlib
import sha1
21 from flask
import render_template
23 from indico
.core
.models
.settings
import EventSettingsProxy
24 from indico
.modules
.events
.agreements
.models
.agreements
import Agreement
25 from indico
.util
.caching
import make_hashable
26 from indico
.util
.decorators
import cached_classproperty
, classproperty
27 from indico
.util
.string
import return_ascii
28 from indico
.web
.flask
.templating
import get_overridable_template_name
, get_template_module
29 from MaKaC
.accessControl
import AccessWrapper
32 class AgreementPersonInfo(object):
33 def __init__(self
, name
=None, email
=None, user
=None, data
=None):
36 name
= user
.getStraightFullName()
38 email
= user
.getEmail()
40 raise ValueError('name is missing')
42 raise ValueError('email is missing')
50 return '<AgreementPersonInfo({}, {}, {})>'.format(self
.name
, self
.email
, self
.identifier
)
56 data_string
= '-'.join('{}={}'.format(k
, make_hashable(v
)) for k
, v
in sorted(self
.data
.viewitems()))
57 identifier
= '{}:{}'.format(self
.email
, data_string
or None)
58 return sha1(identifier
).hexdigest()
61 class EmailPlaceholderBase(object):
62 """Base class for agreement email placeholders"""
67 def render(cls
, agreement
):
68 """Converts the placeholder to a string
70 :param agreement: the `Agreement` object the email is being sent for
72 raise NotImplementedError
75 class AgreementDefinitionBase(object):
76 """Base class for agreement definitions"""
78 #: unique name of the agreement definition
80 #: readable name of the agreement definition
82 #: optional and short description of the agreement definition
84 #: url to obtain the paper version of the agreement form
86 #: template of the agreement form - agreement definition name by default
87 form_template_name
= None
88 #: template of the email body - emails/agreement_default_body.html by default
89 email_body_template_name
= None
90 #: dict containing custom email placeholders
91 email_placeholders
= {}
92 #: plugin containing this agreement definition - assigned automatically
95 #: default settings for an event
96 default_event_settings
= {'manager_notifications_enabled': False}
101 return {'definition': cls
.name
}
103 @cached_classproperty
105 def event_settings(cls
):
106 return EventSettingsProxy('agreement_{}'.format(cls
.name
), cls
.default_event_settings
)
109 def can_access_api(cls
, user
, event
):
110 """Checks if a user can list the agreements for an event"""
111 return event
.canModify(AccessWrapper(user
))
114 def extend_api_data(cls
, event
, person
, agreement
, data
): # pragma: no cover
115 """Extends the data returned in the HTTP API
117 :param event: the event
118 :param person: the :class:`AgreementPersonInfo`
119 :param agreement: the :class:`Agreement` if available
120 :param data: a dict containing the default data for the agreement
125 def get_email_body_template(cls
, event
, **kwargs
):
126 """Returns the template of the email body for this agreement definition"""
127 template_name
= cls
.email_body_template_name
or 'emails/agreement_default_body.html'
128 template_path
= get_overridable_template_name(template_name
, cls
.plugin
, 'events/agreements/')
129 return get_template_module(template_path
, event
=event
)
132 def get_email_placeholders(cls
):
133 """Returns all available email placeholders"""
134 from indico
.modules
.events
.agreements
.placeholders
import PersonNamePlaceholder
, AgreementLinkPlaceholder
135 placeholders
= {'person_name': PersonNamePlaceholder
,
136 'agreement_link': AgreementLinkPlaceholder
}
137 placeholders
.update(cls
.email_placeholders
or {})
141 def get_people(cls
, event
):
142 """Returns a dictionary of :class:`AgreementPersonInfo` required to sign agreements"""
143 people
= cls
.iter_people(event
)
146 return {p
.identifier
: p
for p
in people
}
149 def get_people_not_notified(cls
, event
):
150 """Returns a dictionary of :class:`AgreementPersonInfo` yet to be notified"""
151 people
= cls
.get_people(event
)
152 sent_agreements
= {a
.identifier
for a
in Agreement
.find(event_id
=event
.getId(), type=cls
.name
)}
153 return {k
: v
for k
, v
in people
.items() if v
.identifier
not in sent_agreements
}
156 def get_stats_for_signed_agreements(cls
, event
):
157 """Returns a digest of signed agreements on an event
159 :param event: the event
160 :return: (everybody_signed, num_accepted, num_rejected)
162 people
= cls
.get_people(event
)
163 identifiers
= [p
.identifier
for p
in people
.itervalues()]
164 query
= Agreement
.find(Agreement
.event_id
== event
.getId(),
165 Agreement
.type == cls
.name
,
166 Agreement
.identifier
.in_(identifiers
))
167 num_accepted
= query
.filter(Agreement
.accepted
).count()
168 num_rejected
= query
.filter(Agreement
.rejected
).count()
169 everybody_signed
= len(people
) == (num_accepted
+ num_rejected
)
170 return (everybody_signed
, num_accepted
, num_rejected
)
173 def render_form(cls
, agreement
, form
, **kwargs
):
174 template_name
= cls
.form_template_name
or '{}.html'.format(cls
.name
.replace('-', '_'))
175 template_path
= get_overridable_template_name(template_name
, cls
.plugin
, 'events/agreements/')
176 return render_template(template_path
, agreement
=agreement
, form
=form
, **kwargs
)
179 def render_data(cls
, event
, data
): # pragma: no cover
180 """Returns extra data to display in the agreement list
182 If you want a column to be rendered as HTML, use a :class:`~markupsafe.Markup`
183 object instead of a plain string.
185 :param event: The event containing the agreements
186 :param data: The data from the :class:`AgreementPersonInfo`
187 :return: List of extra columns for a row
192 def handle_accepted(cls
, agreement
): # pragma: no cover
193 """Handles logic on agreement accepted"""
197 def handle_rejected(cls
, agreement
): # pragma: no cover
198 """Handles logic on agreement rejected"""
202 def handle_reset(cls
, agreement
): # pragma: no cover
203 """Handles logic on agreement reset"""
207 def iter_people(cls
, event
): # pragma: no cover
208 """Yields :class:`AgreementPersonInfo` required to sign agreements"""
209 raise NotImplementedError