def resolve_locales(self, args, context, info): locales = DummyGoogleTranslationService.target_localesC() asPosixLocale = DummyGoogleTranslationService.asPosixLocale target_locale = strip_country( models.Locale.get_or_create(args.get('lang'))) labels = models.LocaleLabel.names_of_locales_in_locale( [strip_country(asPosixLocale(loc)) for loc in locales], target_locale) return [ Locale(locale_code=locale_code, label=label) for locale_code, label in sorted(labels.items(), key=lambda entry: entry[1]) ]
def get_locale_from_request(request, session=None, user=None): if user is None: user_id = request.authenticated_userid or Everyone if user_id != Everyone: user = User.get(user_id) session = session or User.default_db if user: if '_LOCALE_' in request.params: locale = request.params['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Parameter) elif '_LOCALE_' in request.cookies: locale = request.cookies['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Cookie) else: # uses my locale negotiator locale = request.locale_name process_locale(locale, user, session, LanguagePreferenceOrder.OS_Default) else: locale = request.localizer.locale_name target_locale = Locale.get_or_create( strip_country(locale), session) return target_locale
def resolve_locales(self, args, context, info): locales = DummyGoogleTranslationService.target_localesC() asPosixLocale = DummyGoogleTranslationService.asPosixLocale lang = args.get('lang') target_locale = strip_country(models.Locale.get_or_create(lang)) locales_without_country = [ strip_country(asPosixLocale(loc)) for loc in locales ] labels_en = models.LocaleLabel.names_of_locales_in_locale( locales_without_country, models.Locale.get_or_create('en')) if lang == 'en': labels = labels_en else: # labels can be a subset of labels_en or completely empty # if there is no translation of the locales for target_locale labels = models.LocaleLabel.names_of_locales_in_locale( locales_without_country, target_locale) result = [] for locale_code, label_en in labels_en.items(): label = labels.get(locale_code, label_en) result.append(Locale(locale_code=locale_code, label=label)) return sorted(result, key=lambda locale: locale.label)
def resolve_locales(self, args, context, info): locales = DummyGoogleTranslationService.target_localesC() asPosixLocale = DummyGoogleTranslationService.asPosixLocale lang = args.get('lang') target_locale = strip_country( models.Locale.get_or_create(lang)) locales_without_country = [strip_country(asPosixLocale(loc)) for loc in locales] labels_en = models.LocaleLabel.names_of_locales_in_locale( locales_without_country, models.Locale.get_or_create('en')) if lang == 'en': labels = labels_en else: # labels can be a subset of labels_en or completely empty # if there is no translation of the locales for target_locale labels = models.LocaleLabel.names_of_locales_in_locale( locales_without_country, target_locale) result = [] for locale_code, label_en in labels_en.items(): label = labels.get(locale_code, label_en) result.append(Locale(locale_code=locale_code, label=label)) return sorted(result, key=lambda locale: locale.label)
def base_admin_view(request): """The Base admin view, for frontend urls""" user_id = authenticated_userid(request) or Everyone if user_id == Everyone: raise HTTPUnauthorized() context = get_default_context(request) session = Discussion.default_db preferences = Preferences.get_default_preferences(session) user = User.get(user_id) if '_LOCALE_' in request.cookies: locale = request.cookies['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Cookie) elif '_LOCALE_' in request.params: locale = request.params['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Parameter) else: locale = request.locale_name process_locale(locale, user, session, LanguagePreferenceOrder.OS_Default) target_locale = strip_country(locale) locale_labels = json.dumps( DummyGoogleTranslationService.target_locale_labels_cls(target_locale)) context['translation_locale_names_json'] = locale_labels role_names = [x for (x, ) in session.query(Role.name).all()] permission_names = [x for (x, ) in session.query(Permission.name).all()] context['role_names'] = json.dumps(role_names) context['permission_names'] = json.dumps(permission_names) context['discussion'] = PseudoDiscussion() response = render_to_response('../../templates/adminIndex.jinja2', context, request=request) # Prevent caching the home, especially for proper login/logout response.cache_control.max_age = 0 response.cache_control.prevent_auto = True return response
def populate_from_langstring(ls, data, dataPropName): langs = index_languages() if ls: others = [] for entry in ls.entries: if not entry.value: continue locale_code = strip_country(entry.base_locale) if locale_code == 'zh_Hans': locale_code = 'zh_CN' if locale_code in langs: dataPropNameL = "_".join((dataPropName, locale_code)) # Japanese for example is stored in db with a html entity for # each character. # unescape to transform html entities back to characters data[dataPropNameL] = unescape(entry.value) else: others.append(unescape(entry.value)) if others: dataPropNameL = dataPropName + "_other" data[dataPropNameL] = ' '.join(others)
def populate_from_langstring(ls, data, dataPropName): langs = index_languages() if ls: others = [] for entry in ls.entries: if not entry.value: continue locale_code = strip_country(entry.base_locale) if locale_code == 'zh_Hans': locale_code = 'zh_CN' if locale_code in langs: dataPropNameL = "_".join((dataPropName, locale_code)) # Japanese for example is stored in db with a html entity for # each character. # unescape to transform html entities back to characters data[dataPropNameL] = unescape(entry.value) else: others.append(unescape(entry.value)) if others: dataPropNameL = dataPropName + "_other" data[dataPropNameL] = ' '.join(others)
def upgrade(pyramid_env): from assembl import models as m db = m.get_session_maker()() with transaction.manager: for (locales, pref_id, pref) in db.execute( """SELECT discussion.preferred_locales, preferences.id, preferences."values" FROM discussion JOIN preferences ON discussion.preferences_id = preferences.id"""): if locales is not None: pref = {} if pref is None else json.loads(pref) pref["preferred_locales"] = [ strip_country(l) for l in locales.split()] db.execute(sa.text( 'UPDATE preferences SET "values"=:val where id=:pref_id' ).bindparams( val=json.dumps(pref), pref_id=pref_id)) mark_changed() with context.begin_transaction(): op.drop_column("discussion", "preferred_locales")
def upgrade(pyramid_env): from assembl import models as m db = m.get_session_maker()() with transaction.manager: for (locales, pref_id, pref) in db.execute( """SELECT discussion.preferred_locales, preferences.id, preferences."values" FROM discussion JOIN preferences ON discussion.preferences_id = preferences.id"""): if locales is not None: pref = {} if pref is None else json.loads(pref) pref["preferred_locales"] = [ strip_country(l) for l in locales.split() ] db.execute( sa.text( 'UPDATE preferences SET "values"=:val where id=:pref_id' ).bindparams(val=json.dumps(pref), pref_id=pref_id)) mark_changed() with context.begin_transaction(): op.drop_column("discussion", "preferred_locales")
def get_opengraph_locale(request): """ If there is a user logged in, returns his preferred locale If not, returns the first preferred locale of the discussion Otherwise, sets locale to fr """ from ..auth.util import get_user, get_current_discussion from assembl.lib.locale import strip_country user = get_user(request) discussion = get_current_discussion() if not user and not discussion: locale = "fr" elif user: try: locale = user.language_preference[0].locale.code locale = strip_country(locale) except: if discussion: locale = discussion.preferences['preferred_locales'][0] else: locale = "fr" elif discussion and user is None: locale = discussion.preferences['preferred_locales'][0] return locale
def get_locale_from_request(request, session=None, user=None): if user is None: user_id = request.authenticated_userid or Everyone if user_id != Everyone: user = User.get(user_id) session = session or User.default_db if user: if '_LOCALE_' in request.params: locale = request.params['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Parameter) elif '_LOCALE_' in request.cookies: locale = request.cookies['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Cookie) else: # uses my locale negotiator locale = request.locale_name process_locale(locale, user, session, LanguagePreferenceOrder.OS_Default) else: locale = request.localizer.locale_name target_locale = Locale.get_or_create(strip_country(locale), session) return target_locale
def get_opengraph_locale(request): """ If there is a user logged in, returns his preferred locale If not, returns the first preferred locale of the discussion Otherwise, sets locale to fr """ from ..auth.util import get_user, get_current_discussion from assembl.lib.locale import strip_country user = get_user(request) discussion = get_current_discussion() if not user and not discussion: locale = "fr" elif user: try: locale = user.language_preference[0].locale.code locale = strip_country(locale) except: if discussion: locale = discussion.preferences['preferred_locales'][0] else: locale = "fr" elif discussion and user is None: locale = discussion.preferences['preferred_locales'][0] return locale
class Preferences(MutableMapping, Base, NamedClassMixin): """ Cascading preferences """ __metaclass__ = DeclarativeAbstractMeta __tablename__ = "preferences" BASE_PREFS_NAME = "default" id = Column(Integer, primary_key=True) name = Column(CoerceUnicode, nullable=False, unique=True) cascade_id = Column(Integer, ForeignKey(id), nullable=True) pref_json = Column("values", Text()) # JSON blob cascade_preferences = relationship("Preferences", remote_side=[id]) @classmethod def get_naming_column_name(self): return "name" @classmethod def get_by_name(cls, name=None, session=None): name = name or cls.BASE_PREFS_NAME session = session or cls.default_db return session.query(cls).filter_by(name=name).first() @classmethod def get_default_preferences(cls, session=None): return cls.get_by_name('default', session) or cls(name='default') @classmethod def get_discussion_conditions(cls, discussion_id): # This is not a DiscussionBoundBase, but protocol is otherwise useful from .discussion import Discussion return ((cls.id == Discussion.preferences_id), (Discussion.id == discussion_id)) @classmethod def init_from_settings(cls, settings): """Initialize some preference values""" from ..auth.social_auth import get_active_auth_strategies # TODO: Give linguistic names to social auth providers. active_strategies = { k: k for k in get_active_auth_strategies(settings) } active_strategies[''] = _("No special authentication") cls.preference_data['authorization_server_backend'][ 'scalar_values'] = active_strategies @property def local_values_json(self): values = {} if self.pref_json: values = json.loads(self.pref_json) assert isinstance(values, dict) return values @local_values_json.setter def local_values_json(self, val): assert isinstance(val, dict) self.pref_json = json.dumps(val) @property def values_json(self): if not self.cascade_preferences: return self.local_values_json values = self.cascade_preferences.values_json values.update(self.local_values_json) return values def _get_local(self, key): if key not in self.preference_data: raise KeyError("Unknown preference: " + key) values = self.local_values_json if key in values: value = values[key] return True, value return False, None def get_local(self, key): exists, value = self._get_local(key) if exists: return value def __getitem__(self, key): if key == 'name': return self.name if key == '@extends': return (self.uri_generic(self.cascade_id) if self.cascade_id else None) exists, value = self._get_local(key) if exists: return value elif self.cascade_id: return self.cascade_preferences[key] if key == "preference_data": return self.get_preference_data_list() return self.get_preference_data()[key].get("default", None) def __len__(self): return len(self.preference_data_list) + 2 def __iter__(self): return chain(self.preference_data_key_list, ('name', '@extends')) def __contains__(self, key): return key in self.preference_data_key_set def __delitem__(self, key): values = self.local_values_json if key in values: oldval = values[key] del values[key] self.local_values_json = values return oldval def __setitem__(self, key, value): if key == 'name': old_value = self.name self.name = unicode(value) return old_value elif key == '@extends': old_value = self.get('@extends') new_pref = self.get_instance(value) if new_pref is None: raise KeyError("Does not exist:" + value) self.cascade_preferences = new_pref return old_value if key not in self.preference_data_key_set: raise KeyError("Unknown preference: " + key) values = self.local_values_json old_value = values.get(key, None) value = self.validate(key, value) values[key] = value self.local_values_json = values return old_value def can_edit(self, key, permissions=(P_READ, ), pref_data=None): if P_SYSADMIN in permissions: if key == 'name' and self.name == self.BASE_PREFS_NAME: # Protect the base name return False return True if key in ('name', '@extends', 'preference_data'): # TODO: Delegate permissions. return False if key not in self.preference_data_key_set: raise KeyError("Unknown preference: " + key) if pref_data is None: pref_data = self.get_preference_data()[key] req_permission = pref_data.get('modification_permission', P_ADMIN_DISC) if req_permission not in permissions: return False return True def safe_del(self, key, permissions=(P_READ, )): if not self.can_edit(key, permissions): raise HTTPUnauthorized("Cannot delete " + key) del self[key] def safe_set(self, key, value, permissions=(P_READ, )): if not self.can_edit(key, permissions): raise HTTPUnauthorized("Cannot edit " + key) self[key] = value def validate(self, key, value, pref_data=None): if pref_data is None: pref_data = self.get_preference_data()[key] validator = pref_data.get('backend_validator_function', None) if validator: # This has many points of failure, but all failures are meaningful. module, function = validator.rsplit(".", 1) from importlib import import_module mod = import_module(module) try: value = getattr(mod, function)(value) if value is None: raise ValueError("Empty value after validation") except Exception as e: raise ValueError(e.message) data_type = pref_data.get("value_type", "json") return self.validate_single_value(key, value, pref_data, data_type) def validate_single_value(self, key, value, pref_data, data_type): # TODO: Validation for the datatypes. # base_type: (bool|json|int|string|text|scalar|url|email|domain|locale|langstr|permission|role) # type: base_type|list_of_(type)|dict_of_(base_type)_to_(type) if data_type.startswith("list_of_"): assert isinstance(value, (list, tuple)), "Not a list" return [ self.validate_single_value(key, val, pref_data, data_type[8:]) for val in value ] elif data_type.startswith("dict_of_"): assert isinstance(value, (dict)), "Not a dict" key_type, value_type = data_type[8:].split("_to_", 1) assert "_" not in key_type return { self.validate_single_value(key, k, pref_data, key_type): self.validate_single_value(key, v, pref_data, value_type) for (k, v) in value.iteritems() } elif data_type == "langstr": # Syntactic sugar for dict_of_locale_to_string assert isinstance(value, (dict)), "Not a dict" return { self.validate_single_value(key, k, pref_data, "locale"): self.validate_single_value(key, v, pref_data, "string") for (k, v) in value.iteritems() } elif data_type == "bool": assert isinstance(value, bool), "Not a boolean" elif data_type == "int": assert isinstance(value, int), "Not an integer" elif data_type == "json": # Will raise a JSONDecodeError if not a valid JSON json.loads(value) else: assert isinstance(value, (str, unicode)), "Not a string" if data_type in ("string", "text"): pass elif data_type == "scalar": assert value in pref_data.get( "scalar_values", ()), ("value not allowed: " + value) elif data_type == "url": condition = False parsed_val = urlparse(value) val = parsed_val.netloc while not condition: # Whilst not an address, requested feature if value in ("*", ): condition = True break elif not bool(parsed_val.scheme): # Must have a scheme, as defined a definition of a URI break elif not val.strip(): # No empty strings allowed break elif is_valid_ipv4_address(val): condition = True break elif is_valid_ipv6_address(val): condition = True break else: # Must be a regular URL then. TODO: Check that the location has a DNS record condition = True break assert condition, "Not a valid URL. Must follow the specification of a URI." elif data_type == "email": from pyisemail import is_email assert is_email(value), "Not an email" elif data_type == "locale": pass # TODO elif data_type == "permission": assert value in ASSEMBL_PERMISSIONS elif data_type == "role": if value not in SYSTEM_ROLES: from .auth import Role assert self.db.query(Role).filter_by( name=value).count() == 1, "Unknown role" elif data_type == "domain": from pyisemail.validators.dns_validator import DNSValidator v = DNSValidator() assert v.is_valid(value), "Not a valid domain" value = value.lower() else: raise RuntimeError("Invalid data_type: " + data_type) return value def generic_json(self, view_def_name='default', user_id=Everyone, permissions=(P_READ, ), base_uri='local:'): # TODO: permissions values = self.local_values_json values['name'] = self.name if self.cascade_preferences: values['@extends'] = self.cascade_preferences.name values['@id'] = self.uri() return values def _do_update_from_json(self, json, parse_def, aliases, context, permissions, user_id, duplicate_handling=None, jsonld=None): for key, value in json.iteritems(): if key == '@id': if value != self.uri(): raise RuntimeError("Wrong id") else: self[key] = value return self def __hash__(self): return Base.__hash__(self) @classproperty def property_defaults(cls): return { p['id']: p.get("default", None) for p in cls.preference_data_list } def get_preference_data(self): if self.cascade_id: base = self.cascade_preferences.get_preference_data() else: base = self.preference_data exists, patch = self._get_local("preference_data") if exists: return merge_json(base, patch) else: return base def get_preference_data_list(self): data = self.get_preference_data() keys = self.preference_data_key_list return [data[key] for key in keys] crud_permissions = CrudPermissions(create=P_SYSADMIN, update=P_ADMIN_DISC) # This defines the allowed properties and their data format # Each preference metadata has the following format: # id (the key for the preference as a dictionary) # name (for interface) # description (for interface, hover help) # value_type: given by the following grammar: # base_type = (bool|json|int|string|text|scalar|address|url|email|domain|locale|langstr|permission|role) # type = base_type|list_of_(type)|dict_of_(base_type)_to_(type) # more types may be added, but need to be added to both frontend and backend # show_in_preferences: Do we always hide this preference? # modification_permission: What permission do you need to change that preference? # (default: P_DISCUSSION_ADMIN) # allow_user_override: Do we allow users to have their personal value for that permission? # if so what permission is required? (default False) # scalar_values: "{value: "label"}" a dictionary of permitted options for a scalar value type # default: the default value # item_default: the default value for new items in a list_of_T... or dict_of_BT_to_T... preference_data_list = [ # Languages used in the discussion. { "id": "preferred_locales", "value_type": "list_of_locale", "name": _("Languages used"), "description": _("All languages expected in the discussion"), "allow_user_override": None, "item_default": "en", "default": [ strip_country(x) for x in config.get_config().get( 'available_languages', 'fr en').split() ] }, # Whether the discussion uses the new React landing page { "id": "landing_page", "value_type": "bool", "name": _("Use landing page"), "description": _("Are users directed to the landing page and phases at login, or diretly to the debate" ), "allow_user_override": None, "default": False, }, # full class name of translation service to use, if any # e.g. assembl.nlp.translate.GoogleTranslationService { "id": "translation_service", "name": _("Translation service"), "value_type": "scalar", "scalar_values": { "": _("No translation"), "assembl.nlp.translation_service.DummyTranslationServiceTwoSteps": _("Dummy translation service (two steps)"), "assembl.nlp.translation_service.DummyTranslationServiceOneStep": _("Dummy translation service (one step)"), "assembl.nlp.translation_service.DummyTranslationServiceTwoStepsWithErrors": _("Dummy translation service (two steps) with errors"), "assembl.nlp.translation_service.DummyTranslationServiceOneStepWithErrors": _("Dummy translation service (one step) with errors"), "assembl.nlp.translation_service.GoogleTranslationService": _("Google Translate") }, "description": _("Translation service"), "allow_user_override": None, "modification_permission": P_SYSADMIN, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": "" }, # Simple view panel order, eg NIM or NMI { "id": "simple_view_panel_order", "name": _("Panel order in simple view"), "value_type": "scalar", "scalar_values": { "NIM": _("Navigation, Idea, Messages"), "NMI": _("Navigation, Messages, Idea") }, "description": _("Order of panels"), "allow_user_override": P_READ, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": "NMI" }, # Parameters for frontend settings, for experimentation purposes. # What is put there should become separate parameters for typechecking { "id": "extra_json", "name": _("Extra JSON parameters"), "value_type": "json", "description": _("Parameters for quick frontend settings"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": {} }, # Discussion terms of use { "id": "terms_of_use_url", "name": _("Terms of use URL"), "value_type": "url", "description": _("URL of a document presenting terms of use for the discussion"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": None }, # Discussion Video { "id": "video_url", "name": _("Video URL"), "value_type": "url", "description": _("URL of a video presenting the discussion"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": None }, # Title in the tab { "id": "tab_title", "name": _("Tab title"), "value_type": "string", "description": _("Title which appears on the tab, by default assembl"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": "" }, # Discussion Video description { "id": "video_description", "name": _("Video description"), "value_type": "dict_of_locale_to_text", "description": _("Description of the video presenting the discussion"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": None }, # Allow social sharing { "id": "social_sharing", "name": _("Social sharing"), "value_type": "bool", # "scalar_values": {value: "label"}, "description": _("Show the share button on posts and ideas"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": True }, # Public or private social Sharing { "id": "private_social_sharing", "name": _("Private social sharing"), "value_type": "bool", "description": _("Publicizing or privatizing social sharing"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": True, }, # Show generic error message { "id": "generic_errors", "name": _("Generic Errors"), "value_type": "bool", "description": _("Display a generic error message."), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": config.get_config().get('assembl.generic_errors'), }, # Extra data from social fields to put in CSV reports { "id": "extra_csv_data", "name": _("Extra data for CSV reports"), "value_type": "dict_of_string_to_langstr", # "scalar_values": {value: "label"}, "description": _("data taken from social_auth's extra_data to add to CSV reports" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, "backend_validator_function": "assembl.models.social_data_extraction.validate_json_paths", "item_default": { "": { "en": "" } }, "default": {} # for development }, # Require virus check { "id": "requires_virus_check", "name": _("Require anti-virus check"), "value_type": "bool", # "scalar_values": {value: "label"}, "description": _("Run an anti-virus on file attachments"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": False # for development }, { "id": "authorization_server_backend", "value_type": "scalar", "scalar_values": { "": _("No special authentication"), }, "name": _("Authentication service type"), "description": _("A primary authentication server for this discussion, defined " "as a python-social-auth backend. Participants will be " "auto-logged in to that server, and discussion auto-" "subscription will require an account from this backend."), "allow_user_override": None, "modification_permission": P_SYSADMIN, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": "" }, # Are moderated posts simply hidden or made inaccessible by default? { "id": "default_allow_access_to_moderated_text", "name": _("Allow access to moderated text"), "value_type": "bool", # "scalar_values": {value: "label"}, "description": _("Are moderated posts simply hidden or made inaccessible " "by default?"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": True }, # Does the Idea panel automatically open when an idea is clicked? (and close when a special section is clicked) { "id": "idea_panel_opens_automatically", "name": _("Idea panel opens automatically"), "value_type": "bool", # "scalar_values": {value: "label"}, "description": _("Does the Idea panel automatically open when an idea is clicked ? (and close when a special section is clicked)" ), "allow_user_override": P_READ, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": True }, # What are (ordered) identifiers of columns in multi-column views? { "id": "default_column_identifiers", "name": _("Ids of columns in column view"), "value_type": "list_of_string", "description": _("What are (ordered) identifiers of columns in multi-column views?" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "item_default": "", "default": ["positive", "negative"], }, # What are default theme colors of columns in multi-column view { "id": "default_column_colors", "name": _("Default colors of columns in column view"), "value_type": "dict_of_string_to_string", "description": _("What are (default) theme colors of columns in multi-column views?" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": { "positive": "green", "negative": "red" }, }, # What are (default) names of columns in multi-column views, in each discussion language? { "id": "default_column_names", "name": _("Names of columns in column view"), "value_type": "dict_of_string_to_langstr", "description": _("What are (default) names of columns in multi-column views, in each discussion language?" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": { "negative": { "en": "Negative", "fr": "Négatif" }, "positive": { "en": "Positive", "fr": "Positif" } }, "item_default": { "": { "en": "" } }, }, # The specification of the default permissions for a discussion { "id": "default_permissions", "name": _("Default permissions"), "value_type": "dict_of_role_to_list_of_permission", "show_in_preferences": False, "description": _("The basic permissions for a new discussion"), "allow_user_override": None, "modification_permission": P_SYSADMIN, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "item_default": { R_PARTICIPANT: [P_READ], }, "default": { R_ADMINISTRATOR: [ P_ADD_EXTRACT, P_ADD_IDEA, P_ADD_POST, P_ADMIN_DISC, P_DELETE_MY_POST, P_DELETE_POST, P_DISC_STATS, P_EDIT_EXTRACT, P_EDIT_IDEA, P_EDIT_MY_EXTRACT, P_EDIT_MY_POST, P_EDIT_POST, P_EDIT_SYNTHESIS, P_EXPORT_EXTERNAL_SOURCE, P_MANAGE_RESOURCE, P_MODERATE, P_OVERRIDE_SOCIAL_AUTOLOGIN, P_SEND_SYNTHESIS, P_VOTE, ], R_CATCHER: [ P_ADD_EXTRACT, P_ADD_IDEA, P_ADD_POST, P_DELETE_MY_POST, P_EDIT_EXTRACT, P_EDIT_IDEA, P_EDIT_MY_EXTRACT, P_EDIT_MY_POST, P_OVERRIDE_SOCIAL_AUTOLOGIN, P_VOTE, ], R_MODERATOR: [ P_ADD_EXTRACT, P_ADD_IDEA, P_ADD_POST, P_DELETE_MY_POST, P_DELETE_POST, P_DISC_STATS, P_EDIT_EXTRACT, P_EDIT_IDEA, P_EDIT_MY_EXTRACT, P_EDIT_MY_POST, P_EDIT_POST, P_EDIT_SYNTHESIS, P_EXPORT_EXTERNAL_SOURCE, P_MANAGE_RESOURCE, P_MODERATE, P_OVERRIDE_SOCIAL_AUTOLOGIN, P_SEND_SYNTHESIS, P_VOTE, ], R_PARTICIPANT: [ P_ADD_POST, P_DELETE_MY_POST, P_EDIT_MY_POST, P_VOTE, ], Authenticated: [ P_SELF_REGISTER, ], Everyone: [ P_READ, P_READ_PUBLIC_CIF, ], }, }, # Registration requires being a member of this email domain. { "id": "require_email_domain", "name": _("Require Email Domain"), "value_type": "list_of_domain", # "scalar_values": {value: "label"}, "description": _("List of domain names of user email address required for " "self-registration. Only accounts with at least an email from those " "domains can self-register to this discussion. Anyone can " "self-register if this is empty."), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": [], "item_default": "" }, # Allow whitelist to be applied to SSO login process { "id": "whitelist_on_authentication_backend", "name": _("Whitelist on authentication service"), "value_type": "bool", # "scalar_values": {value: "label"}, "description": _("Allow white list to be applied to the authentication service chosen" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": False }, # Allow whitelist to be applied to the regular login process { "id": "whitelist_on_register", "name": _("Whitelist on standard registration"), "value_type": "bool", # "scalar_values": {value: "label"}, "description": _("Allow white list to be applied to the default login process"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": True }, # A discussion administrator, if different from the server administrator { "id": "discussion_administrators", "name": _("Discussion administrators"), "value_type": "list_of_email", # "scalar_values": {value: "label"}, "description": _("A list of discussion administrators, if different from the server administrator" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": False }, # Show the CI Dashboard in the panel group window { "id": "show_ci_dashboard", "name": _("Show CI Dashboard"), "value_type": "bool", # "scalar_values": {value: "label"}, "description": _("Show the CI Dashboard in the panel group window"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": False }, # Configuration of the visualizations shown in the CI Dashboard { "id": "ci_dashboard_url", "name": _("URL of CI Dashboard"), "value_type": "url", "description": _("Configuration of the visualizations shown in the " "CI Dashboard"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": "//cidashboard.net/ui/visualisations/index.php?" "width=1000&height=1000&vis=11,23,p22,13,p7,7,12,p2,p15,p9," "p8,p1,p10,p14,5,6,16,p16,p17,18,p20,p4&lang=<%= lang %>" "&title=&url=<%= url %>&userurl=<%= user_url %>" "&langurl=&timeout=60" }, # List of visualizations { "id": "visualizations", "name": _("Catalyst Visualizations"), "value_type": "json", # "scalar_values": {value: "label"}, "description": _("A JSON description of available Catalyst visualizations"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": {} }, # Extra navigation sections (refers to visualizations) { "id": "navigation_sections", "name": _("Navigation sections"), "value_type": "json", # "scalar_values": {value: "label"}, "description": _("A JSON specification of Catalyst visualizations to show " "in the navigation section"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": {} }, # Translations for the navigation sections { "id": "translations", "name": _("Catalyst translations"), "value_type": "json", # "scalar_values": {value: "label"}, "description": _("Translations applicable to Catalyst visualizations, " "in Jed (JSON) format"), "allow_user_override": None, # "view_permission": P_READ, # by default "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": {} }, # Default expanded/collapsed state of each idea in the table of ideas. # A user can override it by opening/closing an idea. # This is a hash where keys are ideas ids, and values are booleans. # We could use dict_of_string_to_bool, but that would clutter the interface. { "id": "default_table_of_ideas_collapsed_state", "name": _("Default Table of Ideas Collapsed state"), "value_type": "json", # "scalar_values": {value: "label"}, "description": _("Default expanded/collapsed state of each idea in the table " "of ideas. A user can override it by opening/closing an idea"), "allow_user_override": None, # "view_permission": P_READ, # by default "modification_permission": P_ADD_IDEA, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": {}, "show_in_preferences": False }, # The specification of the preference data { "id": "preference_data", "name": _("Preference data"), "value_type": "json", "show_in_preferences": False, "description": _("The preference configuration; override only with care"), "allow_user_override": None, "modification_permission": P_SYSADMIN, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": None # this should be recursive... }, # The specification of the cookies banner { "id": "cookies_banner", "name": _("Cookies banner"), "value_type": "bool", "show_in_preferences": True, "description": _("Show the banner offering to disable Piwik cookies"), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, # "frontend_validator_function": func_name...?, # "backend_validator_function": func_name...?, "default": True # this should be recursive... }, # Custom HTML code that will be integrated on the landing page of the debate, right after the <body> tag { "id": "custom_html_code_landing_page", "name": _("Custom HTML code on the landing page"), "value_type": "text", "show_in_preferences": True, "description": _("Custom HTML code that will be integrated on the landing page of the debate, right after the <body> tag" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": None }, # Custom HTML code that will be integrated on the user registration page of the debate, right after the <body> tag { "id": "custom_html_code_user_registration_page", "name": _("Custom HTML code on the user registration page"), "value_type": "text", "show_in_preferences": True, "description": _("Custom HTML code that will be integrated on the user registration page of the debate, right after the <body> tag" ), "allow_user_override": None, "modification_permission": P_ADMIN_DISC, "default": None }, # Harvesting translation { "id": "harvesting_translation", "name": _("Harvesting translation"), "value_type": "dict_of_string_to_string", "show_in_preferences": True, "description": _("Harvesting translation"), "allow_user_override": P_READ, "default": None }, # Valid CORS paths { "id": "graphql_valid_cors", "name": _("Valid CORS paths for GraphQL API calls"), "value_type": "list_of_url", "show_in_preferences": True, "description": _("A list of valid domain names or IP addresses that are allowed to make CORS api calls to the GraphQL API" ), "allow_user_override": False, "default": [], "item_default": "" }, ] # Precompute, this is not mutable. preference_data_key_list = [p["id"] for p in preference_data_list] preference_data_key_set = set(preference_data_key_list) preference_data = {p["id"]: p for p in preference_data_list}
def home_view(request): """The main view on a discussion""" user_id = authenticated_userid(request) or Everyone context = get_default_context(request) discussion = context["discussion"] canRead = user_has_permission(discussion.id, user_id, P_READ) if not canRead and user_id == Everyone: # User isn't logged-in and discussion isn't public: # redirect to login page # need to pass the route to go to *after* login as well # With regards to a next_view, if explicitly stated, then # that is the next view. If not stated, the referer takes # precedence. In case of failure, login redirects to the # discussion which is its context. next_view = request.params.get('next', None) if not next_view and discussion: # If referred here from a post url, want to be able to # send the user back. Usually, Assembl will send the user # here to login on private discussions. referrer = request.url next_view = path_qs(referrer) if discussion.preferences['authorization_server_backend']: login_url = request.route_url( "contextual_social_auth", discussion_slug=discussion.slug, backend=discussion.preferences['authorization_server_backend'], _query={"next": next_view}) elif next_view: login_url = request.route_url("contextual_login", discussion_slug=discussion.slug, _query={"next": next_view}) else: login_url = request.route_url('contextual_login', discussion_slug=discussion.slug) return HTTPTemporaryRedirect(login_url) elif not canRead: # User is logged-in but doesn't have access to the discussion # Would use render_to_response, except for the 401 from pyramid_jinja2 import IJinja2Environment jinja_env = request.registry.queryUtility(IJinja2Environment, name='.jinja2') template = jinja_env.get_template('cannot_read_discussion.jinja2') body = template.render(get_default_context(request)) return Response(body, 401) # if the route asks for a post, get post content (because this is needed for meta tags) route_name = request.matched_route.name if route_name == "purl_posts": post_id = FrontendUrls.getRequestedPostId(request) if not post_id: return HTTPSeeOther( request.route_url('home', discussion_slug=discussion.slug)) post = Post.get_instance(post_id) if not post or post.discussion_id != discussion.id: return HTTPSeeOther( request.route_url('home', discussion_slug=discussion.slug)) context['post'] = post elif route_name == "purl_idea": idea_id = FrontendUrls.getRequestedIdeaId(request) if not idea_id: return HTTPSeeOther( request.route_url('home', discussion_slug=discussion.slug)) idea = Idea.get_instance(idea_id) if not idea or idea.discussion_id != discussion.id: return HTTPSeeOther( request.route_url('home', discussion_slug=discussion.slug)) context['idea'] = idea canAddExtract = user_has_permission(discussion.id, user_id, P_ADD_EXTRACT) context['canAddExtract'] = canAddExtract context['canDisplayTabs'] = True preferences = discussion.preferences if user_id != Everyone: from assembl.models import UserPreferenceCollection user = User.get(user_id) preferences = UserPreferenceCollection(user_id, discussion) # TODO: user may not exist. Case of session with BD change. user.is_visiting_discussion(discussion.id) session = Discussion.default_db if '_LOCALE_' in request.cookies: locale = request.cookies['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Cookie) elif '_LOCALE_' in request.params: locale = request.params['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Parameter) else: locale = locale_negotiator(request) process_locale(locale, user, session, LanguagePreferenceOrder.OS_Default) else: locale = request.localizer.locale_name target_locale = Locale.get_or_create(strip_country(locale), discussion.db) translation_service_data = {} try: service = discussion.translation_service() if service: translation_service_data = service.serviceData() except: pass context['translation_service_data_json'] = json.dumps( translation_service_data) locale_labels = json.dumps( DummyGoogleTranslationService.target_locale_labels_cls(target_locale)) context['translation_locale_names_json'] = locale_labels context['preferences_json'] = json.dumps(dict(preferences)) response = render_to_response('../../templates/index.jinja2', context, request=request) # Prevent caching the home, especially for proper login/logout response.cache_control.max_age = 0 response.cache_control.prevent_auto = True return response
def target_locale_labels_for_locales(cls, locales, target_locale): return LocaleLabel.names_of_locales_in_locale( [strip_country(cls.asPosixLocale(loc)) for loc in locales] + Locale.SPECIAL_LOCALES, target_locale)
def target_locale_labels_for_locales(cls, locales, target_locale): return LocaleLabel.names_of_locales_in_locale( [strip_country(cls.asPosixLocale(loc)) for loc in locales] + Locale.SPECIAL_LOCALES, target_locale)
def get_data(content): """Return uid, dict of fields we want to index, return None if we don't index.""" from assembl.models import Idea, Post, SynthesisPost, AgentProfile, LangString, Extract, Question if type(content) == Idea: # only index Idea, not Thematic or Question data = {} for attr in ('creation_date', 'id', 'discussion_id'): data[attr] = getattr(content, attr) populate_from_langstring_prop(content, data, 'title') populate_from_langstring_prop(content, data, 'synthesis_title') populate_from_langstring_prop(content, data, 'description') announcement = content.get_applicable_announcement() if announcement: populate_from_langstring_prop(announcement, data, 'title', 'announcement_title') populate_from_langstring_prop(announcement, data, 'body', 'announcement_body') phase = content.get_associated_phase() if phase: data['phase_id'] = phase.id data['phase_identifier'] = phase.identifier data['message_view_override'] = content.message_view_override return get_uid(content), data elif isinstance(content, AgentProfile): data = {} for attr in ('creation_date', 'id', 'name'): data[attr] = getattr(content, attr, None) # AgentProfile doesn't have creation_date, User does. # get all discussions that the user is in via AgentStatusInDiscussion data['discussion_id'] = set([s.discussion_id for s in content.agent_status_in_discussion]) # get discussion_id for all posts of this agent data['discussion_id'] = list( data['discussion_id'].union( [post.discussion_id for post in content.posts_created] ) ) return get_uid(content), data elif isinstance(content, Post): data = {} data['_parent'] = 'user:{}'.format(content.creator_id) if content.parent_id is not None: data['parent_creator_id'] = content.parent.creator_id for attr in ('discussion_id', 'creation_date', 'id', 'parent_id', 'creator_id', 'sentiment_counts'): data[attr] = getattr(content, attr) data['creator_display_name'] = AgentProfile.get(content.creator_id).display_name() data['sentiment_tags'] = [key for key in data['sentiment_counts'] if data['sentiment_counts'][key] > 0] like = data['sentiment_counts']['like'] disagree = data['sentiment_counts']['disagree'] dont_understand = data['sentiment_counts']['dont_understand'] more_info = data['sentiment_counts']['more_info'] all_sentiments = [like, disagree, dont_understand, more_info] data['sentiment_counts']['total'] = sum(all_sentiments) data['sentiment_counts']['popularity'] = like - disagree data['sentiment_counts']['consensus'] = max(all_sentiments) / ((sum(all_sentiments) / len(all_sentiments)) or 1) data['sentiment_counts']['controversy'] = max(like, disagree, 1) / min(like or 1, disagree or 1) data['type'] = content.type # this is the subtype (assembl_post, email...) # data['publishes_synthesis_id'] = getattr( # content, 'publishes_synthesis_id', None) phase = content.get_created_phase() if phase: data['phase_id'] = phase.id data['phase_identifier'] = phase.identifier if isinstance(content, SynthesisPost): populate_from_langstring_prop(content.publishes_synthesis, data, 'subject') populate_from_langstring_prop(content.publishes_synthesis, data, 'introduction') populate_from_langstring_prop(content.publishes_synthesis, data, 'conclusion') long_titles = [idea.synthesis_title for idea in content.publishes_synthesis.ideas if idea.synthesis_title] long_titles_c = defaultdict(list) for ls in long_titles: for e in ls.entries: if e.value: long_titles_c[strip_country(e.base_locale)].append(e.value) ls = LangString() for locale, values in long_titles_c.iteritems(): ls.add_value(' '.join(values), locale) populate_from_langstring(ls, data, 'ideas') else: idea_id = get_idea_id_for_post(content) if not idea_id: return None, None data['idea_id'] = idea_id related_idea = Idea.get(idea_id[0]) data['message_view_override'] = related_idea.message_view_override if isinstance(related_idea, Question): related_idea = related_idea.parents[0] # we take the title of the first idea in the list for now (in v2, posts are attached to only one idea) populate_from_langstring_prop( related_idea, data, 'title', 'idea_title') populate_from_langstring_prop(content, data, 'body') populate_from_langstring_prop(content, data, 'subject') return get_uid(content), data elif isinstance(content, Extract): data = {} for attr in ('discussion_id', 'body', 'creation_date', 'id', 'creator_id'): data[attr] = getattr(content, attr) data['post_id'] = content.content_id post = Post.get(content.content_id) populate_from_langstring_prop(post, data, 'subject') phase = post.get_created_phase() if phase: data['phase_id'] = phase.id data['phase_identifier'] = phase.identifier idea_id = get_idea_id_for_post(post) if not idea_id: return None, None data['idea_id'] = idea_id # we take the title of the first idea in the list for now (in v2, posts are attached to only one idea) related_idea = Idea.get(idea_id[0]) data['message_view_override'] = related_idea.message_view_override if isinstance(related_idea, Question): related_idea = related_idea.parents[0] populate_from_langstring_prop( related_idea, data, 'title', 'idea_title') data['extract_state'] = 'taxonomy_state.' + content.extract_state if content.extract_nature: data['extract_nature'] = 'taxonomy_nature.' + content.extract_nature.name if content.extract_action: data['extract_action'] = 'taxonomy_action.' + content.extract_action.name data['creator_display_name'] = AgentProfile.get(content.creator_id).display_name() return get_uid(content), data return None, None
def home_view(request): user_id = authenticated_userid(request) or Everyone context = get_default_context(request) discussion = context["discussion"] request.session["discussion"] = discussion.slug canRead = user_has_permission(discussion.id, user_id, P_READ) if not canRead and user_id == Everyone: # User isn't logged-in and discussion isn't public: # redirect to login page # need to pass the route to go to *after* login as well # With regards to a next_view, if explicitly stated, then # that is the next view. If not stated, the referer takes # precedence. In case of failure, login redirects to the # discussion which is its context. next_view = request.params.get('next_view', None) if not next_view and discussion: # If referred here from a post url, want to be able to # send the user back. Usually, Assembl will send the user # here to login on private discussions. referrer = request.url next_view = path_qs(referrer) if next_view: login_url = request.route_url("contextual_login", discussion_slug=discussion.slug, _query={"next_view": next_view}) else: login_url = request.route_url( 'contextual_login', discussion_slug=discussion.slug) return HTTPSeeOther(login_url) elif not canRead: # User is logged-in but doesn't have access to the discussion # Would use render_to_response, except for the 401 from pyramid_jinja2 import IJinja2Environment jinja_env = request.registry.queryUtility( IJinja2Environment, name='.jinja2') template = jinja_env.get_template('cannot_read_discussion.jinja2') body = template.render(get_default_context(request)) return Response(body, 401) # if the route asks for a post, get post content (because this is needed for meta tags) route_name = request.matched_route.name if route_name == "purl_posts": post_id = FrontendUrls.getRequestedPostId(request) if not post_id: return HTTPSeeOther(request.route_url( 'home', discussion_slug=discussion.slug)) post = Post.get_instance(post_id) if not post or post.discussion_id != discussion.id: return HTTPSeeOther(request.route_url( 'home', discussion_slug=discussion.slug)) context['post'] = post elif route_name == "purl_idea": idea_id = FrontendUrls.getRequestedIdeaId(request) if not idea_id: return HTTPSeeOther(request.route_url( 'home', discussion_slug=discussion.slug)) idea = Idea.get_instance(idea_id) if not idea or idea.discussion_id != discussion.id: return HTTPSeeOther(request.route_url( 'home', discussion_slug=discussion.slug)) context['idea'] = idea canAddExtract = user_has_permission(discussion.id, user_id, P_ADD_EXTRACT) context['canAddExtract'] = canAddExtract context['canDisplayTabs'] = True preferences = discussion.preferences if user_id != Everyone: from assembl.models import UserPreferenceCollection user = User.get(user_id) preferences = UserPreferenceCollection(user_id, discussion) # TODO: user may not exist. Case of session with BD change. user.is_visiting_discussion(discussion.id) session = Discussion.default_db if '_LOCALE_' in request.cookies: locale = request.cookies['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Cookie) elif '_LOCALE_' in request.params: locale = request.params['_LOCALE_'] process_locale(locale, user, session, LanguagePreferenceOrder.Parameter) else: locale = locale_negotiator(request) process_locale(locale, user, session, LanguagePreferenceOrder.OS_Default) else: locale = request.localizer.locale_name target_locale = Locale.get_or_create( strip_country(locale), discussion.db) translation_service_data = {} try: service = discussion.translation_service() if service: translation_service_data = service.serviceData() except: pass context['translation_service_data_json'] = json.dumps( translation_service_data) locale_labels = json.dumps( DummyGoogleTranslationService.target_locale_labels_cls(target_locale)) context['translation_locale_names_json'] = locale_labels context['preferences_json'] = json.dumps(dict(preferences)) response = render_to_response('../../templates/index.jinja2', context, request=request) # Prevent caching the home, especially for proper login/logout response.cache_control.max_age = 0 response.cache_control.prevent_auto = True return response
def get_data(content): """Return uid, dict of fields we want to index, return None if we don't index.""" from assembl.models import Idea, Post, SynthesisPost, AgentProfile, LangString, Extract if type(content) == Idea: # only index Idea, not Thematic or Question data = {} for attr in ('creation_date', 'id', 'discussion_id'): data[attr] = getattr(content, attr) populate_from_langstring_prop(content, data, 'title') populate_from_langstring_prop(content, data, 'synthesis_title') populate_from_langstring_prop(content, data, 'description') announcement = content.get_applicable_announcement() if announcement: populate_from_langstring_prop(announcement, data, 'title', 'announcement_title') populate_from_langstring_prop(announcement, data, 'body', 'announcement_body') phase = content.get_associated_phase() if phase: data['phase_id'] = phase.id data['phase_identifier'] = phase.identifier return get_uid(content), data elif isinstance(content, AgentProfile): data = {} for attr in ('creation_date', 'id', 'name'): data[attr] = getattr(content, attr, None) # AgentProfile doesn't have creation_date, User does. # get all discussions that the user is in via AgentStatusInDiscussion data['discussion_id'] = set( [s.discussion_id for s in content.agent_status_in_discussion]) # get discussion_id for all posts of this agent data['discussion_id'] = list(data['discussion_id'].union( [post.discussion_id for post in content.posts_created])) return get_uid(content), data elif isinstance(content, Post): data = {} data['_parent'] = 'user:{}'.format(content.creator_id) if content.parent_id is not None: data['parent_creator_id'] = content.parent.creator_id for attr in ('discussion_id', 'creation_date', 'id', 'parent_id', 'creator_id', 'sentiment_counts'): data[attr] = getattr(content, attr) data['creator_display_name'] = AgentProfile.get( content.creator_id).display_name() data['sentiment_tags'] = [ key for key in data['sentiment_counts'] if data['sentiment_counts'][key] > 0 ] like = data['sentiment_counts']['like'] disagree = data['sentiment_counts']['disagree'] dont_understand = data['sentiment_counts']['dont_understand'] more_info = data['sentiment_counts']['more_info'] all_sentiments = [like, disagree, dont_understand, more_info] data['sentiment_counts']['total'] = sum(all_sentiments) data['sentiment_counts']['popularity'] = like - disagree data['sentiment_counts']['consensus'] = max(all_sentiments) / ( (sum(all_sentiments) / len(all_sentiments)) or 1) data['sentiment_counts']['controversy'] = max(like, disagree, 1) / min( like or 1, disagree or 1) data[ 'type'] = content.type # this is the subtype (assembl_post, email...) # data['publishes_synthesis_id'] = getattr( # content, 'publishes_synthesis_id', None) phase = content.get_created_phase() if phase: data['phase_id'] = phase.id data['phase_identifier'] = phase.identifier if isinstance(content, SynthesisPost): populate_from_langstring_prop(content.publishes_synthesis, data, 'subject') populate_from_langstring_prop(content.publishes_synthesis, data, 'introduction') populate_from_langstring_prop(content.publishes_synthesis, data, 'conclusion') long_titles = [ idea.synthesis_title for idea in content.publishes_synthesis.ideas if idea.synthesis_title ] long_titles_c = defaultdict(list) for ls in long_titles: for e in ls.entries: if e.value: long_titles_c[strip_country(e.base_locale)].append( e.value) ls = LangString() for locale, values in long_titles_c.iteritems(): ls.add_value(' '.join(values), locale) populate_from_langstring(ls, data, 'ideas') else: idea_id = get_idea_id_for_post(content) if not idea_id: return None, None data['idea_id'] = idea_id populate_from_langstring_prop(content, data, 'body') populate_from_langstring_prop(content, data, 'subject') return get_uid(content), data elif isinstance(content, Extract): data = {} for attr in ('discussion_id', 'body', 'creation_date', 'id', 'creator_id'): data[attr] = getattr(content, attr) data['post_id'] = content.content_id post = Post.get(content.content_id) populate_from_langstring_prop(post, data, 'subject') phase = post.get_created_phase() if phase: data['phase_id'] = phase.id data['phase_identifier'] = phase.identifier idea_id = get_idea_id_for_post(post) if not idea_id: return None, None data['idea_id'] = idea_id data['extract_state'] = 'taxonomy_state.' + content.extract_state if content.extract_nature: data[ 'extract_nature'] = 'taxonomy_nature.' + content.extract_nature.name if content.extract_action: data[ 'extract_action'] = 'taxonomy_action.' + content.extract_action.name data['creator_display_name'] = AgentProfile.get( content.creator_id).display_name() return get_uid(content), data return None, None
def test_strip_country_case1(self): # There is only one case for this function since it assumes a posix # locale locale = "fr_FR" assert strip_country(locale) == "fr"