class Emojiable(Entity): def __init__(self, **kwargs): super(Emojiable, self).__init__(**kwargs) self.emojis = OOBTree() self.users_emoji = OOBTree() def add_emoji(self, emoji, user): user_oid = get_oid(user) current_emoji = self.get_user_emoji(user) if current_emoji: self.remove_emoji(current_emoji, user) if emoji: self.emojis.setdefault(emoji, PersistentList()) self.emojis[emoji].append(user_oid) self.users_emoji[user_oid] = emoji def remove_emoji(self, emoji, user): user_oid = get_oid(user) if emoji in self.emojis and \ user_oid in self.emojis[emoji]: self.emojis[emoji].remove(user_oid) self.users_emoji.pop(user_oid) def get_user_emoji(self, user): user_oid = get_oid(user) return self.users_emoji.get(user_oid, None) def can_add_reaction(self, user, process): return False
class ConsumerManager(Persistent, Contained): """\ A very basic consumer manager for the default layer. This manager only capture the very basics, and does not really allow users to add their own consumers and have them approved in a way that is more integrated into the Plone (or other) CMS. """ zope.component.adapts(IAttributeAnnotatable, zope.interface.Interface) zope.interface.implements(IConsumerManager) __dummy_key = fieldproperty.FieldProperty(IConsumer['key']) __dummy_secret = fieldproperty.FieldProperty(IConsumer['secret']) @property def DUMMY_KEY(self): return self.__dummy_key @property def DUMMY_SECRET(self): return self.__dummy_secret def __init__(self): self.__dummy_key = random_string(24) self.__dummy_secret = random_string(24) self._consumers = OOBTree() def add(self, consumer): assert IConsumer.providedBy(consumer) if self.get(consumer.key): raise ValueError('consumer %s already exists', consumer.key) self._consumers[consumer.key] = consumer def get(self, consumer_key, default=None): return self._consumers.get(consumer_key, default) def getValidated(self, consumer_key, default=None): # Provision for further checks by alternative implementations. return self.get(consumer_key, default) def getAllKeys(self): return self._consumers.keys() def makeDummy(self): return Consumer(str(self.DUMMY_KEY), str(self.DUMMY_SECRET)) def remove(self, consumer): if IConsumer.providedBy(consumer): consumer = consumer.key self._consumers.pop(consumer)
class TemporaryAuth(Persistent, Contained): ttl = FieldProperty(ITemporaryAuth['ttl']) def __init__(self): self._keys = OOBTree() def generateAccessFor(self, userid, target): """ Safeguards on what the target is should be done by the caller of this method. """ key = '%040x' % getrandbits(160) self._keys[key] = (int(time()), userid, target) return key def validateAccess(self, key, request): token = self._keys.get(key, None) if not token: return None created_at, userid, target = token if created_at + self.ttl < time(): # Expired token, by removing it also. self._keys.pop(key, None) # Others will not be removed, figure out how to prune later. return None server_url = request.get('SERVER_URL', '') actual_url = request.get('ACTUAL_URL', '') if target[:1] == '/': target = server_url + target if not (actual_url and actual_url.startswith(target)): return None return userid def validateByCredentials(self, key, credentials): """ For the PAS plugin. """ if not credentials.get('_temp_auth'): return None return self.validateAccess(key, credentials)
class LangField(BaseField): """ Language sensitive field. """ def __init__(self, key=None, main_lang = None, **kwargs): super(LangField, self).__init__(key=key, **kwargs) if not main_lang: request = get_current_request() main_lang = request.registry.settings.get('default_locale_name', 'en') self.main_lang = main_lang self.fuzzy = OOSet() self.__data__ = OOBTree() @property def langs(self): return set(self.__data__.keys()) @property def translated(self): return self.langs - set([self.main_lang]) def get(self, default=None, langs = None, **kwargs): if not langs: request = get_current_request() langs = (get_locale_name(request),) return dict((lang, self.__data__.get(lang, default)) for lang in langs) def set(self, value, **kwargs): if not isinstance(value, dict): raise TypeError("Must be a dict") updated = value.keys() main_updated = self.main_lang in updated for (lang, value) in value.items(): self.__data__[lang] = value if lang in self.fuzzy: self.fuzzy.remove(lang) if main_updated: others = self.translated - set(updated) self.fuzzy.update(others) def remove(self, key): self.__data__.pop(key, None) if key in self.fuzzy: self.fuzzy.remove(key) def __len__(self): return len(self.__data__)
class Sustainable(Entity): """Question class""" def __init__(self, **kwargs): super(Sustainable, self).__init__(**kwargs) self.set_data(kwargs) self.votes_positive = OOBTree() self.votes_negative = OOBTree() @property def len_support(self): return len(self.votes_positive) @property def len_opposition(self): return len(self.votes_negative) def add_vote(self, user, date, kind='positive'): oid = get_oid(user) if kind == 'positive': self.votes_positive[oid] = date else: self.votes_negative[oid] = date def withdraw_vote(self, user): oid = get_oid(user) if oid in self.votes_positive: self.votes_positive.pop(oid) elif oid in self.votes_negative: self.votes_negative.pop(oid) def has_vote(self, user): oid = get_oid(user) return oid in self.votes_positive or \ oid in self.votes_negative def has_negative_vote(self, user): oid = get_oid(user) return oid in self.votes_negative def has_positive_vote(self, user): oid = get_oid(user) return oid in self.votes_positive
class CanonicalStorage(SimpleItem): implements(IMultilingualStorage) id = 'portal_multilingual' def __init__(self): self.id = id self.canonicals = OOBTree() def get_canonical(self, id): """ get a canonical for a specific content-id """ canonical = None if id in self.canonicals: canonical = self.canonicals[id] return canonical def add_canonical(self, id, canonical): """ add a canonical there is a usecase where the id can already exist on the OOBTree """ if not self.canonicals.insert(id, canonical): # We are going to remove the language on a old canonical # so we need to check if the canonical has other translation active # before removing it canonical_old = self.get_canonical(id) # check if there more than one language on the canonical if len(canonical_old.get_keys()) > 1: canonical_old.remove_item_by_id(id) self.remove_canonical(id) else: self.remove_canonical(id) del canonical_old # import transaction; transaction.commit() self.canonicals.insert(id, canonical) def remove_canonical(self, id): """ remove a canonical """ self.canonicals.pop(id) def get_canonicals(self): """ get all canonicals """ return self.canonicals
class Channel(Commentable): """Channel class""" type_title = _('Channel') icon = 'icon novaideo-icon icon-idea' templates = {'default': 'novaideo:views/templates/channel_result.pt'} name = renamer() members = SharedMultipleProperty('members', 'following_channels') subject = SharedUniqueProperty('subject', 'channels') def __init__(self, **kwargs): super(Channel, self).__init__(**kwargs) self.set_data(kwargs) self._comments_at = OOBTree() def add_comment(self, comment): self._comments_at[comment.created_at] = get_oid(comment) def remove_comment(self, comment): self._comments_at.pop(comment.created_at) def get_comments_between(self, start, end): return list(self._comments_at.values( min=start, max=end)) def get_subject(self, user=None): subject = self.subject return subject if subject else getattr(self, '__parent__', None) def get_title(self, user=None): title = getattr(self, 'title', '') if not title: return getattr(self.get_subject(user), 'title', None) return title def is_discuss(self): return self.subject.__class__.__name__.lower() == 'person'
class Channel(Commentable): """Channel class""" type_title = _('Channel') icon = 'icon novaideo-icon icon-idea' templates = {'default': 'novaideo:views/templates/channel_result.pt'} name = renamer() members = SharedMultipleProperty('members', 'following_channels') subject = SharedUniqueProperty('subject', 'channels') def __init__(self, **kwargs): super(Channel, self).__init__(**kwargs) self.set_data(kwargs) self._comments_at = OOBTree() def add_comment(self, comment): self._comments_at[comment.created_at] = get_oid(comment) def remove_comment(self, comment): self._comments_at.pop(comment.created_at) def get_comments_between(self, start, end): return list(self._comments_at.values(min=start, max=end)) def get_subject(self, user=None): subject = self.subject return subject if subject else getattr(self, '__parent__', None) def get_title(self, user=None): title = getattr(self, 'title', '') if not title: return getattr(self.get_subject(user), 'title', None) return title def is_discuss(self): return self.subject.__class__.__name__.lower() == 'person'
class Warehouse(Persistent): _volatile_attributes = {} @property def fs_path(self): return self._volatile_attributes[id(self)]['fs_path'] @property def logger(self): return self._volatile_attributes[id(self)]['logger'] def __init__(self): self._parcels = OOBTree() @property def parcels_path(self): return self.fs_path / 'parcels' @property def tree_path(self): return self.fs_path / 'tree' def new_parcel(self): parcel_path = path(tempfile.mkdtemp(prefix='', dir=self.parcels_path)) parcel_path.chmod(0755) parcel = Parcel(self, parcel_path.name) self._parcels[parcel.name] = parcel self.logger.info("New parcel %r (user %s)", parcel.name, _current_user()) return parcel def delete_parcel(self, name): self.logger.info("Deleting parcel %r (user %s)", name, _current_user()) parcel = self._parcels.pop(name) def get_parcel(self, name): return self._parcels[name] def get_all_parcels(self): return iter(self._parcels.values())
class BookingStorage(Persistent): def __init__(self): self.bookings = OOBTree() # we need real randomness for object keys # but we cannot use the same uid for zope.catalog ids # because it does not support 128bit integers # (and we do not like to use zope.intid). # So, we use different keys for storage and catalog # and we store mapping between them here. self.mapping = IOBTree() self.catalog = setup_catalog() def __len__(self): """Returns the number of bookings. """ return len(self.bookings) def __contains__(self, uid): return uid in self.bookings def __getitem__(self, uid): return self.bookings[uid] def __iter__(self): for x in self.bookings.values(): yield x def add(self, booking): assert IBooking.providedBy(booking) if not booking.uid: # maybe we are not using `create` method booking.uid = self._get_next_uid() booking.cat_id = self._get_next_cat_id() self.bookings[booking.uid] = booking self.index(booking) notify(BookingAddedEvent(booking)) return booking.uid def delete(self, uid): booking = self.bookings.pop(uid) self.unindex(booking) def create(self, **values): booking = Booking(**values) self.add(booking) return booking def _get_next_uid(self): return _get_next_uid() def _get_next_cat_id(self): # we know that this id may not be unique # but we can reindex bookings if needed try: return self.mapping.maxKey() + 1 except ValueError: # no keys yet return 1 def catalog_id_to_object(self, cat_id): uid = self.mapping[cat_id] return self.bookings[uid] def index(self, booking): self.catalog.index_doc(booking.cat_id, booking) self.mapping[booking.cat_id] = booking.uid def unindex(self, booking): self.catalog.unindex_doc(booking.cat_id) self.mapping.pop(booking.cat_id) booking.cat_id = None def reindex(self, booking): self.catalog.unindex_doc(booking.cat_id) self.catalog.index_doc(booking.cat_id, booking) def reindex_catalog(self): self.mapping.clear() self.catalog.clear() for booking in self.bookings.values(): booking.cat_id = self._get_next_cat_id() self.index(booking) def query(self, query=None, start=0, limit=None, sort_on=None, reverse=False): """Searches for bookings. Returns an ordered set of ``IBooking`` objects, which match ``query``. ``query`` is a dictionary with the keys being field names or keys contained in ``references``, and the values either a specific value or a range in the form of a ``(to, from)`` tuple (with ``None`` being no limit). ``start`` and ``limit`` can be used to slice the result set. """ return BookingResults( self, query, start=start, limit=limit, sort_on=sort_on, reverse=reverse ) def vocabulary(self, name): """Returns the list of values for the given index. """ # pylint: disable=protected-access return self.catalog[name]._fwd_index.keys()
class MorreServer(Persistent, Contained): zope.interface.implements(IMorreServer) server_uri = FieldProperty(IMorreServer['server_uri']) endpoints = FieldProperty(IMorreServer['endpoints']) portal_http_host = FieldProperty(IMorreServer['portal_http_host']) index_on_wfstate = FieldProperty(IMorreServer['index_on_wfstate']) features = FieldProperty(IMorreServer['features']) def __init__(self): # XXX uninstalling (via the form) will not clear this, nor does it # deindexes any data stored on that instance of morre. # TODO define behavior relating to that vs. this field. self.path_to_njid = OOBTree() def _loadFeature(self, endpoint): url = self.server_uri + endpoint try: r = requests.get(url, headers={ 'accept': 'application/json', }) try: return r.json() except: logger.info('No json can be decoded from %s', url) # TODO subclass a more appropriate error. raise MorreServerError() except requests.exceptions.RequestException: logger.warning('Error while trying to access %s', url, exc_info=1) return {} def _post(self, endpoint, data): url = self.server_uri + endpoint jstr = json.dumps(data) try: r = requests.post(url, data=jstr, headers={ 'accept': 'application/json', }) try: return r.json() except ValueError: logger.info('No json can be decoded from %s', url) return r.text except requests.exceptions.RequestException: logger.warning('Error while trying to access %s', url, exc_info=1) return {} def update(self): self.features = {} error = None for endpoint in self.endpoints: try: feature = self._loadFeature(endpoint) except MorreServerError as e: # don't overwrite the first error. if error is None: error = e continue self.features[endpoint] = feature # raise the deferred exception. if error: raise error return bool(self.features) def getFeatures(self, end_point): if self.features: return self.features.get(end_point, []) return [] def getAllFeatures(self): if self.features: return self.features return {} def query(self, end_point, params): # TODO filter against self.features url = self.server_uri + end_point data = json.dumps(params) try: r = requests.post(url, data=data, headers={ 'accept': 'application/json', 'content-type': 'application/json', }, ) try: result = r.json() except: logger.info('No json can be decoded from %s, data was %s', url, data) raise MorreServerError() except requests.exceptions.RequestException as e: logger.error('Exception raised:', exc_info=1) raise MorreServerError() return result def add_model(self, object_path, portal_http_host=None, model_type='CELLML'): """ Issue a call to add model to the morre database. Arguments: object_path path to the object to be added. portal_http_host The raw http host (not behind VHM) of the plone instance. Can be derived directly from request.get('HTTP_HOST'), which the installation form should have done. model_type Defaults to CELLML """ if self.path_to_njid.get(object_path): # Don't add this again. return False if portal_http_host is None: portal_http_host = self.portal_http_host endpoint = '/morre/model_update_service/add_model' file_id = object_path.rsplit('/', 1)[-1] url = 'http://' + portal_http_host + object_path try: response = self._post(endpoint, data={ 'fileId': file_id, 'url': url, 'modelType': model_type, }) except MorreServerError: return False else: if 'uID' not in response: logger.warning('no uID in response body: %s', response) return False self.path_to_njid[object_path] = response['uID'] return True def del_model(self, object_path): """ Issue a call to delete model from the morre database using the object_path """ endpoint = '/morre/model_update_service/delete_model' uid = self.path_to_njid.get(object_path) if not uid: # did not delete anything. return False try: response = self._post(endpoint, data={ 'uID': uid, }) except MorreServerError: logger.warning('unable to remove uID:%s from morre server', uid) # Continue anyway... self.path_to_njid.pop(object_path) # assume we got this return True
class UdbBtreeMultivaluedIndex(UdbIndex): is_ranged = True is_multivalued = True is_prefixed = True is_sorted_asc = True type = 'btree_multivalued' def __init__(self, schema, name=None): from BTrees.OOBTree import OOBTree UdbIndex.__init__(self, schema, name) self._btree = OOBTree() def __len__(self): return len(self._btree) def clear(self): self._btree.clear() return self def delete(self, key, uid): old_existing = self._btree.get(key, EMPTY) if old_existing != EMPTY and uid in old_existing: if len(old_existing) == 1: self._btree.pop(key) else: old_existing.remove(uid) return self def insert(self, key, uid): old_existing = self._btree.get(key, EMPTY) if old_existing == EMPTY: self._btree.insert(key, {uid}) else: old_existing.add(uid) return self def search_by_key(self, key): val = self._btree.get(key, EMPTY) if val != EMPTY: for _ in val: yield _ def search_by_key_in(self, keys): for key in keys: val = self._btree.get(key, EMPTY) if val != EMPTY: for _ in val: yield _ def search_by_key_prefix(self, key): for val in self._btree.values(key, key + CHAR255): for _ in val: yield _ def search_by_key_prefix_in(self, keys): for key in keys: for val in self._btree.values(key, key + CHAR255): for _ in val: yield _ def search_by_key_range(self, gte=None, lte=None, gte_excluded=False, lte_excluded=False): for val in self._btree.values(gte, lte, gte_excluded, lte_excluded): for _ in val: yield _ def upsert(self, old, new, uid): if old != new: old_existing = self._btree.get(old, EMPTY) if old_existing != EMPTY and uid in old_existing: if len(old_existing) == 1: self._btree.pop(old) else: old_existing.remove(uid) new_existing = self._btree.get(new, EMPTY) if new_existing == EMPTY: self._btree.insert(new, {uid}) else: new_existing.add(uid) return self
class Alert(VisualisableElement, Entity): """Alert class""" users_to_alert = SharedMultipleProperty('users_to_alert') subjects = SharedMultipleProperty('subjects') def __init__(self, kind, **kwargs): super(Alert, self).__init__(**kwargs) self.set_data(kwargs) self.kind = kind self.users_toalert = OOBTree() self.users_toexclude = OOBTree() @property def pattern(self): return INTERNAL_ALERTS.get(self.kind, None) @property def templates(self): return self.pattern.templates @property def icon(self): return self.pattern.get_icon(self) def init_alert(self, users, subjects=[], exclude=[]): self.subscribe(users) for subject in subjects: self.addtoproperty('subjects', subject) self.exclude(exclude) def subscribe(self, users): if not isinstance(users, (list, tuple, set)): users = [users] for user in users: oid = get_oid(user, user) self.users_toalert[str(oid)] = oid def unsubscribe(self, user): key = str(get_oid(user, user)) if key in self.users_toalert: self.users_toalert.pop(key) user.addtoproperty('old_alerts', self) self.reindex() def exclude(self, users): if not isinstance(users, (list, tuple, set)): users = [users] for user in users: oid = get_oid(user, user) self.users_toexclude[str(oid)] = oid def is_to_alert(self, user): key = str(get_oid(user, user)) #TODO self not in user.old_alerts return key in self.users_toalert and \ key not in self.users_toexclude def get_subject_state(self, subject, user, last_state=False): states = getattr(subject, 'state_or_none', [None]) state = states[0] if last_state: state = states[-1] return get_states_mapping( user, subject, state) def render(self, template, current_user, request): layout_manager = getattr(request, 'layout_manager', None) layout = layout_manager.layout if layout_manager \ else GlobalLayout(None, request) render_dict = { 'object': self, 'current_user': current_user, 'layout': layout } return renderers.render( self.templates[template], render_dict, request) def is_kind_of(self, kind): return kind == self.kind def has_args(self, **kwargs): for key in kwargs: if getattr(self, key, None) != kwargs[key]: return False return True
t.insert(list1[i], list_rid) list_rid = [] ''' ''' else: #print("in else list_rid: ", list_rid) t.insert(list1[i], list2[i]) list_rid = [] ''' for i in range(len(list1)): list10 = [] list10.append(list2[i]) if t.insert(list1[i], list10) == 0: value = t.pop(list1[i]) print("in the loop: ", value) # print("len: ", len(value[0])) ''' if len(str(value)) == 1: list_rid.append(value) elif len(str(value)) > 1: for j in range(len(value)): list_rid.append(value[j]) ''' value.append(list2[i]) #list_rid.append(list2[i]) t.insert(list1[i], value) # list_rid = [] for j in range(len(list6)):
class SignUpAdapter(FormActionAdapter): """A form action adapter that saves signup form.""" implements(IPloneFormGenActionAdapter, ISignUpAdapter) meta_type = 'SignUpAdapter' portal_type = 'SignUpAdapter' archetype_name = 'SignUp Adapter' default_view = 'user_approver_view' schema = SignUpAdapterSchema security = ClassSecurityInfo() manage_all = "*" disabled_email = "USERDISABLED" security.declarePrivate('onSuccess') def __init__(self, oid, **kwargs): """Initialize class.""" FormActionAdapter.__init__(self, oid, **kwargs) self.waiting_list = OOBTree() def getPolicy(self, data): """Get the policy for how the signup adapter should treat the user. auto: automatically create the user, requires a password to be set within the form. email: send the user a password reset to verify the user's email address. approve: hold the user in a list waiting for approval from the approval group """ if data['approval_group']: return 'approve' if self.getPassword_field(): return 'auto' return 'email' def onSuccess(self, fields, REQUEST=None): # noqa C901 """Save form input.""" # get username and password portal_registration = getToolByName(self, 'portal_registration') data = {} for field in fields: field_name = field.fgField.getName() val = REQUEST.form.get(field_name, None) if field_name == self.full_name_field: data['fullname'] = val elif field_name == self.username_field: data['username'] = val elif field_name == self.email_field: data['email'] = val elif field_name == self.password_field: data['password'] = val elif field_name == self.password_verify_field: data['password_verify'] = val else: data[field_name] = val if 'email' not in data: return { FORM_ERROR_MARKER: _(u'Sign Up form is not setup properly.')} if 'username' not in data: data['username'] = data['email'] # TalesField needs variables to be available from the context, so # create a context and add them expression_context = getExprContext(self, self.aq_parent) for key in data.keys(): expression_context.setGlobal(key, REQUEST.form.get(key, None)) data['user_group'] = self.getUser_group_template( expression_context=expression_context, **data) # Split the manage_group_template into two: # manage_group and approval_group # manage_group_template can't use data argument any more # because it will use when form data is not available manage_group = self.getManage_group_template( expression_context=expression_context) is_manage_group_dict = isinstance(manage_group, dict) data['approval_group'] = "" if manage_group and is_manage_group_dict: for manager, user_list in manage_group.iteritems(): if '*' in user_list: data['approval_group'] = manager if data['user_group'] in user_list: data['approval_group'] = manager break if data['email'] is None or data['user_group'] == "": # SignUpAdapter did not setup properly return { FORM_ERROR_MARKER: _(u'Sign Up form is not setup properly.')} if not data['username']: data['username'] = data['email'] # force email and username to lowercase data['username'] = data['username'].lower() data['email'] = data['email'].lower() if not portal_registration.isValidEmail(data['email']): return { FORM_ERROR_MARKER: _(u'You will need to signup again.'), 'email': _(u'This is not a valid email address')} if not portal_registration.isMemberIdAllowed(data['username']): error_text = _(u"""The login name you selected is already in use or is not valid. Please choose another.""") if self.getUsername_field(): return { FORM_ERROR_MARKER: _(u'You will need to signup again.'), 'username': error_text} else: return { FORM_ERROR_MARKER: _(u'You will need to signup again.'), 'email': error_text} check_id = self.check_userid(data) if check_id: return check_id policy = self.getPolicy(data) if policy == 'auto': result = self.autoRegister(REQUEST, data) # Just return the result, this should either be None on success or # an error message return result email_from = getUtility(ISiteRoot).getProperty( 'email_from_address', '') if not portal_registration.isValidEmail(email_from): return {FORM_ERROR_MARKER: _(u'Portal email is not configured.')} if policy == 'email': result = self.emailRegister(REQUEST, data) return result if policy == 'approve': result = self.approvalRegister(data) return result # If we get here, then something went wrong return {FORM_ERROR_MARKER: _(u'The form is currently unavailable')} def check_userid(self, data): """Make sure the user does not already exist or on the waiting list.""" if data['username'] in self.waiting_list.keys(): # user id is already on waiting list error_text = _( u"""The login name you selected is already in use.""") if self.getUsername_field(): return { FORM_ERROR_MARKER: _(u'You will need to signup again.'), 'username': error_text} else: return { FORM_ERROR_MARKER: _(u'You will need to signup again.'), 'email': error_text} # TODO(ivanteoh): check email is not already in use either in existing # users or in waiting_list def approvalRegister(self, data): """User type requires approval. So store them on the approval list. """ portal_groups = getToolByName(self, 'portal_groups') # make sure password fields are empty data['password'] = '' data['password_verify'] = '' self.waiting_list[data['username']] = data self.send_waiting_approval_email(data) # need an email address for the approvers group approval_group = portal_groups.getGroupById(data['approval_group']) if approval_group is None: self.send_approval_group_not_exist_email(data) return approval_email = approval_group.getProperty('email') if not approval_email: approval_group_members = approval_group.getGroupMembers() if approval_group_members: self.send_approval_group_members_email( data, approval_group_members) else: self.send_approval_group_problem_email(data) return self.send_approval_group_email(data) def emailRegister(self, request, data): """User type should be authenticated by email. So randomize their password and send a password reset. """ portal_registration = getToolByName(self, 'portal_registration') data['password'] = portal_registration.generatePassword() result = self.create_member(request, data, True) return result def autoRegister(self, request, data): """User type can be auto registered, so pass them through.""" verified = self.validate_password(data) if verified: return verified self.create_group(data['user_group']) # shouldn't store this in the pfg, as once the user is created, we # shouldn't care result = self.create_member(request, data, False) return result def prepare_member_properties(self): """ Adjust site for custom member properties """ # Need to use ancient Z2 property sheet API here... portal_memberdata = getToolByName(self, "portal_memberdata") # When new member is created, its MemberData # is populated with the values from portal_memberdata property sheet, # so value="" will be the default value for users' home_folder_uid # member property if not portal_memberdata.hasProperty("approved_by"): portal_memberdata.manage_addProperty( id="approved_by", value="", type="string") # PAS does not understand datetime or DateTime, # so we have to use string instead if not portal_memberdata.hasProperty("approved_date"): portal_memberdata.manage_addProperty( id="approved_date", value="", type="string") if not portal_memberdata.hasProperty("last_updated_by"): portal_memberdata.manage_addProperty( id="last_updated_by", value="", type="string") # PAS does not understand datetime or DateTime, # so we have to use string instead if not portal_memberdata.hasProperty("last_updated_date"): portal_memberdata.manage_addProperty( id="last_updated_date", value="", type="string") def create_member(self, request, data, reset_password=False): """Create member.""" self.prepare_member_properties() portal_membership = getToolByName(self, 'portal_membership') portal_registration = getToolByName(self, 'portal_registration') portal_groups = getToolByName(self, 'portal_groups') username = data['username'] # TODO(ivanteoh): add switch to prevent groups being created on the fly user_group = data['user_group'] self.create_group(user_group) # need to recheck the member has not been created in the meantime member = portal_membership.getMemberById(username) if member is None: # need to also pass username in properties, otherwise the user # isn't found when setting the properties try: current_user_id = "" if not portal_membership.isAnonymousUser(): current_user = portal_membership.getAuthenticatedMember() current_user_id = current_user.id current_time = datetime.now().strftime("%d %B %Y %I:%M %p") member = portal_registration.addMember( username, data['password'], [], properties={'username': username, 'fullname': data['fullname'], 'email': data['email'], 'approved_by': current_user_id, 'approved_date': current_time}) except (AttributeError, ValueError) as err: logging.exception(err) return {FORM_ERROR_MARKER: err} portal_groups.addPrincipalToGroup(member.getUserName(), user_group) if reset_password: # send out reset password email portal_registration.mailPassword(username, request) else: return {FORM_ERROR_MARKER: _("This user already exists.")} def update_member(self, request, user_id, user_fullname, current_group, new_group): """Update member with full name and / or group.""" # If we use custom member properties they must be initialized # before regtool is called self.prepare_member_properties() portal_membership = getToolByName(self, 'portal_membership') portal_registration = getToolByName(self, 'portal_registration') portal_groups = getToolByName(self, 'portal_groups') if not user_id: self.plone_utils.addPortalMessage( _(u'User ID is not valid.')) return user = portal_membership.getMemberById(user_id) if not user: self.plone_utils.addPortalMessage( _(u'This user does not exists.')) return if not new_group: self.plone_utils.addPortalMessage( _(u'User group is not valid.')) return new_user_group = portal_groups.getGroupById(new_group) if not new_user_group: self.plone_utils.addPortalMessage( _(u'This user group does not exists.')) return try: current_user_id = "" if not portal_membership.isAnonymousUser(): current_user = portal_membership.getAuthenticatedMember() current_user_id = current_user.id current_time = datetime.now().strftime("%d %B %Y %I:%M %p") # update user_last_updated_date and user_last_updated_by user.setMemberProperties({ 'fullname': user_fullname, 'last_updated_by': current_user_id, 'last_updated_date': current_time}) except (AttributeError, ValueError) as err: logging.exception(err) return {FORM_ERROR_MARKER: err} if current_group != new_group: try: portal_groups.removePrincipalFromGroup(user_id, current_group) except KeyError as err: error_string = _(u'Can not remove group: %s.') % err logging.exception(error_string) self.plone_utils.addPortalMessage(error_string) return try: portal_groups.addPrincipalToGroup(user_id, new_group) except KeyError as err: error_string = _(u'Can not add group: %s.') % err logging.exception(error_string) self.plone_utils.addPortalMessage(error_string) return return def user_activate(self, user_id, request): """Activate user with user_id. Remove USERDISABLED<randomkey>[email protected] from email field. """ if not user_id: self.plone_utils.addPortalMessage( _(u'This user ID is not valid.')) return self.prepare_member_properties() portal_membership = getToolByName(self, 'portal_membership') portal_registration = getToolByName(self, 'portal_registration') user = portal_membership.getMemberById(user_id) if not user: self.plone_utils.addPortalMessage( _(u'This user does not exists.')) return try: current_user_id = "" if not portal_membership.isAnonymousUser(): current_user = portal_membership.getAuthenticatedMember() current_user_id = current_user.id current_time = datetime.now().strftime("%d %B %Y %I:%M %p") current_email = user.getProperty('email', '') if not current_email: # no email return if not current_email.startswith(self.disabled_email): # already active return split = current_email.find("_") if split < len(self.disabled_email): # assume not valid email, should not be the case return new_email = current_email[split+1:] # update user_last_updated_date and user_last_updated_by user.setMemberProperties({ 'email': new_email, 'last_updated_by': current_user_id, 'last_updated_date': current_time}) portal_registration.mailPassword(user.id, request) self.plone_utils.addPortalMessage( _(u"""This user is activated and reset password email is sent to the user.""")) except (AttributeError, ValueError) as err: logging.exception(err) return {FORM_ERROR_MARKER: err} return def user_deactivate(self, user_id): """Deactivate user with user_id. Add USERDISABLED<randomkey>[email protected] to email field. """ if not user_id: self.plone_utils.addPortalMessage( _(u'This user ID is not valid.')) return self.prepare_member_properties() portal_membership = getToolByName(self, 'portal_membership') user = portal_membership.getMemberById(user_id) if not user: self.plone_utils.addPortalMessage( _(u'This user does not exists.')) return try: current_user_id = "" if not portal_membership.isAnonymousUser(): current_user = portal_membership.getAuthenticatedMember() current_user_id = current_user.id current_time = datetime.now().strftime("%d %B %Y %I:%M %p") current_email = user.getProperty('email', '') if not current_email: # no email return if current_email.startswith(self.disabled_email): # already deactivate return new_email = self.disabled_email + self.id_generator() + "_" + \ current_email # update user_last_updated_date and user_last_updated_by user.setMemberProperties({ 'email': new_email, 'last_updated_by': current_user_id, 'last_updated_date': current_time}) passwd = self.id_generator(size=32) user.setSecurityProfile(password=passwd) self.plone_utils.addPortalMessage( _(u'This user is deactivated.')) except (AttributeError, ValueError) as err: logging.exception(err) return {FORM_ERROR_MARKER: err} return def create_group(self, user_group, title=None, email=None): """Create the group.""" # This raises an error, as setGroupProperties does not yet exist on the # group portal_groups = getToolByName(self, 'portal_groups') properties = {} if title is not None: properties['title'] = title if email is not None: properties['email'] = email if user_group not in portal_groups.getGroupIds(): try: portal_groups.addGroup(user_group, properties=properties) except AttributeError: pass if title or email: # commit a subtransaction, to instantiate the group properly, so we # can edit it transaction.get().commit() portal_groups.editGroup(user_group, properties=properties) def validate_password(self, data): """Validate password.""" errors = {} if not data['password']: errors['password'] = _(u'Please enter a password') if not data['password_verify']: errors['password_verify'] = _(u'Please enter a password') if errors: errors[FORM_ERROR_MARKER] = _(u'Please enter a password') return errors if data['password'] != data['password_verify']: errors[FORM_ERROR_MARKER] = _(u'The passwords do not match') errors['password'] = _(u'The passwords do not match') errors['password_verify'] = _(u'The passwords do not match') return errors registration = getToolByName(self, 'portal_registration') # This should ensure that the password is at least 5 chars long, but # if the user filling in the form has ManagePortal permission it is # ignored error_message = registration.testPasswordValidity(data['password']) if error_message: errors[FORM_ERROR_MARKER] = error_message errors['password'] = '******' errors['password_verify'] = ' ' return errors return None def approve_user(self): """Approve the user based on the request.""" request = self.REQUEST portal_registration = getToolByName(self, 'portal_registration') userid = request.form['userid'] user = self.waiting_list.get(userid) if user is None: self.plone_utils.addPortalMessage( _(u'This user has already been dealt with.')) elif self.user_not_permitted(user['approval_group']): self.plone_utils.addPortalMessage( _(u'You do not have permission to manage this user.')) else: user['password'] = portal_registration.generatePassword() self.create_member(request, user) self.send_approval_email(user) self.waiting_list.pop(userid) self.plone_utils.addPortalMessage(_(u'User has been approved.')) request.RESPONSE.redirect(self.absolute_url()) def reject_user(self): """Reject the user based on the request.""" request = self.REQUEST # portal_registration = getToolByName(self, 'portal_registration') userid = request.form['userid'] user = self.waiting_list.get(userid) if user is None: self.plone_utils.addPortalMessage( _(u'This user has already been dealt with.')) elif self.user_not_permitted(user['approval_group']): self.plone_utils.addPortalMessage( _(u'You do not have permission to manage this user.')) else: user = self.waiting_list.pop(userid) self.send_reject_email(user) self.plone_utils.addPortalMessage(_(u'User has been rejected.')) request.RESPONSE.redirect(self.absolute_url()) def user_not_permitted(self, group): """Check the user is permmited to approve/reject the user.""" sm = getSecurityManager() portal = getUtility(ISiteRoot) portal_membership = getToolByName(self, 'portal_membership') if sm.checkPermission(ManagePortal, portal): return False elif portal_membership.isAnonymousUser(): raise Unauthorized('You need to login to access this page.') current_user = portal_membership.getAuthenticatedMember() current_user_groups = current_user.getGroups() if group not in current_user_groups: self.plone_utils.addPortalMessage( _(u'You do not have permission to do this.')) self.REQUEST.RESPONSE.redirect(self.absolute_url()) return True def get_portal_email_properties(self): """Return the portal title for use in emails.""" portal_url = getToolByName(self, 'portal_url') portal = portal_url.getPortalObject() return ( portal.Title(), portal.getProperty('email_from_address'), portal.getProperty('email_from_name')) def send_approval_group_not_exist_email(self, data): """The approval group does not exist.""" portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() portal_url = getToolByName(self, 'portal_url')() portal_groups = getToolByName(self, 'portal_groups') administrators = portal_groups.getGroupById('Administrators') administrators_email = administrators.getProperty('email') if not administrators_email: administrators_email = getUtility(ISiteRoot).getProperty( 'email_from_address', '') portal_groups = getToolByName(self, 'portal_groups') approval_group = portal_groups.getGroupById(data['approval_group']) if approval_group is None: approval_group_title = data['approval_group'] # email_link = portal_url + '/@@usergroup-groupprefs' else: approval_group_title = approval_group.getProperty('title') # email_link = portal_url + '/@@usergroup-groupdetails?groupname=' # + data['approval_group'] if not approval_group_title: approval_group_title = data['approval_group'] messageText = [ self.get_approval_group_email_text(approval_group_title), ] messageText.append('') messageText.append( '---------------------------------------------------') messageText.append('') messageText.append( u"""This email has been sent to this address because the group "%s" currently doesn\'t exist and needs to be created.""" % approval_group_title) messageText.append('') messageText.append( u"""You can add the group using this link: %s""" % portal_url + '/@@usergroup-groupprefs') messageText.append('') messageText.append('Thank you,') messageText.append('') messageText.append(portal_email_name) messageText = '\n'.join(messageText) subject = portal_title + ' approval group problem' try: self.send_email(messageText, mto=administrators_email, mfrom=portal_email, subject=subject) except SMTPServerDisconnected: pass return def send_approval_group_problem_email(self, data): """There is a problem with the approval group so alert someone.""" portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() portal_url = getToolByName(self, 'portal_url')() portal_groups = getToolByName(self, 'portal_groups') administrators = portal_groups.getGroupById('Administrators') administrators_email = administrators.getProperty('email') if not administrators_email: administrators_email = getUtility(ISiteRoot).getProperty( 'email_from_address', '') portal_groups = getToolByName(self, 'portal_groups') approval_group = portal_groups.getGroupById(data['approval_group']) if approval_group is None: approval_group_title = data['approval_group'] # email_link = portal_url + '/@@usergroup-groupprefs' else: approval_group_title = approval_group.getProperty('title') # email_link = portal_url + '/@@usergroup-groupdetails?groupname=' # + data['approval_group'] if not approval_group_title: approval_group_title = data['approval_group'] messageText = [ self.get_approval_group_email_text(approval_group_title), ] messageText.append('') messageText.append( '---------------------------------------------------') messageText.append('') messageText.append( u"""This email has been sent to this address because the group "%s" currently doesn\'t have any members with contact information or the group itself doesn\'t have contact information.""" % approval_group_title) messageText.append('') messageText.append( u"""You can add members to the group using this link: %s""" % portal_url + '/@@usergroup-groupmembership?groupname=' + data['approval_group']) messageText.append('') messageText.append('Thank you,') messageText.append('') messageText.append(portal_email_name) messageText = '\n'.join(messageText) subject = portal_title + ' approval group problem' try: self.send_email(messageText, mto=administrators_email, mfrom=portal_email, subject=subject) except SMTPServerDisconnected: pass return def send_waiting_approval_email(self, data): """Send an approval request email.""" portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() messageText = [] messageText.append(u'Dear %s,' % data['fullname']) messageText.append('') messageText.append( u"""Thank you for registering with the %s site. Your account is waiting for approval.""" % portal_title) messageText.append('') messageText.append('Thank you,') messageText.append('') messageText.append(portal_email_name) messageText = '\n'.join(messageText) subject = portal_title + ' account request submited for approval' try: self.send_email(messageText, mto=data['email'], mfrom=portal_email, subject=subject) except SMTPServerDisconnected: pass return def send_approval_group_email(self, data): """Send an email to approval group. When there is a user waiting for approval. """ portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() portal_groups = getToolByName(self, 'portal_groups') # already checked that the group exists and has an email address approval_group = portal_groups.getGroupById(data['approval_group']) approval_group_email = approval_group.getProperty('email') approval_group_title = approval_group.getProperty('title') if not approval_group_title: approval_group_title = data['approval_group'] messageText = self.get_approval_group_email_text(approval_group_title) subject = portal_title + ' user Waiting for approval' try: self.send_email(messageText, mto=approval_group_email, mfrom=portal_email, subject=subject) except SMTPServerDisconnected: pass return def send_approval_group_members_email(self, data, approval_group_members): """Send an email to each member of the approval group. When there is a user waiting for approval. """ portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() subject = portal_title + ' user Waiting for approval' for member in approval_group_members: email = member.getProperty('email') name = member.getProperty('fullname') if not name: name = email messageText = self.get_approval_group_email_text(name) try: self.send_email(messageText, mto=email, mfrom=portal_email, subject=subject) except SMTPServerDisconnected: pass return def get_approval_group_email_text(self, name): """Construct the body text of the email.""" portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() messageText = [] messageText.append(u'Dear %s,' % name) messageText.append('') messageText.append(u"""There is a user waiting for approval. Please use the following link to login and approve/reject them.""") messageText.append('') messageText.append(self.absolute_url()) messageText.append('') messageText.append('Thank you,') messageText.append('') messageText.append(portal_email_name) messageText = '\n'.join(messageText) return messageText def send_approval_email(self, data): """Send an email confirming approval.""" portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() reset_tool = getToolByName(self, 'portal_password_reset') reset = reset_tool.requestReset(data['username']) portal_url = getToolByName(self, 'portal_url')() password_url = '%s/passwordreset/%s' % \ (portal_url, reset['randomstring']) # should we send the user id with the password link email? messageText = [] messageText.append(u'Dear %s,' % data['fullname']) messageText.append('') messageText.append(u"""Your account request has been accepted. Please use the following link to set your password.""") messageText.append('') messageText.append(password_url) messageText.append('') messageText.append('This link will expire at: %s.' % reset['expires'].strftime('%H:%M %d/%m/%Y')) messageText.append('') messageText.append('Thank you,') messageText.append('') messageText.append(portal_email_name) messageText = '\n'.join(messageText) subject = portal_title + ' account approved' try: self.send_email(messageText, mto=data['email'], mfrom=portal_email, subject=subject) except SMTPServerDisconnected: pass return def send_reject_email(self, data): """Send an email on rejection.""" portal_title, portal_email, portal_email_name = \ self.get_portal_email_properties() messageText = [] messageText.append(u'Dear %s,' % data['fullname']) messageText.append('') messageText.append(u"""Your account request has been declined. If you think this is in error, please contact the site administrator.""") messageText.append('') messageText.append('Thank you,') messageText.append('') messageText.append(portal_email_name) messageText = '\n'.join(messageText) subject = portal_title + ' account request declined' try: self.send_email(messageText, mto=data['email'], mfrom=portal_email, subject=subject) except SMTPServerDisconnected: pass return def send_email(self, messageText, mto, mfrom, subject): """Send email.""" encoding = getUtility(ISiteRoot).getProperty('email_charset', 'utf-8') mail_host = getToolByName(self, 'MailHost') try: messageText = message_from_string(messageText.encode(encoding)) messageText.set_charset(encoding) mail_host.send( messageText, mto=mto, mfrom=mfrom, subject=subject) except SMTPRecipientsRefused: # Don't disclose email address on failure raise SMTPRecipientsRefused( 'Recipient address rejected by server') def get_management_dict(self): """Return dictionary where 'key' group manage 'value' groups. '*' meaning all users. Leave empty to allow creation of user accounts without any management. eg python:{'Administrators': ['*']}. This TALES expression is allowing all the users managed by 'Administrators' group. """ expression_context = getExprContext(self, self.aq_parent) manage_group = self.getManage_group_template( expression_context=expression_context) # make sure manage_group is dictionary if not isinstance(manage_group, dict): return {} return manage_group def get_manager_groups(self, manager=""): """Return common manager groups. If manager parameter is not provided, Current login user will used. """ portal_membership = getToolByName(self, 'portal_membership') portal_groups = getToolByName(self, 'portal_groups') acl = getToolByName(self, 'acl_users') if manager: current_user = acl.getUserById(userId) elif portal_membership.isAnonymousUser(): return [] else: current_user = portal_membership.getAuthenticatedMember() user_groups = current_user.getGroups() user_management_list = self.get_management_dict() common_groups = set(user_management_list.keys()) & set(user_groups) return common_groups def get_manage_by_groups(self, manager=""): """Return a list of group ids that manage by manager. '*' meaning all users. If manager parameter is not provided, current login user will used. """ user_management_list = self.get_management_dict() common_groups = self.get_manager_groups(manager) manage_by_group = [] for common_group in common_groups: if common_group not in user_management_list: continue manage_user_group = user_management_list[common_group] if self.manage_all in manage_user_group: manage_by_group = [self.manage_all] break manage_by_group += manage_user_group return manage_by_group def get_manage_all(self): """Return manage all constant string.""" return self.manage_all def get_status(self, user): """Return user status.""" if not user: return "" current_email = user.getProperty('email', '') status = _("Active") if current_email.startswith(self.disabled_email): status = _("Inactive") return status def is_active(self, user): """Return whether the user is active.""" status = self.get_status(user) if status == "Active": return True return False def get_user_name(self, user_id): """Return user name.""" if not user_id: return "" portal_membership = getToolByName(self, 'portal_membership') user = portal_membership.getMemberById(user_id) if not user: return "" user_fullname = user.getProperty('fullname', '') if user_fullname: return user_fullname return user_id def get_groups_title(self, user_groups): """Return groups id and title as dictionary.""" acl_users = getToolByName(self, 'acl_users') portal_groups = getToolByName(self, 'portal_groups') # if user_groups contains 'manage_all', show all the groups if self.manage_all in user_groups: user_groups = acl_users.source_groups.getGroupIds() group_names = [] for user_group_id in user_groups: # {"group_id": group_name, "group_title": group_name} user_group = portal_groups.getGroupById(user_group_id) # group may not yet exist group_name = "" if user_group is not None: group_name = user_group.getProperty("title", "") if not group_name: # don't have title, use id group_name = user_group_id if group_name: group_names.append( {"group_id": user_group_id, "group_title": group_name}) return group_names def id_generator(self, size=8, chars=string.ascii_uppercase + string.digits): """Random id generator.""" return ''.join( random.SystemRandom().choice(chars) for _ in range(size))
class Question(VersionableEntity, DuplicableEntity, SearchableEntity, CorrelableEntity, PresentableEntity, ExaminableEntity, Node, Emojiable, SignalableEntity, Sustainable, Debatable): """Question class""" type_title = _('Question') icon = 'md md-live-help' templates = {'default': 'novaideo:views/templates/question_result.pt', 'bloc': 'novaideo:views/templates/question_bloc.pt', 'small': 'novaideo:views/templates/small_question_result.pt', 'popover': 'novaideo:views/templates/question_popover.pt'} template = 'novaideo:views/templates/question_list_element.pt' name = renamer() author = SharedUniqueProperty('author', 'questions') organization = SharedUniqueProperty('organization') attached_files = CompositeMultipleProperty('attached_files') url_files = CompositeMultipleProperty('url_files') related_correlation = SharedUniqueProperty('related_correlation', 'targets') answers = CompositeMultipleProperty('answers', 'question') answer = SharedUniqueProperty('answer') challenge = SharedUniqueProperty('challenge', 'questions') def __init__(self, **kwargs): super(Question, self).__init__(**kwargs) self.set_data(kwargs) self.addtoproperty('channels', Channel()) self.selected_options = OOBTree() self.users_options = OOBTree() self.urls = PersistentDict({}) self.len_answers = 0 @property def authors(self): return [self.author] @property def transformed_from(self): """Return all related contents""" transformed_from = [correlation[1].context for correlation in self.get_related_contents( CorrelationType.solid, ['transformation']) if correlation[1].context] return transformed_from[0] if transformed_from else None @property def relevant_data(self): return [getattr(self, 'title', ''), getattr(self, 'text', ''), ', '.join(self.keywords)] def __setattr__(self, name, value): super(Question, self).__setattr__(name, value) if name == 'author': self.init_organization() def is_managed(self, root): return root.manage_questions def update_len_answers(self): self.len_answers = len(self.answers) return self.len_answers def addtoproperty(self, name, value, moving=None): super(Question, self).addtoproperty(name, value, moving) if name == 'answers': self.len_answers += 1 def delfromproperty(self, name, value, moving=None): super(Question, self).delfromproperty(name, value, moving) if name == 'answers': self.len_answers -= 1 def init_organization(self): if not self.organization: organization = getattr(self.author, 'organization', None) if organization: self.setproperty('organization', organization) def presentation_text(self, nb_characters=400): return truncate_text(getattr(self, 'text', ''), nb_characters) def get_more_contents_criteria(self): "return specific query, filter values" return None, { 'metadata_filter': { 'content_types': ['question'], 'keywords': list(self.keywords) } } def get_attached_files_data(self): return get_files_data(self.attached_files) def get_node_descriminator(self): return 'question' def format(self, request): text = getattr(self, 'text', '') all_urls, url_files, text_urls, formatted_text = text_urls_format( text, request) self.urls = PersistentDict(all_urls) self.setproperty('url_files', url_files) self.formatted_text = formatted_text self.formatted_urls = text_urls def add_selected_option(self, user, option): self.remove_selected_option(user) oid = get_oid(user) self.selected_options[oid] = option self.users_options.setdefault(option, []) if option in self.users_options: self.users_options[option].append(oid) else: self.users_options[option] = PersistentList([oid]) def remove_selected_option(self, user): oid = get_oid(user) if oid in self.selected_options: option = self.selected_options.pop(oid) if oid in self.users_options[option]: user_options = self.users_options[option] user_options.remove(oid) self.users_options[option] = PersistentList( list(set(user_options))) def get_selected_option(self, user): oid = get_oid(user) if oid in self.selected_options: return self.selected_options.get(oid) return None def get_user_with_option(self, option): options = getattr(self, 'options', []) if options and option in self.users_options: return self.users_options[option] return []
class ClassificationCategory(DynamicType, Traversable, Implicit, Persistent, BaseContainer): __parent__ = None __allow_access_to_unprotected_subobjects__ = True meta_type = portal_type = "ClassificationCategory" # This needs to be kept in sync with types/ClassificationCategory.xml title fti_title = "ClassificationCategory" identifier = FieldProperty(IClassificationCategory["identifier"]) title = FieldProperty(IClassificationCategory["title"]) informations = FieldProperty(IClassificationCategory["informations"]) enabled = FieldProperty(IClassificationCategory["enabled"]) def __init__(self, *args, **kwargs): self._tree = OOBTree() super(ClassificationCategory, self).__init__(*args, **kwargs) def getId(self): return self.UID() def Title(self): if self.identifier == self.title: return self.title return u"{0} - {1}".format(self.identifier, self.title) def UID(self): return IMutableUUID(self).get() def __len__(self): return len(self._tree) def __contains__(self, key): return key in self._tree def __getitem__(self, key): return self._tree[key].__of__(self) def __delitem__(self, key, suppress_container_modified=False): element = self[key].__of__(self) notify(ObjectWillBeRemovedEvent(element, self, key)) # Remove the element from _tree self._tree.pop(key) notify(ObjectRemovedEvent(element, self, key)) if not suppress_container_modified: notify(ContainerModifiedEvent(self)) def __iter__(self): return iter(self._tree) def get(self, key, default=None): element = self._tree.get(key, default) if element is default: return default return element.__of__(self) def keys(self): return self._tree.keys() def items(self): return [(i[0], i[1].__of__(self)) for i in self._tree.items()] def values(self): return [v.__of__(self) for v in self._tree.values()] def iterkeys(self): return six.iterkeys(self._tree) def itervalues(self): for v in six.itervalues(self._tree): yield v.__of__(self) def iteritems(self): for k, v in six.iteritems(self._tree): yield (k, v.__of__(self)) def allowedContentTypes(self): return [] def getTypeInfo(self): fti = DexterityFTI("ClassificationCategory") return fti def manage_delObjects(self, ids=None, REQUEST=None): """Delete the contained objects with the specified ids""" if ids is None: ids = [] if isinstance(ids, basestring): ids = [ids] for id in ids: del self[id]
class Warehouse(Persistent): _volatile_attributes = {} @property def fs_path(self): return self._volatile_attributes[id(self)]["fs_path"] @property def logger(self): return self._volatile_attributes[id(self)]["logger"] def __init__(self): self._parcels = OOBTree() self._reports = OOBTree() @property def parcels_path(self): return self.fs_path / "parcels" @property def reports_path(self): return self.fs_path / "reports" @property def tree_path(self): return self.fs_path / "tree" def new_parcel(self): parcel_path = path(tempfile.mkdtemp(prefix="", dir=self.parcels_path)) parcel_path.chmod(0755) parcel = Parcel(self, parcel_path.name) self._parcels[parcel.name] = parcel self.logger.info("New parcel %r (user %s)", parcel.name, _current_user()) return parcel def delete_parcel(self, name): self.logger.info("Deleting parcel %r (user %s)", name, _current_user()) self._parcels.pop(name) def get_parcel(self, name): return self._parcels[name] def get_all_parcels(self): return iter(self._parcels.values()) def new_report(self, lot, product): report = Report(lot, product) pk = max(self._reports.keys() or [0]) + 1 report.pk = pk report.user = _current_user() self._reports[pk] = report self.logger.info("New report for %r (user %s)", report.name, _current_user()) return report def get_report(self, report_id): return self._reports[report_id] def get_all_reports(self): return iter(self._reports.values()) def delete_report(self, report_id): self._reports.pop(report_id)
class ContentTypeScopeManager(BTreeScopeManager): """ A scope manager based on content types. This scope manager validates the request using the content type of the accessed object and the subpath of the request against a content type mapping. The content type mapping to be used will be one of specified by the resource access key, the client key or default, and is resolved in this order. One more restriction imposed by this scope manager: mappings are enforced absolutely for access keys. This allows clients to request new default scopes for themselves at will and/or have site-wide default scope changes without compromising the scopes already granted by the resource owner referenced by the access key. This however does not address the case where additional global restrictions that may be placed by the site owner as the focus is ultimately on the access keys. Workaround is to revoke those keys and have the content owners issue new ones regardless of changes. Pruning of unused scope is not implemented. """ zope.interface.implements(IContentTypeScopeManager) default_mapping_id = fieldproperty.FieldProperty( IContentTypeScopeManager['default_mapping_id']) def __init__(self): super(ContentTypeScopeManager, self).__init__() self._mappings = IOBTree() # Methods permitted to access this mapping with. Originally # I wanted to provide alternative sets of mapping on a per # mapping_id basis, however this proved to be complex and # complicated due to extra relationships involved. self._methods = IOBTree() # For metadata related to the above. self._mappings_metadata = IOBTree() # To ease the usage of scopes, the mappings are referenced by # names and are called profiles which add a few useful fields to # allow slightly easier usage. This separates the name from the # already active tokens such that once a token is instantiated # with a scope, the mapping is stuck until the token is revoked. self._named_mappings = OIBTree() # name to id. # To not overburden the named mappings with work-in-progress # profiles, instantiate one here also. self._edit_mappings = OOBTree() self.default_mapping_id = self.addMapping({}) # Main mapping related management methods. def addMapping(self, mapping, methods='GET HEAD OPTIONS', metadata=None): key = 0 # default? if len(self._mappings) > 0: # Can calculate the next key. key = self._mappings.maxKey() + 1 self._mappings[key] = mapping self._methods[key] = methods.split() if metadata is not None: self._mappings_metadata[key] = metadata return key def getMapping(self, mapping_id, default=_marker): result = self._mappings.get(mapping_id, default) if result is _marker: raise KeyError() return result def getMappingMetadata(self, mapping_id, default=None): result = self._mappings_metadata.get(mapping_id, default) return result def getMappingId(self, name): # Returned ID could potentially not exist, what do? return self._named_mappings[name] def getMappingMethods(self, mapping_id, default=_marker): result = self._methods.get(mapping_id, default) if result is _marker: raise KeyError() return result def checkMethodPermission(self, mapping_id, method): methods = self.getMappingMethods(mapping_id, ()) return method in methods def setMappingNameToId(self, name, mapping_id): self._named_mappings[name] = mapping_id def delMappingName(self, name): saved = self._named_mappings.pop(name, None) edits = self._edit_mappings.pop(name, None) return (saved, edits) def getMappingByName(self, name, default=_marker): try: mapping_id = self.getMappingId(name) mapping = self.getMapping(mapping_id) except KeyError: if default == _marker: raise mapping = default return mapping def getMappingNames(self): return self._named_mappings.keys() # Temporary/edited mapping profiles def getEditProfile(self, name, default=None): return self._edit_mappings.get(name, default) def setEditProfile(self, name, value): assert IContentTypeScopeProfile.providedBy(value) or value is None self._edit_mappings[name] = value def commitEditProfile(self, name): profile = self.getEditProfile(name) if not (IContentTypeScopeProfile.providedBy(profile)): raise KeyError('edit profile does not exist') new_mapping = profile.mapping methods = profile.methods metadata = { 'title': profile.title, 'description': profile.description, # Should really not duplicate this there but this is easy # shortcut to take for now. 'methods': methods, } new_id = self.addMapping(new_mapping, methods=methods, metadata=metadata) self.setMappingNameToId(name, new_id) def getEditProfileNames(self): return self._edit_mappings.keys() def isProfileModified(self, name): # TODO I would like some way to compare the two profiles in a # sane way but only using active types and types that have # stuff assigned. So for now just use this naive method. profile = self.getEditProfile(name) try: mapping_id = self.getMappingId(name) mapping = self.getMapping(mapping_id) metadata = self.getMappingMetadata(mapping_id, {}) except KeyError: # If profile exists, no associated ID, definitely modified. return True return not (profile.mapping == mapping and profile.title == metadata.get('title') and profile.description == metadata.get('description') and profile.methods == metadata.get('methods') ) # Scope handling. def requestScope(self, request_key, raw_scope): """ This manager references scope by ids internally. Resolve the raw scope id by the client into the mapping ids. """ raw_scopes = raw_scope and raw_scope.split(',') or [] result = set() for rs in raw_scopes: # Ignoring the current site URI and just capture the final # fragment. name = rs.split('/')[-1] try: mapping_id = self.getMappingId(name) # This verifies the existence of the mapping with id. mapping = self.getMapping(mapping_id) except KeyError: # Failed to fulfill the requested scope. return False result.add(mapping_id) if not result: result.add(self.default_mapping_id) self.setScope(request_key, result) return True def validate(self, request, client_key, access_key, accessed, container, name, value): """ See IScopeManager. """ mappings = self.resolveMapping(client_key, access_key) # multiple rights were requested, check through all of them. for mapping_id in mappings: mapping = self.getMapping(mapping_id, default={}) result = self.validateTargetWithMapping(accessed, name, mapping) method_allowed = self.checkMethodPermission(mapping_id, request.method) if result and method_allowed: return True # no matching mappings. return False def resolveMapping(self, client_key, access_key): """ See IDefaultScopeManager. """ # As all mappings are referenced byh access keys. return self.getAccessScope(access_key, None) def resolveTarget(self, accessed, name): """ Accessed target resolution. Find the type of the container object of the accessed object by traversing upwards, and gather the path to resolve into the content type id. Return both these values. """ logger.debug('resolving %s into types', accessed) # use getSite() instead of container? pt_tool = getToolByName(accessed, 'portal_types', None) if pt_tool is None: return None, None context = aq_inner(accessed) typeinfo = None subpath = [name] while context is not None: typeinfo = pt_tool.getTypeInfo(context) if typeinfo: subpath.reverse() return typeinfo.id, '/'.join(subpath) # It should have a name... subpath.append(context.__name__) context = aq_parent(context) logger.debug('parent of %s failed to resolve into typeinfo', accessed) return None, None def validateTargetWithMapping(self, accessed, name, mapping): atype, subpath = self.resolveTarget(accessed, name) return self.validateTypeSubpathMapping(atype, subpath, mapping) def validateTypeSubpathMapping(self, accessed_type, subpath, mapping): # A simple lookup method. valid_scopes = mapping.get(accessed_type, {}) if not valid_scopes: logger.debug('out of scope: %s has no mapping', accessed_type) return False logger.debug('%s got mapping', accessed_type) for vs in valid_scopes: # XXX ignores second last asterisk, preventing validation # against items that have an asterisk in its name for # whatever reason... if vs.endswith('*') and '/' in vs: match = subpath.startswith(vs[:vs.rindex('*')]) else: match = subpath == vs if match: logger.debug('subpath:%s within scope', subpath) return True logger.debug('out of scope: %s not a subpath in mapping for %s', subpath, accessed_type) return False
class TimesheetFileStore(Persistent): """ The "root" object that stores and controls Timesheets, and handles the Odoo connection. """ def __init__(self): self.sequence_next_id = 1 self.timesheets = IOBTree() self.aliases = OOBTree() # Specific Timesheets of interest self.current_running = None self.last_running = None # Odoo connection details self.odoo_protocol = "jsonrpc+ssl" self.odoo_hostname = "" self.odoo_port = 8069 self.odoo_database = "" self.odoo_username = "" # The ots version this filestore was initiated on. self.version = __version__ def _get_next_id(self): next_id = self.sequence_next_id self.sequence_next_id += 1 return next_id @staticmethod def _split_index(index): index_error = click.UsageError( f"The index needs to be an integer, or two integers " f"separated by a period '.'. Index received: {repr(index)}") index_split = index.split('.') if len(index_split) > 2: raise index_error elif len(index_split) == 1: try: date_offset = 0 task_index = int(index_split[0].strip()) except ValueError: raise index_error else: date_offset_str, task_index_str = index_split try: date_offset = int(date_offset_str.strip()) task_index = int(task_index_str.strip()) except ValueError: raise index_error return date_offset, task_index # ============================ # ===== Timesheet stuffs ===== # ============================ def _add_timesheet(self, timesheet, date=datetime.date.today()): """ :param timesheet: Timesheet :param date: date to add timesheet to :return: """ date_ordinal = date.toordinal() timesheets = self.timesheets.get(date_ordinal, []) timesheet.id = self._get_next_id() timesheets.append(timesheet) self.timesheets[date_ordinal] = timesheets # attempt to update the timesheet, but don't explode even if it fails if self.is_session_stored(): try: timesheet.update(self) except Exception as e: # TODO: Guess click.secho( "Something went wrong when trying to update data from Odoo.\n" f"{e}", fg='yellow', bold=True, ) def add_timesheet( self, task_code="", description="", is_worktime=True, date=datetime.date.today(), duration=None, task_id=None, project_id=None, ): """ :param str task_code: Odoo task code :param str description: Timesheet description :param bool is_worktime: Whether or not the time tracked is work time or not :param datetime.date date: Date of the timesheet :param (datetime.timedelta, str) duration: Duration of tracked time for the timesheet :param int task_id: Odoo database id of the task :param int project_id: Odoo database id of the project :return Timesheet: Return created timesheet """ # Check if the task code is an alias if task_code and task_code in self.aliases: timesheet = self.aliases[task_code].generate_timesheet() edit_vals = {} if description: edit_vals['description'] = description if date != datetime.date.today(): edit_vals['date'] = date if edit_vals: timesheet.edit(**edit_vals) else: timesheet = TimeSheet( task_code=task_code, description=description, is_worktime=is_worktime, date=date, task_id=task_id, project_id=project_id, ) if duration is not None: if not isinstance(duration, datetime.timedelta): duration = apply_duration_string(duration) timesheet.set_duration(duration) self._add_timesheet(timesheet, date=date) return timesheet def add_and_start_timesheet(self, **kwargs): timesheet = self.add_timesheet(**kwargs) if self.current_running: self.current_running.stop() self.last_running = self.current_running timesheet.start() self.current_running = timesheet def resume(self, index=None): """ :param index: index of the timesheet to resume, or optionally date offset and index separated by a period ('.') """ if index: to_resume = self.get_timesheet_by_index(index) else: to_resume = self.last_running if not to_resume: click.echo("No timesheet to resume.") elif to_resume.is_running(): click.echo("The timesheet to resume is already running.") else: if self.current_running: self.stop_running() else: self.last_running = None today = datetime.date.today() # TODO: allow the user to configure that they want a new timesheet # on the same day if the resumed timesheet has been pushed to Odoo? if to_resume.date != today: # Resuming on a different day: start a new timesheet with the # same details new_timesheet = to_resume.copy( date=today, duration=datetime.timedelta(), ) self._add_timesheet(new_timesheet) self.current_running = new_timesheet else: # Still the same day: we can continue the same timesheet self.current_running = to_resume self.current_running.start() def stop_running(self): if self.current_running: if self.current_running.is_running(): # Making sure the `current_running` is actually running before attempting to # stop it, just in case we somehow stopped the timesheet but left it # as `current_running` self.current_running.stop() self.last_running = self.current_running self.current_running = None def edit_timesheet(self, index, **kwargs): timesheet = self.get_timesheet_by_index(index) return timesheet.edit(storage=self, **kwargs) def get_timesheet_by_index(self, index): """ index of a timesheet or optionally negative date offset and an index separated by a period ('.'). Index "2" => timesheets at index 1 for today (no offset). This is same as "0.2" Index "1.2" => Yesterday's (today - date offset of 1) timesheets at index 2 :param index: string :return: timesheet matching the index """ date_offset, task_index = self._split_index(index) timesheet_date = datetime.date.today() - relativedelta.relativedelta( days=date_offset) timesheet_ordinal = timesheet_date.toordinal() timesheets = self.timesheets.get(timesheet_ordinal, []) if not timesheets: raise click.ClickException( f"No timesheets for date {str(timesheet_date)}, nothing to resume." ) max_task_index = len(timesheets) - 1 if task_index > max_task_index: raise click.ClickException( f"Task index out of range. Max task index for {str(timesheet_date)} is " f"{max_task_index}, got {task_index}.") timesheet = timesheets[task_index] return timesheet def find_timesheet(self, date, project_id=None, task_id=None, description=None): return None def get_timesheets(self, date_min, date_max): """ Get all timesheets from the given date range (both limits inclusive) :param date_min: first date to include :param date_max: last date to include :return: """ min_ordinal = date_min.toordinal() max_ordinal = date_max.toordinal() return self.timesheets.values(min=min_ordinal, max=max_ordinal) def drop_timesheet(self, index): date_offset, timesheet_index = self._split_index(index) timesheet_date = datetime.date.today() - relativedelta.relativedelta( days=date_offset) timesheet_ordinal = timesheet_date.toordinal() timesheets = self.timesheets.get(timesheet_ordinal, []) timesheet = timesheets.pop(timesheet_index) self.timesheets[timesheet_ordinal] = timesheets click.echo(f"Dropped timesheet {repr(timesheet)}") def print_date(self, date=None): ordinal_today = datetime.date.today().toordinal() if date is None: date_ordinal = ordinal_today else: date_ordinal = date.toordinal() date_offset = ordinal_today - date_ordinal timesheets_for_date = self.timesheets.get(date_ordinal, []) weekday = calendar.day_name[date.weekday()] headers = ["Project", "Task", "Description", "Duration"] def get_coloured_duration(ts): dur = ts.get_formatted_duration(show_running=True) if ts.is_worktime: # TODO: This doesn't care if the time has changed since last # push, so green is not always a sign of "good status" colour = "green" if ts.odoo_id else "red" dur = click.style(dur, fg=colour) return dur # Table containing the actual data table = [[ limit_str_length(ts.project_title), limit_str_length(f"{ts.task_code} {ts.task_title}"), limit_str_length(ts.description), get_coloured_duration(ts) ] for ts in timesheets_for_date] # Generate values for the index column. This adds the date offset for # dates other than today. no_indices = len(table) index_prefix = str(date_offset) if date_offset else "" indices = [ f"{index_prefix}.{i}" if index_prefix else str(i) for i in range(no_indices) ] # Total work time worktime_sheets = [ts for ts in timesheets_for_date if ts.is_worktime] total_duration = self.count_total_duration(worktime_sheets) # We want to disable tabulate's number parsing on the index column # because it changes '4.0' to '4', which is not desired. # But tabulate doesn't handle this option well if the column is empty, # so we need to only disable it when we actually have something in # the column. disable_numparse = [0] if indices else False click.secho(f"Timesheets for {date.isoformat()}, ({weekday})", fg='green', bold=True) click.echo( tabulate( table, headers=headers, showindex=indices, disable_numparse=disable_numparse, )) click.echo(f"Total Work Time: {format_timedelta(total_duration)}") @staticmethod def count_total_duration(timesheets): total_duration = datetime.timedelta() for ts in timesheets: total_duration += ts.get_duration() return total_duration # ============================ # ===== Aliases ====== # ============================ def _get_alias(self, name): try: alias = self.aliases[name] except KeyError: raise click.ClickException(f"Alias {name} does not exist.") return alias def add_alias(self, name, task_code="", description="", project_id=None, task_id=None): new_alias = TimeSheetAlias( name, task_code=task_code, description=description, project_id=project_id, task_id=task_id, ) self.aliases[name] = new_alias click.echo(f"Alias {name} added.") if self.is_session_stored(): try: new_alias.update(self) except Exception as e: # TODO: Guess click.secho( "Something went wrong when trying to update data from Odoo.\n" f"{e}", fg='yellow', bold=True, ) def delete_alias(self, name): try: self.aliases.pop(name) except KeyError: raise click.UsageError(f"Alias {name} doesn't exist, " "and thus can't be deleted.") click.echo(f"Alias {name} deleted.") def print_aliases(self, include_details=False): attributes = [ ("Alias", "name"), ("Task Code", "task_code"), ("Description", "description"), ("Title", "task_title"), ("Project", "project_title"), ] details = [("Project id", "project_id"), ("Task id", "task_id")] if include_details: attributes.extend(details) aliases = self.aliases.values() headers = [a[0] for a in attributes] table = [[limit_str_length(getattr(alias, a[1])) for a in attributes] for alias in aliases] click.echo(tabulate(table, headers=headers)) def update_alias(self, name): """ Update an alias from Odoo. If no name is given, update all aliases. :param str name: name of an alias, or None to update all aliases """ if self.is_session_stored(): if name is not None: aliases = [self._get_alias(name)] else: aliases = self.aliases.values() try: with click.progressbar(aliases) as alias_bar: for alias in alias_bar: alias.update(self) except Exception as e: # TODO: Guess click.secho( "Something went wrong when trying to update data from Odoo.\n" f"{e}", fg='yellow', bold=True, ) else: raise click.ClickException( "Odoo session not available. To update data from Odoo, " "please log in with 'ots login'.") # ============================ # ===== Odoo connection ====== # ============================ def _get_odoo_session_name(self): return f"ots_{self.odoo_hostname}_{self.odoo_port}_{self.odoo_protocol}_{self.odoo_database}_{self.odoo_username}" def set_odoo_connection_details(self, protocol, hostname, port, database, username): self.odoo_protocol = protocol self.odoo_hostname = hostname self.odoo_port = port self.odoo_database = database self.odoo_username = username def login(self, username, password, *, hostname, port=443, ssl=True, database=None, save=True): """ :param username: :param password: :param hostname: :param port: :param ssl: :param database: :param save: :return: """ protocol = "jsonrpc+ssl" if ssl else "jsonrpc" odoo = odoorpc.ODOO(hostname, protocol=protocol, timeout=60, port=port) if database is None: click.echo("Trying to decide the database.") databases = odoo.db.list() if not databases: raise click.ClickException( "No compatible databases found on target Odoo. " "Either there are no compatible databases, or database listing is turned off. " "Configure the database name if one is supposed to exist.") if len(databases) > 1: raise click.ClickException( "More than one compatible database found on target Odoo. " f"Please configure the correct database. Compatible databases: {databases}" ) else: database = databases[0] click.echo(f"Attempting to connect to database {database}") odoo.login(database, username, password) user_id = odoo.env.uid if save: self.set_odoo_connection_details( protocol=protocol, hostname=hostname, port=port, database=database, username=username, ) odoo.save(self._get_odoo_session_name()) return user_id def logout(self): odoorpc.ODOO.remove(self._get_odoo_session_name()) def load_odoo_session(self): return odoorpc.ODOO.load(self._get_odoo_session_name()) def is_session_stored(self): return self._get_odoo_session_name() in odoorpc.ODOO.list() def push(self, index, date): created = [] wrote = [] if index: timesheets = [self.get_timesheet_by_index(index)] else: if not date: date = datetime.date.today() date_ordinal = date.toordinal() timesheets = self.timesheets.get(date_ordinal, []) odoo = self.load_odoo_session() with click.progressbar(timesheets) as timesheet_bar: for timesheet in timesheet_bar: odoo_id = timesheet.odoo_id new_id = timesheet.odoo_push(odoo) if odoo_id: wrote.append(str(odoo_id)) elif new_id: created.append(str(new_id)) if created: click.echo( f"New timesheets created with ids: {', '.join(created)}") if wrote: click.echo( f"Wrote possible changes to existing timesheets: {', '.join(wrote)}" ) def print_odoo_search_results(self, search_term): raise NotImplementedError() def _odoo_search_task_by_code(self, search_term): if not search_term: raise click.ClickException("Gief search term plz.") odoo = self.load_odoo_session() task_model = odoo.env['project.task'] task_ids = task_model.search([('code', '=', search_term)], limit=1) return task_ids def _odoo_search_tasks_and_projects(self, search_term): """ For now this is just a basic search that searches for a task or project based on a string :param search_term: :return: """ # TODO: This is making a lot of assumptions atm. If we have no project installed, # or if there is no code field, this'll just explode. if not search_term: raise click.ClickException("I need a search term.") result = {} odoo = self.load_odoo_session() task_model = odoo.env['project.task'] project_model = odoo.env['project.project'] # Search direct match by task code task_ids = self._odoo_search_task_by_code(search_term) project_ids = [] if not task_ids: # We found no exact match, search for tasks or projects matching the search term task_ids = task_model.search( [('name', 'ilike', search_term)], order= "project_id, id desc" # TODO: Experimenting, possibly won't need custom order ) project_ids = project_model.search([('name', 'ilike', search_term) ]) result['task_ids'] = task_ids result['project_ids'] = project_ids return result def odoo_search_task(self, search_term): odoo = self.load_odoo_session() search_results = self._odoo_search_tasks_and_projects(search_term) project_ids = search_results.get('project_ids', []) task_ids = search_results.get('task_ids', []) if not project_ids and not task_ids: click.secho("No results found.", fg='yellow', bold=True) return else: click.secho(f"Search results for \"{search_term}\"", fg='green', bold=True) result_strings = [] if task_ids: # read always returns id, even if we don't ask for it, but we use it as a header # so simpler to include it here and reuse the fields-list as the table headers task_fields = [ "code", "name", "project_id", "stage_id", "id", ] task_vals = odoo.env['project.task'].browse(task_ids).read( task_fields) table = [[limit_str_length(data[field]) for field in task_fields] for data in task_vals] ttitle = click.style("Tasks:", fg='green', bold=True) ttable = tabulate(table, headers=task_fields) task_result = f"{ttitle}\n{ttable}" result_strings.append(task_result) if project_ids: project_fields = [ "name", "id", ] project_vals = odoo.env['project.project'].browse( project_ids).read(project_fields) table = [[ limit_str_length(data[field]) for field in project_fields ] for data in project_vals] ptitle = click.style("Projects:", fg='green', bold=True) ptable = tabulate(table, headers=project_fields) project_result = f"{ptitle}\n{ptable}" result_strings.append(project_result) if result_strings: click.echo("\n\n".join(result_strings)) def update_timesheet_odoo_data(self, index): timesheet = self.get_timesheet_by_index(index) # TODO: Create a mass-update version with date-ranges or something. timesheet.update(self)
class EmailManager(Persistent, Contained): """ Alternative email tracker This tracks the alternative email addresses that a user may possess and this provides the methods to track and retrieve them. Does not provide any robust error checking. """ def __init__(self): # for looking up an email address to a login self._email_to_login = OOBTree() # for tracking users' own email addresses self._user_email_lists = OOBTree() def set_email(self, login, emails): self.del_email(login) # we emptied the entire old list anyway... self._user_email_lists[login] = email_list = PersistentList() self._add_email(login, *emails) def add_email(self, login, email): email_list = self._user_email_lists.get(login, None) if email_list is None: self._user_email_lists[login] = email_list = PersistentList() self._add_email(login, email) def _add_email(self, login, *emails): """ This is the one and only method that could add data to both - self._email_to_login (via append) - self._user_email_lists (via set) """ email_list = self._user_email_lists[login] for email in emails: current_owner = self.get_login_for(email) if not current_owner is None: if email in self.get_emails_for(current_owner): # silently fail continue email_list.append(email) self._email_to_login[email] = login def del_email(self, login, *emails): email_list = self._user_email_lists.get(login, ()) if not email_list: return if not emails: # remove all user's email addresses. emails = list(email_list) for email in emails: try: self._email_to_login.pop(email, None) email_list.remove(email) except ValueError: pass def get_emails_for(self, login): """ Return all emails for this login. """ return sorted(self._user_email_lists.get(login, [])) def get_login_for(self, email): """ Return the login for this email address. """ return self._email_to_login.get(email, None)
class PendingList(object): """ Implementation of IPendingList Set up the pending list >>> from Products.listen.content import PendingList >>> plist = PendingList() Add a few pending members >>> plist.add('tom') >>> plist.add('*****@*****.**') >>> plist.add('mikey', time='2006-05-09', pin='4532123') >>> sorted(plist.get_user_emails()) ['*****@*****.**', 'mikey', 'tom'] The time that we set on mikey should be used instead of the default time >>> plist.get_pending_time('mikey') '2006-05-09' >>> plist.get_user_pin('mikey') '4532123' Try and add mikey a second time and make sure data is not lost but time is updated >>> plist.add('mikey') >>> plist.get_user_pin('mikey') '4532123' >>> plist.get_pending_time('mikey') != '2006-05-09' True Now let's remove them >>> plist.remove('tom') >>> plist.remove('*****@*****.**') >>> plist.remove('mikey') >>> plist.get_user_emails() [] Let's create an item with a post >>> plist.add('timmy', post='a new post') >>> post = plist.get_posts('timmy')[0] >>> post['header'] {} >>> post['body'] 'a new post' Verify the id of the post >>> post['postid'] 0 Let's add a new post, and verify its id too >>> plist.add('timmy', post='hi there') >>> newpost = plist.get_posts('timmy')[1] >>> newpost['postid'] 1 Remove the first one >>> plist.pop_post('timmy', 0) is not None True >>> p = plist.get_posts('timmy')[0] >>> p['body'] 'hi there' >>> p['postid'] 1 Trying to pop a fake post returns None >>> plist.pop_post('timmy', 0) is None True >>> plist.pop_post('timmy', 17) is None True """ implements(IPendingList) def __init__(self): self.pend = OOBTree() self.trust_caller = False def add(self, item, **values): self.pend.setdefault(item, OOBTree()) if 'time' not in values: if self.trust_caller: raise AssertionError("No time passed in: %s" % values) values['time'] = DateTime().ISO() if 'post' in values: post_list = self.pend[item].setdefault('post', IOBTree()) new_post = values['post'] if isinstance(new_post, basestring): new_post = dict(header={}, body=new_post) try: nextid = post_list.maxKey() + 1 except ValueError: nextid = 0 if self.trust_caller: assert 'postid' in new_post, new_post else: new_post['postid'] = nextid post_list[new_post['postid']] = new_post values.pop('post') self.pend[item].update(values) def remove(self, item): if item in self.pend: self.pend.pop(item) def pop_post(self, item, postid): posts = self.pend[item]['post'] try: return posts.pop(postid) except KeyError: return None def get_posts(self, user_email): return list(self.pend.get(user_email, {}).get('post', {}).values()) def is_pending(self, item): return item in self.pend def get_user_pin(self, user_email): return self.pend.get(user_email, {}).get('pin') def get_pending_time(self, user_email): return self.pend.get(user_email, {}).get('time') def get_user_emails(self): return list(self.pend.keys()) def get_user_name(self, user_email): return self.pend.get(user_email, {}).get('user_name') def clear(self): for email, item in self.pend.items(): if 'post' in item: for post in item['post'].values(): for k, v in item.items(): if k == 'post': continue post[k] = v post['email'] = email yield post else: item['email'] = email yield item self.pend.clear()
class Tokenable(Entity): """Question class""" tokens_opposition = CompositeMultipleProperty('tokens_opposition') tokens_support = CompositeMultipleProperty('tokens_support') def __init__(self, **kwargs): super(Tokenable, self).__init__(**kwargs) self.set_data(kwargs) self.allocated_tokens = OOBTree() self.len_allocated_tokens = PersistentDict({}) def add_token(self, user, evaluation_type): user_oid = get_oid(user) if user_oid in self.allocated_tokens: self.remove_token(user) self.allocated_tokens[user_oid] = evaluation_type self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] += 1 def remove_token(self, user): user_oid = get_oid(user) if user_oid in self.allocated_tokens: evaluation_type = self.allocated_tokens.pop(user_oid) self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] -= 1 def evaluators(self, evaluation_type=None): if evaluation_type: return [get_obj(key) for value, key in self.allocated_tokens.byValue(evaluation_type)] return [get_obj(key) for key in self.allocated_tokens.keys()] def evaluation(self, user): user_oid = get_oid(user, None) return self.allocated_tokens.get(user_oid, None) def remove_tokens(self, force=False): evaluators = self.evaluators() for user in evaluators: user.remove_token(self) if force: self.remove_token(user) def user_has_token(self, user, root=None): if hasattr(user, 'has_token'): return user.has_token(self, root) return False def init_support_history(self): # [(user_oid, date, support_type), ...], support_type = {1:support, 0:oppose, -1:withdraw} if not hasattr(self, '_support_history'): setattr(self, '_support_history', PersistentList()) @property def len_support(self): return self.len_allocated_tokens.get(Evaluations.support, 0) @property def len_opposition(self): return self.len_allocated_tokens.get(Evaluations.oppose, 0)
class Person(User, SearchableEntity, CorrelableEntity, Debatable): """Person class""" type_title = _('Person') icon = 'icon glyphicon glyphicon-user' #'icon novaideo-icon icon-user' templates = { 'default': 'novaideo:views/templates/person_result.pt', 'bloc': 'novaideo:views/templates/person_bloc.pt', 'small': 'novaideo:views/templates/small_person_result.pt', 'popover': 'novaideo:views/templates/person_popover.pt', 'card': 'novaideo:views/templates/person_card.pt', 'header': 'novaideo:views/templates/person_header.pt', } default_picture = 'novaideo:static/images/user100.png' name = renamer() tokens = CompositeMultipleProperty('tokens') tokens_ref = SharedMultipleProperty('tokens_ref') organization = SharedUniqueProperty('organization', 'members') events = SharedMultipleProperty('events', 'author') picture = CompositeUniqueProperty('picture') cover_picture = CompositeUniqueProperty('cover_picture') ideas = SharedMultipleProperty('ideas', 'author') selections = SharedMultipleProperty('selections') working_groups = SharedMultipleProperty('working_groups', 'members') wg_participations = SharedMultipleProperty('wg_participations', 'wating_list_participation') old_alerts = SharedMultipleProperty('old_alerts') following_channels = SharedMultipleProperty('following_channels', 'members') folders = SharedMultipleProperty('folders', 'author') questions = SharedMultipleProperty('questions', 'author') challenges = SharedMultipleProperty('challenges', 'author') ballots = CompositeMultipleProperty('ballots') mask = SharedUniqueProperty('mask', 'member') def __init__(self, **kwargs): self.branches = PersistentList() self.keywords = PersistentList() super(Person, self).__init__(**kwargs) kwargs.pop('password', None) self.set_data(kwargs) self.set_title() self.last_connection = datetime.datetime.now(tz=pytz.UTC) self._read_at = OOBTree() self.guide_tour_data = PersistentDict({}) self.confidence_index = 0 self._notes = OOBTree() self.allocated_tokens = OOBTree() self.len_allocated_tokens = PersistentDict({}) self.reserved_tokens = PersistentList([]) self._submited_at = OOBTree() self._reported_at = OOBTree() def __setattr__(self, name, value): super(Person, self).__setattr__(name, value) if name == 'organization' and value: self.init_contents_organizations() def get_len_tokens(self, root=None, exclude_reserved_tokens=False): root = root or getSite() return root.tokens_mini if exclude_reserved_tokens \ else root.tokens_mini + len(self.reserved_tokens) def get_len_evaluations(self, exclude_reserved_tokens=False): total = self.len_allocated_tokens.get(Evaluations.support, 0) + \ self.len_allocated_tokens.get(Evaluations.oppose, 0) if exclude_reserved_tokens: return total - len([ o for o in self.reserved_tokens if o in self.allocated_tokens ]) return total def get_len_free_tokens(self, root=None, exclude_reserved_tokens=False): root = root or getSite() return self.get_len_tokens(root, exclude_reserved_tokens) - \ self.get_len_evaluations(exclude_reserved_tokens) def has_token(self, obj=None, root=None): root = root or getSite() obj_oid = get_oid(obj, None) if obj_oid and obj_oid in self.reserved_tokens: return obj_oid not in self.allocated_tokens return self.get_len_free_tokens(root, True) > 0 def add_token(self, obj, evaluation_type, root=None): if self.has_token(obj, root): self.allocated_tokens[get_oid(obj)] = evaluation_type self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] += 1 def remove_token(self, obj): obj_oid = get_oid(obj) if obj_oid in self.allocated_tokens: evaluation_type = self.allocated_tokens.pop(obj_oid) self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] -= 1 def add_reserved_token(self, obj): obj_oid = get_oid(obj) if obj_oid not in self.reserved_tokens: self.reserved_tokens.append(obj_oid) def remove_reserved_token(self, obj): obj_oid = get_oid(obj) if obj_oid in self.reserved_tokens: self.reserved_tokens.remove(obj_oid) def evaluated_objs(self, evaluation_type=None): if evaluation_type: return [ get_obj(key) for value, key in self.allocated_tokens.byValue( evaluation_type) ] return [get_obj(key) for key in self.allocated_tokens.keys()] def evaluated_objs_ids(self, evaluation_type=None): if evaluation_type: return [ key for value, key in self.allocated_tokens.byValue( evaluation_type) ] return list(self.allocated_tokens.keys()) def init_contents_organizations(self): novaideo_catalog = find_catalog('novaideo') dace_catalog = find_catalog('dace') organizations_index = novaideo_catalog['organizations'] object_authors_index = novaideo_catalog['object_authors'] object_provides_index = dace_catalog['object_provides'] query = object_authors_index.any([get_oid(self)]) & \ object_provides_index.any( [Iidea.__identifier__, IProposal.__identifier__]) & \ organizations_index.any([0]) for entity in query.execute().all(): entity.init_organization() entity.reindex() def set_read_date(self, channel, date): self._read_at[get_oid(channel)] = date def get_read_date(self, channel): return self._read_at.get(get_oid(channel), datetime.datetime.now(tz=pytz.UTC)) def get_channel(self, user): all_channels = list(self.channels) all_channels.extend(list(getattr(user, 'channels', []))) for channel in all_channels: if user in channel.members and self in channel.members: return channel return None def addtoproperty(self, name, value, moving=None): super(Person, self).addtoproperty(name, value, moving) if name == 'selections': value.len_selections = getattr(value, 'len_selections', 0) value.len_selections += 1 def delfromproperty(self, name, value, moving=None): super(Person, self).delfromproperty(name, value, moving) if name == 'selections': value.len_selections = getattr(value, 'len_selections', 0) if value.len_selections > 0: value.len_selections -= 1 def set_title(self): if getattr(self, 'pseudonym', ''): self.title = self.pseudonym else: self.title = getattr(self, 'first_name', '') + ' ' + \ getattr(self, 'last_name', '') def add_note(self, user, context, note, date, time_constant): self._notes[date] = (get_oid(user), get_oid(context), note) self.calculate_confidence_index(time_constant) def get_questions(self, user): if user is self: return self.questions + getattr(self.mask, 'questions', []) return self.questions def get_ideas(self, user): if user is self: return self.ideas + getattr(self.mask, 'ideas', []) return self.ideas def get_working_groups(self, user): if user is self: return self.working_groups + getattr(self.mask, 'working_groups', []) return self.working_groups @property def proposals(self): return [wg.proposal for wg in self.working_groups] def get_proposals(self, user): if user is self: return self.proposals + getattr(self.mask, 'proposals', []) return self.proposals @property def contacts(self): return [s for s in self.selections if isinstance(s, Person)] @property def participations(self): result = [ p for p in list(self.proposals) if any(s in p.state for s in [ 'amendable', 'open to a working group', 'votes for publishing', 'votes for amendments' ]) ] return result def get_participations(self, user): if user is self: return self.participations + getattr(self.mask, 'participations', []) return self.participations @property def contents(self): result = [i for i in list(self.ideas) if i is i.current_version] result.extend(self.proposals) result.extend(self.questions) result.extend(self.challenges) result.extend(self.events) return result def get_contents(self, user): if user is self: return self.contents + getattr(self.mask, 'contents', []) return self.contents @property def active_working_groups(self): return [p.working_group for p in self.participations] def get_active_working_groups(self, user): if user is self: return self.active_working_groups + getattr( self.mask, 'active_working_groups', []) return self.active_working_groups def get_wg_participations(self, user): if user is self: return self.wg_participations + getattr(self.mask, 'wg_participations', []) return self.wg_participations @property def is_published(self): return 'active' in self.state @property def managed_organization(self): return get_objects_with_role(user=self, role='OrganizationResponsible') def get_confidence_index(self): return getattr(self, 'confidence_index', 0) def reindex(self): super(Person, self).reindex() root = getSite() self.__access_keys__ = PersistentList(generate_access_keys(self, root)) def get_picture_url(self, kind, default): if self.picture: img = getattr(self.picture, kind, None) if img: return img.url return default def get_more_contents_criteria(self): "return specific query, filter values" return None, None def set_organization(self, organization): current_organization = self.organization if organization: if current_organization is not organization: is_manager = current_organization and has_role( ('OrganizationResponsible', current_organization), self, ignore_superiors=True) if current_organization and is_manager: revoke_roles( self, (('OrganizationResponsible', current_organization), )) self.setproperty('organization', organization) elif current_organization: is_manager = has_role( ('OrganizationResponsible', current_organization), self, ignore_superiors=True) if is_manager: revoke_roles( self, (('OrganizationResponsible', current_organization), )) self.delfromproperty('organization', current_organization) @property def all_alerts(self): novaideo_catalog = find_catalog('novaideo') dace_catalog = find_catalog('dace') alert_keys_index = novaideo_catalog['alert_keys'] alert_exclude_keys_index = novaideo_catalog['alert_exclude_keys'] object_provides_index = dace_catalog['object_provides'] exclude = [str(get_oid(self))] if self.mask: exclude.append(str(get_oid(self.mask))) query = object_provides_index.any([IAlert.__identifier__]) & \ alert_keys_index.any(self.get_alerts_keys()) & \ alert_exclude_keys_index.notany(exclude) return query.execute() @property def alerts(self): old_alerts = [get_oid(a) for a in self.old_alerts] result = self.all_alerts def exclude(result_set, docids): filtered_ids = list(result_set.ids) for _id in docids: if _id in docids and _id in filtered_ids: filtered_ids.remove(_id) return result_set.__class__(filtered_ids, len(filtered_ids), result_set.resolver) return exclude(result, old_alerts) def get_alerts_keys(self): result = ['all', str(get_oid(self))] if self.mask: result.append(str(get_oid(self.mask))) return result def get_alerts(self, alerts=None, kind=None, subject=None, **kwargs): if alerts is None: alerts = self.alerts if kind: alerts = [a for a in alerts if a.is_kind_of(kind)] if subject: alerts = [a for a in alerts if subject in a.subjects] if kwargs: alerts = [a for a in alerts if a.has_args(**kwargs)] return alerts def calculate_confidence_index(self, time_constant): now = datetime.datetime.utcnow().timestamp() notes = np.array([v[2] for v in self._notes.values()]) dates = np.array([int(t.timestamp()) for t in self._notes.keys()]) time_c = time_constant * 86400 confidence_index = np.sum( np.dot(notes, np.exp(-np.log(2) * (now - dates) / time_c))) self.confidence_index = round(confidence_index, 1) @property def user_groups(self): groups = list(self.groups) if self.organization: groups.append(self.organization) if self.mask: groups.append(self.mask) return groups @property def user_locale(self): locale = getattr(self, 'locale', None) if not locale: locale = getSite(self).locale return locale def _init_mask(self, root): if not self.mask: mask = Mask() root.addtoproperty('masks', mask) self.setproperty('mask', mask) def get_mask(self, root=None): root = root if root else getSite() if not getattr(root, 'anonymisation', False): return self self._init_mask(root) return self.mask def add_submission(self, obj): now = datetime.datetime.now(tz=pytz.UTC) self._submited_at[now] = get_oid(obj) def add_report(self, obj): now = datetime.datetime.now(tz=pytz.UTC) self._reported_at[now] = get_oid(obj) def can_submit_idea(self, root=None): root = root if root else getSite() now = datetime.datetime.now(tz=pytz.UTC) monday = datetime.datetime.combine((now - datetime.timedelta(days=7)), datetime.time(0, 0, 0, tzinfo=pytz.UTC)) return len(self._submited_at.values(min=monday, max=now)) < getattr( root, 'nb_submission_maxi', 3) def can_report(self, root=None): root = root if root else getSite() now = datetime.datetime.now(tz=pytz.UTC) monday = datetime.datetime.combine((now - datetime.timedelta(days=7)), datetime.time(0, 0, 0, tzinfo=pytz.UTC)) return len(self._reported_at.values(min=monday, max=now)) < getattr( root, 'nb_reports_maxi', 3)
class PasswordTool(BaseTool): """ PasswordTool is used to allow a user to change its password """ title = 'Password Tool' id = 'portal_password' meta_type = 'ERP5 Password Tool' portal_type = 'Password Tool' allowed_types = () # Declarative Security security = ClassSecurityInfo() security.declareProtected(Permissions.ManagePortal, 'manage_overview' ) manage_overview = DTMLFile( 'explainPasswordTool', _dtmldir ) _expiration_day = 1 def __init__(self, id=None): if id is None: id = self.__class__.id self._password_request_dict = OOBTree() # XXX no call to BaseTool.__init__ ? # BaseTool.__init__(self, id) security.declareProtected('Manage users', 'getResetPasswordKey') def getResetPasswordKey(self, user_login, expiration_date=None): if expiration_date is None: # generate expiration date expiration_date = DateTime() + self._expiration_day # generate a random string key = self._generateUUID() if isinstance(self._password_request_dict, PersistentMapping): LOG('ERP5.PasswordTool', INFO, 'Migrating password_request_dict to' ' OOBTree') self._password_request_dict = OOBTree(self._password_request_dict) # register request self._password_request_dict[key] = (user_login, expiration_date) return key security.declareProtected('Manage users', 'getResetPasswordUrl') def getResetPasswordUrl(self, user_login=None, key=None, site_url=None): if user_login is not None: # XXX Backward compatibility key = self.getResetPasswordKey(user_login) parameter = urlencode(dict(reset_key=key)) method = self._getTypeBasedMethod("getSiteUrl") if method is not None: base_url = method() else: base_url = "%s/portal_password/PasswordTool_viewResetPassword" % ( site_url,) url = "%s?%s" %(base_url, parameter) return url security.declareProtected('Manage users', 'getResetPasswordUrl') def getExpirationDateForKey(self, key=None): return self._password_request_dict[key][1] def mailPasswordResetRequest(self, user_login=None, REQUEST=None, notification_message=None, sender=None, store_as_event=False, expiration_date=None, substitution_method_parameter_dict=None): """ Create a random string and expiration date for request Parameters: user_login -- Reference of the user to send password reset link REQUEST -- Request object notification_message -- Notification Message Document used to build the email. As default, a standart text will be used. sender -- Sender (Person or Organisation) of the email. As default, the default email address will be used store_as_event -- whenever CRM is available, store notifications as events expiration_date -- If not set, expiration date is current date + 1 day. substitution_method_parameter_dict -- additional substitution dict for creating an email. """ if REQUEST is None: REQUEST = get_request() if user_login is None: user_login = REQUEST["user_login"] site_url = self.getPortalObject().absolute_url() if REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from msg = None # check user exists, and have an email user_list = self.getPortalObject().acl_users.\ erp5_users.getUserByLogin(user_login) if len(user_list) == 0: msg = translateString("User ${user} does not exist.", mapping={'user':user_login}) else: # We use checked_permission to prevent errors when trying to acquire # email from organisation user = user_list[0] email_value = user.getDefaultEmailValue( checked_permission='Access content information') if email_value is None or not email_value.asText(): msg = translateString( "User ${user} does not have an email address, please contact site " "administrator directly", mapping={'user':user_login}) if msg: if REQUEST is not None: parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % \ (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url ) return msg key = self.getResetPasswordKey(user_login=user_login, expiration_date=expiration_date) url = self.getResetPasswordUrl(key=key, site_url=site_url) # send mail message_dict = {'instance_name':self.getPortalObject().getTitle(), 'reset_password_link':url, 'expiration_date':self.getExpirationDateForKey(key)} if substitution_method_parameter_dict is not None: message_dict.update(substitution_method_parameter_dict) if notification_message is None: subject = translateString("[${instance_name}] Reset of your password", mapping={'instance_name': self.getPortalObject().getTitle()}) subject = subject.translate() message = translateString("\nYou requested to reset your ${instance_name}"\ " account password.\n\n" \ "Please copy and paste the following link into your browser: \n"\ "${reset_password_link}\n\n" \ "Please note that this link will be valid only one time, until "\ "${expiration_date}.\n" \ "After this date, or after having used this link, you will have to make " \ "a new request\n\n" \ "Thank you", mapping=message_dict) message = message.translate() event_keyword_argument_dict={} message_text_format = 'text/plain' else: message_text_format = notification_message.getContentType() subject = notification_message.getTitle() if message_text_format == "text/html": message = notification_message.asEntireHTML(substitution_method_parameter_dict=message_dict) else: message = notification_message.asText(substitution_method_parameter_dict=message_dict) event_keyword_argument_dict={ 'resource':notification_message.getSpecialise(), 'language':notification_message.getLanguage(), } self.getPortalObject().portal_notifications.sendMessage(sender=sender, recipient=[user,], subject=subject, message=message, store_as_event=store_as_event, message_text_format=message_text_format, event_keyword_argument_dict=event_keyword_argument_dict) if REQUEST is not None: msg = translateString("An email has been sent to you.") parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url ) def _generateUUID(self, args=""): """ Generate a unique id that will be used as url for password """ # this code is based on # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/213761 # by Carl Free Jr # as uuid module is only available in pyhton 2.5 t = long( time.time() * 1000 ) r = long( random.random()*100000000000000000L ) try: a = socket.gethostbyname( socket.gethostname() ) except: # if we can't get a network address, just imagine one a = random.random()*100000000000000000L data = ' '.join((str(t), str(r), str(a), str(args))) return md5(data).hexdigest() def resetPassword(self, reset_key=None, REQUEST=None): """ """ # XXX-Aurel : is it used ? if REQUEST is None: REQUEST = get_request() user_login, expiration_date = self._password_request_dict.get(reset_key, (None, None)) site_url = self.getPortalObject().absolute_url() if REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from if reset_key is None or user_login is None: ret_url = '%s/login_form' % site_url return REQUEST.RESPONSE.redirect( ret_url ) # check date current_date = DateTime() if current_date > expiration_date: msg = translateString("Date has expire.") parameter = urlencode(dict(portal_status_message=msg)) ret_url = '%s/login_form?%s' % (site_url, parameter) return REQUEST.RESPONSE.redirect( ret_url ) # redirect to form as all is ok REQUEST.set("password_key", reset_key) return self.reset_password_form(REQUEST=REQUEST) def removeExpiredRequests(self, **kw): """ Browse dict and remove expired request """ current_date = DateTime() for key, (login, date) in self._password_request_dict.items(): if date < current_date: self._password_request_dict.pop(key) def changeUserPassword(self, password, password_key, password_confirm=None, user_login=None, REQUEST=None, **kw): """ Reset the password for a given login """ # BBB: password_confirm: unused argument def error(message): # BBB: should "raise Redirect" instead of just returning, simplifying # calling code and making mistakes more difficult # BBB: should probably not translate message when REQUEST is None message = translateString(message) if REQUEST is None: return message return REQUEST.RESPONSE.redirect( site_url + '/login_form?' + urlencode({ 'portal_status_message': message, }) ) if REQUEST is None: REQUEST = get_request() if self.getWebSiteValue(): site_url = self.getWebSiteValue().absolute_url() elif REQUEST and 'came_from' in REQUEST: site_url = REQUEST.came_from else: site_url = self.getPortalObject().absolute_url() try: register_user_login, expiration_date = self._password_request_dict[ password_key] except KeyError: # XXX: incorrect grammar and not descriptive enough return error('Key not known. Please ask reset password.') if user_login is not None and register_user_login != user_login: # XXX: not descriptive enough return error("Bad login provided.") if DateTime() > expiration_date: # XXX: incorrect grammar return error("Date has expire.") del self._password_request_dict[password_key] persons = self.getPortalObject().acl_users.erp5_users.getUserByLogin( register_user_login) person = persons[0] person._forceSetPassword(password) person.reindexObject() if REQUEST is not None: return REQUEST.RESPONSE.redirect( site_url + '/login_form?' + urlencode({ 'portal_status_message': translateString("Password changed."), }) )
class Subscriptions(SimpleItem): security = ClassSecurityInfo() title = "Meeting registrations" def __init__(self, id): """ """ super(SimpleItem, self).__init__(id) self.id = id self._signups = OOBTree() self._account_subscriptions = OOBTree() security.declarePublic('getMeeting') def getMeeting(self): return self.aq_parent.aq_parent def _validate_signup(self, form): """ """ formdata = {} formerrors = {} keys = ('first_name', 'last_name', 'email', 'organization', 'phone') formdata = dict((key, form.get(key, '')) for key in keys) for key in formdata: if formdata[key] == '': formerrors[key] = 'This field is mandatory' if formerrors == {}: if formdata['email'].count('@') != 1: formerrors['email'] = ('An email address must contain ' 'a single @') if formerrors == {}: formerrors = None return formdata, formerrors def _add_signup(self, formdata): """ """ meeting = self.getMeeting() key = random_key() name = formdata['first_name'] + ' ' + formdata['last_name'] email = formdata['email'] organization = formdata['organization'] phone = formdata['phone'] signup = SignUp(key, name, email, organization, phone) self._signups.insert(key, signup) if meeting.auto_register: self._accept_signup(key) email_sender = self.getMeeting().getEmailSender() email_sender.send_signup_email(signup) if self.REQUEST.AUTHENTICATED_USER.getUserName() == 'Anonymous User': self.REQUEST.SESSION['nymt-current-key'] = key security.declareProtected(view, 'signup') def signup(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') if REQUEST.get('add_users'): return self.subscribe_accounts(REQUEST) if REQUEST.get('add_signup'): formdata, formerrors = self._validate_signup(REQUEST.form) # check Captcha/reCaptcha if not (self.checkPermissionSkipCaptcha() or REQUEST.SESSION.get('captcha_passed')): recaptcha_response = REQUEST.form.get('g-recaptcha-response', '') captcha_validator = self.validateCaptcha(recaptcha_response, REQUEST) if captcha_validator: if formerrors is None: formerrors = {} formerrors['captcha'] = captcha_validator else: REQUEST.SESSION['captcha_passed'] = True if formerrors is not None: return self.getFormsTool().getContent( {'here': self, 'formdata': formdata, 'formerrors': formerrors}, 'naaya.content.meeting.subscription_signup') else: self._add_signup(formdata) if self.getMeeting().survey_required: REQUEST.RESPONSE.redirect( self.getMeeting().absolute_url()) else: REQUEST.RESPONSE.redirect(self.absolute_url() + '/signup_successful') # check Captcha/reCaptcha also for searching users captcha_validator = None if (REQUEST.get('search_user') or REQUEST.get('search_user_with_role')): if not (self.checkPermissionSkipCaptcha() or REQUEST.SESSION.get('captcha_passed')): recaptcha_response = REQUEST.form.get('g-recaptcha-response', '') captcha_validator = self.validateCaptcha(recaptcha_response, REQUEST) if not captcha_validator: REQUEST.SESSION['captcha_passed'] = True return self.getFormsTool().getContent( {'here': self, 'captcha_errors': captcha_validator}, 'naaya.content.meeting.subscription_signup') security.declareProtected(view, 'signup_successful') def signup_successful(self, REQUEST): """ """ return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_signup_successful') security.declareProtected(view, 'subscribe') def subscribe(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_subscribe') def getSignups(self): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized return self._signups.itervalues() security.declareProtected(PERMISSION_ADMIN_MEETING, 'getSignup') def getSignup(self, key): """ """ return self._signups.get(key, None) def index_html(self, REQUEST): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_index') def _accept_signup(self, key): """ """ meeting = self.getMeeting() meeting.getParticipants()._set_attendee(key, PARTICIPANT_ROLE) signup = self._signups[key] signup.accepted = 'accepted' email_sender = meeting.getEmailSender() email_sender.send_signup_accepted_email(signup) def _reject_signup(self, key): """ """ meeting = self.getMeeting() signup = self._signups[key] signup.accepted = 'rejected' participants = meeting.getParticipants() # delete the 'reimbursed' status participants.setAttendeeInfo([key], 'reimbursed', False) if key in participants._get_attendees(): participants._del_attendee(key) email_sender = meeting.getEmailSender() email_sender.send_signup_rejected_email(signup) def _delete_signup(self, key): """ """ meeting = self.getMeeting() signup = self._signups.pop(key, None) if signup is None: return participants = meeting.getParticipants() if key in participants._get_attendees(): participants._del_attendee(key) email_sender = meeting.getEmailSender() email_sender.send_signup_rejected_email(signup) def _is_signup(self, key): """ """ return key in self._signups def _is_accepted_signup(self, key): """ """ return self._is_signup(key) and \ self._signups[key].accepted == 'accepted' def _is_pending_signup(self, key): """ """ return self._is_signup(key) and \ self._signups[key].accepted == 'new' def manageSubscriptions(self, REQUEST): """ """ if not (self.checkPermissionAdminMeeting() or self.nfp_for_country()): raise Unauthorized uids = REQUEST.form.get('uids', []) assert isinstance(uids, list) for uid in uids: if 'accept' in REQUEST.form: if self._is_signup(uid): self._accept_signup(uid) else: self._accept_account_subscription(uid) elif 'reject' in REQUEST.form: if self._is_signup(uid): self._reject_signup(uid) else: self._reject_account_subscription(uid) elif 'delete' in REQUEST.form: if not self.checkPermissionAdminMeeting(): raise Unauthorized if self._is_signup(uid): self._delete_signup(uid) else: self._delete_account_subscription(uid) if 'set_representative' in REQUEST.form: self.setRepresentatives(REQUEST) elif 'unset_representative' in REQUEST.form: self.setRepresentatives(REQUEST, remove=True) elif 'set_reimbursement' in REQUEST.form: self.setReimbursement(REQUEST) elif 'unset_reimbursement' in REQUEST.form: self.setReimbursement(REQUEST, remove=True) elif 'save_changes' in REQUEST.form: self.save_changes(REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()) security.declarePublic('welcome') def welcome(self, REQUEST, came_from=None): """ """ if 'logout' in REQUEST.form: REQUEST.SESSION['nymt-current-key'] = None return REQUEST.RESPONSE.redirect(self.getMeeting().absolute_url()) key = REQUEST.get('key', None) signup = self.getSignup(key) if self._is_signup(key): REQUEST.SESSION['nymt-current-key'] = key if came_from: return REQUEST.RESPONSE.redirect(came_from) else: return REQUEST.RESPONSE.redirect( self.getMeeting().absolute_url()) return self.getFormsTool().getContent( {'here': self, 'signup': signup}, 'naaya.content.meeting.subscription_welcome') def _add_account_subscription(self, uid, accept=False): """ """ # If the subscription already exists or the user is alread signed up # skip the whole thing if self._is_account_subscription(uid): return key = uid.replace('signup:', '') if self._is_signup(key): return site = self.getSite() meeting = self.getMeeting() name = getUserFullName(site, uid) # If for any reason we still don't have a name, at least use UID if not name: name = uid email = getUserEmail(site, uid) organization = getUserOrganization(site, uid) if not organization: organization = self.get_survey_answer(uid, 'w_organization') if not organization: organization = self.get_survey_answer(uid, 'w_organisation') phone = getUserPhoneNumber(site, uid) if not phone: phone = self.get_survey_answer(uid, 'w_telephone') if not phone: phone = self.get_survey_answer(uid, 'w_phone') account_subscription = AccountSubscription(uid, name, email, organization, phone) self._account_subscriptions.insert(uid, account_subscription) if meeting.auto_register or accept: self._accept_account_subscription(uid) email_sender = self.getMeeting().getEmailSender() email_sender.send_account_subscription_email(account_subscription) security.declareProtected(PERMISSION_ADMIN_MEETING, 'update_account_subscription') def update_account_subscription(self, uid): """ """ site = self.getSite() name = getUserFullName(site, uid) email = getUserEmail(site, uid) organization = getUserOrganization(site, uid) phone = getUserPhoneNumber(site, uid) account_subscription = AccountSubscription(uid, name, email, organization, phone) self._account_subscriptions.update({uid: account_subscription}) security.declareProtected(view, 'subscribe_accounts') def subscribe_accounts(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') # check Captcha/reCaptcha also for searching users if not (self.checkPermissionSkipCaptcha() or REQUEST.SESSION.get('captcha_passed')): recaptcha_response = REQUEST.form.get('g-recaptcha-response', '') captcha_validator = self.validateCaptcha(recaptcha_response, REQUEST) if captcha_validator: return self.getFormsTool().getContent( {'here': self, 'captcha_errors': captcha_validator}, 'naaya.content.meeting.subscription_signup') else: REQUEST.SESSION['captcha_passed'] = True uids = REQUEST.form.get('uids', []) assert isinstance(uids, list) for uid in uids: self._add_account_subscription(uid) return REQUEST.RESPONSE.redirect( self.absolute_url() + '/subscribe_account_successful?uids='+','.join(uids)) security.declareProtected(view, 'subscribe_my_account') def subscribe_my_account(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') self._add_account_subscription(REQUEST.AUTHENTICATED_USER.getId()) if self.survey_required: site = self.getSite() path = str(self.survey_pointer) survey_ob = site.unrestrictedTraverse(path, None) if survey_ob is not None and \ survey_ob.meta_type == 'Naaya Mega Survey': answers = survey_ob.getAnswers() respondents = [a.respondent for a in answers] current_user = REQUEST.AUTHENTICATED_USER.getUserName() if current_user not in respondents: self.setSessionInfoTrans( 'Registration successfully sent for approval. ' 'Please also respond to the following questionaire.') return REQUEST.RESPONSE.redirect( '%s/%s' % (self.getSite().absolute_url(), self.survey_pointer)) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscribe_account_successful') security.declareProtected(view, 'subscribe_account_successful') def subscribe_account_successful(self, REQUEST): """ """ return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_subscribe_successful') def getAccountSubscriptions(self): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized return self._account_subscriptions.itervalues() def getSubscriptions(self): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized subscriptions = (list(self._signups.itervalues()) + list(self._account_subscriptions.itervalues())) statuses = {'new': 0, 'accepted': 1, 'rejected': 2 } return sorted(subscriptions, key=lambda x: statuses.get(x.accepted)) security.declareProtected(PERMISSION_ADMIN_MEETING, 'getAccountSubscription') def getAccountSubscription(self, uid): """ """ return self._account_subscriptions.get(uid, None) def _is_account_subscription(self, uid): """ """ return uid in self._account_subscriptions and \ self._account_subscriptions[uid].accepted == 'accepted' def _accept_account_subscription(self, uid): """ """ meeting = self.getMeeting() meeting.getParticipants()._set_attendee(uid, PARTICIPANT_ROLE) account_subscription = self._account_subscriptions[uid] account_subscription.accepted = 'accepted' email_sender = meeting.getEmailSender() email_sender.send_account_subscription_accepted_email( account_subscription) def _reject_account_subscription(self, uid): """ """ meeting = self.getMeeting() account_subscription = self._account_subscriptions[uid] account_subscription.accepted = 'rejected' participants = meeting.getParticipants() # remove the 'reimbursed' status participants.setAttendeeInfo([uid], 'reimbursed', False) if uid in participants._get_attendees(): participants._del_attendee(uid) email_sender = meeting.getEmailSender() email_sender.send_account_subscription_rejected_email( account_subscription) def _delete_account_subscription(self, uid): """ """ meeting = self.getMeeting() account_subscription = self._account_subscriptions.pop(uid, None) if account_subscription is None: return participants = meeting.getParticipants() if uid in participants._get_attendees(): participants._del_attendee(uid) email_sender = meeting.getEmailSender() email_sender.send_account_subscription_rejected_email( account_subscription) security.declareProtected(view, 'subscription_not_allowed') def subscription_not_allowed(self, REQUEST): """ """ return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_not_allowed')
class Alert(VisualisableElement, Entity): """Alert class""" users_to_alert = SharedMultipleProperty('users_to_alert') subjects = SharedMultipleProperty('subjects') def __init__(self, kind, **kwargs): super(Alert, self).__init__(**kwargs) self.set_data(kwargs) self.kind = kind self.users_toalert = OOBTree() self.users_toexclude = OOBTree() @property def pattern(self): return INTERNAL_ALERTS.get(self.kind, None) @property def templates(self): return self.pattern.templates @property def icon(self): return self.pattern.get_icon(self) def init_alert(self, users, subjects=[], exclude=[]): self.subscribe(users) for subject in subjects: self.addtoproperty('subjects', subject) self.exclude(exclude) def subscribe(self, users): if not isinstance(users, (list, tuple, set)): users = [users] for user in users: oid = get_oid(user, user) self.users_toalert[str(oid)] = oid def unsubscribe(self, user): key = str(get_oid(user, user)) if key in self.users_toalert: self.users_toalert.pop(key) user.addtoproperty('old_alerts', self) self.reindex() def exclude(self, users): if not isinstance(users, (list, tuple, set)): users = [users] for user in users: oid = get_oid(user, user) self.users_toexclude[str(oid)] = oid def is_to_alert(self, user): key = str(get_oid(user, user)) # TODO self not in user.old_alerts return key in self.users_toalert and \ key not in self.users_toexclude def get_subject_state(self, subject, user, last_state=False): states = getattr(subject, 'state_or_none', [None]) state = states[0] if last_state: state = states[-1] return get_states_mapping( user, subject, state) def render(self, template, current_user, request): layout_manager = getattr(request, 'layout_manager', None) layout = layout_manager.layout if layout_manager \ else GlobalLayout(None, request) render_dict = { 'object': self, 'current_user': current_user, 'layout': layout } return renderers.render( self.templates[template], render_dict, request) def is_kind_of(self, kind): return kind == self.kind def has_args(self, **kwargs): for key in kwargs: if getattr(self, key, None) != kwargs[key]: return False return True
class BookingStorage(Persistent): def __init__(self): self.bookings = OOBTree() # we need real randomness for object keys # but we cannot use the same uid for zope.catalog ids # because it does not support 128bit integers # (and we do not like to use zope.intid). # So, we use different keys for storage and catalog # and we store mapping between them here. self.mapping = IOBTree() self.catalog = setup_catalog() def __len__(self): """Returns the number of bookings. """ return len(self.bookings) def __contains__(self, uid): return uid in self.bookings def __getitem__(self, uid): return self.bookings[uid] def __iter__(self): for x in self.bookings.values(): yield x def add(self, booking): assert IBooking.providedBy(booking) if not booking.uid: # maybe we are not using `create` method booking.uid = self._get_next_uid() booking.cat_id = self._get_next_cat_id() self.bookings[booking.uid] = booking self.index(booking) notify(BookingAddedEvent(booking)) return booking.uid def delete(self, uid): booking = self.bookings.pop(uid) self.unindex(booking) def create(self, **values): booking = Booking(**values) self.add(booking) return booking def _get_next_uid(self): return _get_next_uid() def _get_next_cat_id(self): # we know that this id may not be unique # but we can reindex bookings if needed try: return self.mapping.maxKey() + 1 except ValueError: # no keys yet return 1 def catalog_id_to_object(self, cat_id): uid = self.mapping[cat_id] return self.bookings[uid] def index(self, booking): self.catalog.index_doc(booking.cat_id, booking) self.mapping[booking.cat_id] = booking.uid def unindex(self, booking): self.catalog.unindex_doc(booking.cat_id) self.mapping.pop(booking.cat_id) booking.cat_id = None def reindex(self, booking): self.catalog.unindex_doc(booking.cat_id) self.catalog.index_doc(booking.cat_id, booking) def reindex_catalog(self): self.mapping.clear() self.catalog.clear() for booking in self.bookings.values(): booking.cat_id = self._get_next_cat_id() self.index(booking) def query(self, query=None, start=0, limit=None, sort_on=None, reverse=False): """Searches for bookings. Returns an ordered set of ``IBooking`` objects, which match ``query``. ``query`` is a dictionary with the keys being field names or keys contained in ``references``, and the values either a specific value or a range in the form of a ``(to, from)`` tuple (with ``None`` being no limit). ``start`` and ``limit`` can be used to slice the result set. """ return BookingResults(self, query, start=start, limit=limit, sort_on=sort_on, reverse=reverse) def vocabulary(self, name): """Returns the list of values for the given index. """ # pylint: disable=protected-access return self.catalog[name]._fwd_index.keys()
class Person(User, SearchableEntity, CorrelableEntity, Debatable): """Person class""" type_title = _('Person') icon = 'icon glyphicon glyphicon-user' #'icon novaideo-icon icon-user' templates = {'default': 'novaideo:views/templates/person_result.pt', 'bloc': 'novaideo:views/templates/person_bloc.pt', 'small': 'novaideo:views/templates/small_person_result.pt', 'popover': 'novaideo:views/templates/person_popover.pt', 'card': 'novaideo:views/templates/person_card.pt', 'header': 'novaideo:views/templates/person_header.pt',} default_picture = 'novaideo:static/images/user100.png' name = renamer() tokens = CompositeMultipleProperty('tokens') tokens_ref = SharedMultipleProperty('tokens_ref') organization = SharedUniqueProperty('organization', 'members') picture = CompositeUniqueProperty('picture') cover_picture = CompositeUniqueProperty('cover_picture') ideas = SharedMultipleProperty('ideas', 'author') selections = SharedMultipleProperty('selections') working_groups = SharedMultipleProperty('working_groups', 'members') old_alerts = SharedMultipleProperty('old_alerts') following_channels = SharedMultipleProperty('following_channels', 'members') folders = SharedMultipleProperty('folders', 'author') questions = SharedMultipleProperty('questions', 'author') challenges = SharedMultipleProperty('challenges', 'author') mask = SharedUniqueProperty('mask', 'member') def __init__(self, **kwargs): super(Person, self).__init__(**kwargs) kwargs.pop('password', None) self.set_data(kwargs) self.set_title() self.last_connection = datetime.datetime.now(tz=pytz.UTC) self._read_at = OOBTree() self.guide_tour_data = PersistentDict({}) self.allocated_tokens = OOBTree() self.len_allocated_tokens = PersistentDict({}) self.reserved_tokens = PersistentList([]) def __setattr__(self, name, value): super(Person, self).__setattr__(name, value) if name == 'organization' and value: self.init_contents_organizations() def get_len_tokens(self, root=None, exclude_reserved_tokens=False): root = root or getSite() return root.tokens_mini if exclude_reserved_tokens \ else root.tokens_mini + len(self.reserved_tokens) def get_len_evaluations(self, exclude_reserved_tokens=False): total = self.len_allocated_tokens.get(Evaluations.support, 0) + \ self.len_allocated_tokens.get(Evaluations.oppose, 0) if exclude_reserved_tokens: return total - len([o for o in self.reserved_tokens if o in self.allocated_tokens]) return total def get_len_free_tokens(self, root=None, exclude_reserved_tokens=False): root = root or getSite() return self.get_len_tokens(root, exclude_reserved_tokens) - \ self.get_len_evaluations(exclude_reserved_tokens) def has_token(self, obj=None, root=None): root = root or getSite() obj_oid = get_oid(obj, None) if obj_oid and obj_oid in self.reserved_tokens: return obj_oid not in self.allocated_tokens return self.get_len_free_tokens(root, True)>0 def add_token(self, obj, evaluation_type, root=None): if self.has_token(obj, root): self.allocated_tokens[get_oid(obj)] = evaluation_type self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] += 1 def remove_token(self, obj): obj_oid = get_oid(obj) if obj_oid in self.allocated_tokens: evaluation_type = self.allocated_tokens.pop(obj_oid) self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] -= 1 def add_reserved_token(self, obj): obj_oid = get_oid(obj) if obj_oid not in self.reserved_tokens: self.reserved_tokens.append(obj_oid) def remove_reserved_token(self, obj): obj_oid = get_oid(obj) if obj_oid in self.reserved_tokens: self.reserved_tokens.remove(obj_oid) def evaluated_objs(self, evaluation_type=None): if evaluation_type: return [get_obj(key) for value, key in self.allocated_tokens.byValue(evaluation_type)] return [get_obj(key) for key in self.allocated_tokens.keys()] def evaluated_objs_ids(self, evaluation_type=None): if evaluation_type: return [key for value, key in self.allocated_tokens.byValue(evaluation_type)] return list(self.allocated_tokens.keys()) def init_contents_organizations(self): novaideo_catalog = find_catalog('novaideo') dace_catalog = find_catalog('dace') organizations_index = novaideo_catalog['organizations'] object_authors_index = novaideo_catalog['object_authors'] object_provides_index = dace_catalog['object_provides'] query = object_authors_index.any([get_oid(self)]) & \ object_provides_index.any( [Iidea.__identifier__, IProposal.__identifier__]) & \ organizations_index.any([0]) for entity in query.execute().all(): entity.init_organization() entity.reindex() def set_read_date(self, channel, date): self._read_at[get_oid(channel)] = date def get_read_date(self, channel): return self._read_at.get( get_oid(channel), datetime.datetime.now(tz=pytz.UTC)) def get_channel(self, user): all_channels = list(self.channels) all_channels.extend(list(getattr(user, 'channels', []))) for channel in all_channels: if user in channel.members and self in channel.members: return channel return None def addtoproperty(self, name, value, moving=None): super(Person, self).addtoproperty(name, value, moving) if name == 'selections': value.len_selections = getattr(value, 'len_selections', 0) value.len_selections += 1 def delfromproperty(self, name, value, moving=None): super(Person, self).delfromproperty(name, value, moving) if name == 'selections': value.len_selections = getattr(value, 'len_selections', 0) if value.len_selections > 0: value.len_selections -= 1 def set_title(self): self.title = getattr(self, 'first_name', '') + ' ' + \ getattr(self, 'last_name', '') def get_questions(self, user): if user is self: return self.questions + getattr(self.mask, 'questions', []) return self.questions def get_ideas(self, user): if user is self: return self.ideas + getattr(self.mask, 'ideas', []) return self.ideas def get_working_groups(self, user): if user is self: return self.working_groups + getattr(self.mask, 'working_groups', []) return self.working_groups @property def proposals(self): return [wg.proposal for wg in self.working_groups] def get_proposals(self, user): if user is self: return self.proposals + getattr(self.mask, 'proposals', []) return self.proposals @property def contacts(self): return [s for s in self.selections if isinstance(s, Person)] @property def participations(self): result = [p for p in list(self.proposals) if any(s in p.state for s in ['amendable', 'open to a working group', 'votes for publishing', 'votes for amendments'])] return result def get_participations(self, user): if user is self: return self.participations + getattr(self.mask, 'participations', []) return self.participations @property def contents(self): result = [i for i in list(self.ideas) if i is i.current_version] result.extend(self.proposals) result.extend(self.questions) result.extend(self.challenges) return result def get_contents(self, user): if user is self: return self.contents + getattr(self.mask, 'contents', []) return self.contents @property def active_working_groups(self): return [p.working_group for p in self.participations] def get_active_working_groups(self, user): if user is self: return self.active_working_groups + getattr(self.mask, 'active_working_groups', []) return self.active_working_groups @property def is_published(self): return 'active' in self.state @property def managed_organization(self): return get_objects_with_role(user=self, role='OrganizationResponsible') def reindex(self): super(Person, self).reindex() root = getSite() self.__access_keys__ = PersistentList(generate_access_keys( self, root)) def get_picture_url(self, kind, default): if self.picture: img = getattr(self.picture, kind, None) if img: return img.url return default def get_more_contents_criteria(self): "return specific query, filter values" return None, None def set_organization(self, organization): current_organization = self.organization if organization: if current_organization is not organization: is_manager = current_organization and has_role( ('OrganizationResponsible', current_organization), self, ignore_superiors=True) if current_organization and is_manager: revoke_roles( self, (('OrganizationResponsible', current_organization),)) self.setproperty('organization', organization) elif current_organization: is_manager = has_role( ('OrganizationResponsible', current_organization), self, ignore_superiors=True) if is_manager: revoke_roles( self, (('OrganizationResponsible', current_organization),)) self.delfromproperty('organization', current_organization) @property def all_alerts(self): novaideo_catalog = find_catalog('novaideo') dace_catalog = find_catalog('dace') alert_keys_index = novaideo_catalog['alert_keys'] alert_exclude_keys_index = novaideo_catalog['alert_exclude_keys'] object_provides_index = dace_catalog['object_provides'] exclude = [str(get_oid(self))] if self.mask: exclude.append(str(get_oid(self.mask))) query = object_provides_index.any([IAlert.__identifier__]) & \ alert_keys_index.any(self.get_alerts_keys()) & \ alert_exclude_keys_index.notany(exclude) return query.execute() @property def alerts(self): old_alerts = [get_oid(a) for a in self.old_alerts] result = self.all_alerts def exclude(result_set, docids): filtered_ids = list(result_set.ids) for _id in docids: if _id in docids and _id in filtered_ids: filtered_ids.remove(_id) return result_set.__class__( filtered_ids, len(filtered_ids), result_set.resolver) return exclude(result, old_alerts) def get_alerts_keys(self): result = ['all', str(get_oid(self))] if self.mask: result.append(str(get_oid(self.mask))) return result def get_alerts(self, alerts=None, kind=None, subject=None, **kwargs): if alerts is None: alerts = self.alerts if kind: alerts = [a for a in alerts if a.is_kind_of(kind)] if subject: alerts = [a for a in alerts if subject in a.subjects] if kwargs: alerts = [a for a in alerts if a.has_args(**kwargs)] return alerts @property def user_groups(self): groups = list(self.groups) if self.organization: groups.append(self.organization) if self.mask: groups.append(self.mask) return groups @property def user_locale(self): locale = getattr(self, 'locale', None) if not locale: locale = getSite(self).locale return locale def _init_mask(self, root): if not self.mask: mask = Mask() root.addtoproperty('masks', mask) self.setproperty('mask', mask) def get_mask(self, root=None): root = root if root else getSite() if not getattr(root, 'anonymisation', False): return self self._init_mask(root) return self.mask
class TokenManager(Persistent, Contained): """\ A basic token manager for the default layer. This manager provides the bare minimum functionality, currently it does not easily provide a way for users to check what tokens they have approved. """ zope.component.adapts(IAttributeAnnotatable, zope.interface.Interface) zope.interface.implements(ITokenManager) DUMMY_KEY = 'dummy' DUMMY_SECRET = 'dummy' # expiry claim_timeout = 180 def __init__(self): self._tokens = OOBTree() self._user_token_map = OOBTree() dummy = self._makeDummy() self.add(dummy) def _makeDummy(self): dummy = Token(self.DUMMY_KEY, self.DUMMY_SECRET) return dummy def _add_user_map(self, token): if not token.access or token.user is None: return # only tracking access tokens with user defined. user_tokens = self._user_token_map.get(token.user, None) if user_tokens is None: user_tokens = PersistentList() self._user_token_map[token.user] = user_tokens user_tokens.append(token.key) def _del_user_map(self, token): if token.user is None: return # only tracking access tokens with user defined. user_tokens = self._user_token_map.get(token.user, None) if user_tokens is None: # guess this user didn't have any tokens tracked before. return if token.key in user_tokens: # Well this key may not have been mapped. user_tokens.remove(token.key) def add(self, token): assert IToken.providedBy(token) if self.get(token.key): raise ValueError('token %s already exists', token.key) self._tokens[token.key] = token self._add_user_map(token) def _generateBaseToken(self, consumer_key): key = random_string(24) secret = random_string(24) token = Token(key, secret) token.consumer_key = consumer_key token.timestamp = int(time.time()) return token def generateRequestToken(self, consumer_key, callback): """\ Generate request token from consumer and request. """ # This is our constraint. if callback is None: raise CallbackValueError( 'callback must be specified or set to `oob`') token = self._generateBaseToken(consumer_key) token.set_callback(callback) token.set_verifier() token.expiry = int(time.time()) + self.claim_timeout self.add(token) return token def generateAccessToken(self, consumer_key, request_token): # Get the copy that is being tracked here. old_token = self.get(request_token) if not old_token: raise TokenInvalidError('invalid token') old_key = old_token.key token = self._generateBaseToken(consumer_key) token.access = True # Must have a user. if not old_token.user: raise TokenInvalidError('token has no user') token.user = old_token.user # Now add token. self.add(token) return token def claimRequestToken(self, token, user): token = self.get(token) if not token: raise TokenInvalidError('invalid token') if token.access: raise TokenInvalidError('not request token') token.user = user token.expiry = int(time.time()) + self.claim_timeout def get(self, token, default=None): token_key = IToken.providedBy(token) and token.key or token return self._tokens.get(token_key, default) def getRequestToken(self, token, default=False): token = self.get(token, default) if token is default: if default is False: raise TokenInvalidError('no such request token.') return default if token.access: if default is False: raise NotRequestTokenError('not a request token.') return default return token def getAccessToken(self, token, default=False): token = self.get(token, default) if token is default: if default is False: raise TokenInvalidError('no such access token.') return default if not token.access: if default is False: raise NotAccessTokenError('not an access token.') return default # must be identified if not token.user: raise TokenInvalidError('token has no user') raw_keys = self._user_token_map.get(token.user, []) if token.key not in raw_keys: raise TokenInvalidError('user `%s` does not own this key' % token.user) return token def hasTokensForUser(self, user): return bool(self._user_token_map.get(user, [])) def getTokensForUser(self, user): raw_keys = self._user_token_map.get(user, []) result = [self.get(t) for t in raw_keys] return result def remove(self, token): if IToken.providedBy(token): token = token.key token = self._tokens.pop(token) self._del_user_map(token) return token def requestTokenVerify(self, consumer_key, token, verifier): """\ Verify that the request results in a valid token by checking for validity of the consumer_key and verifier. """ token = self.getRequestToken(token) return (token.consumer_key == consumer_key and token.verifier == verifier and token.user is not None )
class Subscriptions(SimpleItem): security = ClassSecurityInfo() title = "Meeting registrations" def __init__(self, id): """ """ super(SimpleItem, self).__init__(id) self.id = id self._signups = OOBTree() self._account_subscriptions = OOBTree() security.declarePublic('getMeeting') def getMeeting(self): return self.aq_parent.aq_parent def _validate_signup(self, form): """ """ formdata = {} formerrors = {} keys = ('first_name', 'last_name', 'email', 'organization', 'phone') formdata = dict( (key, form.get(key, '')) for key in keys ) for key in formdata: if formdata[key] == '': formerrors[key] = 'This field is mandatory' if formerrors == {}: if formdata['email'].count('@') != 1: formerrors['email'] = 'An email address must contain a single @' if formerrors == {}: formerrors = None return formdata, formerrors def _add_signup(self, formdata): """ """ meeting = self.getMeeting() key = random_key() name = formdata['first_name'] + ' ' + formdata['last_name'] email = formdata['email'] organization = formdata['organization'] phone = formdata['phone'] signup = SignUp(key, name, email, organization, phone) self._signups.insert(key, signup) if meeting.auto_register: self._accept_signup(key) email_sender = self.getMeeting().getEmailSender() email_sender.send_signup_email(signup) security.declareProtected(view, 'signup') def signup(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') if REQUEST.REQUEST_METHOD == 'GET': return self.getFormsTool().getContent({'here': self}, 'naaya.content.meeting.subscription_signup') if REQUEST.REQUEST_METHOD == 'POST': formdata, formerrors = self._validate_signup(REQUEST.form) #check Captcha/reCaptcha if not self.checkPermissionSkipCaptcha(): contact_word = REQUEST.form.get('contact_word', '') captcha_validator = self.validateCaptcha(contact_word, REQUEST) if captcha_validator: if formerrors is None: formerrors = {} formerrors['captcha'] = captcha_validator if formerrors is not None: return self.getFormsTool().getContent({'here': self, 'formdata': formdata, 'formerrors': formerrors}, 'naaya.content.meeting.subscription_signup') else: self._add_signup(formdata) REQUEST.RESPONSE.redirect(self.absolute_url() + '/signup_successful') security.declareProtected(view, 'signup_successful') def signup_successful(self, REQUEST): """ """ return self.getFormsTool().getContent({'here': self}, 'naaya.content.meeting.subscription_signup_successful') security.declareProtected(view, 'subscribe') def subscribe(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') return self.getFormsTool().getContent({'here': self}, 'naaya.content.meeting.subscription_subscribe') security.declareProtected(PERMISSION_ADMIN_MEETING, 'getSignups') def getSignups(self): """ """ return self._signups.itervalues() security.declareProtected(PERMISSION_ADMIN_MEETING, 'getSignup') def getSignup(self, key): """ """ return self._signups.get(key, None) security.declareProtected(PERMISSION_ADMIN_MEETING, 'index_html') def index_html(self, REQUEST): """ """ return self.getFormsTool().getContent({'here': self}, 'naaya.content.meeting.subscription_index') def _accept_signup(self, key): """ """ meeting = self.getMeeting() meeting.getParticipants()._set_attendee(key, PARTICIPANT_ROLE) signup = self._signups[key] signup.accepted = 'accepted' email_sender = meeting.getEmailSender() result = email_sender.send_signup_accepted_email(signup) def _reject_signup(self, key): """ """ meeting = self.getMeeting() signup = self._signups[key] signup.accepted = 'rejected' participants = meeting.getParticipants() if key in participants._get_attendees(): participants._del_attendee(key) email_sender = meeting.getEmailSender() result = email_sender.send_signup_rejected_email(signup) def _delete_signup(self, key): """ """ meeting = self.getMeeting() signup = self._signups.pop(key, None) if signup is None: return participants = meeting.getParticipants() if key in participants._get_attendees(): participants._del_attendee(key) email_sender = meeting.getEmailSender() result = email_sender.send_signup_rejected_email(signup) def _is_signup(self, key): """ """ return self._signups.has_key(key) and self._signups[key].accepted == 'accepted' security.declareProtected(PERMISSION_ADMIN_MEETING, 'manageSignups') def manageSignups(self, REQUEST): """ """ keys = REQUEST.form.get('keys', []) assert isinstance(keys, list) if 'accept' in REQUEST.form: for key in keys: self._accept_signup(key) elif 'reject' in REQUEST.form: for key in keys: self._reject_signup(key) elif 'delete' in REQUEST.form: for key in keys: self._delete_signup(key) return REQUEST.RESPONSE.redirect(self.absolute_url()) security.declarePublic('welcome') def welcome(self, REQUEST): """ """ if 'logout' in REQUEST.form: REQUEST.SESSION['nymt-current-key'] = None return REQUEST.RESPONSE.redirect(self.getMeeting().absolute_url()) key = REQUEST.get('key', None) signup = self.getSignup(key) if self._is_signup(key): REQUEST.SESSION['nymt-current-key'] = key return self.getFormsTool().getContent({'here': self, 'signup': signup}, 'naaya.content.meeting.subscription_welcome') def _add_account_subscription(self, uid): """ """ site = self.getSite() meeting = self.getMeeting() name = getUserFullName(site, uid) email = getUserEmail(site, uid) organization = getUserOrganization(site, uid) if not organization: organization = self.get_survey_answer(uid, 'w_organization') if not organization: organization = self.get_survey_answer(uid, 'w_organisation') phone = getUserPhoneNumber(site, uid) if not phone: phone = self.get_survey_answer(uid, 'w_telephone') if not phone: phone = self.get_survey_answer(uid, 'w_phone') account_subscription = AccountSubscription(uid, name, email, organization, phone) self._account_subscriptions.insert(uid, account_subscription) if meeting.auto_register: self._accept_account_subscription(uid) email_sender = self.getMeeting().getEmailSender() email_sender.send_account_subscription_email(account_subscription) security.declareProtected(PERMISSION_ADMIN_MEETING, 'update_account_subscription') def update_account_subscription(self, uid): """ """ site = self.getSite() name = getUserFullName(site, uid) email = getUserEmail(site, uid) organization = getUserOrganization(site, uid) phone = getUserPhoneNumber(site, uid) account_subscription = AccountSubscription(uid, name, email, organization, phone) self._account_subscriptions.update({uid: account_subscription}) security.declareProtected(view, 'subscribe_account') def subscribe_account(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') self._add_account_subscription(REQUEST.AUTHENTICATED_USER.getId()) if self.survey_required: site = self.getSite() path = str(self.survey_pointer) survey_ob = site.unrestrictedTraverse(path, None) if survey_ob is not None and survey_ob.meta_type == 'Naaya Mega Survey': answers = survey_ob.getAnswers() respondents = [a.respondent for a in answers] current_user = REQUEST.AUTHENTICATED_USER.getUserName() if current_user not in respondents: self.setSessionInfoTrans( 'Registration successfully sent for approval. ' 'Please also respond to the following questionaire.') return REQUEST.RESPONSE.redirect('%s/%s' % (self.getSite().absolute_url(), self.survey_pointer)) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscribe_account_successful') security.declareProtected(view, 'subscribe_account_successful') def subscribe_account_successful(self, REQUEST): """ """ return self.getFormsTool().getContent({'here': self}, 'naaya.content.meeting.subscription_subscribe_successful') security.declareProtected(PERMISSION_ADMIN_MEETING, 'getAccountSubscriptions') def getAccountSubscriptions(self): """ """ return self._account_subscriptions.itervalues() security.declareProtected(PERMISSION_ADMIN_MEETING, 'getAccountSubscription') def getAccountSubscription(self, uid): """ """ return self._account_subscriptions.get(uid, None) def _is_account_subscription(self, uid): """ """ return self._account_subscriptions.has_key(uid) and self._account_subscriptions[uid].accepted == 'accepted' security.declareProtected(PERMISSION_ADMIN_MEETING, 'manageSignups') def manageAccountSubscriptions(self, REQUEST): """ """ uids = REQUEST.form.get('uids', []) assert isinstance(uids, list) if 'accept' in REQUEST.form: for uid in uids: self._accept_account_subscription(uid) elif 'reject' in REQUEST.form: for uid in uids: self._reject_account_subscription(uid) elif 'delete' in REQUEST.form: for uid in uids: self._delete_account_subscription(uid) return REQUEST.RESPONSE.redirect(self.absolute_url()) def _accept_account_subscription(self, uid): """ """ meeting = self.getMeeting() meeting.getParticipants()._set_attendee(uid, PARTICIPANT_ROLE) account_subscription = self._account_subscriptions[uid] account_subscription.accepted = 'accepted' email_sender = meeting.getEmailSender() result = email_sender.send_account_subscription_accepted_email(account_subscription) def _reject_account_subscription(self, uid): """ """ meeting = self.getMeeting() account_subscription = self._account_subscriptions[uid] account_subscription.accepted = 'rejected' participants = meeting.getParticipants() if uid in participants._get_attendees(): participants._del_attendee(uid) email_sender = meeting.getEmailSender() result = email_sender.send_account_subscription_rejected_email(account_subscription) def _delete_account_subscription(self, uid): """ """ meeting = self.getMeeting() account_subscription = self._account_subscriptions.pop(uid, None) if account_subscription is None: return participants = meeting.getParticipants() if uid in participants._get_attendees(): participants._del_attendee(uid) email_sender = meeting.getEmailSender() result = email_sender.send_account_subscription_rejected_email(account_subscription) security.declareProtected(view, 'subscription_not_allowed') def subscription_not_allowed(self, REQUEST): """ """ return self.getFormsTool().getContent({'here': self}, 'naaya.content.meeting.subscription_not_allowed')
class ClassificationContainer(Container, BaseContainer): __allow_access_to_unprotected_subobjects__ = True def __init__(self, *args, **kwargs): self._tree = OOBTree() super(ClassificationContainer, self).__init__(*args, **kwargs) def __len__(self): return len(self._tree) def __contains__(self, key): return key in self._tree def __getitem__(self, key): return self._tree[key].__of__(self) def __delitem__(self, key, suppress_container_modified=False): element = self[key].__of__(self) notify(ObjectWillBeRemovedEvent(element, self, key)) # Remove the element from _tree self._tree.pop(key) notify(ObjectRemovedEvent(element, self, key)) if not suppress_container_modified: notify(ContainerModifiedEvent(self)) def __iter__(self): return iter(self._tree) def get(self, key, default=None): element = self._tree.get(key, default) if element is default: return default return element.__of__(self) def keys(self): return self._tree.keys() def items(self): return [( i[0], i[1].__of__(self), ) for i in self._tree.items()] def values(self): return [v.__of__(self) for v in self._tree.values()] def iterkeys(self): return six.iterkeys(self._tree) def itervalues(self): for v in six.itervalues(self._tree): yield v.__of__(self) def iteritems(self): for k, v in six.iteritems(self._tree): yield ( k, v.__of__(self), ) def allowedContentTypes(self): return []
class Subscriptions(SimpleItem): security = ClassSecurityInfo() title = "Meeting registrations" def __init__(self, id): """ """ super(SimpleItem, self).__init__(id) self.id = id self._signups = OOBTree() self._account_subscriptions = OOBTree() security.declarePublic('getMeeting') def getMeeting(self): return self.aq_parent.aq_parent def _validate_signup(self, form): """ """ formdata = {} formerrors = {} keys = ('first_name', 'last_name', 'email', 'organization', 'phone') formdata = dict((key, form.get(key, '')) for key in keys) for key in formdata: if formdata[key] == '': formerrors[key] = 'This field is mandatory' if formerrors == {}: if formdata['email'].count('@') != 1: formerrors['email'] = ('An email address must contain ' 'a single @') if formerrors == {}: formerrors = None return formdata, formerrors def _add_signup(self, formdata): """ """ meeting = self.getMeeting() key = random_key() name = formdata['first_name'] + ' ' + formdata['last_name'] email = formdata['email'] organization = formdata['organization'] phone = formdata['phone'] signup = SignUp(key, name, email, organization, phone) self._signups.insert(key, signup) if meeting.auto_register: self._accept_signup(key) email_sender = self.getMeeting().getEmailSender() email_sender.send_signup_email(signup) if self.REQUEST.AUTHENTICATED_USER.getUserName() == 'Anonymous User': self.REQUEST.SESSION['nymt-current-key'] = key security.declareProtected(view, 'signup') def signup(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') if REQUEST.get('add_users'): return self.subscribe_accounts(REQUEST) if REQUEST.get('add_signup'): formdata, formerrors = self._validate_signup(REQUEST.form) # check Captcha/reCaptcha if not (self.checkPermissionSkipCaptcha() or REQUEST.SESSION.get('captcha_passed')): recaptcha_response = REQUEST.form.get('g-recaptcha-response', '') captcha_validator = self.validateCaptcha( recaptcha_response, REQUEST) if captcha_validator: if formerrors is None: formerrors = {} formerrors['captcha'] = captcha_validator else: REQUEST.SESSION['captcha_passed'] = True if formerrors is not None: return self.getFormsTool().getContent( { 'here': self, 'formdata': formdata, 'formerrors': formerrors }, 'naaya.content.meeting.subscription_signup') else: self._add_signup(formdata) if self.getMeeting().survey_required: REQUEST.RESPONSE.redirect(self.getMeeting().absolute_url()) else: REQUEST.RESPONSE.redirect(self.absolute_url() + '/signup_successful') # check Captcha/reCaptcha also for searching users captcha_validator = None if (REQUEST.get('search_user') or REQUEST.get('search_user_with_role')): if not (self.checkPermissionSkipCaptcha() or REQUEST.SESSION.get('captcha_passed')): recaptcha_response = REQUEST.form.get('g-recaptcha-response', '') captcha_validator = self.validateCaptcha( recaptcha_response, REQUEST) if not captcha_validator: REQUEST.SESSION['captcha_passed'] = True return self.getFormsTool().getContent( { 'here': self, 'captcha_errors': captcha_validator }, 'naaya.content.meeting.subscription_signup') security.declareProtected(view, 'signup_successful') def signup_successful(self, REQUEST): """ """ return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_signup_successful') security.declareProtected(view, 'subscribe') def subscribe(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_subscribe') def getSignups(self): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized return self._signups.itervalues() security.declareProtected(PERMISSION_ADMIN_MEETING, 'getSignup') def getSignup(self, key): """ """ return self._signups.get(key, None) def index_html(self, REQUEST): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_index') def _accept_signup(self, key): """ """ meeting = self.getMeeting() meeting.getParticipants()._set_attendee(key, PARTICIPANT_ROLE) signup = self._signups[key] signup.accepted = 'accepted' email_sender = meeting.getEmailSender() email_sender.send_signup_accepted_email(signup) def _reject_signup(self, key): """ """ meeting = self.getMeeting() signup = self._signups[key] signup.accepted = 'rejected' participants = meeting.getParticipants() # delete the 'reimbursed' status participants.setAttendeeInfo([key], 'reimbursed', False) if key in participants._get_attendees(): participants._del_attendee(key) email_sender = meeting.getEmailSender() email_sender.send_signup_rejected_email(signup) def _delete_signup(self, key): """ """ meeting = self.getMeeting() signup = self._signups.pop(key, None) if signup is None: return participants = meeting.getParticipants() if key in participants._get_attendees(): participants._del_attendee(key) email_sender = meeting.getEmailSender() email_sender.send_signup_rejected_email(signup) def _is_signup(self, key): """ """ return key in self._signups and \ self._signups[key].accepted == 'accepted' def _is_pending_signup(self, key): """ """ return key in self._signups and \ self._signups[key].accepted == 'new' def manageSubscriptions(self, REQUEST): """ """ if not (self.checkPermissionAdminMeeting() or self.nfp_for_country()): raise Unauthorized uids = REQUEST.form.get('uids', []) assert isinstance(uids, list) for uid in uids: if 'accept' in REQUEST.form: if self._is_signup(uid): self._accept_signup(uid) else: self._accept_account_subscription(uid) elif 'reject' in REQUEST.form: if self._is_signup(uid): self._reject_signup(uid) else: self._reject_account_subscription(uid) elif 'delete' in REQUEST.form: if not self.checkPermissionAdminMeeting(): raise Unauthorized if self._is_signup(uid): self._delete_signup(uid) else: self._delete_account_subscription(uid) if 'set_representative' in REQUEST.form: self.setRepresentatives(REQUEST) elif 'unset_representative' in REQUEST.form: self.setRepresentatives(REQUEST, remove=True) elif 'set_reimbursement' in REQUEST.form: self.setReimbursement(REQUEST) elif 'unset_reimbursement' in REQUEST.form: self.setReimbursement(REQUEST, remove=True) elif 'save_changes' in REQUEST.form: self.save_changes(REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()) security.declarePublic('welcome') def welcome(self, REQUEST, came_from=None): """ """ if 'logout' in REQUEST.form: REQUEST.SESSION['nymt-current-key'] = None return REQUEST.RESPONSE.redirect(self.getMeeting().absolute_url()) key = REQUEST.get('key', None) signup = self.getSignup(key) if self._is_signup(key) or self._is_pending_signup(key): REQUEST.SESSION['nymt-current-key'] = key if came_from: return REQUEST.RESPONSE.redirect(came_from) else: return REQUEST.RESPONSE.redirect( self.getMeeting().absolute_url()) return self.getFormsTool().getContent({ 'here': self, 'signup': signup }, 'naaya.content.meeting.subscription_welcome') def _add_account_subscription(self, uid, accept=False): """ """ # If the subscription already exists or the user is alread signed up # skip the whole thing if self._is_account_subscription(uid): return key = uid.replace('signup:', '') if self._is_signup(key): return site = self.getSite() meeting = self.getMeeting() name = getUserFullName(site, uid) # If for any reason we still don't have a name, at least use UID if not name: name = uid email = getUserEmail(site, uid) organization = getUserOrganization(site, uid) if not organization: organization = self.get_survey_answer(uid, 'w_organization') if not organization: organization = self.get_survey_answer(uid, 'w_organisation') phone = getUserPhoneNumber(site, uid) if not phone: phone = self.get_survey_answer(uid, 'w_telephone') if not phone: phone = self.get_survey_answer(uid, 'w_phone') account_subscription = AccountSubscription(uid, name, email, organization, phone) self._account_subscriptions.insert(uid, account_subscription) if meeting.auto_register or accept: self._accept_account_subscription(uid) email_sender = self.getMeeting().getEmailSender() email_sender.send_account_subscription_email(account_subscription) security.declareProtected(PERMISSION_ADMIN_MEETING, 'update_account_subscription') def update_account_subscription(self, uid): """ """ site = self.getSite() name = getUserFullName(site, uid) email = getUserEmail(site, uid) organization = getUserOrganization(site, uid) phone = getUserPhoneNumber(site, uid) account_subscription = AccountSubscription(uid, name, email, organization, phone) self._account_subscriptions.update({uid: account_subscription}) security.declareProtected(view, 'subscribe_accounts') def subscribe_accounts(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') # check Captcha/reCaptcha also for searching users if not (self.checkPermissionSkipCaptcha() or REQUEST.SESSION.get('captcha_passed')): recaptcha_response = REQUEST.form.get('g-recaptcha-response', '') captcha_validator = self.validateCaptcha(recaptcha_response, REQUEST) if captcha_validator: return self.getFormsTool().getContent( { 'here': self, 'captcha_errors': captcha_validator }, 'naaya.content.meeting.subscription_signup') else: REQUEST.SESSION['captcha_passed'] = True uids = REQUEST.form.get('uids', []) assert isinstance(uids, list) for uid in uids: self._add_account_subscription(uid) return REQUEST.RESPONSE.redirect( self.absolute_url() + '/subscribe_account_successful?uids=' + ','.join(uids)) security.declareProtected(view, 'subscribe_my_account') def subscribe_my_account(self, REQUEST): """ """ meeting = self.getMeeting() if not meeting.allow_register: return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscription_not_allowed') self._add_account_subscription(REQUEST.AUTHENTICATED_USER.getId()) if self.survey_required: site = self.getSite() path = str(self.survey_pointer) survey_ob = site.unrestrictedTraverse(path, None) if survey_ob is not None and \ survey_ob.meta_type == 'Naaya Mega Survey': answers = survey_ob.getAnswers() respondents = [a.respondent for a in answers] current_user = REQUEST.AUTHENTICATED_USER.getUserName() if current_user not in respondents: self.setSessionInfoTrans( 'Registration successfully sent for approval. ' 'Please also respond to the following questionaire.') return REQUEST.RESPONSE.redirect( '%s/%s' % (self.getSite().absolute_url(), self.survey_pointer)) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/subscribe_account_successful') security.declareProtected(view, 'subscribe_account_successful') def subscribe_account_successful(self, REQUEST): """ """ return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_subscribe_successful') def getAccountSubscriptions(self): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized return self._account_subscriptions.itervalues() def getSubscriptions(self): """ """ if not self.checkPermissionParticipateInMeeting(): raise Unauthorized subscriptions = (list(self._signups.itervalues()) + list(self._account_subscriptions.itervalues())) statuses = {'new': 0, 'accepted': 1, 'rejected': 2} return sorted(subscriptions, key=lambda x: statuses.get(x.accepted)) security.declareProtected(PERMISSION_ADMIN_MEETING, 'getAccountSubscription') def getAccountSubscription(self, uid): """ """ return self._account_subscriptions.get(uid, None) def _is_account_subscription(self, uid): """ """ return uid in self._account_subscriptions and \ self._account_subscriptions[uid].accepted == 'accepted' def _accept_account_subscription(self, uid): """ """ meeting = self.getMeeting() meeting.getParticipants()._set_attendee(uid, PARTICIPANT_ROLE) account_subscription = self._account_subscriptions[uid] account_subscription.accepted = 'accepted' email_sender = meeting.getEmailSender() email_sender.send_account_subscription_accepted_email( account_subscription) def _reject_account_subscription(self, uid): """ """ meeting = self.getMeeting() account_subscription = self._account_subscriptions[uid] account_subscription.accepted = 'rejected' participants = meeting.getParticipants() # remove the 'reimbursed' status participants.setAttendeeInfo([uid], 'reimbursed', False) if uid in participants._get_attendees(): participants._del_attendee(uid) email_sender = meeting.getEmailSender() email_sender.send_account_subscription_rejected_email( account_subscription) def _delete_account_subscription(self, uid): """ """ meeting = self.getMeeting() account_subscription = self._account_subscriptions.pop(uid, None) if account_subscription is None: return participants = meeting.getParticipants() if uid in participants._get_attendees(): participants._del_attendee(uid) email_sender = meeting.getEmailSender() email_sender.send_account_subscription_rejected_email( account_subscription) security.declareProtected(view, 'subscription_not_allowed') def subscription_not_allowed(self, REQUEST): """ """ return self.getFormsTool().getContent( {'here': self}, 'naaya.content.meeting.subscription_not_allowed')
class AuthomaticPlugin(BasePlugin): """Authomatic PAS Plugin """ security = ClassSecurityInfo() meta_type = 'Authomatic Plugin' BasePlugin.manage_options # Tell PAS not to swallow our exceptions _dont_swallow_my_exceptions = True def __init__(self, id, title=None, **kw): self._setId(id) self.title = title self.plugin_caching = True self._init_trees() def _init_trees(self): # (provider_name, provider_userid) -> userid self._userid_by_identityinfo = OOBTree() # userid -> userdata self._useridentities_by_userid = OOBTree() def _provider_id(self, result): """helper to get the provider identifier """ if not result.user.id: raise ValueError('Invalid: Empty user.id') if not result.provider.name: raise ValueError('Invalid: Empty provider.name') return (result.provider.name, result.user.id) def remove_identity(self, userid, provider): """ Remove an identity """ identities = self._useridentities_by_userid.get(userid, None) if identities is not None: identity = identities.unlink(provider) self._userid_by_identityinfo.pop((provider, identity['id'])) @security.private def lookup_identities(self, result): """looks up the UserIdentities by using the provider name and the userid at this provider """ userid = self._userid_by_identityinfo.get(self._provider_id(result), None) return self._useridentities_by_userid.get(userid, None) @security.private def remember_identity(self, result, userid=None): """stores authomatic result data """ if userid is None: # create a new userid userid = new_userid(self, result) useridentities = UserIdentities(userid) self._useridentities_by_userid[userid] = useridentities else: # use existing userid useridentities = self._useridentities_by_userid.get(userid, None) if useridentities is None: raise ValueError('Invalid userid') provider_id = self._provider_id(result) if provider_id in self._userid_by_identityinfo and self._userid_by_identityinfo[ provider_id] != userid: # This provider's identity is already registered for other user raise ValueError( "Your %s account is already linked with other id. \ Please login and unlink first." % provider_id[0]) if provider_id not in self._userid_by_identityinfo: self._userid_by_identityinfo[provider_id] = userid useridentities.handle_result(result) return useridentities @security.private def remember(self, result): """remember user as valid result is authomatic result data. """ do_notify_created = False # lookup user by useridentities = self.lookup_identities(result) if useridentities is None: # new/unknown user useridentities = self.remember_identity(result) do_notify_created = True logger.info('New User: {0}'.format(useridentities.userid)) else: useridentities.update_userdata(result) logger.info('Updated Userdata: {0}'.format(useridentities.userid)) # login (get new security manager) logger.info('Login User: {0}'.format(useridentities.userid)) aclu = api.portal.get_tool('acl_users') user = aclu._findUser(aclu.plugins, useridentities.userid) accessed, container, name, value = aclu._getObjectContext( self.REQUEST['PUBLISHED'], self.REQUEST) user = aclu._authorizeUser(user, accessed, container, name, value, _noroles) if do_notify_created: # be a good citizen in PAS world and notify user creation notify(PrincipalCreated(user)) # do login post-processing self.REQUEST['__ac_password'] = useridentities.secret mt = api.portal.get_tool('portal_membership') logger.info('Login Postprocessing: {0}'.format(useridentities.userid)) mt.loginUser(self.REQUEST) # ## # pas_interfaces.IAuthenticationPlugin @security.public def authenticateCredentials(self, credentials): """ credentials -> (userid, login) - 'credentials' will be a mapping, as returned by IExtractionPlugin. - Return a tuple consisting of user ID (which may be different from the login name) and login - If the credentials cannot be authenticated, return None. """ login = credentials.get('login', None) password = credentials.get('password', None) if not login or login not in self._useridentities_by_userid: return None identities = self._useridentities_by_userid[login] if identities.check_password(password): return login, login # ## # pas_interfaces.plugins.IPropertiesPlugin @security.private def getPropertiesForUser(self, user, request=None): identity = self._useridentities_by_userid.get(user.getId(), _marker) if identity is _marker: return None return identity.propertysheet # ## # pas_interfaces.plugins.IUserEnumaration @security.private def enumerateUsers(self, id=None, login=None, exact_match=False, sort_by=None, max_results=None, **kw): """-> ( user_info_1, ... user_info_N ) o Return mappings for users matching the given criteria. o 'id' or 'login', in combination with 'exact_match' true, will return at most one mapping per supplied ID ('id' and 'login' may be sequences). o If 'exact_match' is False, then 'id' and / or login may be treated by the plugin as "contains" searches (more complicated searches may be supported by some plugins using other keyword arguments). o If 'sort_by' is passed, the results will be sorted accordingly. known valid values are 'id' and 'login' (some plugins may support others). o If 'max_results' is specified, it must be a positive integer, limiting the number of returned mappings. If unspecified, the plugin should return mappings for all users satisfying the criteria. o Minimal keys in the returned mappings: 'id' -- (required) the user ID, which may be different than the login name 'login' -- (required) the login name 'pluginid' -- (required) the plugin ID (as returned by getId()) 'editurl' -- (optional) the URL to a page for updating the mapping's user o Plugin *must* ignore unknown criteria. o Plugin may raise ValueError for invalid criteria. o Insufficiently-specified criteria may have catastrophic scaling issues for some implementations. """ if id and login and id != login: raise ValueError('plugin does not support id different from login') search_id = id or login if search_id: if not isinstance(search_id, basestring): raise NotImplementedError('sequence is not supported.') else: return () pluginid = self.getId() ret = list() # shortcut for exact match of login/id identity = None if (exact_match and search_id and search_id in self._useridentities_by_userid): identity = self._useridentities_by_userid[search_id] if identity is not None: ret.append({ 'id': identity.userid.encode('utf8'), 'login': identity.userid.encode('utf8'), 'pluginid': pluginid }) return ret # non exact expensive search for userid in self._useridentities_by_userid: if not userid: logger.warn('None userid found. This should not happen!') continue if not userid.startswith(search_id): continue identity = self._useridentities_by_userid[userid] ret.append({ 'id': identity.userid.decode('utf8'), 'login': identity.userid, 'pluginid': pluginid }) if max_results and len(ret) >= max_results: break if sort_by in ['id', 'login']: return sorted(ret, key=itemgetter(sort_by)) return ret
class BTreeScopeManager(Persistent, Contained, BaseScopeManager): """ Basic BTree based client/access scope manager. Provides mapping of client and access keys to a scope, but does not provide any validation capabilities. """ zope.component.adapts(IAttributeAnnotatable, zope.interface.Interface) client_prefix = 'client.' access_prefix = 'access.' def __init__(self): self._scope = OOBTree() def setScope(self, key, scope): if self._scope.get(key, _marker) != _marker: raise KeyExistsError() self._scope[key] = scope def getScope(self, key, default=_marker): result = self._scope.get(key, default) if result == _marker: raise KeyError() return result def popScope(self, key, default=_marker): result = self._scope.pop(key, default) return result def setClientScope(self, client_key, scope): key = self.client_prefix + client_key self.setScope(key, scope) def setAccessScope(self, access_key, scope): key = self.access_prefix + access_key self.setScope(key, scope) def getClientScope(self, client_key, default=_marker): key = self.client_prefix + client_key return self.getScope(key, default) def getAccessScope(self, access_key, default=_marker): key = self.access_prefix + access_key return self.getScope(key, default) def delClientScope(self, client_key, default=_marker): key = self.client_prefix + client_key result = self.popScope(key, default) if result == _marker: raise KeyError() def delAccessScope(self, access_key, default=_marker): key = self.access_prefix + access_key result = self.popScope(key, default) if result == _marker: raise KeyError() def requestScope(self, request_key, raw_scope): """ Requesting scope for this key. """ # No reason or means to refuse this request as this doesn't do # any kind of management. self.setScope(request_key, raw_scope) return True
class Tokenable(Entity): """Question class""" tokens_opposition = CompositeMultipleProperty('tokens_opposition') tokens_support = CompositeMultipleProperty('tokens_support') def __init__(self, **kwargs): super(Tokenable, self).__init__(**kwargs) self.set_data(kwargs) self.allocated_tokens = OOBTree() self.len_allocated_tokens = PersistentDict({}) def add_token(self, user, evaluation_type): user_oid = get_oid(user) if user_oid in self.allocated_tokens: self.remove_token(user) self.allocated_tokens[user_oid] = evaluation_type self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] += 1 def remove_token(self, user): user_oid = get_oid(user) if user_oid in self.allocated_tokens: evaluation_type = self.allocated_tokens.pop(user_oid) self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] -= 1 def evaluators(self, evaluation_type=None): if evaluation_type: return [ get_obj(key) for value, key in self.allocated_tokens.byValue( evaluation_type) ] return [get_obj(key) for key in self.allocated_tokens.keys()] def evaluation(self, user): user_oid = get_oid(user, None) return self.allocated_tokens.get(user_oid, None) def remove_tokens(self, force=False): evaluators = self.evaluators() for user in evaluators: user.remove_token(self) if force: self.remove_token(user) def user_has_token(self, user, root=None): if hasattr(user, 'has_token'): return user.has_token(self, root) return False def init_support_history(self): # [(user_oid, date, support_type), ...], support_type = {1:support, 0:oppose, -1:withdraw} if not hasattr(self, '_support_history'): setattr(self, '_support_history', PersistentList()) @property def len_support(self): return self.len_allocated_tokens.get(Evaluations.support, 0) @property def len_opposition(self): return self.len_allocated_tokens.get(Evaluations.oppose, 0)
def __call__(self): alsoProvides(self.request, IDisableCSRFProtection) site = portal.get() storage = IRecommendationStorage(site) storage_recom = storage.get(STORAGE_KEY, None) if 'migrate-recommendations' in self.request.form: new_recommendations = [] for i, (_, recommendation) in enumerate(storage_recom.items()): id_recom = str(i + 1) rec_code = recommendation.code.split('/') if len(rec_code) == 4: rec_code = "/".join( (rec_code[0], rec_code[1], rec_code[3].strip())) else: rec_code = "/".join(rec_code) recommendation = Recommendation(id_recom, rec_code, recommendation.topic, recommendation.text, recommendation.ms_region, recommendation.descriptors) # storage_recom.pop(recommendation.code, None) # storage_recom.pop(id_recom, None) # storage_recom.pop(int(id_recom), None) new_recommendations.append(recommendation) # asd = [x for x in storage_recom.keys()] # asdf = [x._id_recommendation for x in new_recommendations] # import pdb; pdb.set_trace() storage_recom = OOBTree() storage[STORAGE_KEY] = storage_recom for new_rec in new_recommendations: id_recom = new_rec._id_recommendation storage_recom[id_recom] = new_rec if not storage_recom: storage_recom = OOBTree() storage[STORAGE_KEY] = storage_recom if 'add-recommendation' in self.request.form: form_data = self.request.form id_recom = form_data.get('rec_id', '') code = form_data.get('rec_code', '') topic = form_data.get('topic', '') text = form_data.get('rec_text', '') ms_region = form_data.get('ms_or_region', []) descriptors = form_data.get('descriptors', []) if not id_recom: max_id = max([ int(_rec._id_recommendation) for _code, _rec in storage_recom.items() ]) id_recom = str(int(max_id) + 1) recom = Recommendation(id_recom, code, topic, text, ms_region, descriptors) storage_recom[id_recom] = recom if 'remove-recommendation' in self.request.form: form_data = self.request.form id_recom = form_data.get('rec_id', '') storage_recom.pop(id_recom) if 'edit-topics' in self.request.form: topics = self.request.form.get('topics', '') topics = topics.split('\r\n') storage[TOPICS_STORAGE_KEY] = topics recommendations = [] if len(storage_recom.items()): for code, recommendation in storage_recom.items(): recommendations.append(recommendation.data_to_list()) sorted_rec = sorted(recommendations, key=lambda i: i[0]) if 'download-excel' in self.request.form: return self.download(sorted_rec) show_edit_buttons = self.can_view_assessment_data() self.recommendations_table = RecommendationsTable( recommendations=sorted_rec, show_edit_buttons=show_edit_buttons) return self.index()