def _getHTMLHeader( self ): from MaKaC.webinterface.rh.base import RHModificationBaseProtected from MaKaC.webinterface.rh.admins import RHAdminBase area="" if isinstance(self._rh, RHModificationBaseProtected): area=i18nformat(""" - _("Management area")""") elif isinstance(self._rh, RHAdminBase): area=i18nformat(""" - _("Administrator area")""") info = HelperMaKaCInfo().getMaKaCInfoInstance() plugin_css = values_from_signal(signals.plugin.inject_css.send(self.__class__), as_list=True, multi_value_types=list) plugin_js = values_from_signal(signals.plugin.inject_js.send(self.__class__), as_list=True, multi_value_types=list) return wcomponents.WHTMLHeader().getHTML({ "area": area, "baseurl": self._getBaseURL(), "conf": Config.getInstance(), "page": self, "printCSS": map(self._fix_path, self.getPrintCSSFiles()), "extraCSS": map(self._fix_path, self.getCSSFiles() + plugin_css + self.get_extra_css_files()), "extraJSFiles": map(self._fix_path, self.getJSFiles() + plugin_js), "language": session.lang or info.getLang(), "social": info.getSocialAppConfig(), "assets": self._asset_env })
def display(self, **params): from indico.modules.admin import RHAdminBase from indico.modules.core.settings import core_settings, social_settings title_parts = [to_unicode(self._getTitle())] if self.MANAGEMENT: title_parts.append(_('Management area')) elif isinstance(self._rh, RHAdminBase): title_parts.append(_('Administration area')) plugin_css = values_from_signal(signals.plugin.inject_css.send(self.__class__), as_list=True, multi_value_types=list) plugin_js = values_from_signal(signals.plugin.inject_js.send(self.__class__), as_list=True, multi_value_types=list) custom_js = self._asset_env['custom_js'].urls() if 'custom_js' in self._asset_env else [] custom_css = self._asset_env['custom_sass'].urls() if 'custom_sass' in self._asset_env else [] css_files = map(self._fix_path, self.getCSSFiles() + plugin_css + self.get_extra_css_files() + custom_css) print_css_files = map(self._fix_path, self.getPrintCSSFiles()) js_files = map(self._fix_path, self.getJSFiles() + plugin_js + custom_js) body = to_unicode(self._display(params)) return render_template('indico_base.html', css_files=css_files, print_css_files=print_css_files, js_files=js_files, site_name=core_settings.get('site_title'), social=social_settings.get_all(), page_title=' - '.join(title_parts), head_content=to_unicode(self._getHeadContent()), body=body)
def test_values_from_signal_multi_value_types(): vals = ('a', ['b', 'c']) signal_response = _make_signal_response(vals) with pytest.raises(TypeError): # list is unhashable, can't be added to a set values_from_signal(signal_response) assert values_from_signal(signal_response, as_list=True) == list(vals) assert values_from_signal(signal_response, multi_value_types=list) == {'a', 'b', 'c'}
def settings(self): core_path = os.path.join(get_root_path('indico'), 'modules', 'events', 'themes.yaml') with open(core_path) as f: core_data = f.read() core_settings = yaml.safe_load(core_data) # YAML doesn't give us access to anchors so we need to include the base yaml. # Since duplicate keys are invalid (and may start failing in the future) we # rename them - this also makes it easy to throw them away after parsing the # file provided by a plugin. core_data = re.sub(r'^(\S+:)$', r'__core_\1', core_data, flags=re.MULTILINE) for plugin, path in values_from_signal(signals.plugin.get_event_themes_files.send(), return_plugins=True): with open(path) as f: data = f.read() settings = {k: v for k, v in yaml.safe_load(core_data + '\n' + data).viewitems() if not k.startswith('__core_')} # We assume there's no more than one theme plugin that provides defaults. # If that's not the case the last one "wins". We could reject this but it # is quite unlikely that people have multiple theme plugins in the first # place, even more so theme plugins that specify defaults. core_settings['defaults'].update(settings.get('defaults', {})) # Same for definitions - we assume plugin authors are responsible enough # to avoid using definition names that are likely to cause collisions. # Either way, if someone does this on purpose chances are good they want # to override a default style so let them do so... for name, definition in settings.get('definitions', {}).viewitems(): definition['plugin'] = plugin definition.setdefault('user_visible', False) core_settings['definitions'][name] = definition return core_settings
def display(self, **params): from indico.modules.admin import RHAdminBase from indico.modules.core.settings import core_settings, social_settings title_parts = [self.title] if self.MANAGEMENT: title_parts.insert(0, _('Management')) elif isinstance(self._rh, RHAdminBase): title_parts.insert(0, _('Administration')) injected_bundles = values_from_signal(signals.plugin.inject_bundle.send(self.__class__), as_list=True, multi_value_types=list) custom_js = list(current_app.manifest['__custom.js']) custom_css = list(current_app.manifest['__custom.css']) css_files = map(self._fix_path, self.get_extra_css_files() + custom_css) js_files = map(self._fix_path, custom_js) body = to_unicode(self._display(params)) bundles = itertools.chain((current_app.manifest[x] for x in self._resolve_bundles() if x in current_app.manifest._entries), self.additional_bundles['screen'], injected_bundles) print_bundles = itertools.chain((current_app.manifest[x] for x in self.print_bundles), self.additional_bundles['print']) return render_template('indico_base.html', css_files=css_files, js_files=js_files, bundles=bundles, print_bundles=print_bundles, site_name=core_settings.get('site_title'), social=social_settings.get_all(), page_title=' - '.join(unicode(x) for x in title_parts if x), head_content=to_unicode(self._getHeadContent()), body=body)
def display(cls, template_name, **params): from indico.modules.admin import RHAdminBase from indico.modules.core.settings import core_settings, social_settings title_parts = [cls.title] if cls.MANAGEMENT: title_parts.insert(0, _('Management')) elif isinstance(g.rh, RHAdminBase): title_parts.insert(0, _('Administration')) injected_bundles = values_from_signal(signals.plugin.inject_bundle.send(cls), as_list=True, multi_value_types=list) custom_js = list(current_app.manifest['__custom.js']) custom_css = list(current_app.manifest['__custom.css']) css_files = map(cls._fix_path, custom_css) js_files = map(cls._fix_path, custom_js) bundles = itertools.chain((current_app.manifest[x] for x in cls._resolve_bundles() if x in current_app.manifest._entries), cls.additional_bundles['screen'], injected_bundles) print_bundles = itertools.chain((current_app.manifest[x] for x in cls.print_bundles), cls.additional_bundles['print']) template = cls._prefix_template(template_name) return render_template(template, css_files=css_files, js_files=js_files, bundles=bundles, print_bundles=print_bundles, site_name=core_settings.get('site_title'), social=social_settings.get_all(), page_title=' - '.join(unicode(x) for x in title_parts if x), **params)
def can_access(self, user, allow_admin=True): """Checks if the user can access the object. :param user: The :class:`.User` to check. May be None if the user is not logged in. :param allow_admin: If admin users should always have access """ # Trigger signals for protection overrides rv = values_from_signal( signals.acl.can_access.send(type(self), obj=self, user=user, allow_admin=allow_admin), single_value=True ) if rv: # in case of contradictory results (shouldn't happen at all) # we stay on the safe side and deny access return all(rv) # Usually admins can access everything, so no need for checks if allow_admin and user and user.is_admin: return True if self.protection_mode == ProtectionMode.public: # if it's public we completely ignore the parent protection # this is quite ugly which is why it should only be allowed # in rare cases (e.g. events which might be in a protected # category but should be public nonetheless) return True elif self.protection_mode == ProtectionMode.protected: # if it's protected, we also ignore the parent protection # and only check our own ACL return user is not None and any(user in entry.principal for entry in iter_acl(self.acl_entries)) elif self.protection_mode == ProtectionMode.inheriting: # if it's inheriting, we only check the parent protection # unless `inheriting_have_acl` is set, in which case we # might not need to check the parents at all if ( self.inheriting_have_acl and user is not None and any(user in entry.principal for entry in iter_acl(self.acl_entries)) ): return True # the parent can be either an object inheriting from this # mixin or a legacy object with an AccessController parent = self.protection_parent if parent is None: # This should be the case for the top-level object, # i.e. the root category, which shouldn't allow # ProtectionMode.inheriting as it makes no sense. raise TypeError("protection_parent of {} is None".format(self)) elif hasattr(parent, "can_access"): return parent.can_access(user, allow_admin=allow_admin) elif hasattr(parent, "canAccess"): return parent.canAccess(AccessWrapper(user.as_avatar if user else None)) else: raise TypeError("protection_parent of {} is of invalid type {} ({})".format(self, type(parent), parent)) else: # should never happen, but since this is a sensitive area # we better fail loudly if we have garbage raise ValueError("Invalid protection mode: {}".format(self.protection_mode))
def can_access(self, user, acl_attr='acl', legacy_method='canAccess', allow_admin=True): """Checks if the user can access the object. When using a custom `acl_attr` on an object that supports inherited proection, ALL possible `protection_parent` objects need to have an ACL with the same name too! :param user: The :class:`.User` to check. May be None if the user is not logged in. :param acl_attr: The name of the property that contains the set of authorized principals. :param legacy_method: The method name to use when inheriting the protection from a legacy object. :param allow_admin: If admin users should always have access """ # Trigger signals for protection overrides rv = values_from_signal(signals.acl.can_access.send(type(self), obj=self, user=user, acl_attr=acl_attr, legacy_method=legacy_method, allow_admin=allow_admin), single_value=True) if rv: # in case of contradictory results (shouldn't happen at all) # we stay on the safe side and deny access return all(rv) # Usually admins can access everything, so no need for checks if allow_admin and user and user.is_admin: return True if self.protection_mode == ProtectionMode.public: # if it's public we completely ignore the parent protection # this is quite ugly which is why it should only be allowed # in rare cases (e.g. events which might be in a protected # category but should be public nonetheless) return True elif self.protection_mode == ProtectionMode.protected: # if it's protected, we also ignore the parent protection # and only check our own ACL return any(user in principal for principal in iter_acl(getattr(self, acl_attr))) elif self.protection_mode == ProtectionMode.inheriting: # if it's inheriting, we only check the parent protection. # the parent can be either an object inheriting from this # mixin or a legacy object with an AccessController parent = self.protection_parent if parent is None: # This should be the case for the top-level object, # i.e. the root category, which shouldn't allow # ProtectionMode.inheriting as it makes no sense. raise TypeError('protection_parent of {} is None'.format(self)) elif hasattr(parent, 'can_access'): return parent.can_access(user, acl_attr=acl_attr) elif legacy_method is not None and hasattr(parent, legacy_method): return getattr(parent, legacy_method)(AccessWrapper(user.as_avatar if user else None)) else: raise TypeError('protection_parent of {} is of invalid type {} ({})'.format(self, type(parent), parent)) else: # should never happen, but since this is a sensitive area # we better fail loudly if we have garbage raise ValueError('Invalid protection mode: {}'.format(self.protection_mode))
def _checkParams(self, params): RHConferenceModifBase._checkParams(self, params) from MaKaC.webinterface.rh.reviewingModif import RCPaperReviewManager, RCReferee self._isPRM = RCPaperReviewManager.hasRights(self) self._isReferee = RCReferee.hasRights(self) self._requests_manager = is_request_manager(session.user) self._plugin_urls = values_from_signal(signals.event_management.management_url.send(self.event_new), single_value=True)
def _check_can_access_override(self, user, allow_admin, authorized=None): # Trigger signals for protection overrides rv = values_from_signal(signals.acl.can_access.send(type(self), obj=self, user=user, allow_admin=allow_admin, authorized=authorized), single_value=True) # in case of contradictory results (shouldn't happen at all) # we stay on the safe side and deny access return all(rv) if rv else None
def validate(self): valid = super(IndicoForm, self).validate() if not valid: return False if not all(values_from_signal(signals.form_validated.send(self), single_value=True)): return False self.post_validate() return True
def _createSideMenu(self): self._sideMenu = wcomponents.ManagementSideMenu() mainSection = wcomponents.SideMenuSection(id='general') securitySection = wcomponents.SideMenuSection(_('Security'), id='security', icon='icon-shield') userManagementSection = wcomponents.SideMenuSection(_('User management'), id='user_management', icon='icon-users') pluginsSection = wcomponents.SideMenuSection(_('Plugins'), id='plugins', icon='icon-settings') customizationSection = wcomponents.SideMenuSection(_('Customization'), id='customization', icon='icon-wrench') integrationSection = wcomponents.SideMenuSection(_('Integration'), id='integration', icon='icon-earth') self._sideMenu.addSection(mainSection) self._sideMenu.addSection(securitySection) self._sideMenu.addSection(userManagementSection) self._sideMenu.addSection(pluginsSection) self._sideMenu.addSection(customizationSection) self._sideMenu.addSection(integrationSection) self._generalSettingsMenuItem = wcomponents.SideMenuItem( _("General settings"), urlHandlers.UHAdminArea.getURL(), section='general') self._sideMenu.addItem(self._generalSettingsMenuItem) self._domainsMenuItem = wcomponents.SideMenuItem( _("IP Domains"), urlHandlers.UHDomains.getURL(), section='security') self._sideMenu.addItem(self._domainsMenuItem) self._templatesMenuItem = wcomponents.SideMenuItem( _("Layout"), urlHandlers.UHAdminLayoutGeneral.getURL(), section='customization') self._sideMenu.addItem(self._templatesMenuItem) self._ipProtectionMenuItem = wcomponents.SideMenuItem( _("IP-based ACL"), urlHandlers.UHIPBasedACL.getURL(), section='security') self._sideMenu.addItem(self._ipProtectionMenuItem) self._homepageMenuItem = wcomponents.SideMenuItem( _("Homepage"), urlHandlers.UHUpdateNews.getURL(), section='customization') self._sideMenu.addItem(self._homepageMenuItem) self._systemMenuItem = wcomponents.SideMenuItem( _("System"), urlHandlers.UHAdminsSystem.getURL(), section='general') self._sideMenu.addItem(self._systemMenuItem) self._protectionMenuItem = wcomponents.SideMenuItem( _("Protection"), urlHandlers.UHAdminsProtection.getURL(), section='security') self._sideMenu.addItem(self._protectionMenuItem) self.extra_menu_items = {} for name, item in sorted(values_from_signal(signals.admin_sidemenu.send()), key=lambda x: x[1]._title): self.extra_menu_items[name] = item self._sideMenu.addItem(item)
def _getBody(self, params): extra_items = sorted(values_from_signal(signals.users.profile_sidemenu.send(params['user'])), key=attrgetter('title')) params['user_menu_items'] = [ MenuItem(_('Dashboard'), 'users.user_dashboard'), MenuItem(_('Personal data'), 'users.user_profile'), MenuItem(_('Emails'), 'users.user_emails'), MenuItem(_('Preferences'), 'users.user_preferences'), MenuItem(_('Favorites'), 'users.user_favorites'), ] + extra_items return self._getPageContent(params)
def _do_process(self): try: args_result = self._process_args() signals.rh.process_args.send(type(self), rh=self, result=args_result) if isinstance(args_result, (current_app.response_class, Response)): return args_result except NoResultFound: # sqlalchemy .one() not finding anything raise NotFound(_('The specified item could not be found.')) rv = self.normalize_url() if rv is not None: return rv self._check_access() signals.rh.check_access.send(type(self), rh=self) if self.CHECK_HTML: Sanitization.sanitizationCheck(create_flat_args(), self.NOT_SANITIZED_FIELDS) signal_rv = values_from_signal(signals.rh.before_process.send(type(self), rh=self), single_value=True, as_list=True) if signal_rv and len(signal_rv) != 1: raise Exception('More than one signal handler returned custom RH result') elif signal_rv: return signal_rv[0] if config.PROFILE: result = [None] profile_path = os.path.join(config.TEMP_DIR, '{}-{}.prof'.format(type(self).__name__, time.time())) cProfile.runctx('result[0] = self._process()', globals(), locals(), profile_path) rv = result[0] else: rv = self._process() signal_rv = values_from_signal(signals.rh.process.send(type(self), rh=self, result=rv), single_value=True, as_list=True) if signal_rv and len(signal_rv) != 1: raise Exception('More than one signal handler returned new RH result') elif signal_rv: return signal_rv[0] else: return rv
def _check_access(self): if not session.user: raise Forbidden # If the user cannot manage the whole event see if anything gives them # limited management access. if not self.event.can_manage(session.user): urls = sorted(values_from_signal(signals.event_management.management_url.send(self.event), single_value=True)) response = redirect(urls[0]) if urls else None raise Forbidden(response=response) RHManageEventBase._check_access(self) # mainly to trigger the legacy "event locked" check
def _process(self): detail_level = request.args.get('detail', 'events') data = serialize_event_for_ical(self.event, detail_level) # check whether the plugins want to add/override any data for update in values_from_signal( signals.event.metadata_postprocess.send('ical-export', event=self.event, data=data), as_list=True): data.update(update) response = {'results': data} serializer = Serializer.create('ics') return send_file('event.ics', BytesIO(serializer(response)), 'text/calendar')
def _check_can_access_override(self, user, allow_admin, authorized=None): # Trigger signals for protection overrides rv = values_from_signal(signals.acl.can_access.send( type(self), obj=self, user=user, allow_admin=allow_admin, authorized=authorized), single_value=True) # in case of contradictory results (shouldn't happen at all) # we stay on the safe side and deny access return all(rv) if rv else None
def _getBody(self, params): extra_items = sorted(values_from_signal( signals.users.profile_sidemenu.send(params['user'])), key=attrgetter('title')) params['user_menu_items'] = [ MenuItem(_('Dashboard'), 'users.user_dashboard'), MenuItem(_('Personal data'), 'users.user_profile'), MenuItem(_('Emails'), 'users.user_emails'), MenuItem(_('Preferences'), 'users.user_preferences'), MenuItem(_('Favorites'), 'users.user_favorites'), # TODO: other menu items ] + extra_items return self._getPageContent(params)
def add_plugin_blueprints(app): blueprint_names = set() for plugin, blueprint in values_from_signal(signals.plugin.get_blueprints.send(app), return_plugins=True): expected_names = {'plugin_{}'.format(plugin.name), 'plugin_compat_{}'.format(plugin.name)} if blueprint.name not in expected_names: raise Exception("Blueprint '{}' does not match plugin name '{}'".format(blueprint.name, plugin.name)) if blueprint.name in blueprint_names: raise Exception("Blueprint '{}' defined by multiple plugins".format(blueprint.name)) if not app.config['INDICO_COMPAT_ROUTES'] and blueprint.name.startswith('plugin_compat_'): continue blueprint_names.add(blueprint.name) with plugin.plugin_context(): app.register_blueprint(blueprint)
def add_plugin_blueprints(app): blueprint_names = set() for plugin, blueprint in values_from_signal(signals.plugin.get_blueprints.send(app), return_plugins=True): expected_names = {f'plugin_{plugin.name}', f'plugin_compat_{plugin.name}'} if blueprint.name not in expected_names: raise Exception(f"Blueprint '{blueprint.name}' does not match plugin name '{plugin.name}'") if blueprint.name in blueprint_names: raise Exception(f"Blueprint '{blueprint.name}' defined by multiple plugins") if not config.ROUTE_OLD_URLS and blueprint.name.startswith('plugin_compat_'): continue blueprint_names.add(blueprint.name) with plugin.plugin_context(): app.register_blueprint(blueprint)
def _do_process(self): try: args_result = self._process_args() signals.rh.process_args.send(type(self), rh=self, result=args_result) if isinstance(args_result, (current_app.response_class, Response)): return args_result except NoResultFound: # sqlalchemy .one() not finding anything raise NotFound(_('The specified item could not be found.')) rv = self.normalize_url() if rv is not None: return rv self._check_access() signals.rh.check_access.send(type(self), rh=self) signal_rv = values_from_signal(signals.rh.before_process.send(type(self), rh=self), single_value=True, as_list=True) if signal_rv and len(signal_rv) != 1: raise Exception('More than one signal handler returned custom RH result') elif signal_rv: return signal_rv[0] if config.PROFILE: result = [None] profile_path = os.path.join(config.TEMP_DIR, '{}-{}.prof'.format(type(self).__name__, time.time())) cProfile.runctx('result[0] = self._process()', globals(), locals(), profile_path) rv = result[0] else: rv = self._process() signal_rv = values_from_signal(signals.rh.process.send(type(self), rh=self, result=rv), single_value=True, as_list=True) if signal_rv and len(signal_rv) != 1: raise Exception('More than one signal handler returned new RH result') elif signal_rv: return signal_rv[0] else: return rv
def getVars( self ): vars = WTemplated.getVars(self) vars["currentUser"] = self._currentuser config = Config.getInstance() imgLogin = config.getSystemIconURL("login") vars["imgLogin"] = imgLogin vars["isFrontPage"] = self._isFrontPage vars["currentCategory"] = self.__currentCategory vars['prot_obj'] = self._prot_obj current_locale = get_current_locale() vars["ActiveTimezone"] = session.timezone """ Get the timezone for displaying on top of the page. 1. If the user has "LOCAL" timezone then show the timezone of the event/category. If that's not possible just show the standard timezone. 2. If the user has a custom timezone display that one. """ vars["ActiveTimezoneDisplay"] = self._getTimezoneDisplay(vars["ActiveTimezone"]) vars["SelectedLanguage"] = str(current_locale) vars["SelectedLanguageName"] = current_locale.language_name vars["Languages"] = get_all_locales() if DBMgr.getInstance().isConnected(): vars["title"] = info.HelperMaKaCInfo.getMaKaCInfoInstance().getTitle() vars["organization"] = info.HelperMaKaCInfo.getMaKaCInfoInstance().getOrganisation() else: vars["title"] = "Indico" vars["organization"] = "" vars['roomBooking'] = Config.getInstance().getIsRoomBookingActive() vars['protectionDisclaimerProtected'] = legal_settings.get('network_protected_disclaimer') vars['protectionDisclaimerRestricted'] = legal_settings.get('restricted_disclaimer') #Build a list of items for the administration menu adminItemList = [] if session.user and session.user.is_admin: adminItemList.append({'id': 'serverAdmin', 'url': urlHandlers.UHAdminArea.getURL(), 'text': _("Server admin")}) vars["adminItemList"] = adminItemList vars['extra_items'] = HeaderMenuEntry.group(values_from_signal(signals.indico_menu.send())) vars["getProtection"] = self._getProtection vars["show_contact"] = config.getPublicSupportEmail() is not None return vars
def _createSideMenu(self): self._sideMenu = wcomponents.ManagementSideMenu() mainSection = wcomponents.SideMenuSection() self._generalSettingsMenuItem = wcomponents.SideMenuItem( _("General settings"), urlHandlers.UHAdminArea.getURL()) mainSection.addItem(self._generalSettingsMenuItem) self._domainsMenuItem = wcomponents.SideMenuItem( _("IP Domains"), urlHandlers.UHDomains.getURL()) mainSection.addItem(self._domainsMenuItem) self._roomsMenuItem = wcomponents.SideMenuItem( _("Rooms"), url_for('rooms_admin.settings')) mainSection.addItem(self._roomsMenuItem) self._templatesMenuItem = wcomponents.SideMenuItem( _("Layout"), urlHandlers.UHAdminLayoutGeneral.getURL()) mainSection.addItem(self._templatesMenuItem) self._servicesMenuItem = wcomponents.SideMenuItem( _("Services"), urlHandlers.UHIPBasedACL.getURL()) mainSection.addItem(self._servicesMenuItem) self._pluginsMenuItem = wcomponents.SideMenuItem( _("Plugins"), url_for('plugins.index')) mainSection.addItem(self._pluginsMenuItem) self._homepageMenuItem = wcomponents.SideMenuItem( _("Homepage"), urlHandlers.UHUpdateNews.getURL()) mainSection.addItem(self._homepageMenuItem) self._systemMenuItem = wcomponents.SideMenuItem( _("System"), urlHandlers.UHAdminsSystem.getURL()) mainSection.addItem(self._systemMenuItem) self._protectionMenuItem = wcomponents.SideMenuItem( _("Protection"), urlHandlers.UHAdminsProtection.getURL()) mainSection.addItem(self._protectionMenuItem) self.extra_menu_items = {} for name, item in sorted(values_from_signal( signals.admin_sidemenu.send()), key=lambda x: x[1]._title): self.extra_menu_items[name] = item mainSection.addItem(item) self._sideMenu.addSection(mainSection)
def get_search_provider(): from indico.modules.search.controllers import InternalSearch providers = values_from_signal(signals.get_search_providers.send(), as_list=True) if not providers: return InternalSearch elif len(providers) == 1: return providers[0] else: providers_str = ', '.join(f'{x.__module__}.{x.__name__}' for x in providers) raise RuntimeError( f'Only one search provider can be defined (found: {providers_str})' )
def _notify_registration(registration, template, to_managers=False): attachments = None regform = registration.registration_form tickets_handled = values_from_signal(signals.event.is_ticketing_handled.send(regform), single_value=True) if (not to_managers and regform.tickets_enabled and regform.ticket_on_email and not any(tickets_handled) and registration.state == RegistrationState.complete): attachments = get_ticket_attachment(registration) template = get_template_module('events/registration/emails/{}'.format(template), registration=registration) to_list = registration.email if not to_managers else registration.registration_form.manager_notification_recipients from_address = registration.registration_form.sender_address if not to_managers else None mail = make_email(to_list=to_list, template=template, html=True, from_address=from_address, attachments=attachments) send_email(mail, event=registration.registration_form.event, module='Registration', user=session.user)
def _notify_registration(registration, template_name, to_managers=False, attach_rejection_reason=False, diff=None, old_price=None): from indico.modules.events.registration.util import get_ticket_attachments attachments = [] regform = registration.registration_form tickets_handled = values_from_signal( signals.event.is_ticketing_handled.send(regform), single_value=True) if (not to_managers and regform.tickets_enabled and regform.ticket_on_email and not any(tickets_handled) and registration.state == RegistrationState.complete): attachments += get_ticket_attachments(registration) if not to_managers and registration.registration_form.attach_ical: event_ical = event_to_ical(registration.event, method='REQUEST', organizer=(core_settings.get('site_title'), config.NO_REPLY_EMAIL)) attachments.append(MIMECalendar('event.ics', event_ical)) tpl = get_template_module(f'events/registration/emails/{template_name}', registration=registration, attach_rejection_reason=attach_rejection_reason, diff=diff, old_price=old_price) to_list = registration.email if not to_managers else registration.registration_form.manager_notification_recipients from_address = registration.registration_form.notification_sender_address if not to_managers else None mail = make_email(to_list=to_list, template=tpl, html=True, from_address=from_address, attachments=attachments) user = session.user if session else None signals.core.before_notification_send.send( 'notify-registration', email=mail, registration=registration, template_name=template_name, attach_rejection_reason=attach_rejection_reason) send_email(mail, event=registration.registration_form.event, module='Registration', user=user, log_metadata={'registration_id': registration.id})
def _createSideMenu(self): self._sideMenu = wcomponents.ManagementSideMenu() mainSection = wcomponents.SideMenuSection() self._generalSettingsMenuItem = wcomponents.SideMenuItem(_("General settings"), urlHandlers.UHAdminArea.getURL()) mainSection.addItem( self._generalSettingsMenuItem) self._domainsMenuItem = wcomponents.SideMenuItem(_("IP Domains"), urlHandlers.UHDomains.getURL()) mainSection.addItem( self._domainsMenuItem) self._roomsMenuItem = wcomponents.SideMenuItem(_("Rooms"), url_for('rooms_admin.settings')) mainSection.addItem(self._roomsMenuItem) self._templatesMenuItem = wcomponents.SideMenuItem(_("Layout"), urlHandlers.UHAdminLayoutGeneral.getURL()) mainSection.addItem( self._templatesMenuItem) self._servicesMenuItem = wcomponents.SideMenuItem(_("Services"), urlHandlers.UHIPBasedACL.getURL()) mainSection.addItem(self._servicesMenuItem) self._pluginsMenuItem = wcomponents.SideMenuItem(_("Plugins"), url_for('plugins.index')) mainSection.addItem(self._pluginsMenuItem) self._homepageMenuItem = wcomponents.SideMenuItem(_("Homepage"), urlHandlers.UHUpdateNews.getURL()) mainSection.addItem( self._homepageMenuItem) self._systemMenuItem = wcomponents.SideMenuItem(_("System"), urlHandlers.UHAdminsSystem.getURL()) mainSection.addItem( self._systemMenuItem) self._protectionMenuItem = wcomponents.SideMenuItem(_("Protection"), urlHandlers.UHAdminsProtection.getURL()) mainSection.addItem( self._protectionMenuItem) self.extra_menu_items = {} for name, item in sorted(values_from_signal(signals.admin_sidemenu.send()), key=lambda x: x[1]._title): self.extra_menu_items[name] = item mainSection.addItem(item) self._sideMenu.addSection(mainSection)
def _process(self): extra_preferences = [pref(self.user) for pref in values_from_signal(signals.users.preferences.send(self.user))] form_class = UserPreferencesForm defaults = FormDefaults(**self.user.settings.get_all(self.user)) for pref in extra_preferences: form_class = pref.extend_form(form_class) pref.extend_defaults(defaults) form = form_class(obj=defaults) if form.validate_on_submit(): data = form.data for pref in extra_preferences: pref.process_form_data(data) self.user.settings.set_multi(data) session.timezone = (self.user.settings.get('timezone') if self.user.settings.get('force_timezone') else 'LOCAL') flash(_('Preferences saved'), 'success') return redirect(url_for('.user_preferences')) return WPUser.render_template('preferences.html', user=self.user, form=form)
def get_search_provider(only_active=True): """Get the search provider to use for a search. :param only_active: Whether to check that the provider is active; in case it isn't, the default InternalSearch provider will be used. """ from indico.modules.search.controllers import InternalSearch providers = values_from_signal(signals.get_search_providers.send(), as_list=True) if not providers: return InternalSearch elif len(providers) == 1: provider = providers[0] return provider if not only_active or provider.active else InternalSearch else: providers_str = ', '.join(f'{x.__module__}.{x.__name__}' for x in providers) raise RuntimeError(f'Only one search provider can be defined (found: {providers_str})')
def call_template_hook(*name, **kwargs): """Template function to let plugins add their own data to a template. :param name: The name of the hook. Only accepts one argument. :param as_list: Return a list instead of a concatenated string :param kwargs: Data to pass to the signal receivers. """ if len(name) != 1: raise TypeError(f'call_template_hook() accepts only one positional argument, {len(name)} given') name = name[0] as_list = kwargs.pop('as_list', False) values = [] for snippet in values_from_signal(signals.plugin.template_hook.send(str(name), **kwargs), multi_value_types=tuple, as_list=True): if not (value := snippet.content): continue if snippet.markup: value = Markup(value) values.append((snippet.priority, value))
def _notify_registration(registration, template, to_managers=False): from indico.modules.events.registration.util import get_ticket_attachments attachments = None regform = registration.registration_form tickets_handled = values_from_signal(signals.event.is_ticketing_handled.send(regform), single_value=True) if (not to_managers and regform.tickets_enabled and regform.ticket_on_email and not any(tickets_handled) and registration.state == RegistrationState.complete): attachments = get_ticket_attachments(registration) template = get_template_module(f'events/registration/emails/{template}', registration=registration) to_list = registration.email if not to_managers else registration.registration_form.manager_notification_recipients from_address = registration.registration_form.sender_address if not to_managers else None mail = make_email(to_list=to_list, template=template, html=True, from_address=from_address, attachments=attachments) user = session.user if session else None send_email(mail, event=registration.registration_form.event, module='Registration', user=user, log_metadata={'registration_id': registration.id})
def _process(self): extra_preferences = [pref(self.user) for pref in values_from_signal(signals.users.preferences.send(self.user))] form_class = UserPreferencesForm defaults = FormDefaults(**self.user.settings.get_all(self.user)) for pref in extra_preferences: form_class = pref.extend_form(form_class) pref.extend_defaults(defaults) form = form_class(obj=defaults) if form.validate_on_submit(): data = form.data for pref in extra_preferences: pref.process_form_data(data) self.user.settings.set_multi(data) session.lang = self.user.settings.get('lang') session.timezone = (self.user.settings.get('timezone') if self.user.settings.get('force_timezone') else 'LOCAL') flash(_('Preferences saved'), 'success') return redirect(url_for('.user_preferences')) return WPUser.render_template('preferences.html', 'preferences', user=self.user, form=form)
def display(self, **params): from indico.modules.admin import RHAdminBase from indico.modules.core.settings import core_settings, social_settings title_parts = [self.title] if self.MANAGEMENT: title_parts.insert(0, _('Management')) elif isinstance(self._rh, RHAdminBase): title_parts.insert(0, _('Administration')) injected_bundles = values_from_signal( signals.plugin.inject_bundle.send(self.__class__), as_list=True, multi_value_types=list) custom_js = list(current_app.manifest['__custom.js']) custom_css = list(current_app.manifest['__custom.css']) css_files = list( map(self._fix_path, self.get_extra_css_files() + custom_css)) js_files = list(map(self._fix_path, custom_js)) body = self._display(params) bundles = itertools.chain( (current_app.manifest[x] for x in self._resolve_bundles() if x in current_app.manifest._entries), self.additional_bundles['screen'], injected_bundles) print_bundles = itertools.chain( (current_app.manifest[x] for x in self.print_bundles), self.additional_bundles['print']) return render_template('indico_base.html', css_files=css_files, js_files=js_files, bundles=bundles, print_bundles=print_bundles, site_name=core_settings.get('site_title'), social=social_settings.get_all(), page_metadata=self.page_metadata, page_title=' - '.join( str(x) for x in title_parts if x), head_content=self._get_head_content(), body=body)
def __call__(cls, *args, **kwargs): # If we are instantiating a form that was just extended, don't # send the signal again - it's pointless to extend the extended # form and doing so could actually result in infinite recursion # if the signal receiver didn't specify a sender. if kwargs.pop('__extended', False): return super(IndicoFormMeta, cls).__call__(*args, **kwargs) extra_fields = values_from_signal(signals.add_form_fields.send(cls)) # If there are no extra fields, we don't need any custom logic # and simply create an instance of the original form. if not extra_fields: return super(IndicoFormMeta, cls).__call__(*args, **kwargs) kwargs['__extended'] = True ext_cls = type(b'_Extended' + cls.__name__, (cls,), {}) for name, field in extra_fields: name = 'ext__' + name if hasattr(ext_cls, name): raise RuntimeError('Field name collision in {}: {}'.format(cls.__name__, name)) setattr(ext_cls, name, field) return ext_cls(*args, **kwargs)
def __call__(cls, *args, **kwargs): # If we are instantiating a form that was just extended, don't # send the signal again - it's pointless to extend the extended # form and doing so could actually result in infinite recursion # if the signal receiver didn't specify a sender. if kwargs.pop('__extended', False): return super(IndicoFormMeta, cls).__call__(*args, **kwargs) extra_fields = values_from_signal(signals.add_form_fields.send(cls)) # If there are no extra fields, we don't need any custom logic # and simply create an instance of the original form. if not extra_fields: return super(IndicoFormMeta, cls).__call__(*args, **kwargs) kwargs['__extended'] = True ext_cls = type(b'_Extended' + cls.__name__, (cls,), {}) for name, field in extra_fields: name = 'ext__' + name if hasattr(ext_cls, name): raise RuntimeError('Preference collision in {}: {}'.format(cls.__name__, name)) setattr(ext_cls, name, field) return ext_cls(*args, **kwargs)
def settings(self): core_path = os.path.join(get_root_path('indico'), 'modules', 'events', 'themes.yaml') with open(core_path) as f: core_data = f.read() core_settings = yaml.safe_load(core_data) # YAML doesn't give us access to anchors so we need to include the base yaml. # Since duplicate keys are invalid (and may start failing in the future) we # rename them - this also makes it easy to throw them away after parsing the # file provided by a plugin. core_data = re.sub(r'^(\S+:)$', r'__core_\1', core_data, flags=re.MULTILINE) for plugin, path in values_from_signal( signals.plugin.get_event_themes_files.send(), return_plugins=True): with open(path) as f: data = f.read() settings = { k: v for k, v in yaml.safe_load(core_data + '\n' + data).viewitems() if not k.startswith('__core_') } # We assume there's no more than one theme plugin that provides defaults. # If that's not the case the last one "wins". We could reject this but it # is quite unlikely that people have multiple theme plugins in the first # place, even more so theme plugins that specify defaults. core_settings['defaults'].update(settings.get('defaults', {})) # Same for definitions - we assume plugin authors are responsible enough # to avoid using definition names that are likely to cause collisions. # Either way, if someone does this on purpose changes are good they want # to override a default style so let them do so... for name, definition in settings.get('definitions', {}).viewitems(): definition['plugin'] = plugin if definition.get('stylesheet'): definition['stylesheet'] = definition['stylesheet'] core_settings['definitions'][name] = definition return core_settings
def call_template_hook(*name, **kwargs): """Template function to let plugins add their own data to a template. :param name: The name of the hook. Only accepts one argument. :param as_list: Return a list instead of a concatenated string :param kwargs: Data to pass to the signal receivers. """ if len(name) != 1: raise TypeError('call_template_hook() accepts only one positional argument, {} given'.format(len(name))) name = name[0] as_list = kwargs.pop('as_list', False) values = [] for is_markup, priority, value in values_from_signal(signals.plugin.template_hook.send(unicode(name), **kwargs), single_value=True): if value: if is_markup: value = Markup(value) heappush(values, (priority, value)) if as_list: return [x[1] for x in values] else: return Markup(u'\n').join(x[1] for x in values) if values else u''
def _notify_registration(registration, template_name, to_managers=False, attach_rejection_reason=False): from indico.modules.events.registration.util import get_ticket_attachments attachments = [] regform = registration.registration_form tickets_handled = values_from_signal( signals.event.is_ticketing_handled.send(regform), single_value=True) if (not to_managers and regform.tickets_enabled and regform.ticket_on_email and not any(tickets_handled) and registration.state == RegistrationState.complete): attachments += get_ticket_attachments(registration) if not to_managers and registration.registration_form.attach_ical: event_ical = event_to_ical(registration.event) attachments.append(('event.ics', event_ical, 'text/calendar')) tpl = get_template_module(f'events/registration/emails/{template_name}', registration=registration, attach_rejection_reason=attach_rejection_reason) to_list = registration.email if not to_managers else registration.registration_form.manager_notification_recipients from_address = registration.registration_form.sender_address if not to_managers else None mail = make_email(to_list=to_list, template=tpl, html=True, from_address=from_address, attachments=attachments) user = session.user if session else None signals.core.before_notification_send.send( 'notify-registration', email=mail, registration=registration, template_name=template_name, attach_rejection_reason=attach_rejection_reason) send_email(mail, event=registration.registration_form.event, module='Registration', user=user, log_metadata={'registration_id': registration.id})
def display(cls, template_name, **params): from indico.modules.admin import RHAdminBase from indico.modules.core.settings import core_settings, social_settings title_parts = [cls.title] if cls.MANAGEMENT: title_parts.insert(0, _('Management')) elif isinstance(g.rh, RHAdminBase): title_parts.insert(0, _('Administration')) injected_bundles = values_from_signal( signals.plugin.inject_bundle.send(cls), as_list=True, multi_value_types=list) custom_js = list(current_app.manifest['__custom.js']) custom_css = list(current_app.manifest['__custom.css']) css_files = map(cls._fix_path, custom_css) js_files = map(cls._fix_path, custom_js) bundles = itertools.chain( (current_app.manifest[x] for x in cls._resolve_bundles() if x in current_app.manifest._entries), cls.additional_bundles['screen'], injected_bundles) print_bundles = itertools.chain( (current_app.manifest[x] for x in cls.print_bundles), cls.additional_bundles['print']) template = cls._prefix_template(template_name) return render_template(template, css_files=css_files, js_files=js_files, page_metadata=cls.page_metadata, bundles=bundles, print_bundles=print_bundles, site_name=core_settings.get('site_title'), social=social_settings.get_all(), page_title=' - '.join( unicode(x) for x in title_parts if x), **params)
def generate_event_component(event, user=None): """Generate an event icalendar component from an Indico event.""" uid = f'indico-event-{event.id}@{url_parse(config.BASE_URL).host}' component = generate_basic_component(event, uid) # add contact information contact_info = event.contact_emails + event.contact_phones if contact_info: component.add('contact', ';'.join(contact_info)) # add logo url if event is public if event.effective_protection_mode == ProtectionMode.public and event.has_logo: component.add('image', event.external_logo_url, {'VALUE': 'URI'}) # send description to plugins in case one wants to add anything to it data = {'description': component.get('description', '')} for update in values_from_signal(signals.event.metadata_postprocess.send( 'ical-export', event=event, data=data, user=user), as_list=True): data.update(update) component.add('description', data['description']) return component
def _notify_registration(registration, template, to_managers=False): attachments = None regform = registration.registration_form tickets_handled = values_from_signal( signals.event.is_ticketing_handled.send(regform), single_value=True) if (not to_managers and regform.tickets_enabled and regform.ticket_on_email and not any(tickets_handled) and registration.state == RegistrationState.complete): attachments = get_ticket_attachment(registration) template = get_template_module( 'events/registration/emails/{}'.format(template), registration=registration) to_list = registration.email if not to_managers else registration.registration_form.manager_notification_recipients from_address = registration.registration_form.sender_address if not to_managers else None mail = make_email(to_list=to_list, template=template, html=True, from_address=from_address, attachments=attachments) send_email(mail, event=registration.registration_form.event, module='Registration', user=session.user)
def test_values_from_signal_as_list(): vals = ('a', 'b', 'c') signal_response = _make_signal_response(vals) assert values_from_signal(signal_response, as_list=True) == list(vals) assert values_from_signal(signal_response) == set(vals)
def get_file_previewers(): return values_from_signal(signals.attachments.get_file_previewers.send())
def _checkProtection(self): # check users allowed by plugins if any(values_from_signal(signals.event.has_read_access.send(self._conf, user=session.user))): return RHConferenceBaseDisplay._checkProtection(self)
def can_manage(self, user, role=None, allow_admin=True, check_parent=True, explicit_role=False): """Checks if the user can manage the object. :param user: The :class:`.User` to check. May be None if the user is not logged in. :param: role: The management role that is needed for the check to succeed. If not specified, full management privs are required. May be set to the string ``'ANY'`` to check if the user has any management privileges. If the user has `full_access` privileges, he's assumed to have all possible roles. :param allow_admin: If admin users should always have access :param check_parent: If the parent object should be checked. In this case the role is ignored; only full management access is inherited to children. :param explicit_role: If the specified role should be checked explicitly instead of short-circuiting the check for Indico admins or managers. When this option is set to ``True``, the values of `allow_admin` and `check_parent` are ignored. This also applies if `role` is None in which case this argument being set to ``True`` is equivalent to `allow_admin` and `check_parent` being set to ``False``. """ if role is not None and role != 'ANY' and role not in get_available_roles( type(self)): raise ValueError("role '{}' is not valid for '{}' objects".format( role, type(self).__name__)) if user is None: # An unauthorized user is never allowed to perform management operations. # Not even signals may override this since management code generally # expects session.user to be not None. # XXX: Legacy modification keys are checked outside return False # Trigger signals for protection overrides rv = values_from_signal(signals.acl.can_manage.send( type(self), obj=self, user=user, role=role, allow_admin=allow_admin, check_parent=check_parent, explicit_role=explicit_role), single_value=True) if rv: # in case of contradictory results (shouldn't happen at all) # we stay on the safe side and deny access return all(rv) # Usually admins can access everything, so no need for checks if not explicit_role and allow_admin and user.is_admin: return True if any(user in entry.principal for entry in iter_acl(self.acl_entries) if entry.has_management_role( role, explicit=(explicit_role and role is not None))): return True if not check_parent or explicit_role: return False # the parent can be either an object inheriting from this # mixin or a legacy object with an AccessController parent = self.protection_parent if parent is None: # This should be the case for the top-level object, # i.e. the root category return False elif hasattr(parent, 'can_manage'): return parent.can_manage(user, allow_admin=allow_admin) elif hasattr(parent, 'canUserModify'): return parent.canUserModify(user.as_avatar) else: raise TypeError( 'protection_parent of {} is of invalid type {} ({})'.format( self, type(parent), parent))
def can_access(self, user, allow_admin=True): """Checks if the user can access the object. :param user: The :class:`.User` to check. May be None if the user is not logged in. :param allow_admin: If admin users should always have access """ # Trigger signals for protection overrides rv = values_from_signal(signals.acl.can_access.send( type(self), obj=self, user=user, allow_admin=allow_admin), single_value=True) if rv: # in case of contradictory results (shouldn't happen at all) # we stay on the safe side and deny access return all(rv) # Usually admins can access everything, so no need for checks if allow_admin and user and user.is_admin: return True if self.protection_mode == ProtectionMode.public: # if it's public we completely ignore the parent protection # this is quite ugly which is why it should only be allowed # in rare cases (e.g. events which might be in a protected # category but should be public nonetheless) return True elif self.protection_mode == ProtectionMode.protected: # if it's protected, we also ignore the parent protection # and only check our own ACL if user is None: return False elif any(user in entry.principal for entry in iter_acl(self.acl_entries)): return True elif isinstance(self, ProtectionManagersMixin): return self.can_manage(user, allow_admin=allow_admin) else: return False elif self.protection_mode == ProtectionMode.inheriting: # if it's inheriting, we only check the parent protection # unless `inheriting_have_acl` is set, in which case we # might not need to check the parents at all if (self.inheriting_have_acl and user is not None and any(user in entry.principal for entry in iter_acl(self.acl_entries))): return True # the parent can be either an object inheriting from this # mixin or a legacy object with an AccessController parent = self.protection_parent if parent is None: # This should be the case for the top-level object, # i.e. the root category, which shouldn't allow # ProtectionMode.inheriting as it makes no sense. raise TypeError('protection_parent of {} is None'.format(self)) elif hasattr(parent, 'can_access'): return parent.can_access(user, allow_admin=allow_admin) elif hasattr(parent, 'canAccess'): return parent.canAccess( AccessWrapper(user.as_avatar if user else None)) else: raise TypeError( 'protection_parent of {} is of invalid type {} ({})'. format(self, type(parent), parent)) else: # should never happen, but since this is a sensitive area # we better fail loudly if we have garbage raise ValueError('Invalid protection mode: {}'.format( self.protection_mode))
def is_ticket_blocked(self): """Check whether the ticket is blocked by a plugin""" return any( values_from_signal(signals.event.is_ticket_blocked.send(self), single_value=True))
def test_values_from_signal_skip_none(): vals = ('a', None, 'b', 'c') signal_response = _make_signal_response(vals) assert values_from_signal(signal_response, skip_none=False) == set(vals) assert values_from_signal(signal_response) == set(vals) - {None}
def test_values_from_signal_single_value(): vals = ('a', _make_gen('b', 'c')) signal_response = _make_signal_response(vals) assert values_from_signal(signal_response, single_value=True) == set(vals) assert values_from_signal(signal_response) == {'a', 'b', 'c'}
def __init__(self, rh, event_, **kwargs): custom_links = dict(values_from_signal(signals.event.timetable_buttons.send(self))) WPEventManagement.__init__(self, rh, event_, custom_links=custom_links, **kwargs)
def __init__(self, rh, conference, **kwargs): custom_links = dict(values_from_signal(signals.event.timetable_buttons.send(self))) WPConferenceModifBase.__init__(self, rh, conference, custom_links=custom_links, **kwargs)
def test_values_from_signal_return_plugins(): vals = ('a', 'b', 'c') signal_response = _make_signal_response(vals) + [(MagicMock(indico_plugin='foo'), 'd')] assert values_from_signal(signal_response, return_plugins=True) == set(zip([None] * 3, vals) + [('foo', 'd')]) assert values_from_signal(signal_response) == set(vals + ('d',))
def setup_jinja_customization(app): # add template customization paths provided by plugins paths = values_from_signal( signals.plugin.get_template_customization_paths.send()) app.jinja_env.loader.fs_loader.searchpath += sorted(paths)
def _build_event_api_data(self, event): can_manage = self.user is not None and event.can_manage(self.user) data = self._build_event_api_data_base(event) data.update({ '_fossil': self.fossils_mapping['event'].get(self._detail_level), 'categoryId': event.category_id, 'category': event.category.title, 'note': build_note_api_data(event.note), 'roomFullname': event.room_name, 'url': event.external_url, 'creationDate': self._serialize_date(event.created_dt), 'creator': self._serialize_person(event.creator, person_type='Avatar', can_manage=can_manage), 'hasAnyProtection': event.effective_protection_mode != ProtectionMode.public, 'roomMapURL': event.room.map_url if event.room else None, 'folders': build_folders_api_data(event), 'chairs': self._serialize_persons(event.person_links, person_type='ConferenceChair', can_manage=can_manage), 'material': build_material_legacy_api_data(event) + filter(None, [build_note_legacy_api_data(event.note)]) }) event_category_path = event.category.chain visibility = {'id': '', 'name': 'Everywhere'} if event.visibility is None: pass # keep default elif event.visibility == 0: visibility['name'] = 'Nowhere' elif event.visibility: try: path_segment = event_category_path[-event.visibility] except IndexError: pass else: visibility['id'] = path_segment['id'] visibility['name'] = path_segment['title'] data['visibility'] = visibility if can_manage: data['allowed'] = self._serialize_access_list(event) if self._detail_level in {'contributions', 'subcontributions'}: data['contributions'] = [] for contribution in event.contributions: include_subcontribs = self._detail_level == 'subcontributions' serialized_contrib = self._serialize_contribution(contribution, include_subcontribs) data['contributions'].append(serialized_contrib) elif self._detail_level == 'sessions': # Contributions without a session data['contributions'] = [] for contribution in event.contributions: if not contribution.session: serialized_contrib = self._serialize_contribution(contribution) data['contributions'].append(serialized_contrib) data['sessions'] = [] for session_ in event.sessions: data['sessions'].extend(self._build_session_api_data(session_)) if self._occurrences: data['occurrences'] = fossilize(self._calculate_occurrences(event, self._fromDT, self._toDT, pytz.timezone(config.DEFAULT_TIMEZONE)), {Period: IPeriodFossil}, tz=self._tz, naiveTZ=config.DEFAULT_TIMEZONE) # check whether the plugins want to add/override any data for update in values_from_signal( signals.event.metadata_postprocess.send('http-api', event=event, data=data), as_list=True): data.update(update) return data