class ICategoryMappingRowSchema(Interface): local_category_id = schema.TextLine(title=_(u"Local category id")) global_category_id = schema.Choice( title=_(u"Global category"), vocabulary="plonemeeting.portal.vocabularies.global_categories", required=True, )
class ImportMeetingForm(AutoExtensibleForm, Form): """ """ schema = IImportMeetingForm ignoreContext = True label = _(u"Meeting import form") description = _(u"Choose the meeting you want to import in the portal.") @button.buttonAndHandler(_(u"Import")) def handle_apply(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return institution = self.context meeting_uid = data.get("meeting") _sync_meeting(institution, meeting_uid, self.request) @button.buttonAndHandler(_(u"Cancel")) def handle_cancel(self, action): """ """ self.request.response.redirect(self.context.absolute_url())
def handle_institution_creation(obj, event): current_lang = api.portal.get_default_language()[:2] institution_title = obj.title # Configure manager group & local permissions group_id = format_institution_managers_group_id(obj) group_title = "{0} Institution Managers".format(institution_title) api.group.create(groupname=group_id, title=group_title) obj.manage_setLocalRoles(group_id, ["Institution Manager", "Contributor"]) # Create meetings faceted folder meetings = create_faceted_folder( obj, translate(_(u"Meetings"), target_language=current_lang)) alsoProvides(meetings, IMeetingsFolder) IFacetedLayout(meetings).update_layout("faceted-preview-meeting") # Create items faceted folder items = create_faceted_folder( obj, translate(_(u"Decisions"), target_language=current_lang)) alsoProvides(items, IItemsFolder) IFacetedLayout(items).update_layout("faceted-preview-meeting-items") request = getRequest() if request: # Request can be `None` during test setup request.response.redirect(obj.absolute_url())
def post_install(context): """Post install script""" portal = api.portal.get() current_lang = api.portal.get_default_language()[:2] faceted_config = "/faceted/config/items.xml" if "config" in portal.objectIds(): return remove_left_portlets() remove_right_portlets() cleanup_contents() # Create global config folder config_folder = api.content.create( container=portal, type="Folder", title=translate(_(u"Configuration folder"), target_language=current_lang), id=CONFIG_FOLDER_ID, ) config_folder.exclude_from_nav = True # Create global faceted folder faceted = create_faceted_folder( config_folder, translate(_(u"Faceted"), target_language=current_lang), id=FACETED_FOLDER_ID, ) subtyper = faceted.restrictedTraverse("@@faceted_subtyper") subtyper.enable() with open(os.path.dirname(__file__) + faceted_config, "rb") as faceted_config: faceted.unrestrictedTraverse("@@faceted_exportimport").import_xml( import_file=faceted_config)
class ItemsSortWidget(Widget): """ Sort items with custom (multiple) sort orders """ widget_type = "items_sort" widget_label = _("Items sort order") groups = (DefaultSchemata,) index = ViewPageTemplateFile("sort.pt") def query(self, form): """ Sort items by meeting date (desc) and by item number (asc) """ # XXX avoid double sort_on when we selected a meeting # this is not necessary and it some case, produce weird results if "seance" in form: query = { "sort_on": ["sortable_number"], "sort_order": ["ascending"], } else: query = { "sort_on": ["linkedMeetingDate", "sortable_number"], "sort_order": ["descending", "ascending"], } return query
class IImportMeetingForm(Interface): """ """ meeting = schema.Choice( title=_(u"Meeting"), vocabulary="plonemeeting.portal.vocabularies.remote_meetings", required=True, )
class IMeeting(model.Schema): """ Marker interface and Dexterity Python Schema for Meeting """ title = schema.TextLine(title=plone_(u"Title"), required=True, readonly=True) plonemeeting_uid = schema.TextLine( title=_(u"UID Plonemeeting"), required=True, readonly=True, ) form.write_permission(date_time=ManagePortal) date_time = schema.Datetime(title=plone_(u"Date"), required=True, readonly=False,) custom_info = RichText(title=_(u"Custom info"), required=False) plonemeeting_last_modified = schema.Datetime( title=_(u"Last modification in iA.Delib"), required=True, readonly=True )
class IItem(model.Schema): """ Marker interface and Dexterity Python Schema for Item """ dexteritytextindexer.searchable("formatted_title") formatted_title = RichText(title=plone_(u"Title"), required=False, readonly=True) number = schema.TextLine(title=_(u"Item number"), required=True, readonly=True) sortable_number = schema.Int(title=_(u"Sortable Item number"), required=True, readonly=True) plonemeeting_uid = schema.TextLine(title=_(u"UID Plonemeeting"), required=True, readonly=True) representatives_in_charge = schema.List( value_type=schema.Choice( vocabulary="plonemeeting.portal.vocabularies.representatives"), title=_(u"Representative group in charge"), required=False, readonly=True, ) additional_data = RichText(title=_(u"Additional data"), required=False, readonly=True) dexteritytextindexer.searchable("decision") decision = RichText(title=_(u"Decision"), required=False, readonly=True) category = schema.Choice( vocabulary="plonemeeting.portal.vocabularies.global_categories", title=_(u"Category"), required=False, readonly=True, ) custom_info = RichText(title=_(u"Custom info"), required=False) plonemeeting_last_modified = schema.Datetime( title=_(u"Last modification in iA.Delib"), required=True, readonly=True)
def format_meeting_date_and_state(date, state_id, format="%d %B %Y (%H:%M)", lang=None): """ Format the meeting date while managing translations of months and weekdays :param date: Datetime reprensenting the meeting date :param format: format of the returning date. See strftime for directives. """ MONTHS_IDS = { 1: "month_jan", 2: "month_feb", 3: "month_mar", 4: "month_apr", 5: "month_may", 6: "month_jun", 7: "month_jul", 8: "month_aug", 9: "month_sep", 10: "month_oct", 11: "month_nov", 12: "month_dec", } WEEKDAYS_IDS = { 0: "weekday_mon", 1: "weekday_tue", 2: "weekday_wed", 3: "weekday_thu", 4: "weekday_fri", 5: "weekday_sat", 6: "weekday_sun", } format = format.replace("%B", "[month]").replace("%A", "[weekday]") date_str = safe_unicode(date.strftime(format)) if not lang: lang = api.portal.get_tool("portal_languages").getDefaultLanguage() # in some cases month are not properly translated using sublocales lang = lang.split("-")[0] if u"[month]" in date_str: month = translate(MONTHS_IDS[date.month], domain="plonelocales", target_language=lang) date_str = date_str.replace("[month]", month) if u"[weekday]" in date_str: weekday = translate(WEEKDAYS_IDS[date.weekday()], domain="plonelocales", target_language=lang) date_str = date_str.replace(u"[weekday]", weekday) state = translate(_(state_id), target_language=lang) return "{0} — {1}".format(date_str, state)
def sync_meeting(institution, meeting_uid, force=False): """ synchronizes a single meeting through ia.Delib web services (Rest/JSON) :param force: Should force reload items. Default False :param institution: current institution :param meeting_uid: the uid of the meeting to fetch from ia.Delib :return: the sync status, the new meeting's UID status may be an error or a short summary of what happened. UID may be none in case of error from the web service """ url = get_api_url_for_meetings(institution, meeting_UID=meeting_uid) response = _call_delib_rest_api(url, institution) json_meeting = json.loads(response.text) if json_meeting.get("items_total") != 1: raise ValueError( _(u"Unexpected meeting count in webservice response !")) meeting = sync_meeting_data(institution, json_meeting.get("items")[0]) url = get_api_url_for_meeting_items(institution, meeting_UID=meeting_uid) response = _call_delib_rest_api(url, institution) json_items = json.loads(response.text) results = sync_items_data(meeting, json_items, institution, force) status_msg = _( u"meeting_imported", default=u"Meeting imported ! " u"${created} created items, " u"${modified} modified items, " u"${deleted} deleted items.", mapping={ u"created": results["created"], u"modified": results["modified"], u"deleted": results["deleted"], }, ) current_lang = api.portal.get_default_language()[:2] status = translate(status_msg, target_language=current_lang) return status, meeting.UID()
def _call_delib_rest_api(url, institution): # pragma: no cover start_time = time.time() logger.info("REST API CALL TO {0}".format(url)) response = requests.get(url, auth=(institution.username, institution.password), headers=API_HEADERS) if response.status_code != 200: raise ValueError(_(u"Webservice connection error !")) msg, seconds = end_time(start_time, "REST API CALL MADE IN ", True) if seconds > 1: logger.warning(msg) else: logger.info(msg) return response
class NavigationRootPathWidget(Widget): """ Filter on objects from current navigation root """ widget_type = "navigation_root_path" widget_label = _("Navigation root path") groups = (DefaultSchemata,) index = ViewPageTemplateFile("root_path.pt") def query(self, form): """ Returns only objects from current navigation root """ nav_root = api.portal.get_navigation_root(self.context) path = "/".join(nav_root.getPhysicalPath()) query = {"path": {"query": path}} return query
def __call__(self): # Don't redirect if user can edit institution # Don't use api.user.has_permission since the method breaks robot tests if api.user.get_permissions( obj=self.context).get("Modify portal content"): api.portal.show_message( _("You see this page because you have permissions to edit it. " "Otherwise you would have been redirected to Meetings folder. " "To see the Meetings view, click on Meetings folder."), request=self.request, type="info", ) return super(InstitutionView, self).__call__() institution = api.portal.get_navigation_root(self.context) meeting_folder_brains = api.content.find( context=institution, object_provides=IMeetingsFolder.__identifier__) if not meeting_folder_brains: return super(InstitutionView, self).__call__() url = meeting_folder_brains[0].getURL() self.request.response.redirect(url) return ""
class InvalidColorParameters(ValidationError): """Exception for invalid url parameters""" __doc__ = _( u"Invalid color parameter, the value should be a correct hexadecimal color" )
# -*- coding: utf-8 -*- from plonemeeting.portal.core import _ CONFIG_FOLDER_ID = "config" FACETED_FOLDER_ID = "faceted" CONTENTS_TO_CLEAN = ["Members", "events", "news"] PLONEMEETING_API_MEETING_TYPE = "meeting" PLONEMEETING_API_ITEM_TYPE = "item" API_HEADERS = {"Content-type": "application/json", "Accept": "application/json"} # keep those ids in translations files REVIEW_STATES_IDS = [_("private"), _("in_project"), _("decision")] DEFAULT_CATEGORY_IA_DELIB_FIELD = "category" CATEGORY_IA_DELIB_FIELDS = ( ("category", _("category")), ("classifier", _("classifier")), )
class IInstitution(model.Schema): """ Marker interface and Dexterity Python Schema for Institution """ plonemeeting_url = schema.URI(title=_(u"Plonemeeting URL"), required=False) username = schema.TextLine(title=_(u"Username"), required=False) password = schema.TextLine(title=_(u"Password"), required=False) meeting_config_id = schema.TextLine(title=_(u"Meeting config ID"), required=False) project_decision_disclaimer = RichText( title=_(u"Project decision disclaimer"), required=False, defaultFactory=default_translator( _(u"default_in_project_disclaimer", default="")), ) additional_meeting_query_string_for_list = schema.TextLine( title=_(u"Additional Meeting query string for list"), required=False, constraint=validate_url_parameters, ) additional_published_items_query_string = schema.TextLine( title=_(u"Additional Published Items query string"), required=False, constraint=validate_url_parameters, ) # Formatting fieldset model.fieldset( "formatting", label=_(u"Formatting"), fields=[ "item_title_formatting_tal", "item_decision_formatting_tal", "item_additional_data_formatting_tal", "info_annex_formatting_tal", ], ) item_title_formatting_tal = schema.TextLine( title=_(u"Item title formatting tal expression. " u"If empty the default title will be used"), required=False, ) item_decision_formatting_tal = schema.TextLine( title=_(u"Item decision formatting tal expression"), required=True, default="python: json['decision']['data']", ) item_additional_data_formatting_tal = schema.TextLine( title=_(u"Item additional data formatting tal expression"), required=False) info_annex_formatting_tal = schema.TextLine( title=_(u"Info annex formatting tal expression"), required=False) # Mapping fieldset model.fieldset( "mapping", label=_(u"Mapping"), fields=[ "delib_category_field", "categories_mappings", "representatives_mappings", ], ) delib_category_field = schema.Choice( title=_(u"iA.Delib field to use for category mapping"), vocabulary="plonemeeting.portal.vocabularies.delib_category_fields", required=True, default=DEFAULT_CATEGORY_IA_DELIB_FIELD) directives.widget("categories_mappings", DataGridFieldFactory, allow_reorder=True) categories_mappings = schema.List( title=_(u"Categories mappings"), value_type=DictRow(title=u"Category mapping", schema=ICategoryMappingRowSchema), required=False, ) directives.widget("representatives_mappings", DataGridFieldFactory, allow_reorder=True) representatives_mappings = schema.List( title=_(u"Representatives mappings"), value_type=DictRow(title=u"Representative mapping", schema=IRepresentativeMappingRowSchema), required=False, ) # Styling fieldset model.fieldset( "style", label=_(u"Styling"), fields=[ "logo", "header_color", "nav_color", "nav_text_color", "links_color", "footer_color", "footer_text_color", ], ) logo = NamedBlobImage(title=_(u"Logo"), required=False) directives.widget("header_color", ColorSelectFieldWidget) header_color = schema.TextLine( title=_("Header color"), required=True, default="#ffffff", constraint=validate_color_parameters, ) directives.widget("nav_color", ColorSelectFieldWidget) nav_color = schema.TextLine( title=_("Navigation bar color"), required=True, default="#007bb1", # Plone blue constraint=validate_color_parameters, ) directives.widget("nav_text_color", ColorSelectFieldWidget) nav_text_color = schema.TextLine( title=_("Navigation bar text color"), required=True, default="#ffffff", constraint=validate_color_parameters, ) directives.widget("links_color", ColorSelectFieldWidget) links_color = schema.TextLine( title=_("Links text color"), required=True, default="#007bb1", constraint=validate_color_parameters, ) directives.widget("footer_color", ColorSelectFieldWidget) footer_color = schema.TextLine( title=_("Footer color"), required=True, default="#2e3133", constraint=validate_color_parameters, ) directives.widget("footer_text_color", ColorSelectFieldWidget) footer_text_color = schema.TextLine( title=_("Footer text color"), required=True, default="#cccccc", constraint=validate_color_parameters, )
class IRepresentativeMappingRowSchema(Interface): representative_key = schema.TextLine(title=_(u"Representative key")) representative_value = schema.TextLine(title=_(u"Representative value")) representative_long_value = schema.TextLine( title=_(u"Representative long values")) active = schema.Bool(title=_(u"Active"), default=True)
def validate(password, data): if len(password) < 8: return _(u"Passwords must be at least 8 characters in length.")
class InvalidUrlParameters(ValidationError): """Exception for invalid url parameters""" __doc__ = _(u"Invalid url parameters, the value should start with '&'")