Reorder methods
[cds-indico.git] / indico / modules / events / agreements / base.py
blobb3bbfd27864760ad273e2a40e569940901570054
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):
34 if user:
35 if not name:
36 name = user.getStraightFullName()
37 if not email:
38 email = user.getEmail()
39 if not name:
40 raise ValueError('name is missing')
41 if not email:
42 raise ValueError('email is missing')
43 self.name = name
44 self.email = email
45 self.user = user
46 self.data = data
48 @return_ascii
49 def __repr__(self):
50 return '<AgreementPersonInfo({}, {}, {})>'.format(self.name, self.email, self.identifier)
52 @property
53 def identifier(self):
54 data_string = None
55 if self.data:
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"""
63 required = False
64 description = None
66 @classmethod
67 def render(cls, agreement):
68 """Converts the placeholder to a string
70 :param agreement: the `Agreement` object the email is being sent for
71 """
72 raise NotImplementedError
75 class AgreementDefinitionBase(object):
76 """Base class for agreement definitions"""
78 #: unique name of the agreement definition
79 name = None
80 #: readable name of the agreement definition
81 title = None
82 #: optional and short description of the agreement definition
83 description = None
84 #: url to obtain the paper version of the agreement form
85 paper_form_url = None
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
93 plugin = None
95 #: default settings for an event
96 default_event_settings = {'manager_notifications_enabled': False}
98 @classproperty
99 @classmethod
100 def locator(cls):
101 return {'definition': cls.name}
103 @cached_classproperty
104 @classmethod
105 def event_settings(cls):
106 return EventSettingsProxy('agreement_{}'.format(cls.name), cls.default_event_settings)
108 @classmethod
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))
113 @classmethod
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
122 pass
124 @classmethod
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)
131 @classmethod
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 {})
138 return placeholders
140 @classmethod
141 def get_people(cls, event):
142 """Returns a dictionary of :class:`AgreementPersonInfo` required to sign agreements"""
143 people = cls.iter_people(event)
144 if people is None:
145 return {}
146 return {p.identifier: p for p in people}
148 @classmethod
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}
155 @classmethod
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)
172 @classmethod
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)
178 @classmethod
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
189 return None
191 @classmethod
192 def handle_accepted(cls, agreement): # pragma: no cover
193 """Handles logic on agreement accepted"""
194 pass
196 @classmethod
197 def handle_rejected(cls, agreement): # pragma: no cover
198 """Handles logic on agreement rejected"""
199 pass
201 @classmethod
202 def handle_reset(cls, agreement): # pragma: no cover
203 """Handles logic on agreement reset"""
204 pass
206 @classmethod
207 def iter_people(cls, event): # pragma: no cover
208 """Yields :class:`AgreementPersonInfo` required to sign agreements"""
209 raise NotImplementedError