Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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__)
Exemple #6
0
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
Exemple #8
0
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
Exemple #9
0
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'
Exemple #10
0
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'
Exemple #11
0
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()
Exemple #13
0
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
Exemple #14
0
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
Exemple #15
0
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)):
Exemple #17
0
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))
Exemple #18
0
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 []
Exemple #19
0
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]
Exemple #20
0
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)
Exemple #21
0
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
Exemple #22
0
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)
Exemple #23
0
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()
Exemple #25
0
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)
Exemple #26
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)
Exemple #27
0
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')
Exemple #29
0
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
Exemple #30
0
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()
Exemple #31
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')
    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
Exemple #32
0
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
                )
Exemple #33
0
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 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')
Exemple #35
0
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 []
Exemple #36
0
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
Exemple #38
0
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
Exemple #39
0
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)
Exemple #40
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()