Example #1
0
class DoubleIndex(Index):
    
    def __init__( self, name='' ):
        if name != '':
            self._name = name
        self._words = OOBTree()
        self._ids = OOBTree()
    
    def _addItem( self, value, item ):
        if value != "":
            words = self._words
            if words.has_key(value):
                if item not in words[value]:
                    l = words[value]
                    l.append(item)
                    words[value] = l
            else:
                words[value] = [ item ]
            self.setIndex(words)
            id = self._itemToId(item)
            if self._ids.has_key(value):
                if id not in self._ids[value]:
                    l = self._ids[value]
                    l.append(id)
                    self._ids[value] = l
            else:
                self._ids[value] = [id]
            self._p_changed = 1
            
    def _withdrawItem( self, value, item ):
        if self._words.has_key(value):            
            if item in self._words[value]:
                words = self._words
                l = words[value]
                l.remove(item)
                words[value] = l
                self.setIndex(words)
        id = self._itemToId(item)
        if self._ids.has_key(value):
            if id in self._ids[value]:
                l = self._ids[value]
                l.remove(id)
                self._ids[value] = l
        self._p_changed = 1
    
    def _itemToId(self, item):
        #to be overloaded
        return ""
    
    def initIndex(self):
        self._words = OOBTree()
        self._ids = OOBTree()
        self._p_changed = 1
    
    def getLowerIndex(self):
        if self._words.keys():
            return min(self._words.keys())
        return None
Example #2
0
class OOBTreeBag(DictMixin, Persistent):
    def __init__(self):
        self._data = OOBTree()
    def __getitem__(self, item):
        return self._data[item]
    def __setitem__(self, item, value):
        self._data[item] = value
    def __delitem__(self, item):
        del self._data[item]
    def keys(self):
        return [x for x in self._data.keys()]
    def lazy_keys(self):
        return self._data.keys()
class NonceManager(Persistent):
    """ A BTree-based persistent storage of nonces.
    
    For the purposes of this package, a nonce is a temporary mapping of a
    unique identifier to some data.
    
    A nonce manager may be instantiated and inserted into the persistent
    object graph. (Typically, it could be registered as a named local utility.)
    
      >>> from collective.confirm_email.nonce import NonceManager
      >>> nm = NonceManager()
    
    A nonce may be created on demand, and is stored until it is retrieved.
    It is deleted when it is retrieved, so it may only be retrieved once.
    
      >>> uuid = nm.store('foobar')
      >>> nm.retrieve(uuid)
      'foobar'
      >>> nm.retrieve(uuid) # doctest: +ELLIPSIS
      Traceback (most recent call last):
      KeyError: ...
    
    When a nonce is retrieved, a cleanup procedure removes nonces older
    than a defined threshold.
    
      >>> uuid2 = nm.store('foobar2')
      >>> nm.max_age = 0
      >>> time.sleep(1)
      >>> nm.retrieve(uuid2) # doctest: +ELLIPSIS
      Traceback (most recent call last):
      KeyError: ...
    """
    implements(INonceManager)
    
    _nonces = None
    max_age = 10
    
    def __init__(self):
        self._nonces = OOBTree()
    
    def store(self, value):
        # get UUID and timestamp
        uuid = str(uuid4())
        timestamp = int(time.time())
        
        self._nonces[uuid] = (timestamp, value)
        return uuid
    
    def retrieve(self, key):
        self.clean()
        res = self._nonces[key][1]
        del self._nonces[key]
        return res
    
    def clean(self):
        cutoff = int(time.time()) - self.max_age * 86400
        for uuid in self._nonces.keys():
            timestamp = self._nonces[uuid][0]
            if timestamp < cutoff:
                del self._nonces[uuid]
Example #4
0
class PersitentOOBTree(Persistent):
    """A persitent wrapper around a OOBTree"""

    def __init__(self):
        self._data = OOBTree()
        Persistent.__init__(self)
        self.__len = Length()

    @Lazy
    def _PersitentOOBTree__len(self):
        l = Length()
        ol = len(self._data)
        if ol > 0:
            l.change(ol)
        self._p_changed = True
        return l

    def __len__(self):
        return self.__len()

    def __setitem__(self, key, value):
        # make sure our lazy property gets set
        l = self.__len
        self._data[key] = value
        l.change(1)

    def __delitem__(self, key):
        # make sure our lazy property gets set
        l = self.__len
        del self._data[key]
        l.change(-1)

    def __iter__(self):
        return iter(self._data)

    def __getitem__(self, key):
        """See interface `IReadContainer`.
        """
        return self._data[key]

    def get(self, key, default=None):
        """See interface `IReadContainer`.
        """
        return self._data.get(key, default)

    def __contains__(self, key):
        """See interface `IReadContainer`.
        """
        return key in self._data

    has_key = __contains__

    def items(self, key=None):
        return self._data.items(key)

    def keys(self, key=None):
        return self._data.keys(key)

    def values(self, key=None):
        return self._data.values(key)
Example #5
0
class CategoryIndex(Persistent):
    
    def __init__( self ):
        self._idxCategItem = OOBTree()
        
    def dump(self):
        return list(self._idxCategItem.items())
        
    def _indexConfById(self, categid, confid):
        # only the more restrictive setup is taken into account
        categid = str(categid)
        if self._idxCategItem.has_key(categid):
            res = self._idxCategItem[categid]
        else:
            res = []
        res.append(confid)
        self._idxCategItem[categid] = res
        
    def unindexConf(self, conf):
        confid = str(conf.getId())
        self.unindexConfById(confid)
    
    def unindexConfById(self, confid):
        for categid in self._idxCategItem.keys():
            if confid in self._idxCategItem[categid]:
                res = self._idxCategItem[categid]
                res.remove(confid)
                self._idxCategItem[categid] = res
        
    def reindexCateg(self, categ):
        for subcat in categ.getSubCategoryList():
            self.reindexCateg(subcat)
        for conf in categ.getConferenceList():
            self.reindexConf(conf)
        
    def reindexConf(self, conf):
        self.unindexConf(conf)
        self.indexConf(conf)

    def indexConf(self, conf):
        categs = conf.getOwnerPath()
        level = 0 
        for categ in conf.getOwnerPath():
            if conf.getFullVisibility() > level:
                self._indexConfById(categ.getId(),conf.getId())
            level+=1
        if conf.getFullVisibility() > level:
            self._indexConfById("0",conf.getId())
    
    def getItems(self, categid):
        categid = str(categid)
        if self._idxCategItem.has_key(categid):
            return self._idxCategItem[categid]
        else:
            return []
    def updateEveryLoginName(self, quit_on_first_error=True):
        # Update all login names to their canonical value.  This
        # should be done after changing the login_transform property
        # of pas.  You can set quit_on_first_error to False to report
        # all errors before quitting with an error.  This can be
        # useful if you want to know how many problems there are, if
        # any.
        pas = self._getPAS()
        transform = pas._get_login_transform_method()
        if not transform:
            logger.warn("PAS has a non-existing, empty or wrong "
                        "login_transform property.")
            return

        # Make a fresh mapping, as we do not want to add or remove
        # items to the original mapping while we are iterating over
        # it.
        new_login_to_userid = OOBTree()
        errors = []
        for old_login_name, user_id in self._login_to_userid.items():
            new_login_name = transform(old_login_name)
            if new_login_name in new_login_to_userid:
                logger.error("User id %s: login name %r already taken.",
                             user_id, new_login_name)
                errors.append(new_login_name)
                if quit_on_first_error:
                    break
            new_login_to_userid[new_login_name] = user_id
            if new_login_name != old_login_name:
                self._userid_to_login[user_id] = new_login_name
                # Also, remove from the cache
                view_name = createViewName('enumerateUsers', user_id)
                self.ZCacheable_invalidate(view_name=view_name)
                logger.debug("User id %s: changed login name from %r to %r.",
                             user_id, old_login_name, new_login_name)

        # If there were errors, we do not want to save any changes.
        if errors:
            logger.error("There were %d errors when updating login names. "
                         "quit_on_first_error was %r", len(errors),
                         quit_on_first_error)
            # Make sure the exception we raise is not swallowed.
            self._dont_swallow_my_exceptions = True
            raise ValueError("Transformed login names are not unique: %s." %
                             ', '.join(errors))

        # Make sure we did not lose any users.
        assert(len(self._login_to_userid.keys())
               == len(new_login_to_userid.keys()))
        # Empty the main cache.
        view_name = createViewName('enumerateUsers')
        self.ZCacheable_invalidate(view_name=view_name)
        # Store the new login mapping.
        self._login_to_userid = new_login_to_userid
Example #7
0
class Package(Persistent):
    pypi_url = 'http://pypi.python.org/pypi/{}/json'

    def __init__(self, name):
        self.__name__ = name
        self.name = name
        self.releases = OOBTree()

    def __getitem__(self, release_name):
        return self.releases[release_name]

    def __setitem__(self, key, value):
        key = format_key(key)
        self.releases[key] = value
        self.releases[key].__parent__ = self

    @classmethod
    @cache_region('pypi', 'get_last_remote_filename')
    def get_last_remote_version(cls, proxy, package_name):
        logger.debug('Not in cache')
        if not proxy:
            return None
        try:
            result = requests.get('http://pypi.python.org/pypi/{}/json'.format(package_name))
            if not result.status_code == 200:
                return None
            result = json.loads(result.content.decode('utf-8'))
            return result['info']['version']
        except ConnectionError:
            pass
        return None

    def repository_is_up_to_date(self, last_remote_release):
        if not last_remote_release:
            return True
        remote_version = parse_version(last_remote_release)

        local_versions = [release.version for release in self.releases.values()]
        for version in local_versions:
            if parse_version(version) >= remote_version:
                return True
        return False

    @classmethod
    def by_name(cls, name, request):
        root = repository_root_factory(request)
        return root[name] if name in root else None

    def get_last_release(self):
        max_version = max([parse_version(version) for version in self.releases.keys()])
        for version, release in self.releases.items():
            if parse_version(version) == max_version:
                return release
Example #8
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)
Example #9
0
class DefaultTokenManager(Persistent):
    """Tokens manager persistent utility (per site)
    """
    implements(ITokenManager)

    def __init__(self):
        # {token: user id, ...}
        self._token2uid = OOBTree()
        # {user id: token, ...}
        self._uid2token = OOBTree()
        return

    def userIdForToken(self, token):
        """See ITokensManager
        """
        if token in self._token2uid:
            return self._token2uid[token]
        return

    def tokenForUserId(self, user_id):
        """See ITokensManager
        """
        if user_id in self._uid2token:
            return self._uid2token[user_id]

        # We'll make a token for this new user id
        return self.resetToken(user_id)

    def resetToken(self, user_id):
        """See ITokensManager
        """
        generator = getUtility(IUUIDGenerator)
        token = generator()
        self._token2uid[token] = user_id
        self._uid2token[user_id] = token
        return token

    def pruneUserId(self, user_id):
        """See ITokensManager
        """
        token = self._uid2token[user_id]
        del self._uid2token[user_id]
        del self._token2uid[token]
        return

    def knownUserIds(self):
        """See ITokensManager
        """
        for user_id in self._uid2token.keys():
            yield user_id
Example #10
0
class UpgradeRegistry(object):
    """Registry of upgrade steps, by profile.
    
    Registry keys are profile ids.

    Each registry value is a nested mapping:
      - id -> step for single steps
      - id -> [ (id1, step1), (id2, step2) ] for nested steps
    """
    def __init__(self):
        self._registry = OOBTree()

    def __getitem__(self, key):
        return self._registry.get(key)

    def keys(self):
        return self._registry.keys()

    def clear(self):
        self._registry.clear()

    def getUpgradeStepsForProfile(self, profile_id):
        """Return the upgrade steps mapping for a given profile, or
        None if there are no steps registered for a profile matching
        that id.
        """
        profile_steps = self._registry.get(profile_id, None)
        if profile_steps is None:
            self._registry[profile_id] = OOBTree()
            profile_steps = self._registry.get(profile_id)
        return profile_steps

    def getUpgradeStep(self, profile_id, step_id):
        """Returns the specified upgrade step for the specified
        profile, or None if it doesn't exist.
        """
        profile_steps = self._registry.get(profile_id, None)
        if profile_steps is not None:
            step = profile_steps.get(step_id, None)
            if step is None:
                for key in profile_steps.keys():
                    if type(profile_steps[key]) == list:
                        subs = dict(profile_steps[key])
                        step = subs.get(step_id, None)
                        if step is not None:
                            break
            elif type(step) == list:
                subs = dict(step)
                step = subs.get(step_id, None)
            return step
Example #11
0
class FileSystem(persistent.Persistent):
    u""" Failų sistema.
    """

    def __init__(self):
        u""" Inicijuoja failų sistemą.
        """

        self.files = OOBTree()

    def create(self, name):
        u""" Sukuria failą rašymui.

        + ``name`` – failo pavadinimas.

        """

        self.files[name] = File()
        transaction.commit()
        return self.files[name]

    def open(self, name):
        u""" Atidaro egzistuojantį failą skaitymui.

        + ``name`` – failo pavadinimas.

        """

        file = self.files[name]
        file.reset_reader()
        transaction.commit()
        return file

    def delete(self, name):
        u""" Ištrina failą.

        + ``name`` – failo pavadinimas.

        """

        del self.files[name]
        transaction.commit()

    def get_files(self):
        u""" Grąžina failų sistemoje esančių failų vardų, kaip utf-8
        baitų sekų, sąrašą.
        """

        return list(self.files.keys())
Example #12
0
class TopicIndex(Persistent):

    implements(IInjection, ITopicQuerying)

    def __init__(self):
        self.clear()

    def clear(self):
        # mapping filter id -> filter
        self._filters = OOBTree()

    def addFilter(self, f ):
        """ Add filter 'f' with ID 'id' """
        self._filters[f.getId()] = f

    def delFilter(self, id):
        """ remove a filter given by its ID 'id' """
        del self._filters[id]

    def index_doc(self, docid, obj):
        """index an object"""

        for f in self._filters.values():
            f.index_doc(docid, obj)

    def unindex_doc(self, docid):
        """unindex an object"""

        for f in self._filters.values():
            f.unindex_doc(docid)

    def search(self, query, operator='and'):

        if isinstance(query, StringTypes): query = [query]
        if not isinstance(query, (TupleType, ListType)):
            raise TypeError('query argument must be a list/tuple of filter ids')

        f = {'and' : intersection, 'or' : union}[operator]
    
        rs = None
        for id in self._filters.keys():
            if id in query:
                docids = self._filters[id].getIds()
                rs = f(rs, docids)
            
        if rs:  return rs
        else: return IISet()
Example #13
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__)
class AttachmentStorage(Traversable, Persistent, Explicit):
    """ The attachment storage is a container for all attachments on content
        objects (that provide IAttachmentStoragable).

        All objects including non-folderish ones, can be adapted to store
        attachments, since attached objects are not stored inside the object,
        but inside this AttachmentStorage container (which is stored as an
        annotation on the object being attached to).
    """
    interface.implements(IAttachmentStorage, IHideFromBreadcrumbs)
    __allow_access_to_unprotected_subobjects__ = True

    def __init__(self, id="++attachments++default"):
        self.id = id
        if not shasattr(self, '_attachments'):
            self.init_storage()

    def __getitem__(self, id):
        """ """
        return self.get(id)

    def getId(self):
        """ Get the id of the storage. This is used to construct a URL.
        """
        return self.id

    def init_storage(self):
        self._attachments = OOBTree()

    def keys(self):
        return self._attachments.keys()

    def values(self):
        return [att.__of__(self) for att in
                self._attachments.values()]

    def get(self, id):
        return self._attachments.get(id).__of__(self)

    def add(self, attachment):
        if attachment.getId() in self._attachments:
            raise DuplicateIDError
        self._attachments[attachment.getId()] = attachment

    def remove(self, id):
        del self._attachments[id]
Example #15
0
class SubsiteSkinStorage(Persistent):
    """Stores subsite paths and skinnames.
    """
    implements(ISubsiteSkinStorage)

    def __init__(self):
        self._paths = OOBTree()

    def add(self, path, skin):
        path = self._canonical(path)

        if not skin or not path:
            return

        self._paths[path] = skin

    def remove(self, path):
        path = self._canonical(path)
        del self._paths[path]

    def has_path(self, path):
        path = self._canonical(path)
        return bool(path in self._paths)

    def get(self, path, default=None):
        path = self._canonical(path)
        longest = ''
        for p in self._paths.keys():
            if path.startswith(p) and len(p) > len(longest):
                longest = p
        return self._paths.get(longest, default)

    def _canonical(self, path):
        if path.endswith('/'):
            path = path[:-1]
        return path

    def __iter__(self):
        return iter(self._paths)
Example #16
0
class UserEventLog(Container):
    implements(IUserEventLogContainer)
    __contains__ = IUserEvent

    def __init__(self, username, sizelimit=None):
        self.__name__ = username
        self.sizelimit = None
        self.cur_index = 0
        self._items = OOBTree()

    def add_event(self, rawevent):
        if rawevent.username != self.__name__:
            return

        if self.sizelimit is not None:
            while self.sizelimit <= len(self._items):
                del self._items[min(self._items.keys())]

        self.cur_index += 1
        item = UserEvent(rawevent, self.cur_index)
        return self.add(item)

    def __str__(self):
        return '<UserEventLog %s sizelimit=%s>' % (self._items, self.sizelimit)
class zodbBTreeAuthSource(Folder):
	""" Authenticate users against a ZODB BTree """

	meta_type='Authentication Source'
	title	 ='ZODB BTree Authentication'
	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'

	manage_properties=HTMLFile('properties', globals())

	manage_editForm=manage_editzodbBTreeAuthSourceForm
	manage_tabs=Acquisition.Acquired

	#
	# You can define this to go off and do the authentication instead of
	# using the basic one inside the User Object
	#
	remoteAuthMethod=None

	def __init__(self):
		self.id = 'zodbBTreeAuthSource'
		self.userBTree=OOBTree()

	def cryptPassword_old(self, username, password):
			salt = username[:2]
			secret = crypt(password, salt)
			return secret

	def deleteUsers(self, userids):
		for name in userids:
			del self.userBTree[name]

	def createUser(self, username, password, roles=[]):
		""" Add a Username """
		if type(roles) != type([]):
			if roles:
				roles=list(roles)
			else:
				roles=[]

		secret=self.cryptPassword(username, password)			
		myUser=zodbUser()
		myUser.username=username
		myUser.password=secret
		myUser.roles=roles
		self.userBTree[username]=myUser

	def updateUser(self, username, password, roles):
		if type(roles) != type([]):
			if roles:
				roles=list(roles)
			else:
				roles=[]
		
		self.userBTree[username].roles = roles
		if password:
			secret = self.cryptPassword(username, password)
			self.userBTree[username].password = secret

	def listUserNames(self):
		return self.userBTree.keys()

	def listUsers(self):
		""" return a list of users or [] if no users exist"""
		users=[]
		for u in self.userBTree.keys():
			n = self.userBTree[u]
			N={'username':n.username, 'password':n.password, 'roles':n.roles}
			users.append(N)
		return users

	def listOneUser(self, username):
		users = []
		try:
			n = self.userBTree[username]
			N={'username':n.username, 'password':n.password, 'roles':n.roles}
			users.append(N)
		except:
			pass
		return users

	def postInitialisation(self, REQUEST):
		pass
Example #18
0
class CategoryIndex(Persistent):
    def __init__(self):
        self._idxCategItem = OOBTree()

    def dump(self):
        return list(self._idxCategItem.items())

    def _indexConfById(self, categid, confid):
        # only the more restrictive setup is taken into account
        categid = str(categid)
        if self._idxCategItem.has_key(categid):
            res = self._idxCategItem[categid]
        else:
            res = []
        res.append(confid)
        self._idxCategItem[categid] = res

    def unindexConf(self, conf):
        confid = str(conf.getId())
        self.unindexConfById(confid)

    def unindexConfById(self, confid):
        for categid in self._idxCategItem.keys():
            if confid in self._idxCategItem[categid]:
                res = self._idxCategItem[categid]
                res.remove(confid)
                self._idxCategItem[categid] = res

    def reindexCateg(self, categ):
        for subcat in categ.getSubCategoryList():
            self.reindexCateg(subcat)
        for conf in categ.getConferenceList():
            self.reindexConf(conf)

    def reindexConf(self, conf):
        self.unindexConf(conf)
        self.indexConf(conf)

    def indexConf(self, conf):
        categs = conf.getOwnerPath()
        level = 0
        for categ in conf.getOwnerPath():
            if conf.getFullVisibility() > level:
                self._indexConfById(categ.getId(), conf.getId())
            level += 1
        if conf.getFullVisibility() > level:
            self._indexConfById("0", conf.getId())

    def getItems(self, categid):
        categid = str(categid)
        if self._idxCategItem.has_key(categid):
            return self._idxCategItem[categid]
        else:
            return []

    def _check(self, dbi=None):
        """
        Performs some sanity checks
        """
        i = 0
        from MaKaC.conference import ConferenceHolder
        confIdx = ConferenceHolder()._getIdx()

        for cid, confs in self._idxCategItem.iteritems():
            for confId in confs:
                # it has to be in the conference holder
                if confId not in confIdx:
                    yield "[%s] '%s' not in ConferenceHolder" % (cid, confId)
                # the category has to be one of the owners
                elif cid not in (map(
                        lambda x: x.id,
                        ConferenceHolder().getById(confId).getOwnerPath()) +
                                 ['0']):
                    yield "[%s] Conference '%s' is not owned" % (cid, confId)
            if dbi and i % 100 == 99:
                dbi.sync()
            i += 1
Example #19
0
class ZODBUserManager( BasePlugin ):

    """ PAS plugin for managing users in the ZODB.
    """
    __implements__ = ( IAuthenticationPlugin
                     , IUserEnumerationPlugin
                     , IUserAdderPlugin
                     )

    meta_type = 'ZODB User Manager'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title

        self._user_passwords = OOBTree()
        self._login_to_userid = OOBTree()
        self._userid_to_login = OOBTree()

    #
    #   IAuthenticationPlugin implementation
    #
    security.declarePrivate( 'authenticateCredentials' )
    def authenticateCredentials( self, credentials ):

        """ See IAuthenticationPlugin.

        o We expect the credentials to be those returned by
          ILoginPasswordExtractionPlugin.
        """
        login = credentials.get( 'login' )
        password = credentials.get( 'password' )

        if login is None or password is None:
            return None

        userid = self._login_to_userid.get( login, login )
        digested = sha.sha( password ).hexdigest()
        
        if self._user_passwords.get( userid ) == digested:
            return userid, login

        return None

    #
    #   IUserEnumerationPlugin implementation
    #
    security.declarePrivate( 'enumerateUsers' )
    def enumerateUsers( self
                      , id=None
                      , login=None
                      , exact_match=False
                      , sort_by=None
                      , max_results=None
                      , **kw
                      ):

        """ See IUserEnumerationPlugin.
        """
        user_info = []
        user_ids = []
        plugin_id = self.getId()

        if isinstance( id, str ):
            id = [ id ]

        if isinstance( login, str ):
            login = [ login ]

        if exact_match and ( id or login ):

            if id:
                user_ids.extend( id )
            elif login:
                user_ids.extend( [ self._login_to_userid.get( x )
                                    for x in login ] )

        if user_ids:
            user_filter = None

        else:   # Searching
            user_ids = self.listUserIds()
            user_filter = _ZODBUserFilter( id, login, **kw )

        for user_id in user_ids:

            if self._userid_to_login.get( user_id ):
                e_url = '%s/manage_users' % self.getId()
                qs = 'user_id=%s' % user_id

                info = { 'id' : user_id
                       , 'login' : self._userid_to_login[ user_id ]
                       , 'pluginid' : plugin_id
                       , 'editurl' : '%s?%s' % (e_url, qs)
                       } 

                if not user_filter or user_filter( info ):
                    user_info.append( info )

        return tuple( user_info )

    #
    #   IUserAdderPlugin implementation
    #
    security.declarePrivate( 'doAddUser' )
    def doAddUser( self, login, password ):
        try:
            self.addUser( login, login, password )
        except KeyError:
            return False
        return True

    #
    #   (notional)IZODBUserManager interface
    #
    security.declareProtected( ManageUsers, 'listUserIds' )
    def listUserIds( self ):

        """ -> ( user_id_1, ... user_id_n )
        """
        return self._user_passwords.keys()

    security.declareProtected( ManageUsers, 'getUserInfo' )
    def getUserInfo( self, user_id ):

        """ user_id -> {}
        """
        return { 'user_id' : user_id
               , 'login_name' : self._userid_to_login[ user_id ]
               , 'pluginid' : self.getId()
               }

    security.declareProtected( ManageUsers, 'listUserInfo' )
    def listUserInfo( self ):

        """ -> ( {}, ...{} )

        o Return one mapping per user, with the following keys:

          - 'user_id' 
          - 'login_name'
        """
        return [ self.getUserInfo( x ) for x in self._user_passwords.keys() ]

    security.declareProtected( ManageUsers, 'getUserIdForLogin' )
    def getUserIdForLogin( self, login_name ):

        """ login_name -> user_id

        o Raise KeyError if no user exists for the login name.
        """
        return self._login_to_userid[ login_name ]

    security.declareProtected( ManageUsers, 'getLoginForUserId' )
    def getLoginForUserId( self, user_id ):

        """ user_id -> login_name

        o Raise KeyError if no user exists for that ID.
        """
        return self._userid_to_login[ user_id ]

    security.declarePrivate( 'addUser' )
    def addUser( self, user_id, login_name, password ):

        if self._user_passwords.get( user_id ) is not None:
            raise KeyError, 'Duplicate user ID: %s' % user_id

        if self._login_to_userid.get( login_name ) is not None:
            raise KeyError, 'Duplicate login name: %s' % login_name

        self._user_passwords[ user_id ] = sha.sha( password ).hexdigest()
        self._login_to_userid[ login_name ] = user_id
        self._userid_to_login[ user_id ] = login_name

    security.declarePrivate( 'removeUser' )
    def removeUser( self, user_id ):

        if self._user_passwords.get( user_id ) is None:
            raise KeyError, 'Invalid user ID: %s' % user_id

        login_name = self._userid_to_login[ user_id ]

        del self._user_passwords[ user_id ]
        del self._login_to_userid[ login_name ]
        del self._userid_to_login[ user_id ]

    security.declarePrivate( 'updateUserPassword' )
    def updateUserPassword( self, user_id, login_name, password ):

        if self._user_passwords.get( user_id ) is None:
            raise KeyError, 'Invalid user ID: %s' % user_id

        old_login_name = self._userid_to_login[ user_id ]

        if old_login_name != login_name:
            del self._login_to_userid[ old_login_name ]
            self._login_to_userid[ login_name ] = user_id
            self._userid_to_login[ user_id ] = login_name

        if password:
            digested = sha.sha( password ).hexdigest()
            self._user_passwords[ user_id ] = digested

    #
    #   ZMI
    #
    manage_options = ( ( { 'label': 'Users', 
                           'action': 'manage_users', }
                         ,
                       )
                     + BasePlugin.manage_options
                     )

    security.declarePublic( 'manage_widgets' )
    manage_widgets = PageTemplateFile( 'www/zuWidgets'
                                     , globals()
                                     , __name__='manage_widgets'
                                     )

    security.declareProtected( ManageUsers, 'manage_users' )
    manage_users = PageTemplateFile( 'www/zuUsers'
                                   , globals()
                                   , __name__='manage_users'
                                   )

    security.declareProtected( ManageUsers, 'manage_addUser' )
    def manage_addUser( self
                      , user_id
                      , login_name
                      , password
                      , confirm
                      , RESPONSE=None
                      ):
        """ Add a user via the ZMI.
        """
        if password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:
        
            if not login_name:
                login_name = user_id

            # XXX:  validate 'user_id', 'login_name' against policies?

            self.addUser( user_id, login_name, password )

            message = 'User+added'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageUsers, 'manage_updateUserPassword' )
    def manage_updateUserPassword( self
                                 , user_id
                                 , login_name
                                 , password
                                 , confirm
                                 , RESPONSE=None
                                 ):
        """ Update a user's login name / password via the ZMI.
        """
        if password and password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:
        
            if not login_name:
                login_name = user_id

            # XXX:  validate 'user_id', 'login_name' against policies?

            self.updateUserPassword( user_id, login_name, password )

            message = 'password+updated'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageUsers, 'manage_removeUsers' )
    def manage_removeUsers( self
                          , user_ids
                          , RESPONSE=None
                          ):
        """ Remove one or more users via the ZMI.
        """
        user_ids = filter( None, user_ids )

        if not user_ids:
            message = 'no+users+selected'

        else:
        
            for user_id in user_ids:
                self.removeUser( user_id )

            message = 'Users+removed'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    #
    #   Allow users to change their own login name and password.
    #
    security.declareProtected( SetOwnPassword, 'getOwnUserInfo' )
    def getOwnUserInfo( self ):

        """ Return current user's info.
        """
        user_id = getSecurityManager().getUser().getId()

        return self.getUserInfo( user_id )

    security.declareProtected( SetOwnPassword, 'manage_updatePasswordForm' )
    manage_updatePasswordForm = PageTemplateFile( 'www/zuPasswd'
                                   , globals()
                                   , __name__='manage_updatePasswordForm'
                                   )

    security.declareProtected( SetOwnPassword, 'manage_updatePassword' )
    def manage_updatePassword( self
                             , login_name
                             , password
                             , confirm
                             , RESPONSE=None
                             ):
        """ Update the current user's password and login name.
        """
        user_id = getSecurityManager().getUser().getId()
        if password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:
        
            if not login_name:
                login_name = user_id

            # XXX:  validate 'user_id', 'login_name' against policies?

            self.updateUserPassword( user_id, login_name, password )

            message = 'password+updated'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_updatePasswordForm'
                               '?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )
Example #20
0
class UnIndex(SimpleItem):

    """Simple forward and reverse index.
    """

    __implements__ = (PluggableIndex.UniqueValueIndex,
                      PluggableIndex.SortIndex)
    implements(IPluggableIndex, IUniqueValueIndex, ISortIndex)

    def __init__(
        self, id, ignore_ex=None, call_methods=None, extra=None, caller=None):
        """Create an unindex

        UnIndexes are indexes that contain two index components, the
        forward index (like plain index objects) and an inverted
        index.  The inverted index is so that objects can be unindexed
        even when the old value of the object is not known.

        e.g.

        self._index = {datum:[documentId1, documentId2]}
        self._unindex = {documentId:datum}

        If any item in self._index has a length-one value, the value is an
        integer, and not a set.  There are special cases in the code to deal
        with this.

        The arguments are:

          'id' -- the name of the item attribute to index.  This is
          either an attribute name or a record key.

          'ignore_ex' -- should be set to true if you want the index
          to ignore exceptions raised while indexing instead of
          propagating them.

          'call_methods' -- should be set to true if you want the index
          to call the attribute 'id' (note: 'id' should be callable!)
          You will also need to pass in an object in the index and
          uninded methods for this to work.

          'extra' -- a mapping object that keeps additional
          index-related parameters - subitem 'indexed_attrs'
          can be string with comma separated attribute names or
          a list

          'caller' -- reference to the calling object (usually
          a (Z)Catalog instance
        """

        def _get(o, k, default):
            """ return a value for a given key of a dict/record 'o' """
            if isinstance(o, dict):
                return o.get(k, default)
            else:
                return getattr(o, k, default)

        self.id = id
        self.ignore_ex=ignore_ex        # currently unimplimented
        self.call_methods=call_methods

        self.operators = ('or', 'and')
        self.useOperator = 'or'

        # allow index to index multiple attributes
        ia = _get(extra, 'indexed_attrs', id)
        if isinstance(ia, str):
            self.indexed_attrs = ia.split(',')
        else:
            self.indexed_attrs = list(ia)
        self.indexed_attrs = [ attr.strip()
                               for attr in self.indexed_attrs if attr ]
        if not self.indexed_attrs:
            self.indexed_attrs = [id]

        self._length = BTrees.Length.Length()
        self.clear()

    def __len__(self):
        return self._length()

    def getId(self):
        return self.id

    def clear(self):
        self._length = BTrees.Length.Length()
        self._index = OOBTree()
        self._unindex = IOBTree()

    def __nonzero__(self):
        return not not self._unindex

    def histogram(self):
        """Return a mapping which provides a histogram of the number of
        elements found at each point in the index.
        """
        histogram = {}
        for item in self._index.items():
            if isinstance(item,int):
                entry = 1 # "set" length is 1
            else:
                key, value = item
                entry = len(value)
            histogram[entry] = histogram.get(entry, 0) + 1

        return histogram

    def referencedObjects(self):
        """Generate a list of IDs for which we have referenced objects."""
        return self._unindex.keys()

    def getEntryForObject(self, documentId, default=_marker):
        """Takes a document ID and returns all the information we have
        on that specific object.
        """
        if default is _marker:
            return self._unindex.get(documentId)
        else:
            return self._unindex.get(documentId, default)

    def removeForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and remove any reference to documentId
        in its entry in the index.
        """
        indexRow = self._index.get(entry, _marker)
        if indexRow is not _marker:
            try:
                indexRow.remove(documentId)
                if not indexRow:
                    del self._index[entry]
                    self._length.change(-1)

            except ConflictError:
                raise

            except AttributeError:
                # index row is an int
                try:
                    del self._index[entry]
                except KeyError:
                    # XXX swallow KeyError because it was probably
                    # removed and then _length AttributeError raised
                    pass 
                if isinstance(self.__len__, BTrees.Length.Length):
                    self._length = self.__len__
                    del self.__len__ 
                self._length.change(-1)

            except:
                LOG.error('%s: unindex_object could not remove '
                          'documentId %s from index %s.  This '
                          'should not happen.' % (self.__class__.__name__,
                           str(documentId), str(self.id)),
                           exc_info=sys.exc_info())
        else:
            LOG.error('%s: unindex_object tried to retrieve set %s '
                      'from index %s but couldn\'t.  This '
                      'should not happen.' % (self.__class__.__name__,
                      repr(entry), str(self.id)))

    def insertForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and put it in the correct place
        in the forward index.

        This will also deal with creating the entire row if necessary.
        """
        indexRow = self._index.get(entry, _marker)

        # Make sure there's actually a row there already.  If not, create
        # an IntSet and stuff it in first.
        if indexRow is _marker:
            self._index[entry] = documentId
            # XXX _length needs to be migrated to Length object
            try:
                self._length.change(1)
            except AttributeError:
                if isinstance(self.__len__, BTrees.Length.Length):
                    self._length = self.__len__
                    del self.__len__
                self._length.change(1)
        else:
            try: indexRow.insert(documentId)
            except AttributeError:
                # index row is an int
                indexRow=IITreeSet((indexRow, documentId))
                self._index[entry] = indexRow

    def index_object(self, documentId, obj, threshold=None):
        """ wrapper to handle indexing of multiple attributes """

        fields = self.getIndexSourceNames()

        res = 0
        for attr in fields:
            res += self._index_object(documentId, obj, threshold, attr)

        return res > 0

    def _index_object(self, documentId, obj, threshold=None, attr=''):
        """ index and object 'obj' with integer id 'documentId'"""
        returnStatus = 0

        # First we need to see if there's anything interesting to look at
        datum = self._get_object_datum(obj, attr)

        # We don't want to do anything that we don't have to here, so we'll
        # check to see if the new and existing information is the same.
        oldDatum = self._unindex.get(documentId, _marker)
        if datum != oldDatum:
            if oldDatum is not _marker:
                self.removeForwardIndexEntry(oldDatum, documentId)
                if datum is _marker:
                    try:
                        del self._unindex[documentId]
                    except ConflictError:
                        raise
                    except:
                        LOG.error('Should not happen: oldDatum was there, now its not,'
                                  'for document with id %s' % documentId)

            if datum is not _marker:
                self.insertForwardIndexEntry(datum, documentId)
                self._unindex[documentId] = datum

            returnStatus = 1

        return returnStatus

    def _get_object_datum(self,obj, attr):
        # self.id is the name of the index, which is also the name of the
        # attribute we're interested in.  If the attribute is callable,
        # we'll do so.
        try:
            datum = getattr(obj, attr)
            if safe_callable(datum):
                datum = datum()
        except AttributeError:
            datum = _marker
        return datum

    def numObjects(self):
        """ return number of indexed objects """
        return len(self._unindex)

    def indexSize(self):
        """ return of distinct values indexed"""
        return len(self)

    def unindex_object(self, documentId):
        """ Unindex the object with integer id 'documentId' and don't
        raise an exception if we fail
        """
        unindexRecord = self._unindex.get(documentId, _marker)
        if unindexRecord is _marker:
            return None

        self.removeForwardIndexEntry(unindexRecord, documentId)

        try:
            del self._unindex[documentId]
        except ConflictError:
            raise
        except:
            LOG.debug('Attempt to unindex nonexistent document'
                      ' with id %s' % documentId,exc_info=True)

    def _apply_index(self, request, cid='', type=type):
        """Apply the index to query parameters given in the request arg.

        The request argument should be a mapping object.

        If the request does not have a key which matches the "id" of
        the index instance, then None is returned.

        If the request *does* have a key which matches the "id" of
        the index instance, one of a few things can happen:

          - if the value is a blank string, None is returned (in
            order to support requests from web forms where
            you can't tell a blank string from empty).

          - if the value is a nonblank string, turn the value into
            a single-element sequence, and proceed.

          - if the value is a sequence, return a union search.

        If the request contains a parameter with the name of the
        column + '_usage', it is sniffed for information on how to
        handle applying the index.

        If the request contains a parameter with the name of the
        column = '_operator' this overrides the default method
        ('or') to combine search results. Valid values are "or"
        and "and".

        If None is not returned as a result of the abovementioned
        constraints, two objects are returned.  The first object is a
        ResultSet containing the record numbers of the matching
        records.  The second object is a tuple containing the names of
        all data fields used.

        FAQ answer:  to search a Field Index for documents that
        have a blank string as their value, wrap the request value
        up in a tuple ala: request = {'id':('',)}
        """
        record = parseIndexRequest(request, self.id, self.query_options)
        if record.keys==None: return None

        index = self._index
        r     = None
        opr   = None

        # experimental code for specifing the operator
        operator = record.get('operator',self.useOperator)
        if not operator in self.operators :
            raise RuntimeError,"operator not valid: %s" % escape(operator)

        # depending on the operator we use intersection or union
        if operator=="or":  set_func = union
        else:               set_func = intersection

        # Range parameter
        range_parm = record.get('range',None)
        if range_parm:
            opr = "range"
            opr_args = []
            if range_parm.find("min")>-1:
                opr_args.append("min")
            if range_parm.find("max")>-1:
                opr_args.append("max")

        if record.get('usage',None):
            # see if any usage params are sent to field
            opr = record.usage.lower().split(':')
            opr, opr_args=opr[0], opr[1:]

        if opr=="range":   # range search
            if 'min' in opr_args: lo = min(record.keys)
            else: lo = None
            if 'max' in opr_args: hi = max(record.keys)
            else: hi = None
            if hi:
                setlist = index.items(lo,hi)
            else:
                setlist = index.items(lo)

            for k, set in setlist:
                if isinstance(set, int):
                    set = IISet((set,))
                r = set_func(r, set)
        else: # not a range search
            for key in record.keys:
                set=index.get(key, None)
                if set is None:
                    set = IISet(())
                elif isinstance(set, int):
                    set = IISet((set,))
                r = set_func(r, set)

        if isinstance(r, int):  r=IISet((r,))
        if r is None:
            return IISet(), (self.id,)
        else:
            return r, (self.id,)

    def hasUniqueValuesFor(self, name):
        """has unique values for column name"""
        if name == self.id:
            return 1
        else:
            return 0

    def getIndexSourceNames(self):
        """ return sequence of indexed attributes """
        # BBB:  older indexes didn't have 'indexed_attrs'
        return getattr(self, 'indexed_attrs', [self.id])

    def uniqueValues(self, name=None, withLengths=0):
        """returns the unique values for name

        if withLengths is true, returns a sequence of
        tuples of (value, length)
        """
        if name is None:
            name = self.id
        elif name != self.id:
            return []

        if not withLengths:
            return tuple(self._index.keys())
        else:
            rl=[]
            for i in self._index.keys():
                set = self._index[i]
                if isinstance(set, int):
                    l = 1
                else:
                    l = len(set)
                rl.append((i, l))
            return tuple(rl)

    def keyForDocument(self, id):
        # This method is superceded by documentToKeyMap
        return self._unindex[id]

    def documentToKeyMap(self):
        return self._unindex

    def items(self):
        items = []
        for k,v in self._index.items():
            if isinstance(v, int):
                v = IISet((v,))
            items.append((k, v))
        return items
class ZODBRoleManager(BasePlugin):
    """ PAS plugin for managing roles in the ZODB.
    """
    meta_type = 'ZODB Role Manager'
    zmi_icon = 'fas fa-user-tag'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title

        self._roles = OOBTree()
        self._principal_roles = OOBTree()

    def manage_afterAdd(self, item, container):

        if item is self:
            role_holder = aq_parent(aq_inner(container))
            for role in getattr(role_holder, '__ac_roles__', ()):
                try:
                    if role not in ('Anonymous', 'Authenticated'):
                        self.addRole(role)
                except KeyError:
                    pass

        if 'Manager' not in self._roles:
            self.addRole('Manager')

    #
    #   IRolesPlugin implementation
    #
    @security.private
    def getRolesForPrincipal(self, principal, request=None):
        """ See IRolesPlugin.
        """
        result = list(self._principal_roles.get(principal.getId(), ()))

        getGroups = getattr(principal, 'getGroups', lambda: ())
        for group_id in getGroups():
            result.extend(self._principal_roles.get(group_id, ()))

        return tuple(result)

    #
    #   IRoleEnumerationPlugin implementation
    #
    def enumerateRoles(self,
                       id=None,
                       exact_match=False,
                       sort_by=None,
                       max_results=None,
                       **kw):
        """ See IRoleEnumerationPlugin.
        """
        role_info = []
        role_ids = []
        plugin_id = self.getId()

        if isinstance(id, str):
            id = [id]

        if exact_match and (id):
            role_ids.extend(id)

        if role_ids:
            role_filter = None

        else:  # Searching
            role_ids = self.listRoleIds()
            role_filter = _ZODBRoleFilter(id, **kw)

        for role_id in role_ids:

            if self._roles.get(role_id):
                e_url = '%s/manage_roles' % self.getId()
                p_qs = 'role_id=%s' % role_id
                m_qs = 'role_id=%s&assign=1' % role_id

                info = {}
                info.update(self._roles[role_id])

                info['pluginid'] = plugin_id
                info['properties_url'] = '%s?%s' % (e_url, p_qs)
                info['members_url'] = '%s?%s' % (e_url, m_qs)

                if not role_filter or role_filter(info):
                    role_info.append(info)

        return tuple(role_info)

    #
    #   IRoleAssignerPlugin implementation
    #
    @security.private
    def doAssignRoleToPrincipal(self, principal_id, role):
        return self.assignRoleToPrincipal(role, principal_id)

    @security.private
    def doRemoveRoleFromPrincipal(self, principal_id, role):
        return self.removeRoleFromPrincipal(role, principal_id)

    #
    #   Role management API
    #
    @security.protected(ManageUsers)
    def listRoleIds(self):
        """ Return a list of the role IDs managed by this object.
        """
        return self._roles.keys()

    @security.protected(ManageUsers)
    def listRoleInfo(self):
        """ Return a list of the role mappings.
        """
        return self._roles.values()

    @security.protected(ManageUsers)
    def getRoleInfo(self, role_id):
        """ Return a role mapping.
        """
        return self._roles[role_id]

    @security.private
    def addRole(self, role_id, title='', description=''):
        """ Add 'role_id' to the list of roles managed by this object.

        o Raise KeyError on duplicate.
        """
        if self._roles.get(role_id) is not None:
            raise KeyError('Duplicate role: %s' % role_id)

        self._roles[role_id] = {
            'id': role_id,
            'title': title,
            'description': description
        }

    @security.private
    def updateRole(self, role_id, title, description):
        """ Update title and description for the role.

        o Raise KeyError if not found.
        """
        self._roles[role_id].update({
            'title': title,
            'description': description
        })

    @security.private
    def removeRole(self, role_id, REQUEST=None):
        """ Remove 'role_id' from the list of roles managed by this object.

        o Raise KeyError if not found.

        Note that if you really want to remove a role you should first
        remove it from the roles in the root of the site (at the
        bottom of the Security tab at manage_access).
        """
        for principal_id in self._principal_roles.keys():
            self.removeRoleFromPrincipal(role_id, principal_id)

        del self._roles[role_id]

    #
    #   Role assignment API
    #
    @security.protected(ManageUsers)
    def listAvailablePrincipals(self, role_id, search_id):
        """ Return a list of principal IDs to whom a role can be assigned.

        o If supplied, 'search_id' constrains the principal IDs;  if not,
          return empty list.

        o Omit principals with existing assignments.
        """
        result = []

        if search_id:  # don't bother searching if no criteria

            parent = aq_parent(self)

            for info in parent.searchPrincipals(max_results=20,
                                                sort_by='id',
                                                id=search_id,
                                                exact_match=False):
                id = info['id']
                title = info.get('title', id)
                if role_id not in self._principal_roles.get(id, ()) and \
                        role_id != id:
                    result.append((id, title))

        return result

    @security.protected(ManageUsers)
    def listAssignedPrincipals(self, role_id):
        """ Return a list of principal IDs to whom a role is assigned.
        """
        result = []

        for k, v in self._principal_roles.items():
            if role_id in v:
                # should be at most one and only one mapping to 'k'

                parent = aq_parent(self)
                info = parent.searchPrincipals(id=k, exact_match=True)

                if len(info) > 1:
                    message = ('Multiple groups or users exist with the '
                               'name "%s". Remove one of the duplicate groups '
                               'or users.' % (k))
                    LOG.error(message)
                    raise MultiplePrincipalError(message)

                if len(info) == 0:
                    title = '<%s: not found>' % k
                else:
                    title = info[0].get('title', k)
                result.append((k, title))

        return result

    @security.private
    def assignRoleToPrincipal(self, role_id, principal_id):
        """ Assign a role to a principal (user or group).

        o Return a boolean indicating whether a new assignment was created.

        o Raise KeyError if 'role_id' is unknown.
        """
        # raise KeyError if unknown!
        role_info = self._roles[role_id]  # noqa

        current = self._principal_roles.get(principal_id, ())
        already = role_id in current

        if not already:
            new = current + (role_id, )
            self._principal_roles[principal_id] = new
            self._invalidatePrincipalCache(principal_id)

        return not already

    @security.private
    def removeRoleFromPrincipal(self, role_id, principal_id):
        """ Remove a role from a principal (user or group).

        o Return a boolean indicating whether the role was already present.

        o Raise KeyError if 'role_id' is unknown.

        o Ignore requests to remove a role not already assigned to the
          principal.
        """
        # raise KeyError if unknown!
        role_info = self._roles[role_id]  # noqa

        current = self._principal_roles.get(principal_id, ())
        new = tuple([x for x in current if x != role_id])
        already = current != new

        if already:
            self._principal_roles[principal_id] = new
            self._invalidatePrincipalCache(principal_id)

        return already

    #
    #   ZMI
    #
    manage_options = (({
        'label': 'Roles',
        'action': 'manage_roles'
    }, ) + BasePlugin.manage_options)

    security.declareProtected(ManageUsers, 'manage_roles')  # NOQA: D001
    manage_roles = PageTemplateFile('www/zrRoles',
                                    globals(),
                                    __name__='manage_roles')

    security.declareProtected(ManageUsers, 'manage_twoLists')  # NOQA: D001
    manage_twoLists = PageTemplateFile('../www/two_lists',
                                       globals(),
                                       __name__='manage_twoLists')

    @security.protected(ManageUsers)
    @csrf_only
    @postonly
    def manage_addRole(self,
                       role_id,
                       title,
                       description,
                       RESPONSE=None,
                       REQUEST=None):
        """ Add a role via the ZMI.
        """
        if not role_id:
            message = 'Please+provide+a+Role+ID'
        else:
            self.addRole(role_id, title, description)
            message = 'Role+added'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_roles?manage_tabs_message=%s' %
                              (self.absolute_url(), message))

    @security.protected(ManageUsers)
    @csrf_only
    @postonly
    def manage_updateRole(self,
                          role_id,
                          title,
                          description,
                          RESPONSE=None,
                          REQUEST=None):
        """ Update a role via the ZMI.
        """
        self.updateRole(role_id, title, description)

        message = 'Role+updated'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_roles?role_id=%s&'
                              'manage_tabs_message=%s' %
                              (self.absolute_url(), role_id, message))

    @security.protected(ManageUsers)
    @csrf_only
    @postonly
    def manage_removeRoles(self, role_ids, RESPONSE=None, REQUEST=None):
        """ Remove one or more role assignments via the ZMI.

        Note that if you really want to remove a role you should first
        remove it from the roles in the root of the site (at the
        bottom of the Security tab at manage_access).
        """
        role_ids = [_f for _f in role_ids if _f]

        if not role_ids:
            message = 'no+roles+selected'

        else:

            for role_id in role_ids:
                self.removeRole(role_id)

            message = 'Role+assignments+removed'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_roles?manage_tabs_message=%s' %
                              (self.absolute_url(), message))

    @security.protected(ManageUsers)
    @csrf_only
    @postonly
    def manage_assignRoleToPrincipals(self,
                                      role_id,
                                      principal_ids,
                                      RESPONSE,
                                      REQUEST=None):
        """ Assign a role to one or more principals via the ZMI.
        """
        assigned = []

        for principal_id in principal_ids:
            if self.assignRoleToPrincipal(role_id, principal_id):
                assigned.append(principal_id)

        if not assigned:
            message = 'Role+%s+already+assigned+to+all+principals' % role_id
        else:
            message = 'Role+%s+assigned+to+%s' % (role_id, '+'.join(assigned))

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_roles?role_id=%s&assign=1'
                              '&manage_tabs_message=%s' %
                              (self.absolute_url(), role_id, message))

    @security.protected(ManageUsers)
    @csrf_only
    @postonly
    def manage_removeRoleFromPrincipals(self,
                                        role_id,
                                        principal_ids,
                                        RESPONSE=None,
                                        REQUEST=None):
        """ Remove a role from one or more principals via the ZMI.
        """
        removed = []

        for principal_id in principal_ids:
            if self.removeRoleFromPrincipal(role_id, principal_id):
                removed.append(principal_id)

        if not removed:
            message = 'Role+%s+alread+removed+from+all+principals' % role_id
        else:
            message = 'Role+%s+removed+from+%s' % (role_id, '+'.join(removed))

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_roles?role_id=%s&assign=1'
                              '&manage_tabs_message=%s' %
                              (self.absolute_url(), role_id, message))
Example #22
0
class ZODBUserManager(BasePlugin, Cacheable):
    """ PAS plugin for managing users in the ZODB.
    """

    meta_type = 'ZODB User Manager'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title

        self._user_passwords = OOBTree()
        self._login_to_userid = OOBTree()
        self._userid_to_login = OOBTree()

    #
    #   IAuthenticationPlugin implementation
    #
    security.declarePrivate('authenticateCredentials')

    def authenticateCredentials(self, credentials):
        """ See IAuthenticationPlugin.

        o We expect the credentials to be those returned by
          ILoginPasswordExtractionPlugin.
        """
        login = credentials.get('login')
        password = credentials.get('password')

        if login is None or password is None:
            return None

        userid = self._login_to_userid.get(login, login)

        reference = self._user_passwords.get(userid)

        if reference is None:
            return None

        if AuthEncoding.is_encrypted(reference):
            if AuthEncoding.pw_validate(reference, password):
                return userid, login

        # Support previous naive behavior
        digested = sha.sha(password).hexdigest()

        if reference == digested:
            return userid, login

        return None

    #
    #   IUserEnumerationPlugin implementation
    #
    security.declarePrivate('enumerateUsers')

    def enumerateUsers(self,
                       id=None,
                       login=None,
                       exact_match=False,
                       sort_by=None,
                       max_results=None,
                       **kw):
        """ See IUserEnumerationPlugin.
        """
        user_info = []
        user_ids = []
        plugin_id = self.getId()
        view_name = createViewName('enumerateUsers', id or login)

        if isinstance(id, basestring):
            id = [id]

        if isinstance(login, basestring):
            login = [login]

        # Look in the cache first...
        keywords = copy.deepcopy(kw)
        keywords.update({
            'id': id,
            'login': login,
            'exact_match': exact_match,
            'sort_by': sort_by,
            'max_results': max_results
        })
        cached_info = self.ZCacheable_get(view_name=view_name,
                                          keywords=keywords,
                                          default=None)
        if cached_info is not None:
            return tuple(cached_info)

        terms = id or login

        if exact_match:
            if terms:

                if id:
                    # if we're doing an exact match based on id, it
                    # absolutely will have been qualified (if we have a
                    # prefix), so we can ignore any that don't begin with
                    # our prefix
                    id = [x for x in id if x.startswith(self.prefix)]
                    user_ids.extend([x[len(self.prefix):] for x in id])
                elif login:
                    user_ids.extend(
                        [self._login_to_userid.get(x) for x in login])

                # we're claiming an exact match search, if we still don't
                # have anything, better bail.
                if not user_ids:
                    return ()
            else:
                # insane - exact match with neither login nor id
                return ()

        if user_ids:
            user_filter = None

        else:  # Searching
            user_ids = self.listUserIds()
            user_filter = _ZODBUserFilter(id, login, **kw)

        for user_id in user_ids:

            if self._userid_to_login.get(user_id):
                e_url = '%s/manage_users' % self.getId()
                qs = 'user_id=%s' % user_id

                info = {
                    'id': self.prefix + user_id,
                    'login': self._userid_to_login[user_id],
                    'pluginid': plugin_id,
                    'editurl': '%s?%s' % (e_url, qs)
                }

                if not user_filter or user_filter(info):
                    user_info.append(info)

        # Put the computed value into the cache
        self.ZCacheable_set(user_info, view_name=view_name, keywords=keywords)

        return tuple(user_info)

    #
    #   IUserAdderPlugin implementation
    #
    security.declarePrivate('doAddUser')

    def doAddUser(self, login, password):
        try:
            self.addUser(login, login, password)
        except KeyError:
            return False
        return True

    #
    #   (notional)IZODBUserManager interface
    #
    security.declareProtected(ManageUsers, 'listUserIds')

    def listUserIds(self):
        """ -> ( user_id_1, ... user_id_n )
        """
        return self._user_passwords.keys()

    security.declareProtected(ManageUsers, 'getUserInfo')

    def getUserInfo(self, user_id):
        """ user_id -> {}
        """
        return {
            'user_id': user_id,
            'login_name': self._userid_to_login[user_id],
            'pluginid': self.getId()
        }

    security.declareProtected(ManageUsers, 'listUserInfo')

    def listUserInfo(self):
        """ -> ( {}, ...{} )

        o Return one mapping per user, with the following keys:

          - 'user_id' 
          - 'login_name'
        """
        return [self.getUserInfo(x) for x in self._user_passwords.keys()]

    security.declareProtected(ManageUsers, 'getUserIdForLogin')

    def getUserIdForLogin(self, login_name):
        """ login_name -> user_id

        o Raise KeyError if no user exists for the login name.
        """
        return self._login_to_userid[login_name]

    security.declareProtected(ManageUsers, 'getLoginForUserId')

    def getLoginForUserId(self, user_id):
        """ user_id -> login_name

        o Raise KeyError if no user exists for that ID.
        """
        return self._userid_to_login[user_id]

    security.declarePrivate('addUser')

    def addUser(self, user_id, login_name, password):

        if self._user_passwords.get(user_id) is not None:
            raise KeyError, 'Duplicate user ID: %s' % user_id

        if self._login_to_userid.get(login_name) is not None:
            raise KeyError, 'Duplicate login name: %s' % login_name

        self._user_passwords[user_id] = self._pw_encrypt(password)
        self._login_to_userid[login_name] = user_id
        self._userid_to_login[user_id] = login_name

        # enumerateUsers return value has changed
        view_name = createViewName('enumerateUsers')
        self.ZCacheable_invalidate(view_name=view_name)

    security.declarePrivate('updateUser')

    def updateUser(self, user_id, login_name):

        # The following raises a KeyError if the user_id is invalid
        old_login = self.getLoginForUserId(user_id)

        if old_login != login_name:

            if self._login_to_userid.get(login_name) is not None:
                raise ValueError('Login name not available: %s' % login_name)

            del self._login_to_userid[old_login]
            self._login_to_userid[login_name] = user_id
            self._userid_to_login[user_id] = login_name

    security.declarePrivate('removeUser')

    def removeUser(self, user_id):

        if self._user_passwords.get(user_id) is None:
            raise KeyError, 'Invalid user ID: %s' % user_id

        login_name = self._userid_to_login[user_id]

        del self._user_passwords[user_id]
        del self._login_to_userid[login_name]
        del self._userid_to_login[user_id]

        # Also, remove from the cache
        view_name = createViewName('enumerateUsers')
        self.ZCacheable_invalidate(view_name=view_name)
        view_name = createViewName('enumerateUsers', user_id)
        self.ZCacheable_invalidate(view_name=view_name)

    security.declarePrivate('updateUserPassword')

    def updateUserPassword(self, user_id, password):

        if self._user_passwords.get(user_id) is None:
            raise KeyError, 'Invalid user ID: %s' % user_id

        if password:
            self._user_passwords[user_id] = self._pw_encrypt(password)

    security.declarePrivate('_pw_encrypt')

    def _pw_encrypt(self, password):
        """Returns the AuthEncoding encrypted password

        If 'password' is already encrypted, it is returned
        as is and not encrypted again.
        """
        if AuthEncoding.is_encrypted(password):
            return password
        return AuthEncoding.pw_encrypt(password)

    #
    #   ZMI
    #
    manage_options = (({
        'label': 'Users',
        'action': 'manage_users',
    }, ) + BasePlugin.manage_options + Cacheable.manage_options)

    security.declarePublic('manage_widgets')
    manage_widgets = PageTemplateFile('www/zuWidgets',
                                      globals(),
                                      __name__='manage_widgets')

    security.declareProtected(ManageUsers, 'manage_users')
    manage_users = PageTemplateFile('www/zuUsers',
                                    globals(),
                                    __name__='manage_users')

    security.declareProtected(ManageUsers, 'manage_addUser')

    def manage_addUser(self,
                       user_id,
                       login_name,
                       password,
                       confirm,
                       RESPONSE=None):
        """ Add a user via the ZMI.
        """
        if password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:

            if not login_name:
                login_name = user_id

            # XXX:  validate 'user_id', 'login_name' against policies?

            self.addUser(user_id, login_name, password)

            message = 'User+added'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' %
                              (self.absolute_url(), message))

    security.declareProtected(ManageUsers, 'manage_updateUserPassword')

    def manage_updateUserPassword(self,
                                  user_id,
                                  password,
                                  confirm,
                                  RESPONSE=None,
                                  REQUEST=None):
        """ Update a user's login name / password via the ZMI.
        """
        if password and password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:

            self.updateUserPassword(user_id, password)

            message = 'password+updated'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' %
                              (self.absolute_url(), message))

    manage_updateUserPassword = postonly(manage_updateUserPassword)

    security.declareProtected(ManageUsers, 'manage_updateUser')

    def manage_updateUser(self, user_id, login_name, RESPONSE=None):
        """ Update a user's login name via the ZMI.
        """
        if not login_name:
            login_name = user_id

        # XXX:  validate 'user_id', 'login_name' against policies?

        self.updateUser(user_id, login_name)

        message = 'Login+name+updated'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' %
                              (self.absolute_url(), message))

    security.declareProtected(ManageUsers, 'manage_removeUsers')

    def manage_removeUsers(self, user_ids, RESPONSE=None, REQUEST=None):
        """ Remove one or more users via the ZMI.
        """
        user_ids = filter(None, user_ids)

        if not user_ids:
            message = 'no+users+selected'

        else:

            for user_id in user_ids:
                self.removeUser(user_id)

            message = 'Users+removed'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' %
                              (self.absolute_url(), message))

    manage_removeUsers = postonly(manage_removeUsers)

    #
    #   Allow users to change their own login name and password.
    #
    security.declareProtected(SetOwnPassword, 'getOwnUserInfo')

    def getOwnUserInfo(self):
        """ Return current user's info.
        """
        user_id = getSecurityManager().getUser().getId()

        return self.getUserInfo(user_id)

    security.declareProtected(SetOwnPassword, 'manage_updatePasswordForm')
    manage_updatePasswordForm = PageTemplateFile(
        'www/zuPasswd', globals(), __name__='manage_updatePasswordForm')

    security.declareProtected(SetOwnPassword, 'manage_updatePassword')

    def manage_updatePassword(self,
                              login_name,
                              password,
                              confirm,
                              RESPONSE=None,
                              REQUEST=None):
        """ Update the current user's password and login name.
        """
        user_id = getSecurityManager().getUser().getId()
        if password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:

            if not login_name:
                login_name = user_id

            # XXX:  validate 'user_id', 'login_name' against policies?
            self.updateUser(user_id, login_name)
            self.updateUserPassword(user_id, password)

            message = 'password+updated'

        if RESPONSE is not None:
            RESPONSE.redirect('%s/manage_updatePasswordForm'
                              '?manage_tabs_message=%s' %
                              (self.absolute_url(), message))

    manage_updatePassword = postonly(manage_updatePassword)
class BaseQuestion(ATCTContent):
    """Base class for survey questions"""
    immediate_view = "base_edit"
    global_allow = 0
    filter_content_types = 1
    allowed_content_types = ()
    include_default_actions = 1
    _at_rename_after_creation = True
    security = ClassSecurityInfo()

    @security.protected(ModifyPortalContent)
    def reset(self):
        """Remove answers for all users."""
        self.answers = OOBTree()

    @security.protected(ModifyPortalContent)
    def resetForUser(self, userid):
        """Remove answer for a single user"""
        if userid in self.answers:
            del self.answers[userid]

    @security.protected(View)
    def addAnswer(self, value, comments=""):
        """Add an answer and optional comments for a user.
        This method protects _addAnswer from anonymous users specifying a
        userid when they vote, and thus apparently voting as another user
        of their choice.
        """
        # Get hold of the parent survey
        survey = None
        ob = self
        while survey is None:
            ob = aq_parent(aq_inner(ob))
            if ob.meta_type == 'Survey':
                survey = ob
            elif getattr(ob, '_isPortalRoot', False):
                raise Exception("Could not find a parent Survey.")
        portal_membership = getToolByName(self, 'portal_membership')
        is_anon = portal_membership.isAnonymousUser()
        if is_anon and not survey.getAllowAnonymous():
            raise Unauthorized(
                "This survey is not available to anonymous users."
            )
        userid = self.getSurveyId()
        if is_anon and userid not in survey.getRespondentsList():
            # anon is not added on survey view, so may need to be added
            survey.addRespondent(userid)
        # Call the real method for storing the answer for this user.
        return self._addAnswer(userid, value, comments)

    def _addAnswer(self, userid, value, comments=""):
        """Add an answer and optional comments for a user."""
        # We don't let users over-write answers that they've already made.
        # Their first answer must be explicitly 'reset' before another
        # answer can be supplied.
        # XXX this causes problem when survey fails validation
        # will also cause problem with save function
#        if self.answers.has_key(userid):
#            # XXX Should this get raised?  If so, a more appropriate
#            # exception is probably in order.
#            msg = "User '%s' has already answered this question.
#                   Reset the original response to supply a new answer."
#            raise Exception(msg % userid)
#        else:
        self.answers[userid] = PersistentMapping(value=value,
                                                 comments=comments)
        if not isinstance(self.answers, (PersistentMapping, OOBTree)):
            # It must be a standard dictionary from an old install, so
            # we need to inform the ZODB about the change manually.
            self.answers._p_changed = 1

    @security.protected(View)
    def getAnswerFor(self, userid):
        """Get a specific user's answer"""
        answer = self.answers.get(userid, {}).get('value', None)
        if self.getInputType() in ['multipleSelect', 'checkbox']:
            if type(answer) == 'NoneType':
                return []
        if self.getInputType() in ['radio', 'selectionBox']:
            if not answer:
                return ""
            if isinstance(answer, unicode):
                answer = answer.encode('utf8')
            return str(answer)
        return answer

    @security.protected(View)
    def getCommentsFor(self, userid):
        """Get a specific user's comments"""
        return self.answers.get(userid, {}).get('comments', None)

    @security.protected(View)
    def getComments(self):
        """Return a userid, comments mapping"""
        mlist = []
        for k, v in self.answers.items():
            mapping = {}
            mapping['userid'] = k
            mapping['comments'] = v.get('comments', '')
            mlist.append(mapping)
        return mlist

    @security.protected(View)
    def getNumberOfRespondents(self):
        return len(self.answers.keys())

    @security.private
    def _get_yes_no_default(self):
        translation_service = getToolByName(self, 'translation_service')
        return (translation_service.utranslate(domain='plonesurvey',
                                               msgid=u'Yes',
                                               context=self),
                translation_service.utranslate(domain='plonesurvey',
                                               msgid=u'No',
                                               context=self), )

    @security.private
    def _get_commentLabel_default(self):
        translation_service = getToolByName(self, 'translation_service')
        return translation_service.utranslate(
            domain='plonesurvey',
            msgid=u'commentLabelDefault',
            default=u'Comment - mandatory if "no"', context=self)
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()
Example #25
0
class Survey(ATCTOrderedFolder):
    """You can add questions to surveys"""
    schema = SurveySchema
    _at_rename_after_creation = True

    implements(ISurvey)

    security = ClassSecurityInfo()

    security.declareProtected(permissions.ModifyPortalContent, 'reset')

    def reset(self):
        """Remove all respondents."""
        self.respondents = OOBTree()

    def _checkId(self, id, allow_dup=0):
        """Bypass the root object check for the local acl_users"""
        try:
            return super(Survey, self)._checkId(id, allow_dup=0)
        except BadRequest:
            if id != 'acl_users':
                raise

    security.declarePrivate('createLocalPas')

    def createLocalPas(self):
        """Create PAS acl_users else login_form does not work"""
        # need Manager role to add an acl_users object
        remove_role = False
        if not getSecurityManager().checkPermission(permissions.ManagePortal,
                                                    self):
            portal_membership = getToolByName(self, 'portal_membership')
            current_user = portal_membership.getAuthenticatedMember()
            current_userid = current_user.getId()
            self.manage_addLocalRoles(userid=current_userid,
                                      roles=[
                                          'Manager',
                                      ])
            remove_role = True
        # Re-use code in PlonePAS install
        addPluggableAuthService(self)
        out = StringIO()
        try:
            challenge_chooser_setup(self)
        except TypeError:
            # BBB needed for Plone 3.3.5
            challenge_chooser_setup(self, out)
        registerPluginTypes(self.acl_users)
        try:
            setupPlugins(self)
        except TypeError:
            # BBB needed for Plone 3.3.5
            setupPlugins(self, out)

        # Recreate mutable_properties but specify fields
        uf = self.acl_users
        uf.manage_addProduct['PluggableAuthService']
        plone_pas = uf.manage_addProduct['PlonePAS']
        plone_pas.manage_delObjects('mutable_properties')
        plone_pas.manage_addZODBMutablePropertyProvider('mutable_properties',
                                                        fullname='',
                                                        key='',
                                                        email_sent='')
        activatePluginInterfaces(self, 'mutable_properties', out)
        if remove_role:
            self.manage_delLocalRoles(userids=[
                current_userid,
            ])

    security.declarePublic('canSetDefaultPage')

    def canSetDefaultPage(self):
        """Doesn't make sense for surveys to allow alternate views"""
        return False

    security.declarePublic('canConstrainTypes')

    def canConstrainTypes(self):
        """Should not be able to add non survey types"""
        return False

    security.declareProtected(permissions.View, 'isMultipage')

    def isMultipage(self):
        """Return true if there is more than one page in the survey"""
        if self.getFolderContents(contentFilter={'portal_type': 'Sub Survey'}):
            return True

    security.declareProtected(permissions.View, 'getQuestions')

    def getQuestions(self):
        """Return the questions for this part of the survey"""
        questions = self.getFolderContents(contentFilter={
            'portal_type': [
                'Survey Date Question',
                'Survey Matrix',
                'Survey Select Question',
                'Survey Text Question',
            ]
        },
                                           full_objects=True)
        return questions

    security.declareProtected(permissions.View, 'getAllQuestions')

    def getAllQuestions(self):
        """Return all the questions in the survey"""
        portal_catalog = getToolByName(self, 'portal_catalog')
        questions = []
        path = string.join(self.getPhysicalPath(), '/')
        results = portal_catalog.searchResults(portal_type=[
            'Survey Date Question', 'Survey Matrix Question',
            'Survey Select Question', 'Survey Text Question'
        ],
                                               path=path,
                                               order='getObjPositionInParent')
        for result in results:
            questions.append(result.getObject())
        return questions

    security.declareProtected(permissions.View, 'getAllQuestionsInOrder')

    def getAllQuestionsInOrder(self, include_sub_survey=False):
        """Return all the questions in the survey"""
        questions = []
        objects = self.getFolderContents(contentFilter={
            'portal_type': [
                'Sub Survey',
                'Survey Date Question',
                'Survey Matrix',
                'Survey Select Question',
                'Survey Text Question',
            ]
        },
                                         full_objects=True)
        for object in objects:
            if object.portal_type == 'Sub Survey':
                if include_sub_survey:
                    questions.append(object)
                sub_survey_objects = object.getFolderContents(
                    contentFilter={
                        'portal_type': [
                            'Survey Matrix',
                            'Survey Date Question',
                            'Survey Select Question',
                            'Survey Text Question',
                        ]
                    },
                    full_objects=True)
                for sub_survey_object in sub_survey_objects:
                    questions.append(sub_survey_object)
                    if sub_survey_object.portal_type == 'Survey Matrix':
                        survey_matrix_objects = sub_survey_object.getFolderContents(
                            contentFilter={
                                'portal_type': 'Survey Matrix Question'
                            },
                            full_objects=True)
                        for survey_matrix_object in survey_matrix_objects:
                            questions.append(survey_matrix_object)
            elif object.portal_type == 'Survey Matrix':
                questions.append(object)
                survey_matrix_objects = object.getFolderContents(
                    contentFilter={'portal_type': 'Survey Matrix Question'},
                    full_objects=True)
                for survey_matrix_object in survey_matrix_objects:
                    questions.append(survey_matrix_object)
                # XXX should check if comment is present
            else:
                questions.append(object)
        return questions

    security.declareProtected(permissions.View, 'getAllSelectQuestionsInOrder')

    def getAllSelectQuestionsInOrder(self):
        """Return all the vocab driven questions in the survey"""
        questions = []
        objects = self.getFolderContents(contentFilter={
            'portal_type': [
                'Sub Survey',
                'Survey Matrix',
                'Survey Select Question',
            ]
        },
                                         full_objects=True)
        for object in objects:
            if object.portal_type == 'Sub Survey':
                sub_survey_objects = object.getFolderContents(
                    contentFilter={
                        'portal_type': [
                            'Survey Matrix',
                            'Survey Select Question',
                        ]
                    },
                    full_objects=True)
                for sub_survey_object in sub_survey_objects:
                    if sub_survey_object.portal_type == 'Survey Matrix':
                        survey_matrix_objects = sub_survey_object.getFolderContents(
                            contentFilter={
                                'portal_type': 'Survey Matrix Question'
                            },
                            full_objects=True)
                        for survey_matrix_object in survey_matrix_objects:
                            questions.append(survey_matrix_object)
                    else:
                        questions.append(sub_survey_object)
            elif object.portal_type == 'Survey Matrix':
                survey_matrix_objects = object.getFolderContents(
                    contentFilter={'portal_type': 'Survey Matrix Question'},
                    full_objects=True)
                for survey_matrix_object in survey_matrix_objects:
                    questions.append(survey_matrix_object)
            else:
                questions.append(object)
        return questions

    security.declareProtected(permissions.View, 'hasDateQuestion')

    def hasDateQuestion(self):
        """Return true if there is a date question in this part of the survey
        to import the js"""
        objects = self.getFolderContents(
            contentFilter={'portal_type': 'Survey Date Question'})
        if objects:
            return True
        return False

    security.declareProtected(permissions.View, 'getNextPage')

    def getNextPage(self):
        """Return the next page of the survey"""
        pages = self.getFolderContents(contentFilter={
            'portal_type': 'Sub Survey',
        },
                                       full_objects=True)
        for page in pages:
            if page.displaySubSurvey():
                return page()
        return self.exitSurvey()

    security.declareProtected(permissions.View, 'hasMorePages')

    def hasMorePages(self):
        """Return True if survey has more pages to display"""
        pages = self.getFolderContents(contentFilter={
            'portal_type': 'Sub Survey',
        },
                                       full_objects=True)
        if not pages:
            return False
        return True

    security.declareProtected(permissions.View, 'exitSurvey')

    def exitSurvey(self):
        """Return the defined exit url"""
        self.setCompletedForUser()
        exit_url = self.getExitUrl()
        if exit_url[:4] != 'http':
            self.plone_utils.addPortalMessage(
                safe_unicode(self.getThankYouMessage()))
            exit_url = self.portal_url() + '/' + exit_url
        return self.REQUEST.RESPONSE.redirect(exit_url)

    security.declareProtected(permissions.View, 'saveSurvey')

    def saveSurvey(self):
        """Return the defined exit url"""
        exit_url = self.getExitUrl()
        if exit_url[:4] != 'http':
            self.plone_utils.addPortalMessage(
                safe_unicode(self.getSavedMessage()))
            exit_url = self.portal_url() + '/' + exit_url
        return self.REQUEST.RESPONSE.redirect(exit_url)

    security.declareProtected(permissions.View, 'setCompletedForUser')

    def setCompletedForUser(self):
        """Set completed for a user"""
        userid = self.getSurveyId()
        respondents = self.respondents
        respondents[userid]['end'] = DateTime()
        completed = self.getCompletedFor()
        completed.append(userid)
        self.setCompletedFor(completed)

        # Scramble respondent's password because we don't want him back
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(userid)
        if user is not None:
            portal_registration = getToolByName(self, 'portal_registration')
            pw = portal_registration.generatePassword()

            self.acl_users.userFolderEditUser(userid,
                                              pw,
                                              user.getRoles(),
                                              user.getDomains(),
                                              key=pw)

            # Set key
            props = acl_users.mutable_properties.getPropertiesForUser(user)
            props = BasicPropertySheet(props)
            props.setProperty('key', pw)
            acl_users.mutable_properties.setPropertiesForUser(user, props)

        if self.getSurveyNotificationMethod() == 'each_submission':
            self.send_email(userid)

    security.declareProtected(permissions.View, 'checkCompletedFor')

    def checkCompletedFor(self, user_id):
        """Check whether a user has completed the survey"""
        completed = self.getCompletedFor()
        if user_id in completed:
            return True
        return False

    security.declareProtected(permissions.View, 'getSurveyId')

    def getSurveyId(self):
        """Return the userid for the survey"""
        request = self.REQUEST
        # Check the request for saving questions on the first survey page
        try:
            user_id = request.form['survey_user_id']
        except KeyError:
            pass
        else:
            return user_id
        portal_membership = getToolByName(self, 'portal_membership')
        if not portal_membership.isAnonymousUser():
            user_id = portal_membership.getAuthenticatedMember().getId()
            self.addRespondent(user_id)
            return user_id
        response = request.RESPONSE
        survey_cookie = self.getId()
        if self.getAllowAnonymous() and survey_cookie in request:
            return request.get(survey_cookie, "Anonymous")
        user_id = self.getAnonymousId()
        # expires = (DateTime() + 365).toZone('GMT').rfc822()
        # cookie expires in 1 year (365 days)
        response.setCookie(survey_cookie, user_id, path='/')
        return user_id

    security.declareProtected(permissions.View, 'getAnonymousId')

    def getAnonymousId(self):
        """returns the id to use for an anonymous user"""
        portal_membership = getToolByName(self, 'portal_membership')
        if portal_membership.isAnonymousUser() and self.getAllowAnonymous():
            anon_id = 'Anonymous@'
            if not self.getConfidential():
                remote_ip = self.getRemoteIp()
                if remote_ip:
                    anon_id = anon_id + remote_ip + '@'
            return anon_id + str(DateTime())
        elif portal_membership.isAnonymousUser():
            return self.REQUEST.RESPONSE.redirect(self.portal_url() +
                                                  '/login_form?came_from=' +
                                                  self.absolute_url())
        return portal_membership.getAuthenticatedMember().getId()

    security.declareProtected(permissions.View, 'getRemoteIp')

    def getRemoteIp(self, request=None):
        """returns the ip address of the survey respondent"""
        if self.getConfidential():
            return
        if request is None:
            request = getattr(self, 'REQUEST', None)
        return request.getClientAddr()

    security.declareProtected(permissions.ModifyPortalContent,
                              'getRespondentsDetails')

    def getRespondentsDetails(self):
        """Return a list of respondents details"""
        return self.respondents

    security.declareProtected(permissions.ModifyPortalContent,
                              'getRespondentsList')

    def getRespondentsList(self):
        """Return a list of respondents details"""
        users = {}
        for user in self.respondents.keys():
            users[user] = 1
        return users.keys()

    security.declareProtected(ViewSurveyResults, 'getRespondentDetails')

    def getRespondentDetails(self, respondent):
        """Return details of a respondent"""
        try:
            self.respondents
        except AttributeError:
            self.reset()
        try:
            details = self.respondents[respondent]
        except KeyError:
            # TODO try/except should be removed at some point
            # probably old survey, create respondent details
            self.respondents[respondent] = PersistentMapping(start='',
                                                             ip_address='',
                                                             end='')
        details_dict = {}
        details = self.respondents[respondent]
        for k in details.keys():
            details_dict[k] = details[k]
        if details['start'] and details['end']:
            details_dict['time_taken'] = DT2dt(details['end']) - DT2dt(
                details['start'])
        else:
            details_dict['time_taken'] = ''
        return details_dict

    security.declareProtected(permissions.ModifyPortalContent, 'addRespondent')

    def addRespondent(self, user_id):
        """Add a respondent to the survey"""
        # TODO needs moving to an event handler
        try:
            self.respondents
        except AttributeError:
            self.reset()
        if user_id in self.respondents:
            return
        self.respondents[user_id] = PersistentMapping(
            start=DateTime(), ip_address=self.getRemoteIp(), end='')

    security.declareProtected(ViewSurveyResults, 'getRespondentFullName')

    def getRespondentFullName(self, userid):
        """get user. used by results spreadsheets to show fullname"""
        portal_membership = getToolByName(self, 'portal_membership')
        member = portal_membership.getMemberById(userid)
        if member is None:
            return
        full_name = member.getProperty('fullname')
        if full_name:
            return full_name
        return member.id

    security.declareProtected(ViewSurveyResults, 'getAnswersByUser')

    def getAnswersByUser(self, userid):
        """Return a set of answers by user id"""
        questions = self.getAllQuestionsInOrder()
        answers = {}
        for question in questions:
            answer = question.getAnswerFor(userid)
            answers[question.getId()] = answer
        return answers

    security.declareProtected(permissions.View, 'getQuestionsCount')

    def getQuestionsCount(self):
        """Return a count of questions asked"""
        # XXX is this used anywhere?
        return len(self.questions)

    security.declareProtected(permissions.View, 'getSurveyColors')

    def getSurveyColors(self, num_options):
        """Return the colors for the barchart"""
        colors = BARCHART_COLORS
        num_colors = len(colors)
        while num_colors < num_options:
            colors = colors + colors
            num_colors = len(colors)
        return colors

    security.declareProtected(ResetOwnResponses, 'resetForAuthenticatedUser')

    def resetForAuthenticatedUser(self):
        mtool = getToolByName(self, 'portal_membership')
        member = mtool.getAuthenticatedMember()
        user_id = member.getMemberId()
        return self._resetForUser(user_id)

    security.declareProtected(permissions.ModifyPortalContent, 'resetForUser')

    def resetForUser(self, userid):
        """Remove answer for a single user"""
        self._resetForUser(userid)

    def _resetForUser(self, userid):
        """Remove answer for a single user"""
        completed = self.getCompletedFor()
        if userid in completed:
            completed.remove(userid)
        self.setCompletedFor(completed)
        questions = self.getAllQuestionsInOrder()
        for question in questions:
            question.resetForUser(userid)
        try:
            if userid in self.respondents:
                del self.respondents[userid]
        except AttributeError:
            # TODO old survey instance
            self.reset()

    security.declareProtected(permissions.View, 'send_email')

    def send_email(self, userid):
        """ Send email to nominated address """
        properties = self.portal_properties.site_properties
        mTo = self.getSurveyNotificationEmail()
        mFrom = properties.email_from_address
        mSubj = translate(_('[${survey_title}] New survey submitted',
                            mapping={'survey_title': self.Title()}),
                          context=self.REQUEST)
        message = []
        message.append(
            translate(_('Survey ${survey_title}',
                        mapping={'survey_title': self.Title()}),
                      context=self.REQUEST))
        message.append(
            translate(_('has been completed by user: ${userid}',
                        mapping={'userid': userid}),
                      context=self.REQUEST))
        message.append(self.absolute_url() +
                       '/@@Products.PloneSurvey.survey_view_results')
        mMsg = '\n\n'.join(message)
        try:
            self.MailHost.send(mMsg.encode('utf-8'), mTo, mFrom, mSubj)
        except ConflictError:
            raise
        except:
            # XXX too many things can go wrong
            pass

    security.declarePublic('translateThankYouMessage')

    def translateThankYouMessage(self):
        """ """
        return self.translate(msgid="text_default_thank_you",
                              default="Thank you for completing the survey.",
                              domain="plonesurvey")

    security.declarePublic('translateSavedMessage')

    def translateSavedMessage(self):
        """ """
        return self.translate(msgid="text_default_saved_message",
                              default=u"You have saved the survey.\n "
                              u"Don't forget to come back and finish it.",
                              domain="plonesurvey")

    security.declareProtected(permissions.ModifyPortalContent,
                              'deleteAuthenticatedRespondent')

    def deleteAuthenticatedRespondent(self, email, REQUEST=None):
        """Delete authenticated respondent"""
        # xxx: delete answers by this user as well?
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(email)
        props = acl_users.mutable_properties.getPropertiesForUser(user)
        props = BasicPropertySheet(props)
        for prop in props.propertyItems():
            props.setProperty(prop[0], '')
        acl_users.mutable_properties.setPropertiesForUser(user, props)
        acl_users.userFolderDelUsers([email])
        if REQUEST is not None:
            pu = getToolByName(self, 'plone_utils')
            pu.addPortalMessage(safe_unicode("Respondent %s deleted" % email))
            REQUEST.RESPONSE.redirect(REQUEST.HTTP_REFERER)

    security.declareProtected(permissions.ModifyPortalContent,
                              'addAuthenticatedRespondent')

    def addAuthenticatedRespondent(self, emailaddress, **kw):
        acl_users = self.get_acl_users()
        portal_registration = getToolByName(self, 'portal_registration')
        if not emailaddress:
            return False
        # Create user
        password = portal_registration.generatePassword()
        acl_users.userFolderAddUser(emailaddress,
                                    password,
                                    roles=['Member'],
                                    domains=[],
                                    groups=())
        # Set user properties
        user = acl_users.getUserById(emailaddress)
        props = acl_users.mutable_properties.getPropertiesForUser(user)
        props = BasicPropertySheet(props)
        for k, v in kw.items():
            props.setProperty(k, v)
        props.setProperty('key', password)
        acl_users.mutable_properties.setPropertiesForUser(user, props)
        return True

    security.declareProtected(permissions.ModifyPortalContent,
                              'registerRespondentSent')

    def registerRespondentSent(self, email_address):
        """Mark the respondent as being sent an email"""
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(email_address)
        props = acl_users.mutable_properties.getPropertiesForUser(user)
        props = BasicPropertySheet(props)
        props.setProperty('email_sent', str(DateTime()))
        acl_users.mutable_properties.setPropertiesForUser(user, props)

    security.declareProtected(permissions.ModifyPortalContent,
                              'getAuthenticatedRespondent')

    def getAuthenticatedRespondent(self, emailaddress):
        """
        Return dictionary with respondent details.
        This method is needed because
        getProperty is hosed on the user object.
        """
        di = {'emailaddress': emailaddress, 'id': emailaddress}
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(emailaddress)
        props = user.getPropertysheet('mutable_properties')
        for k, v in props.propertyItems():
            di[k] = v
        return di

    security.declareProtected(permissions.ModifyPortalContent,
                              'getAuthenticatedRespondents')

    def getAuthenticatedRespondents(self):
        """Build up the list of users"""
        respondents = []
        users = self.get_acl_users().getUsers()
        for user in users:
            respondents.append(user.getId())
        return [
            self.getAuthenticatedRespondent(user_id) for user_id in respondents
        ]

    security.declareProtected(permissions.ModifyPortalContent,
                              'sendSurveyInvite')

    def sendSurveyInvite(self, email_address):
        """Send a survey Invite"""
        portal_properties = getToolByName(self, 'portal_properties')
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(email_address)
        user_details = self.getAuthenticatedRespondent(email_address)
        email_from_name = self.getInviteFromName()
        if not email_from_name:
            email_from_name = self.email_from_name
        email_from_address = self.getInviteFromEmail()
        if not email_from_address:
            email_from_address = self.email_from_address
        email_body = self.getEmailInvite()
        email_body = email_body.replace('**Name**', user_details['fullname'])
        survey_url = self.absolute_url() + '/login_form_bridge?email=' + \
            email_address + '&amp;key=' + urllib.quote(user_details['key'])
        email_body = email_body.replace(
            '**Survey**',
            '<a href="' + survey_url + '">' + self.Title() + '</a>')
        mail_text = self.survey_send_invite_template(
            user=user,
            recipient=user.getId(),
            email_from_name=email_from_name,
            email_from_address=email_from_address,
            email_body=email_body,
            subject="Survey %s" % self.title_or_id())
        host = self.MailHost
        site_props = portal_properties.site_properties
        mail_text = mail_text.encode(site_props.default_charset or 'utf-8')
        host.send(mail_text)
        self.registerRespondentSent(email_address)

    security.declareProtected(permissions.ModifyPortalContent,
                              'sendSurveyInviteAll')

    def sendSurveyInviteAll(self, send_to_all=False, use_transactions=False):
        """Send survey Invites to all respondents"""
        number_sent = 0
        if use_transactions:
            transaction.abort()
        respondents = self.acl_users.getUsers()
        already_completed = self.getRespondentsList()
        for respondent in respondents:
            if use_transactions:
                transaction.get()
            email_address = respondent.getId()
            respondent_details = self.getAuthenticatedRespondent(email_address)
            if email_address in already_completed:
                # don't send out an invite if already responded
                continue
            if not send_to_all:
                # don't send an email if one already sent
                if respondent_details['email_sent']:
                    continue
            self.sendSurveyInvite(email_address)
            number_sent += 1
        # return number of invites sent
        return number_sent

    def get_acl_users(self):
        """Fetch acl_users. Create if it does not yet exist."""
        if 'acl_users' not in self.objectIds():
            self.createLocalPas()
        return self.acl_users

    security.declareProtected(ViewSurveyResults, 'setCsvHeaders')

    def setCsvHeaders(self, filetype='csv'):
        """Set the CSV headers"""
        REQUEST = self.REQUEST
        file = self.buildSpreadsheetUrl(filetype=filetype)
        REQUEST.RESPONSE.setHeader(
            'Content-Type', 'text/x-comma-separated-values; charset=utf-8')
        REQUEST.RESPONSE.setHeader('Content-disposition',
                                   'attachment; filename=%s' % file)
        return REQUEST

    security.declareProtected(ViewSurveyResults, 'buildSpreadsheetUrl')

    def buildSpreadsheetUrl(self, filetype='csv'):
        """Create a filename for the spreadsheets"""
        date = DateTime().strftime("%Y-%m-%d")
        id = self.getId()
        id = "%s-%s" % (id, date)
        url = "%s.%s" % (id, filetype)
        return url

    security.declareProtected(ViewSurveyResults, 'spreadsheet2')

    def spreadsheet2(self):
        """Return spreadsheet 2"""
        self.setCsvHeaders()
        return self.buildSpreadsheet2()

    security.declareProtected(ViewSurveyResults, 'spreadsheet2_tab')

    def spreadsheet2_tab(self):
        """Return spreadsheet 2 tab"""
        self.setCsvHeaders(filetype='tsv')
        dialect = csv.excel_tab
        return self.buildSpreadsheet2(dialect)

    security.declareProtected(ViewSurveyResults, 'buildSpreadsheet2')

    def buildSpreadsheet2(self, dialect=csv.excel):
        """Build spreadsheet 2.
            excel_tab
            excel
        """
        data = StringIO()
        sheet = csv.writer(data, dialect=dialect, quoting=csv.QUOTE_ALL)
        questions = self.getAllQuestionsInOrder()

        sheet.writerow(('user', ) + tuple(q.Title() for q in questions) +
                       ('completed', ))

        for user in self.getRespondentsList():
            if self.getConfidential():
                row = ['Anonymous']
            else:
                row = [self.getRespondentFullName(user) or user]
            for question in questions:
                answer = question.getAnswerFor(user) or ''
                # handle there being no answer (e.g branched question)
                if answer:
                    if not (isinstance(answer, str) or \
                            isinstance(answer, unicode) or \
                            isinstance(answer, int)):
                        # It's a sequence, filter out empty values
                        answer = ', '.join(filter(None, answer))
                row.append(answer.replace('"', "'").replace('\r\n', ' '))

            row.append(
                self.checkCompletedFor(user) and 'Completed'
                or 'Not Completed')
            for i, col in enumerate(row):
                if isinstance(col, unicode):
                    col = col.encode('utf8')
                row[i] = col
            sheet.writerow(row)

        return data.getvalue()

    security.declareProtected(ViewSurveyResults, 'spreadsheet3')

    def spreadsheet3(self):
        """Return spreadsheet 3"""
        self.setCsvHeaders()
        return self.buildSpreadsheet3()

    security.declareProtected(ViewSurveyResults,
                              'get_all_questions_in_order_filtered')

    def get_all_questions_in_order_filtered(self,
                                            include_sub_survey=False,
                                            ignore_meta_types=[],
                                            ignore_input_types=[],
                                            restrict_meta_types=[]):
        """This is only used in buildSpreadsheet3, and should be moved into
        another method."""
        questions = self.getAllQuestionsInOrder(
            include_sub_survey=include_sub_survey)
        result = []
        for question in questions:
            ok = True
            if ignore_meta_types:
                if question.meta_type in ignore_meta_types:
                    ok = ok and False
            if ignore_input_types:
                if question.getInputType() in ignore_input_types:
                    ok = ok and False
            if restrict_meta_types:
                if question.meta_type not in restrict_meta_types:
                    ok = ok and False
            if ok:
                result.append(question)
        return result

    security.declareProtected(ViewSurveyResults, 'buildSpreadsheet3')

    def buildSpreadsheet3(self):
        """Build spreadsheet 3."""
        data = StringIO()
        sheet = csv.writer(data)
        questions = self.get_all_questions_in_order_filtered(
            ignore_meta_types=[
                'SurveyMatrix',
            ])
        sheet.writerow(('user', ) + tuple(q.Title() for q in questions) +
                       ('completed', ))
        for user in self.getRespondentsList():
            if self.getConfidential():
                row = ['Anonymous']
            else:
                row = [self.getRespondentFullName(user) or user]
            for question in questions:
                answer = ""
                if question.getInputType() in ['text', 'area']:
                    if question.getAnswerFor(user):
                        answer = '"' + question.getAnswerFor(user).replace(
                            '"', "'") + '"'
                    else:
                        answer = ""
                elif question.getInputType() in ['checkbox', 'multipleSelect']:
                    options = question.getQuestionOptions()
                    answerList = question.getAnswerFor(user)
                    if answerList and not isinstance(answerList, str):
                        if not isinstance(answerList, list):
                            answerList = [answerList]
                        for option in options:
                            if answerList.count(option) > 0:
                                answer += '1;'
                            else:
                                answer += '0;'
                        answer = '"' + answer[0:len(answer) - 1] + '"'
                    elif answerList:
                        answer = '"' + answerList + '"'
                    else:
                        answer = ''
                else:
                    if not hasattr(question, 'getQuestionOptions'):
                        options = question.title
                    else:
                        options = question.getQuestionOptions()
                    answerLabel = question.getAnswerFor(user)
                    answer = str(len(options))
                    i = 0
                    while i < len(options):
                        if options[i] == answerLabel:
                            answer = str(i)
                            break
                        i = i + 1
                row.append(answer)


#                if question.getCommentType():
#                line.append('"' + test(question.getCommentsFor(user), question.getCommentsFor(user).replace('"',"'"), "Blank") + '"')
            row.append(
                self.checkCompletedFor(user) and 'Completed'
                or 'Not Completed')
            sheet.writerow(row)
        return data.getvalue()

    security.declareProtected(ViewSurveyResults, 'summary_spreadsheet')

    def summary_spreadsheet(self):
        """Return summary spreadsheet"""
        self.setCsvHeaders()
        return self.buildSummarySpreadsheet()

    security.declareProtected(permissions.ModifyPortalContent,
                              'buildSummarySpreadsheet')

    def buildSummarySpreadsheet(self):
        """Build the summary spreadsheet."""
        data = StringIO()
        sheet = csv.writer(data)
        row = ['Question', 'Option', 'Number of Responses', 'Percentage']
        sheet.writerow(row)
        questions = self.getAllQuestionsInOrder()
        for question in questions:
            row = [question.Title(), '']
            row.append(question.getNumberOfRespondents())
            sheet.writerow(row)
            if question.portal_type in [
                    'Survey Select Question', 'Survey Matrix Question'
            ]:
                options = question.getQuestionOptions()
                number_options = question.getAggregateAnswers()
                percentage_options = question.getPercentageAnswers()
                for option in options:
                    row = ['', option]
                    row.append(number_options[option])
                    row.append(percentage_options[option])
                    sheet.writerow(row)
        return data.getvalue()

    security.declareProtected(ViewSurveyResults, 'spreadsheet_select')

    def spreadsheet_select(self):
        """Return spreadsheet select"""
        self.setCsvHeaders()
        try:
            self.REQUEST.form['answers']
        except KeyError:
            return self.buildSelectSpreadsheet()
        return self.buildSelectSpreadsheet(boolean=True)

    security.declareProtected(ViewSurveyResults, 'buildSelectSpreadsheet')

    def buildSelectSpreadsheet(self, boolean=False):
        """Build the select spreadsheet."""
        data = StringIO()
        sheet = csv.writer(data)
        questions = self.getAllSelectQuestionsInOrder()
        row = [
            'user',
        ]
        options_row = [
            '',
        ]
        for question in questions:
            options = question.getQuestionOptions()
            for i in range(len(options)):
                if i == 0:
                    row.append(question.Title())
                else:
                    row.append('')
                options_row.append(options[i])
        sheet.writerow(row)
        sheet.writerow(options_row)
        for user in self.getRespondentsList():
            if self.getConfidential():
                row = ['Anonymous']
            else:
                row = [self.getRespondentFullName(user) or user]
            for question in questions:
                options = question.getQuestionOptions()
                answer = question.getAnswerFor(user)
                for i in range(len(options)):
                    if answer is None:
                        if boolean:
                            row.append(0)
                        else:
                            row.append('')
                    else:
                        if type(answer) == int:
                            answer = str(answer)
                        if options[i] in answer:
                            if boolean:
                                row.append(1)
                            else:
                                row.append(options[i])
                        else:
                            if boolean:
                                row.append(0)
                            else:
                                row.append('')
            row.append(
                self.checkCompletedFor(user) and 'Completed'
                or 'Not Completed')
            sheet.writerow(row)
        return data.getvalue()

    # TODO next two methods are still needed for the tests,
    # but should be removed and the tests fixed
    security.declareProtected(permissions.ModifyPortalContent, 'openFile')

    def openFile(self):
        """open the file, and return the file contents"""
        data_path = os.path.abspath('import')
        try:
            data_catch = open(data_path + '/user_import', 'rU')
        except IOError:  # file does not exist, or path is wrong
            try:
                # we might be in foreground mode
                data_path = os.path.abspath('../import')
                data_catch = open(data_path + '/user_import', 'rU')
            except IOError:  # file does not exist, or path is wrong
                return 'File does not exist'
        input = data_catch.read()
        data_catch.close()
        return input

    security.declareProtected(permissions.ModifyPortalContent,
                              'uploadRespondents')

    def uploadRespondents(self, input=None):
        """upload the respondents"""
        if input is None:
            input = self.openFile()
        input = input.split('\n')
        errors = []
        for user in input:
            if not user:  # empty line
                continue
            user_details = user.split('|')
            if not self.addAuthenticatedRespondent(user_details[1],
                                                   fullname=user_details[0]):
                errors.append(user)
        return errors

    security.declareProtected(permissions.View, 'collective_recaptcha_enabled')

    def collective_recaptcha_enabled(self):
        if using_collective_recaptcha:
            try:
                settings = getRecaptchaSettings()
            except TypeError:
                # collective.recaptcha not configured
                return False
            if settings.public_key and settings.private_key:
                return True
        return False

    security.declareProtected(permissions.ModifyPortalContent, 'pre_validate')

    def pre_validate(self, REQUEST, errors):
        """ checks captcha """
        product_installed = self.portal_quickinstaller.isProductInstalled(
            'quintagroup.plonecaptchas')
        if not product_installed and not self.collective_recaptcha_enabled(
        ) and REQUEST.get('showCaptcha', 0):
            if int(REQUEST.get('showCaptcha')):
                errors['showCaptcha'] = _(
                    'showCaptcha',
                    default='Product quintagroup.plonecaptchas not installed. '
                    'If you prefer to use the product collective.recaptcha instead of quintagroup.plonecaptchas '
                    'then verifies that recaptcha private and public keys are configured. '
                    'Go to path/to/site/@@recaptcha-settings to configure.')

    security.declareProtected(permissions.View, 'isCaptchaInstalled')

    def isCaptchaInstalled(self):
        """ checks captcha """
        product_installed = self.portal_quickinstaller.isProductInstalled(
            'quintagroup.plonecaptchas')
        return product_installed

    security.declarePrivate('_get_emailInvite_default')

    def _get_emailInvite_default(self):
        foo = _('emailInviteDefault', default=DEFAULT_SURVEY_INVITE)
        translation_service = getToolByName(self, 'translation_service')
        return translation_service.utranslate(domain='plonesurvey',
                                              msgid='emailInviteDefault',
                                              default=DEFAULT_SURVEY_INVITE,
                                              context=self)
Example #26
0
class ZODBRoleManager(BasePlugin):
    """ PAS plugin for managing roles in the ZODB.
    """
    __implements__ = (IRolesPlugin, IRoleEnumerationPlugin,
                      IRoleAssignerPlugin)

    meta_type = 'ZODB Role Manager'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title

        self._roles = OOBTree()
        self._principal_roles = OOBTree()

    def manage_afterAdd(self, item, container):

        self.addRole('Manager')

        if item is self:
            role_holder = aq_parent(aq_inner(container))
            for role in getattr(role_holder, '__ac_roles__', ()):
                try:
                    self.addRole(role)
                except KeyError:
                    pass

    #
    #   IRolesPlugin implementation
    #
    security.declarePrivate('getRolesForPrincipal')

    def getRolesForPrincipal(self, principal, request=None):
        """ See IRolesPlugin.
        """
        return tuple(self._principal_roles.get(principal.getId(), ()))

    #
    #   IRoleEnumerationPlugin implementation
    #
    def enumerateRoles(self,
                       id=None,
                       exact_match=False,
                       sort_by=None,
                       max_results=None,
                       **kw):
        """ See IRoleEnumerationPlugin.
        """
        role_info = []
        role_ids = []
        plugin_id = self.getId()

        if isinstance(id, str):
            id = [id]

        if exact_match and (id):
            role_ids.extend(id)

        if role_ids:
            role_filter = None

        else:  # Searching
            role_ids = self.listRoleIds()
            role_filter = _ZODBRoleFilter(id, **kw)

        for role_id in role_ids:

            if self._roles.get(role_id):
                e_url = '%s/manage_roles' % self.getId()
                p_qs = 'role_id=%s' % role_id
                m_qs = 'role_id=%s&assign=1' % role_id

                info = {}
                info.update(self._roles[role_id])

                info['pluginid'] = plugin_id
                info['properties_url'] = '%s?%s' % (e_url, p_qs)
                info['members_url'] = '%s?%s' % (e_url, m_qs)

                if not role_filter or role_filter(info):
                    role_info.append(info)

        return tuple(role_info)

    #
    #   IRoleAssignerPlugin implementation
    #
    security.declarePrivate('doAssignRoleToPrincipal')

    def doAssignRoleToPrincipal(self, principal_id, role):
        return self.assignRoleToPrincipal(role, principal_id)

    #
    #   Role management API
    #
    security.declareProtected(ManageUsers, 'listRoleIds')

    def listRoleIds(self):
        """ Return a list of the role IDs managed by this object.
        """
        return self._roles.keys()

    security.declareProtected(ManageUsers, 'listRoleInfo')

    def listRoleInfo(self):
        """ Return a list of the role mappings.
        """
        return self._roles.values()

    security.declareProtected(ManageUsers, 'getRoleInfo')

    def getRoleInfo(self, role_id):
        """ Return a role mapping.
        """
        return self._roles[role_id]

    security.declareProtected(ManageUsers, 'addRole')

    def addRole(self, role_id, title='', description=''):
        """ Add 'role_id' to the list of roles managed by this object.

        o Raise KeyError on duplicate.
        """
        if self._roles.get(role_id) is not None:
            raise KeyError, 'Duplicate role: %s' % role_id

        self._roles[role_id] = {
            'id': role_id,
            'title': title,
            'description': description
        }

    security.declareProtected(ManageUsers, 'updateRole')

    def updateRole(self, role_id, title, description):
        """ Update title and description for the role.

        o Raise KeyError if not found.
        """
        self._roles[role_id].update({
            'title': title,
            'description': description
        })

    security.declareProtected(ManageUsers, 'removeRole')

    def removeRole(self, role_id):
        """ Remove 'role_id' from the list of roles managed by this object.

        o Raise KeyError if not found.
        """
        for principal_id in self._principal_roles.keys():
            self.removeRoleFromPrincipal(role_id, principal_id)

        del self._roles[role_id]

    #
    #   Role assignment API
    #
    security.declareProtected(ManageUsers, 'listAvailablePrincipals')

    def listAvailablePrincipals(self, role_id, search_id):
        """ Return a list of principal IDs to whom a role can be assigned.

        o If supplied, 'search_id' constrains the principal IDs;  if not,
          return empty list.

        o Omit principals with existing assignments.
        """
        result = []

        if search_id:  # don't bother searching if no criteria

            parent = aq_parent(self)

            for info in parent.searchPrincipals(max_results=20,
                                                sort_by='id',
                                                id=search_id,
                                                exact_match=False):
                id = info['id']
                title = info.get('title', id)
                if (role_id not in self._principal_roles.get(id, ())
                        and role_id != id):
                    result.append((id, title))

        return result

    security.declareProtected(ManageUsers, 'listAssignedPrincipals')

    def listAssignedPrincipals(self, role_id):
        """ Return a list of principal IDs to whom a role is assigned.
        """
        result = []

        for k, v in self._principal_roles.items():
            if role_id in v:
                # should be one and only one mapping to 'k'

                parent = aq_parent(self)
                info = parent.searchPrincipals(id=k, exact_match=True)
                assert (len(info) == 1)
                result.append((k, info[0].get('title', k)))

        return result

    security.declareProtected(ManageUsers, 'assignRoleToPrincipal')

    def assignRoleToPrincipal(self, role_id, principal_id):
        """ Assign a role to a principal (user or group).

        o Return a boolean indicating whether a new assignment was created.

        o Raise KeyError if 'role_id' is unknown.
        """
        role_info = self._roles[role_id]  # raise KeyError if unknown!

        current = self._principal_roles.get(principal_id, ())
        already = role_id in current

        if not already:
            new = current + (role_id, )
            self._principal_roles[principal_id] = new

        return not already

    security.declareProtected(ManageUsers, 'removeRoleFromPrincipal')

    def removeRoleFromPrincipal(self, role_id, principal_id):
        """ Remove a role from a principal (user or group).

        o Return a boolean indicating whether the role was already present.

        o Raise KeyError if 'role_id' is unknown.

        o Ignore requests to remove a role not already assigned to the
          principal.
        """
        role_info = self._roles[role_id]  # raise KeyError if unknown!

        current = self._principal_roles.get(principal_id, ())
        new = tuple([x for x in current if x != role_id])
        already = current != new

        if already:
            self._principal_roles[principal_id] = new

        return already

    #
    #   ZMI
    #
    manage_options = (({
        'label': 'Roles',
        'action': 'manage_roles',
    }, ) + BasePlugin.manage_options)

    security.declareProtected(ManageUsers, 'manage_roles')
    manage_roles = PageTemplateFile('www/zrRoles',
                                    globals(),
                                    __name__='manage_roles')

    security.declareProtected(ManageUsers, 'manage_twoLists')
    manage_twoLists = PageTemplateFile('../www/two_lists',
                                       globals(),
                                       __name__='manage_twoLists')

    security.declareProtected(ManageUsers, 'manage_addRole')

    def manage_addRole(self, role_id, title, description, RESPONSE):
        """ Add a role via the ZMI.
        """
        self.addRole(role_id, title, description)

        message = 'Role+added'

        RESPONSE.redirect('%s/manage_roles?manage_tabs_message=%s' %
                          (self.absolute_url(), message))

    security.declareProtected(ManageUsers, 'manage_updateRole')

    def manage_updateRole(self, role_id, title, description, RESPONSE):
        """ Update a role via the ZMI.
        """
        self.updateRole(role_id, title, description)

        message = 'Role+updated'

        RESPONSE.redirect('%s/manage_roles?role_id=%s&manage_tabs_message=%s' %
                          (self.absolute_url(), role_id, message))

    security.declareProtected(ManageUsers, 'manage_removeRole')

    def manage_removeRoles(self, role_ids, RESPONSE):
        """ Remove one or more roles via the ZMI.
        """
        role_ids = filter(None, role_ids)

        if not role_ids:
            message = 'no+roles+selected'

        else:

            for role_id in role_ids:
                self.removeRole(role_id)

            message = 'Roles+removed'

        RESPONSE.redirect('%s/manage_roles?manage_tabs_message=%s' %
                          (self.absolute_url(), message))

    security.declareProtected(ManageUsers, 'manage_assignRoleToPrincipal')

    def manage_assignRoleToPrincipals(self, role_id, principal_ids, RESPONSE):
        """ Assign a role to one or more principals via the ZMI.
        """
        assigned = []

        for principal_id in principal_ids:
            if self.assignRoleToPrincipal(role_id, principal_id):
                assigned.append(principal_id)

        if not assigned:
            message = 'Role+%s+already+assigned+to+all+principals' % role_id
        else:
            message = 'Role+%s+assigned+to+%s' % (role_id, '+'.join(assigned))

        RESPONSE.redirect(
            ('%s/manage_roles?role_id=%s&assign=1' + '&manage_tabs_message=%s')
            % (self.absolute_url(), role_id, message))

    security.declareProtected(ManageUsers, 'manage_removeRoleFromPrincipals')

    def manage_removeRoleFromPrincipals(self, role_id, principal_ids,
                                        RESPONSE):
        """ Remove a role from one or more principals via the ZMI.
        """
        removed = []

        for principal_id in principal_ids:
            if self.removeRoleFromPrincipal(role_id, principal_id):
                removed.append(principal_id)

        if not removed:
            message = 'Role+%s+alread+removed+from+all+principals' % role_id
        else:
            message = 'Role+%s+removed+from+%s' % (role_id, '+'.join(removed))

        RESPONSE.redirect(
            ('%s/manage_roles?role_id=%s&assign=1' + '&manage_tabs_message=%s')
            % (self.absolute_url(), role_id, message))
Example #27
0
class MemberDataTool(UniqueObject, SimpleItem, PropertyManager):
    """ This tool wraps user objects, making them act as Member objects.
    """

    id = 'portal_memberdata'
    meta_type = 'CMF Member Data Tool'
    _properties = (
        {
            'id': 'email',
            'type': 'string',
            'mode': 'w'
        },
        {
            'id': 'portal_skin',
            'type': 'string',
            'mode': 'w'
        },
        {
            'id': 'listed',
            'type': 'boolean',
            'mode': 'w'
        },
        {
            'id': 'login_time',
            'type': 'date',
            'mode': 'w'
        },
        {
            'id': 'last_login_time',
            'type': 'date',
            'mode': 'w'
        },
        {
            'id': 'fullname',
            'type': 'string',
            'mode': 'w'
        },
    )
    email = ''
    fullname = ''
    last_login_time = DateTime('1970/01/01 00:00:00 UTC')  # epoch
    listed = False
    login_time = DateTime('1970/01/01 00:00:00 UTC')  # epoch
    portal_skin = ''

    security = ClassSecurityInfo()

    manage_options = (({
        'label': 'Overview',
        'action': 'manage_overview'
    }, {
        'label': 'Contents',
        'action': 'manage_showContents'
    }) + PropertyManager.manage_options + SimpleItem.manage_options)

    #
    #   ZMI methods
    #
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = DTMLFile('explainMemberDataTool', _dtmldir)

    security.declareProtected(ViewManagementScreens, 'manage_showContents')
    manage_showContents = DTMLFile('memberdataContents', _dtmldir)

    def __init__(self):
        self._members = OOBTree()

    #
    #   'portal_memberdata' interface methods
    #
    @security.private
    def getMemberDataContents(self):
        '''
        Return the number of members stored in the _members
        BTree and some other useful info
        '''
        mtool = getUtility(IMembershipTool)
        members = self._members
        user_list = mtool.listMemberIds()
        member_list = members.keys()
        member_count = len(members)
        orphan_count = 0

        for member in member_list:
            if member not in user_list:
                orphan_count = orphan_count + 1

        return [{'member_count': member_count, 'orphan_count': orphan_count}]

    @security.private
    def searchMemberData(self, search_param, search_term, attributes=()):
        """ Search members. """
        res = []

        if not search_param:
            return res

        mtool = getUtility(IMembershipTool)

        if len(attributes) == 0:
            attributes = ('id', 'email')

        if search_param == 'username':
            search_param = 'id'

        for user_id in self._members.keys():
            u = mtool.getMemberById(user_id)

            if u is not None:
                memberProperty = u.getProperty
                searched = memberProperty(search_param, None)

                if searched is not None and searched.find(search_term) != -1:
                    user_data = {}

                    for desired in attributes:
                        if desired == 'id':
                            user_data['username'] = memberProperty(desired, '')
                        else:
                            user_data[desired] = memberProperty(desired, '')

                    res.append(user_data)

        return res

    @security.private
    def searchMemberDataContents(self, search_param, search_term):
        """ Search members. This method will be deprecated soon. """
        res = []

        if search_param == 'username':
            search_param = 'id'

        mtool = getUtility(IMembershipTool)

        for member_id in self._members.keys():

            user_wrapper = mtool.getMemberById(member_id)

            if user_wrapper is not None:
                memberProperty = user_wrapper.getProperty
                searched = memberProperty(search_param, None)

                if searched is not None and searched.find(search_term) != -1:

                    res.append({
                        'username': memberProperty('id'),
                        'email': memberProperty('email', '')
                    })
        return res

    @security.private
    def pruneMemberDataContents(self):
        """ Delete data contents of all members not listet in acl_users.
        """
        mtool = getUtility(IMembershipTool)
        members = self._members
        user_list = mtool.listMemberIds()

        for member_id in list(members.keys()):
            if member_id not in user_list:
                del members[member_id]

    @security.private
    def wrapUser(self, u):
        '''
        If possible, returns the Member object that corresponds
        to the given User object.
        '''
        return getMultiAdapter((u, self), IMember)

    @security.private
    def registerMemberData(self, m, id):
        """ Add the given member data to the _members btree.
        """
        self._members[id] = aq_base(m)

    @security.private
    def deleteMemberData(self, member_id):
        """ Delete member data of specified member.
        """
        members = self._members
        if member_id in members:
            del members[member_id]
            return 1
        else:
            return 0
Example #28
0
class _Records(object):
    """The records stored in the registry. This implements dict-like access
    to records, where as the Registry object implements dict-like read-only
    access to values.
    """
    __parent__ = None

    # Similar to zope.schema._field._isdotted, but allows up to one '/'
    _validkey = re.compile(r"([a-zA-Z][a-zA-Z0-9_-]*)"
                           r"([.][a-zA-Z][a-zA-Z0-9_-]*)*"
                           r"([/][a-zA-Z][a-zA-Z0-9_-]*)?"
                           r"([.][a-zA-Z][a-zA-Z0-9_-]*)*"
                           # use the whole line
                           r"$").match

    def __init__(self, parent):
        self.__parent__ = parent

        self._fields = OOBTree()
        self._values = OOBTree()

    def __setitem__(self, name, record):
        if not self._validkey(name):
            raise InvalidRegistryKey(record)
        if not IRecord.providedBy(record):
            raise ValueError("Value must be a record")

        self._setField(name, record.field)
        self._values[name] = record.value

        record.__name__ = name
        record.__parent__ = self.__parent__

        notify(RecordAddedEvent(record))

    def __delitem__(self, name):
        record = self[name]

        # unbind the record so that it won't attempt to look up values from
        # the registry anymore
        record.__parent__ = None

        del self._fields[name]
        del self._values[name]

        notify(RecordRemovedEvent(record))

    def __getitem__(self, name):

        field = self._getField(name)
        value = self._values[name]

        record = Record(field, value, _validate=False)
        record.__name__ = name
        record.__parent__ = self.__parent__

        return record

    def get(self, name, default=None):
        try:
            return self[name]
        except KeyError:
            return default

    def __nonzero__(self):
        return self._values.__nonzero__()

    def __len__(self):
        return self._values.__len__()

    def __iter__(self):
        return self._values.__iter__()

    def has_key(self, name):
        return self._values.__contains__(name)

    def __contains__(self, name):
        return self._values.__contains__(name)

    def keys(self, min=None, max=None):
        return self._values.keys(min, max)

    def maxKey(self, key=None):
        return self._values.maxKey(key)

    def minKey(self, key=None):
        return self._values.minKey(key)

    def values(self, min=None, max=None):
        return [self[name] for name in self.keys(min, max)]

    def items(self, min=None, max=None):
        return [(
            name,
            self[name],
        ) for name in self.keys(min, max)]

    def setdefault(self, key, value):
        if key not in self:
            self[key] = value
        return self[key]

    def clear(self):
        self._fields.clear()
        self._values.clear()

    # Helper methods

    def _getField(self, name):
        field = self._fields[name]

        # Handle field reference pointers
        if isinstance(field, basestring):
            recordName = field
            while isinstance(field, basestring):
                recordName = field
                field = self._fields[recordName]
            field = FieldRef(recordName, field)

        return field

    def _setField(self, name, field):
        if not IPersistentField.providedBy(field):
            raise ValueError("The record's field must be an IPersistentField.")
        if IFieldRef.providedBy(field):
            if field.recordName not in self._fields:
                raise ValueError(
                    "Field reference points to non-existent record")
            self._fields[name] = field.recordName  # a pointer, of sorts
        else:
            field.__name__ = 'value'
            self._fields[name] = field
class InvertedIndex:
    def __init__(self):
        super().__init__()
        self._btree = BTree()
        self.build()

    def build(self):
        """ 
            Reads files one-by-one and builds the index

            Arguments:
            None

            Returns:
            None
        """

        if os.path.isfile("InvertedIndex"):
            print("Loading the Inverted Index from file")
            with open("InvertedIndex", "rb") as file:
                self._btree = pickle.load(file)
        else:
            print("Building the Inverted Index")
            dirPath = os.path.dirname(os.path.realpath(__file__))
            dataPath = os.path.realpath(os.path.join(dirPath, "..", "data"))
            files = [
                os.path.join(dataPath, file)
                for file in sorted(os.listdir(dataPath))
            ]

            for file in files:
                snippets = getSnippets(file)
                for index, snippet in enumerate(snippets):
                    filename = int(os.path.split(file)[1].split(".csv")[0])
                    docId = (filename, index + 2)
                    tokens, snippetMetadata = preProcess(snippet)
                    self.updateIndex(tokens, docId)

            self.sortPostingLists()

            sys.setrecursionlimit(10000)
            with open("InvertedIndex", "wb") as file:
                print("Saving the Inverted Index to file")
                pickle.dump(self._btree, file)

    def sortPostingLists(self):
        words = list(self.getKeys())

        for word in words:
            postingList = self._btree.get(word)
            postingList.sort(key=lambda x: ((10000 * x[0][0]) + x[0][1]))

    def getKeys(self):
        return self._btree.keys()

    def getValues(self):
        return self._btree.values()

    def getPostingListCollection(self, termList):

        result = []

        for term in termList:
            postingList = self._btree.get(term)
            if (postingList != None):
                result.append(postingList)
            else:
                result.append([])

        return result

    def documentUnion(self, postingListCollection):
        docList = set()
        for postingList in postingListCollection:
            for doc in postingList:
                docList.add(doc[0])

        return docList

    def getDocuments(self,
                     termList,
                     queryType=0,
                     queryMetadata=defaultdict(int)):
        """
            Finds the documents containing the terms

            Arguments:
            termList - List of query terms

            Returns:
            List of document numbers
        """
        docList = []

        # Union
        if (queryType == 0):
            postingListCollection = self.getPostingListCollection(termList)
            docList = self.documentUnion(postingListCollection)

        # intersection/positional
        elif (queryType == 1):
            postingListCollection = self.getPostingListCollection(termList)
            docList = self.documentIntersection(postingListCollection,
                                                queryMetadata)

        return docList

    def documentIntersection(self, documents, queryMetadata):

        while (len(documents) > 1):
            isPositional = False
            diff = queryMetadata[(len(documents) - 2, len(documents) - 1)]
            if (diff != 0):
                isPositional = True

            list1 = documents.pop()
            list2 = documents.pop()

            ptr1 = 0
            ptr2 = 0

            intersection = []

            while (ptr1 < len(list1) and ptr2 < len(list2)):

                fileNo1 = list1[ptr1][0][0]
                rowNo1 = list1[ptr1][0][1]
                fileNo2 = list2[ptr2][0][0]
                rowNo2 = list2[ptr2][0][1]

                if (fileNo1 == fileNo2 and rowNo1 == rowNo2):
                    if (isPositional):
                        for postion1 in list1[ptr1][1]:
                            for postion2 in list2[ptr2][1]:
                                if ((postion1 - postion2 + 1) == diff):
                                    if (len(intersection) == 0
                                            or intersection[-1][0] !=
                                            list1[ptr1][0]):
                                        intersection.append(
                                            (list1[ptr1][0], [postion2]))
                                    elif (intersection[-1][0] == list1[ptr1][0]
                                          ):
                                        intersection[-1][1].append(postion2)
                    else:
                        intersection.append((list1[ptr1][0], list2[ptr2][1]))
                    ptr1 += 1
                    ptr2 += 1
                elif (fileNo1 == fileNo2 and rowNo1 < rowNo2):
                    ptr1 += 1
                elif (fileNo1 == fileNo2 and rowNo1 > rowNo2):
                    ptr2 += 1
                elif (fileNo1 < fileNo2):
                    ptr1 += 1
                elif (fileNo1 > fileNo2):
                    ptr2 += 1

            documents.append(intersection)
        docNolist = [x[0] for x in documents[0]]
        return docNolist

    def updateIndex(self, docTokens, docId):
        """
            Updates the inverted index

            Arguments:
            docText - Document to be added to the index
            docId - ID of the document

            Returns:
            None
        """

        for word, wordIndex in docTokens:
            postingList = self._btree.get(word)
            if postingList is not None:
                lastdocId = postingList[-1][0]
                if docId == lastdocId:
                    postingList[-1][1].append(wordIndex)
                else:
                    postingList.append((docId, [wordIndex]))
            else:
                postingList = [(docId, [wordIndex])]
                self._btree.insert(word, postingList)
Example #30
0
class Survey(ATCTOrderedFolder):
    """You can add questions to surveys"""

    schema = SurveySchema
    _at_rename_after_creation = True

    implements(ISurvey)

    security = ClassSecurityInfo()

    security.declareProtected(permissions.ModifyPortalContent, "reset")

    def reset(self):
        """Remove all respondents."""
        self.respondents = OOBTree()

    def _checkId(self, id, allow_dup=0):
        """Bypass the root object check for the local acl_users"""
        try:
            return super(Survey, self)._checkId(id, allow_dup=0)
        except BadRequest:
            if id != "acl_users":
                raise

    security.declarePrivate("createLocalPas")

    def createLocalPas(self):
        """Create PAS acl_users else login_form does not work"""
        # need Manager role to add an acl_users object
        remove_role = False
        if not getSecurityManager().checkPermission(permissions.ManagePortal, self):
            portal_membership = getToolByName(self, "portal_membership")
            current_user = portal_membership.getAuthenticatedMember()
            current_userid = current_user.getId()
            self.manage_addLocalRoles(userid=current_userid, roles=["Manager"])
            remove_role = True
        # Re-use code in PlonePAS install
        addPluggableAuthService(self)
        out = StringIO()
        try:
            challenge_chooser_setup(self)
        except TypeError:
            # BBB needed for Plone 3.3.5
            challenge_chooser_setup(self, out)
        registerPluginTypes(self.acl_users)
        try:
            setupPlugins(self)
        except TypeError:
            # BBB needed for Plone 3.3.5
            setupPlugins(self, out)

        # Recreate mutable_properties but specify fields
        uf = self.acl_users
        uf.manage_addProduct["PluggableAuthService"]
        plone_pas = uf.manage_addProduct["PlonePAS"]
        plone_pas.manage_delObjects("mutable_properties")
        plone_pas.manage_addZODBMutablePropertyProvider("mutable_properties", fullname="", key="", email_sent="")
        activatePluginInterfaces(self, "mutable_properties", out)
        if remove_role:
            self.manage_delLocalRoles(userids=[current_userid])

    security.declarePublic("canSetDefaultPage")

    def canSetDefaultPage(self):
        """Doesn't make sense for surveys to allow alternate views"""
        return False

    security.declarePublic("canConstrainTypes")

    def canConstrainTypes(self):
        """Should not be able to add non survey types"""
        return False

    security.declareProtected(permissions.View, "isMultipage")

    def isMultipage(self):
        """Return true if there is more than one page in the survey"""
        if self.getFolderContents(contentFilter={"portal_type": "Sub Survey"}):
            return True

    security.declareProtected(permissions.View, "getQuestions")

    def getQuestions(self):
        """Return the questions for this part of the survey"""
        questions = self.getFolderContents(
            contentFilter={
                "portal_type": [
                    "Survey Date Question",
                    "Survey Matrix",
                    "Survey Select Question",
                    "Survey Text Question",
                ]
            },
            full_objects=True,
        )
        return questions

    security.declareProtected(permissions.View, "getAllQuestions")

    def getAllQuestions(self):
        """Return all the questions in the survey"""
        portal_catalog = getToolByName(self, "portal_catalog")
        questions = []
        path = string.join(self.getPhysicalPath(), "/")
        results = portal_catalog.searchResults(
            portal_type=[
                "Survey Date Question",
                "Survey Matrix Question",
                "Survey Select Question",
                "Survey Text Question",
            ],
            path=path,
            order="getObjPositionInParent",
        )
        for result in results:
            questions.append(result.getObject())
        return questions

    security.declareProtected(permissions.View, "getAllQuestionsInOrder")

    def getAllQuestionsInOrder(self, include_sub_survey=False):
        """Return all the questions in the survey"""
        questions = []
        objects = self.getFolderContents(
            contentFilter={
                "portal_type": [
                    "Sub Survey",
                    "Survey Date Question",
                    "Survey Matrix",
                    "Survey Select Question",
                    "Survey Text Question",
                ]
            },
            full_objects=True,
        )
        for object in objects:
            if object.portal_type == "Sub Survey":
                if include_sub_survey:
                    questions.append(object)
                sub_survey_objects = object.getFolderContents(
                    contentFilter={
                        "portal_type": [
                            "Survey Matrix",
                            "Survey Date Question",
                            "Survey Select Question",
                            "Survey Text Question",
                        ]
                    },
                    full_objects=True,
                )
                for sub_survey_object in sub_survey_objects:
                    questions.append(sub_survey_object)
                    if sub_survey_object.portal_type == "Survey Matrix":
                        survey_matrix_objects = sub_survey_object.getFolderContents(
                            contentFilter={"portal_type": "Survey Matrix Question"}, full_objects=True
                        )
                        for survey_matrix_object in survey_matrix_objects:
                            questions.append(survey_matrix_object)
            elif object.portal_type == "Survey Matrix":
                questions.append(object)
                survey_matrix_objects = object.getFolderContents(
                    contentFilter={"portal_type": "Survey Matrix Question"}, full_objects=True
                )
                for survey_matrix_object in survey_matrix_objects:
                    questions.append(survey_matrix_object)
                # XXX should check if comment is present
            else:
                questions.append(object)
        return questions

    security.declareProtected(permissions.View, "getAllSelectQuestionsInOrder")

    def getAllSelectQuestionsInOrder(self):
        """Return all the vocab driven questions in the survey"""
        questions = []
        objects = self.getFolderContents(
            contentFilter={"portal_type": ["Sub Survey", "Survey Matrix", "Survey Select Question"]}, full_objects=True
        )
        for object in objects:
            if object.portal_type == "Sub Survey":
                sub_survey_objects = object.getFolderContents(
                    contentFilter={"portal_type": ["Survey Matrix", "Survey Select Question"]}, full_objects=True
                )
                for sub_survey_object in sub_survey_objects:
                    if sub_survey_object.portal_type == "Survey Matrix":
                        survey_matrix_objects = sub_survey_object.getFolderContents(
                            contentFilter={"portal_type": "Survey Matrix Question"}, full_objects=True
                        )
                        for survey_matrix_object in survey_matrix_objects:
                            questions.append(survey_matrix_object)
                    else:
                        questions.append(sub_survey_object)
            elif object.portal_type == "Survey Matrix":
                survey_matrix_objects = object.getFolderContents(
                    contentFilter={"portal_type": "Survey Matrix Question"}, full_objects=True
                )
                for survey_matrix_object in survey_matrix_objects:
                    questions.append(survey_matrix_object)
            else:
                questions.append(object)
        return questions

    security.declareProtected(permissions.View, "hasDateQuestion")

    def hasDateQuestion(self):
        """Return true if there is a date question in this part of the survey
        to import the js"""
        objects = self.getFolderContents(contentFilter={"portal_type": "Survey Date Question"})
        if objects:
            return True
        return False

    security.declareProtected(permissions.View, "getNextPage")

    def getNextPage(self):
        """Return the next page of the survey"""
        pages = self.getFolderContents(contentFilter={"portal_type": "Sub Survey"}, full_objects=True)
        for page in pages:
            if page.displaySubSurvey():
                return page()
        return self.exitSurvey()

    security.declareProtected(permissions.View, "hasMorePages")

    def hasMorePages(self):
        """Return True if survey has more pages to display"""
        pages = self.getFolderContents(contentFilter={"portal_type": "Sub Survey"}, full_objects=True)
        if not pages:
            return False
        return True

    security.declareProtected(permissions.View, "exitSurvey")

    def exitSurvey(self):
        """Return the defined exit url"""
        self.setCompletedForUser()
        exit_url = self.getExitUrl()
        if exit_url[:4] != "http":
            self.plone_utils.addPortalMessage(safe_unicode(self.getThankYouMessage()))
            exit_url = self.portal_url() + "/" + exit_url
        return self.REQUEST.RESPONSE.redirect(exit_url)

    security.declareProtected(permissions.View, "saveSurvey")

    def saveSurvey(self):
        """Return the defined exit url"""
        exit_url = self.getExitUrl()
        if exit_url[:4] != "http":
            self.plone_utils.addPortalMessage(safe_unicode(self.getSavedMessage()))
            exit_url = self.portal_url() + "/" + exit_url
        return self.REQUEST.RESPONSE.redirect(exit_url)

    security.declareProtected(permissions.View, "setCompletedForUser")

    def setCompletedForUser(self):
        """Set completed for a user"""
        userid = self.getSurveyId()
        respondents = self.respondents
        respondents[userid]["end"] = DateTime()
        completed = self.getCompletedFor()
        completed.append(userid)
        self.setCompletedFor(completed)

        # Scramble respondent's password because we don't want him back
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(userid)
        if user is not None:
            portal_registration = getToolByName(self, "portal_registration")
            pw = portal_registration.generatePassword()

            self.acl_users.userFolderEditUser(userid, pw, user.getRoles(), user.getDomains(), key=pw)

            # Set key
            props = acl_users.mutable_properties.getPropertiesForUser(user)
            props = BasicPropertySheet(props)
            props.setProperty("key", pw)
            acl_users.mutable_properties.setPropertiesForUser(user, props)

        if self.getSurveyNotificationMethod() == "each_submission":
            self.send_email(userid)

    security.declareProtected(permissions.View, "checkCompletedFor")

    def checkCompletedFor(self, user_id):
        """Check whether a user has completed the survey"""
        completed = self.getCompletedFor()
        if user_id in completed:
            return True
        return False

    security.declareProtected(permissions.View, "getSurveyId")

    def getSurveyId(self):
        """Return the userid for the survey"""
        request = self.REQUEST
        # Check the request for saving questions on the first survey page
        try:
            user_id = request.form["survey_user_id"]
        except KeyError:
            pass
        else:
            return user_id
        portal_membership = getToolByName(self, "portal_membership")
        if not portal_membership.isAnonymousUser():
            user_id = portal_membership.getAuthenticatedMember().getId()
            self.addRespondent(user_id)
            return user_id
        response = request.RESPONSE
        survey_cookie = self.getId()
        if self.getAllowAnonymous() and survey_cookie in request:
            return request.get(survey_cookie, "Anonymous")
        user_id = self.getAnonymousId()
        # expires = (DateTime() + 365).toZone('GMT').rfc822()
        # cookie expires in 1 year (365 days)
        response.setCookie(survey_cookie, user_id, path="/")
        return user_id

    security.declareProtected(permissions.View, "getAnonymousId")

    def getAnonymousId(self):
        """returns the id to use for an anonymous user"""
        portal_membership = getToolByName(self, "portal_membership")
        if portal_membership.isAnonymousUser() and self.getAllowAnonymous():
            anon_id = "Anonymous@"
            if not self.getConfidential():
                remote_ip = self.getRemoteIp()
                if remote_ip:
                    anon_id = anon_id + remote_ip + "@"
            return anon_id + str(DateTime())
        elif portal_membership.isAnonymousUser():
            return self.REQUEST.RESPONSE.redirect(self.portal_url() + "/login_form?came_from=" + self.absolute_url())
        return portal_membership.getAuthenticatedMember().getId()

    security.declareProtected(permissions.View, "getRemoteIp")

    def getRemoteIp(self, request=None):
        """returns the ip address of the survey respondent"""
        if self.getConfidential():
            return
        if request is None:
            request = getattr(self, "REQUEST", None)
        return request.getClientAddr()

    security.declareProtected(permissions.ModifyPortalContent, "getRespondentsDetails")

    def getRespondentsDetails(self):
        """Return a list of respondents details"""
        return self.respondents

    security.declareProtected(permissions.ModifyPortalContent, "getRespondentsList")

    def getRespondentsList(self):
        """Return a list of respondents details"""
        users = {}
        for user in self.respondents.keys():
            users[user] = 1
        return users.keys()

    security.declareProtected(ViewSurveyResults, "getRespondentDetails")

    def getRespondentDetails(self, respondent):
        """Return details of a respondent"""
        try:
            self.respondents
        except AttributeError:
            self.reset()
        try:
            details = self.respondents[respondent]
        except KeyError:
            # TODO try/except should be removed at some point
            # probably old survey, create respondent details
            self.respondents[respondent] = PersistentMapping(start="", ip_address="", end="")
        details_dict = {}
        details = self.respondents[respondent]
        for k in details.keys():
            details_dict[k] = details[k]
        if details["start"] and details["end"]:
            details_dict["time_taken"] = DT2dt(details["end"]) - DT2dt(details["start"])
        else:
            details_dict["time_taken"] = ""
        return details_dict

    security.declareProtected(permissions.ModifyPortalContent, "addRespondent")

    def addRespondent(self, user_id):
        """Add a respondent to the survey"""
        # TODO needs moving to an event handler
        try:
            self.respondents
        except AttributeError:
            self.reset()
        if user_id in self.respondents:
            return
        self.respondents[user_id] = PersistentMapping(start=DateTime(), ip_address=self.getRemoteIp(), end="")

    security.declareProtected(ViewSurveyResults, "getRespondentFullName")

    def getRespondentFullName(self, userid):
        """get user. used by results spreadsheets to show fullname"""
        portal_membership = getToolByName(self, "portal_membership")
        member = portal_membership.getMemberById(userid)
        if member is None:
            return
        full_name = member.getProperty("fullname")
        if full_name:
            return full_name
        return member.id

    security.declareProtected(ViewSurveyResults, "getAnswersByUser")

    def getAnswersByUser(self, userid):
        """Return a set of answers by user id"""
        questions = self.getAllQuestionsInOrder()
        answers = {}
        for question in questions:
            answer = question.getAnswerFor(userid)
            answers[question.getId()] = answer
        return answers

    security.declareProtected(permissions.View, "getQuestionsCount")

    def getQuestionsCount(self):
        """Return a count of questions asked"""
        # XXX is this used anywhere?
        return len(self.questions)

    security.declareProtected(permissions.View, "getSurveyColors")

    def getSurveyColors(self, num_options):
        """Return the colors for the barchart"""
        colors = BARCHART_COLORS
        num_colors = len(colors)
        while num_colors < num_options:
            colors = colors + colors
            num_colors = len(colors)
        return colors

    security.declareProtected(ResetOwnResponses, "resetForAuthenticatedUser")

    def resetForAuthenticatedUser(self):
        mtool = getToolByName(self, "portal_membership")
        member = mtool.getAuthenticatedMember()
        user_id = member.getMemberId()
        return self._resetForUser(user_id)

    security.declareProtected(permissions.ModifyPortalContent, "resetForUser")

    def resetForUser(self, userid):
        """Remove answer for a single user"""
        self._resetForUser(userid)

    def _resetForUser(self, userid):
        """Remove answer for a single user"""
        completed = self.getCompletedFor()
        if userid in completed:
            completed.remove(userid)
        self.setCompletedFor(completed)
        questions = self.getAllQuestionsInOrder()
        for question in questions:
            question.resetForUser(userid)
        try:
            if userid in self.respondents:
                del self.respondents[userid]
        except AttributeError:
            # TODO old survey instance
            self.reset()

    security.declareProtected(permissions.View, "send_email")

    def send_email(self, userid):
        """ Send email to nominated address """
        properties = self.portal_properties.site_properties
        mTo = self.getSurveyNotificationEmail()
        mFrom = properties.email_from_address
        mSubj = translate(
            _("[${survey_title}] New survey submitted", mapping={"survey_title": self.Title()}), context=self.REQUEST
        )
        message = []
        message.append(
            translate(_("Survey ${survey_title}", mapping={"survey_title": self.Title()}), context=self.REQUEST)
        )
        message.append(
            translate(_("has been completed by user: ${userid}", mapping={"userid": userid}), context=self.REQUEST)
        )
        message.append(self.absolute_url() + "/@@Products.PloneSurvey.survey_view_results")
        mMsg = "\n\n".join(message)
        try:
            self.MailHost.send(mMsg.encode("utf-8"), mTo, mFrom, mSubj)
        except ConflictError:
            raise
        except:
            # XXX too many things can go wrong
            pass

    security.declarePublic("translateThankYouMessage")

    def translateThankYouMessage(self):
        """ """
        return self.translate(
            msgid="text_default_thank_you", default="Thank you for completing the survey.", domain="plonesurvey"
        )

    security.declarePublic("translateSavedMessage")

    def translateSavedMessage(self):
        """ """
        return self.translate(
            msgid="text_default_saved_message",
            default=u"You have saved the survey.\n " u"Don't forget to come back and finish it.",
            domain="plonesurvey",
        )

    security.declareProtected(permissions.ModifyPortalContent, "deleteAuthenticatedRespondent")

    def deleteAuthenticatedRespondent(self, email, REQUEST=None):
        """Delete authenticated respondent"""
        # xxx: delete answers by this user as well?
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(email)
        props = acl_users.mutable_properties.getPropertiesForUser(user)
        props = BasicPropertySheet(props)
        for prop in props.propertyItems():
            props.setProperty(prop[0], "")
        acl_users.mutable_properties.setPropertiesForUser(user, props)
        acl_users.userFolderDelUsers([email])
        if REQUEST is not None:
            pu = getToolByName(self, "plone_utils")
            pu.addPortalMessage(safe_unicode("Respondent %s deleted" % email))
            REQUEST.RESPONSE.redirect(REQUEST.HTTP_REFERER)

    security.declareProtected(permissions.ModifyPortalContent, "addAuthenticatedRespondent")

    def addAuthenticatedRespondent(self, emailaddress, **kw):
        acl_users = self.get_acl_users()
        portal_registration = getToolByName(self, "portal_registration")
        if not emailaddress:
            return False
        # Create user
        password = portal_registration.generatePassword()
        acl_users.userFolderAddUser(emailaddress, password, roles=["Member"], domains=[], groups=())
        # Set user properties
        user = acl_users.getUserById(emailaddress)
        props = acl_users.mutable_properties.getPropertiesForUser(user)
        props = BasicPropertySheet(props)
        for k, v in kw.items():
            props.setProperty(k, v)
        props.setProperty("key", password)
        acl_users.mutable_properties.setPropertiesForUser(user, props)
        return True

    security.declareProtected(permissions.ModifyPortalContent, "registerRespondentSent")

    def registerRespondentSent(self, email_address):
        """Mark the respondent as being sent an email"""
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(email_address)
        props = acl_users.mutable_properties.getPropertiesForUser(user)
        props = BasicPropertySheet(props)
        props.setProperty("email_sent", str(DateTime()))
        acl_users.mutable_properties.setPropertiesForUser(user, props)

    security.declareProtected(permissions.ModifyPortalContent, "getAuthenticatedRespondent")

    def getAuthenticatedRespondent(self, emailaddress):
        """
        Return dictionary with respondent details.
        This method is needed because
        getProperty is hosed on the user object.
        """
        di = {"emailaddress": emailaddress, "id": emailaddress}
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(emailaddress)
        props = user.getPropertysheet("mutable_properties")
        for k, v in props.propertyItems():
            di[k] = v
        return di

    security.declareProtected(permissions.ModifyPortalContent, "getAuthenticatedRespondents")

    def getAuthenticatedRespondents(self):
        """Build up the list of users"""
        respondents = []
        users = self.get_acl_users().getUsers()
        for user in users:
            respondents.append(user.getId())
        return [self.getAuthenticatedRespondent(user_id) for user_id in respondents]

    security.declareProtected(permissions.ModifyPortalContent, "sendSurveyInvite")

    def sendSurveyInvite(self, email_address):
        """Send a survey Invite"""
        portal_properties = getToolByName(self, "portal_properties")
        acl_users = self.get_acl_users()
        user = acl_users.getUserById(email_address)
        user_details = self.getAuthenticatedRespondent(email_address)
        email_from_name = self.getInviteFromName()
        if not email_from_name:
            email_from_name = self.email_from_name
        email_from_address = self.getInviteFromEmail()
        if not email_from_address:
            email_from_address = self.email_from_address
        email_body = self.getEmailInvite()
        email_body = email_body.replace("**Name**", user_details["fullname"])
        survey_url = (
            self.absolute_url()
            + "/login_form_bridge?email="
            + email_address
            + "&amp;key="
            + urllib.quote(user_details["key"])
        )
        email_body = email_body.replace("**Survey**", '<a href="' + survey_url + '">' + self.Title() + "</a>")
        mail_text = self.survey_send_invite_template(
            user=user,
            recipient=user.getId(),
            email_from_name=email_from_name,
            email_from_address=email_from_address,
            email_body=email_body,
            subject="Survey %s" % self.title_or_id(),
        )
        host = self.MailHost
        site_props = portal_properties.site_properties
        mail_text = mail_text.encode(site_props.default_charset or "utf-8")
        host.send(mail_text)
        self.registerRespondentSent(email_address)

    security.declareProtected(permissions.ModifyPortalContent, "sendSurveyInviteAll")

    def sendSurveyInviteAll(self, send_to_all=False, use_transactions=False):
        """Send survey Invites to all respondents"""
        number_sent = 0
        if use_transactions:
            transaction.abort()
        respondents = self.acl_users.getUsers()
        already_completed = self.getRespondentsList()
        for respondent in respondents:
            if use_transactions:
                transaction.get()
            email_address = respondent.getId()
            respondent_details = self.getAuthenticatedRespondent(email_address)
            if email_address in already_completed:
                # don't send out an invite if already responded
                continue
            if not send_to_all:
                # don't send an email if one already sent
                if respondent_details["email_sent"]:
                    continue
            self.sendSurveyInvite(email_address)
            number_sent += 1
        # return number of invites sent
        return number_sent

    def get_acl_users(self):
        """Fetch acl_users. Create if it does not yet exist."""
        if "acl_users" not in self.objectIds():
            self.createLocalPas()
        return self.acl_users

    security.declareProtected(ViewSurveyResults, "setCsvHeaders")

    def setCsvHeaders(self, filetype="csv"):
        """Set the CSV headers"""
        REQUEST = self.REQUEST
        file = self.buildSpreadsheetUrl(filetype=filetype)
        REQUEST.RESPONSE.setHeader("Content-Type", "text/x-comma-separated-values; charset=utf-8")
        REQUEST.RESPONSE.setHeader("Content-disposition", "attachment; filename=%s" % file)
        return REQUEST

    security.declareProtected(ViewSurveyResults, "buildSpreadsheetUrl")

    def buildSpreadsheetUrl(self, filetype="csv"):
        """Create a filename for the spreadsheets"""
        date = DateTime().strftime("%Y-%m-%d")
        id = self.getId()
        id = "%s-%s" % (id, date)
        url = "%s.%s" % (id, filetype)
        return url

    security.declareProtected(ViewSurveyResults, "spreadsheet2")

    def spreadsheet2(self):
        """Return spreadsheet 2"""
        self.setCsvHeaders()
        return self.buildSpreadsheet2()

    security.declareProtected(ViewSurveyResults, "spreadsheet2_tab")

    def spreadsheet2_tab(self):
        """Return spreadsheet 2 tab"""
        self.setCsvHeaders(filetype="tsv")
        dialect = csv.excel_tab
        return self.buildSpreadsheet2(dialect)

    security.declareProtected(ViewSurveyResults, "buildSpreadsheet2")

    def buildSpreadsheet2(self, dialect=csv.excel):
        """Build spreadsheet 2.
            excel_tab
            excel
        """
        data = StringIO()
        sheet = csv.writer(data, dialect=dialect, quoting=csv.QUOTE_ALL)
        questions = self.getAllQuestionsInOrder()

        sheet.writerow(("user",) + tuple(q.Title() for q in questions) + ("completed",))

        for user in self.getRespondentsList():
            if self.getConfidential():
                row = ["Anonymous"]
            else:
                row = [self.getRespondentFullName(user) or user]
            for question in questions:
                answer = question.getAnswerFor(user) or ""
                # handle there being no answer (e.g branched question)
                if answer:
                    if not (isinstance(answer, str) or isinstance(answer, unicode) or isinstance(answer, int)):
                        # It's a sequence, filter out empty values
                        answer = ", ".join(filter(None, answer))
                row.append(answer.replace('"', "'").replace("\r\n", " "))

            row.append(self.checkCompletedFor(user) and "Completed" or "Not Completed")
            for i, col in enumerate(row):
                if isinstance(col, unicode):
                    col = col.encode("utf8")
                row[i] = col
            sheet.writerow(row)

        return data.getvalue()

    security.declareProtected(ViewSurveyResults, "spreadsheet3")

    def spreadsheet3(self):
        """Return spreadsheet 3"""
        self.setCsvHeaders()
        return self.buildSpreadsheet3()

    security.declareProtected(ViewSurveyResults, "get_all_questions_in_order_filtered")

    def get_all_questions_in_order_filtered(
        self, include_sub_survey=False, ignore_meta_types=[], ignore_input_types=[], restrict_meta_types=[]
    ):
        """This is only used in buildSpreadsheet3, and should be moved into
        another method."""
        questions = self.getAllQuestionsInOrder(include_sub_survey=include_sub_survey)
        result = []
        for question in questions:
            ok = True
            if ignore_meta_types:
                if question.meta_type in ignore_meta_types:
                    ok = ok and False
            if ignore_input_types:
                if question.getInputType() in ignore_input_types:
                    ok = ok and False
            if restrict_meta_types:
                if question.meta_type not in restrict_meta_types:
                    ok = ok and False
            if ok:
                result.append(question)
        return result

    security.declareProtected(ViewSurveyResults, "buildSpreadsheet3")

    def buildSpreadsheet3(self):
        """Build spreadsheet 3."""
        data = StringIO()
        sheet = csv.writer(data)
        questions = self.get_all_questions_in_order_filtered(ignore_meta_types=["SurveyMatrix"])
        sheet.writerow(("user",) + tuple(q.Title() for q in questions) + ("completed",))
        for user in self.getRespondentsList():
            if self.getConfidential():
                row = ["Anonymous"]
            else:
                row = [self.getRespondentFullName(user) or user]
            for question in questions:
                answer = ""
                if question.getInputType() in ["text", "area"]:
                    if question.getAnswerFor(user):
                        answer = '"' + question.getAnswerFor(user).replace('"', "'") + '"'
                    else:
                        answer = ""
                elif question.getInputType() in ["checkbox", "multipleSelect"]:
                    options = question.getQuestionOptions()
                    answerList = question.getAnswerFor(user)
                    if answerList and not isinstance(answerList, str):
                        if not isinstance(answerList, list):
                            answerList = [answerList]
                        for option in options:
                            if answerList.count(option) > 0:
                                answer += "1;"
                            else:
                                answer += "0;"
                        answer = '"' + answer[0 : len(answer) - 1] + '"'
                    elif answerList:
                        answer = '"' + answerList + '"'
                    else:
                        answer = ""
                else:
                    if not hasattr(question, "getQuestionOptions"):
                        options = question.title
                    else:
                        options = question.getQuestionOptions()
                    answerLabel = question.getAnswerFor(user)
                    answer = str(len(options))
                    i = 0
                    while i < len(options):
                        if options[i] == answerLabel:
                            answer = str(i)
                            break
                        i = i + 1
                row.append(answer)
            #                if question.getCommentType():
            #                line.append('"' + test(question.getCommentsFor(user), question.getCommentsFor(user).replace('"',"'"), "Blank") + '"')
            row.append(self.checkCompletedFor(user) and "Completed" or "Not Completed")
            sheet.writerow(row)
        return data.getvalue()

    security.declareProtected(ViewSurveyResults, "summary_spreadsheet")

    def summary_spreadsheet(self):
        """Return summary spreadsheet"""
        self.setCsvHeaders()
        return self.buildSummarySpreadsheet()

    security.declareProtected(permissions.ModifyPortalContent, "buildSummarySpreadsheet")

    def buildSummarySpreadsheet(self):
        """Build the summary spreadsheet."""
        data = StringIO()
        sheet = csv.writer(data)
        row = ["Question", "Option", "Number of Responses", "Percentage"]
        sheet.writerow(row)
        questions = self.getAllQuestionsInOrder()
        for question in questions:
            row = [question.Title(), ""]
            row.append(question.getNumberOfRespondents())
            sheet.writerow(row)
            if question.portal_type in ["Survey Select Question", "Survey Matrix Question"]:
                options = question.getQuestionOptions()
                number_options = question.getAggregateAnswers()
                percentage_options = question.getPercentageAnswers()
                for option in options:
                    row = ["", option]
                    row.append(number_options[option])
                    row.append(percentage_options[option])
                    sheet.writerow(row)
        return data.getvalue()

    security.declareProtected(ViewSurveyResults, "spreadsheet_select")

    def spreadsheet_select(self):
        """Return spreadsheet select"""
        self.setCsvHeaders()
        try:
            self.REQUEST.form["answers"]
        except KeyError:
            return self.buildSelectSpreadsheet()
        return self.buildSelectSpreadsheet(boolean=True)

    security.declareProtected(ViewSurveyResults, "buildSelectSpreadsheet")

    def buildSelectSpreadsheet(self, boolean=False):
        """Build the select spreadsheet."""
        data = StringIO()
        sheet = csv.writer(data)
        questions = self.getAllSelectQuestionsInOrder()
        row = ["user"]
        options_row = [""]
        for question in questions:
            options = question.getQuestionOptions()
            for i in range(len(options)):
                if i == 0:
                    row.append(question.Title())
                else:
                    row.append("")
                options_row.append(options[i])
        sheet.writerow(row)
        sheet.writerow(options_row)
        for user in self.getRespondentsList():
            if self.getConfidential():
                row = ["Anonymous"]
            else:
                row = [self.getRespondentFullName(user) or user]
            for question in questions:
                options = question.getQuestionOptions()
                answer = question.getAnswerFor(user)
                for i in range(len(options)):
                    if answer is None:
                        if boolean:
                            row.append(0)
                        else:
                            row.append("")
                    else:
                        if type(answer) == int:
                            answer = str(answer)
                        if options[i] in answer:
                            if boolean:
                                row.append(1)
                            else:
                                row.append(options[i])
                        else:
                            if boolean:
                                row.append(0)
                            else:
                                row.append("")
            row.append(self.checkCompletedFor(user) and "Completed" or "Not Completed")
            sheet.writerow(row)
        return data.getvalue()

    # TODO next two methods are still needed for the tests,
    # but should be removed and the tests fixed
    security.declareProtected(permissions.ModifyPortalContent, "openFile")

    def openFile(self):
        """open the file, and return the file contents"""
        data_path = os.path.abspath("import")
        try:
            data_catch = open(data_path + "/user_import", "rU")
        except IOError:  # file does not exist, or path is wrong
            try:
                # we might be in foreground mode
                data_path = os.path.abspath("../import")
                data_catch = open(data_path + "/user_import", "rU")
            except IOError:  # file does not exist, or path is wrong
                return "File does not exist"
        input = data_catch.read()
        data_catch.close()
        return input

    security.declareProtected(permissions.ModifyPortalContent, "uploadRespondents")

    def uploadRespondents(self, input=None):
        """upload the respondents"""
        if input is None:
            input = self.openFile()
        input = input.split("\n")
        errors = []
        for user in input:
            if not user:  # empty line
                continue
            user_details = user.split("|")
            if not self.addAuthenticatedRespondent(user_details[1], fullname=user_details[0]):
                errors.append(user)
        return errors

    security.declareProtected(permissions.View, "collective_recaptcha_enabled")

    def collective_recaptcha_enabled(self):
        if using_collective_recaptcha:
            try:
                settings = getRecaptchaSettings()
            except TypeError:
                # collective.recaptcha not configured
                return False
            if settings.public_key and settings.private_key:
                return True
        return False

    security.declareProtected(permissions.ModifyPortalContent, "pre_validate")

    def pre_validate(self, REQUEST, errors):
        """ checks captcha """
        product_installed = self.portal_quickinstaller.isProductInstalled("quintagroup.plonecaptchas")
        if not product_installed and not self.collective_recaptcha_enabled() and REQUEST.get("showCaptcha", 0):
            if int(REQUEST.get("showCaptcha")):
                errors["showCaptcha"] = _(
                    "showCaptcha",
                    default="Product quintagroup.plonecaptchas not installed. "
                    "If you prefer to use the product collective.recaptcha instead of quintagroup.plonecaptchas "
                    "then verifies that recaptcha private and public keys are configured. "
                    "Go to path/to/site/@@recaptcha-settings to configure.",
                )

    security.declareProtected(permissions.View, "isCaptchaInstalled")

    def isCaptchaInstalled(self):
        """ checks captcha """
        product_installed = self.portal_quickinstaller.isProductInstalled("quintagroup.plonecaptchas")
        return product_installed

    security.declarePrivate("_get_emailInvite_default")

    def _get_emailInvite_default(self):
        foo = _("emailInviteDefault", default=DEFAULT_SURVEY_INVITE)
        translation_service = getToolByName(self, "translation_service")
        return translation_service.utranslate(
            domain="plonesurvey", msgid="emailInviteDefault", default=DEFAULT_SURVEY_INVITE, context=self
        )
Example #31
0
class UnIndex(SimpleItem):

    """Simple forward and reverse index.
    """
    implements(ILimitedResultIndex, IUniqueValueIndex, ISortIndex)
    _counter = None

    def __init__(self, id, ignore_ex=None, call_methods=None,
                 extra=None, caller=None):
        """Create an unindex

        UnIndexes are indexes that contain two index components, the
        forward index (like plain index objects) and an inverted
        index.  The inverted index is so that objects can be unindexed
        even when the old value of the object is not known.

        e.g.

        self._index = {datum:[documentId1, documentId2]}
        self._unindex = {documentId:datum}

        The arguments are:

          'id' -- the name of the item attribute to index.  This is
          either an attribute name or a record key.

          'ignore_ex' -- should be set to true if you want the index
          to ignore exceptions raised while indexing instead of
          propagating them.

          'call_methods' -- should be set to true if you want the index
          to call the attribute 'id' (note: 'id' should be callable!)
          You will also need to pass in an object in the index and
          uninded methods for this to work.

          'extra' -- a mapping object that keeps additional
          index-related parameters - subitem 'indexed_attrs'
          can be string with comma separated attribute names or
          a list

          'caller' -- reference to the calling object (usually
          a (Z)Catalog instance
        """

        def _get(o, k, default):
            """ return a value for a given key of a dict/record 'o' """
            if isinstance(o, dict):
                return o.get(k, default)
            else:
                return getattr(o, k, default)

        self.id = id
        self.ignore_ex = ignore_ex  # currently unimplemented
        self.call_methods = call_methods

        self.operators = ('or', 'and')
        self.useOperator = 'or'

        # allow index to index multiple attributes
        ia = _get(extra, 'indexed_attrs', id)
        if isinstance(ia, str):
            self.indexed_attrs = ia.split(',')
        else:
            self.indexed_attrs = list(ia)
        self.indexed_attrs = [
            attr.strip() for attr in self.indexed_attrs if attr]
        if not self.indexed_attrs:
            self.indexed_attrs = [id]

        self.clear()

    def __len__(self):
        return self._length()

    def getId(self):
        return self.id

    def clear(self):
        self._length = Length()
        self._index = OOBTree()
        self._unindex = IOBTree()
        self._counter = Length()

    def __nonzero__(self):
        return not not self._unindex

    def histogram(self):
        """Return a mapping which provides a histogram of the number of
        elements found at each point in the index.
        """
        histogram = {}
        for item in self._index.items():
            if isinstance(item, int):
                entry = 1  # "set" length is 1
            else:
                key, value = item
                entry = len(value)
            histogram[entry] = histogram.get(entry, 0) + 1
        return histogram

    def referencedObjects(self):
        """Generate a list of IDs for which we have referenced objects."""
        return self._unindex.keys()

    def getEntryForObject(self, documentId, default=_marker):
        """Takes a document ID and returns all the information we have
        on that specific object.
        """
        if default is _marker:
            return self._unindex.get(documentId)
        return self._unindex.get(documentId, default)

    def removeForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and remove any reference to documentId
        in its entry in the index.
        """
        indexRow = self._index.get(entry, _marker)
        if indexRow is not _marker:
            try:
                indexRow.remove(documentId)
                if not indexRow:
                    del self._index[entry]
                    self._length.change(-1)
            except ConflictError:
                raise
            except AttributeError:
                # index row is an int
                try:
                    del self._index[entry]
                except KeyError:
                    # XXX swallow KeyError because it was probably
                    # removed and then _length AttributeError raised
                    pass
                if isinstance(self.__len__, Length):
                    self._length = self.__len__
                    del self.__len__
                self._length.change(-1)
            except Exception:
                LOG.error('%s: unindex_object could not remove '
                          'documentId %s from index %s.  This '
                          'should not happen.' %
                          (self.__class__.__name__,
                           str(documentId), str(self.id)),
                          exc_info=sys.exc_info())
        else:
            LOG.error('%s: unindex_object tried to retrieve set %s '
                      'from index %s but couldn\'t.  This '
                      'should not happen.' %
                      (self.__class__.__name__,
                       repr(entry), str(self.id)))

    def insertForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and put it in the correct place
        in the forward index.

        This will also deal with creating the entire row if necessary.
        """
        indexRow = self._index.get(entry, _marker)

        # Make sure there's actually a row there already. If not, create
        # a set and stuff it in first.
        if indexRow is _marker:
            # We always use a set to avoid getting conflict errors on
            # multiple threads adding a new row at the same time
            self._index[entry] = IITreeSet((documentId, ))
            self._length.change(1)
        else:
            try:
                indexRow.insert(documentId)
            except AttributeError:
                # Inline migration: index row with one element was an int at
                # first (before Zope 2.13).
                indexRow = IITreeSet((indexRow, documentId))
                self._index[entry] = indexRow

    def index_object(self, documentId, obj, threshold=None):
        """ wrapper to handle indexing of multiple attributes """

        fields = self.getIndexSourceNames()
        res = 0
        for attr in fields:
            res += self._index_object(documentId, obj, threshold, attr)

        if res > 0:
            self._increment_counter()

        return res > 0

    def _index_object(self, documentId, obj, threshold=None, attr=''):
        """ index and object 'obj' with integer id 'documentId'"""
        returnStatus = 0

        # First we need to see if there's anything interesting to look at
        datum = self._get_object_datum(obj, attr)
        if datum is None:
            # Prevent None from being indexed. None doesn't have a valid
            # ordering definition compared to any other object.
            # BTrees 4.0+ will throw a TypeError
            # "object has default comparison" and won't let it be indexed.
            raise TypeError('None cannot be indexed.')

        # We don't want to do anything that we don't have to here, so we'll
        # check to see if the new and existing information is the same.
        oldDatum = self._unindex.get(documentId, _marker)
        if datum != oldDatum:
            if oldDatum is not _marker:
                self.removeForwardIndexEntry(oldDatum, documentId)
                if datum is _marker:
                    try:
                        del self._unindex[documentId]
                    except ConflictError:
                        raise
                    except Exception:
                        LOG.error('Should not happen: oldDatum was there, '
                                  'now its not, for document: %s' % documentId)

            if datum is not _marker:
                self.insertForwardIndexEntry(datum, documentId)
                self._unindex[documentId] = datum

            returnStatus = 1

        return returnStatus

    def _get_object_datum(self, obj, attr):
        # self.id is the name of the index, which is also the name of the
        # attribute we're interested in.  If the attribute is callable,
        # we'll do so.
        try:
            datum = getattr(obj, attr)
            if safe_callable(datum):
                datum = datum()
        except (AttributeError, TypeError):
            datum = _marker
        return datum

    def _increment_counter(self):
        if self._counter is None:
            self._counter = Length()
        self._counter.change(1)

    def getCounter(self):
        """Return a counter which is increased on index changes"""
        return self._counter is not None and self._counter() or 0

    def numObjects(self):
        """Return the number of indexed objects."""
        return len(self._unindex)

    def indexSize(self):
        """Return the size of the index in terms of distinct values."""
        return len(self)

    def unindex_object(self, documentId):
        """ Unindex the object with integer id 'documentId' and don't
        raise an exception if we fail
        """
        unindexRecord = self._unindex.get(documentId, _marker)
        if unindexRecord is _marker:
            return None

        self._increment_counter()

        self.removeForwardIndexEntry(unindexRecord, documentId)
        try:
            del self._unindex[documentId]
        except ConflictError:
            raise
        except Exception:
            LOG.debug('Attempt to unindex nonexistent document'
                      ' with id %s' % documentId, exc_info=True)

    def _apply_not(self, not_parm, resultset=None):
        index = self._index
        setlist = []
        for k in not_parm:
            s = index.get(k, None)
            if s is None:
                continue
            elif isinstance(s, int):
                s = IISet((s, ))
            setlist.append(s)
        return multiunion(setlist)

    def _convert(self, value, default=None):
        return value

    def _apply_index(self, request, resultset=None):
        """Apply the index to query parameters given in the request arg.

        The request argument should be a mapping object.

        If the request does not have a key which matches the "id" of
        the index instance, then None is returned.

        If the request *does* have a key which matches the "id" of
        the index instance, one of a few things can happen:

          - if the value is a blank string, None is returned (in
            order to support requests from web forms where
            you can't tell a blank string from empty).

          - if the value is a nonblank string, turn the value into
            a single-element sequence, and proceed.

          - if the value is a sequence, return a union search.

          - If the value is a dict and contains a key of the form
            '<index>_operator' this overrides the default method
            ('or') to combine search results. Valid values are "or"
            and "and".

        If None is not returned as a result of the abovementioned
        constraints, two objects are returned.  The first object is a
        ResultSet containing the record numbers of the matching
        records.  The second object is a tuple containing the names of
        all data fields used.

        FAQ answer:  to search a Field Index for documents that
        have a blank string as their value, wrap the request value
        up in a tuple ala: request = {'id':('',)}
        """
        record = parseIndexRequest(request, self.id, self.query_options)
        if record.keys is None:
            return None

        index = self._index
        r = None
        opr = None

        # not / exclude parameter
        not_parm = record.get('not', None)
        if not record.keys and not_parm:
            # convert into indexed format
            not_parm = map(self._convert, not_parm)
            # we have only a 'not' query
            record.keys = [k for k in index.keys() if k not in not_parm]
        else:
            # convert query arguments into indexed format
            record.keys = map(self._convert, record.keys)

        # experimental code for specifing the operator
        operator = record.get('operator', self.useOperator)
        if not operator in self.operators:
            raise RuntimeError("operator not valid: %s" % escape(operator))

        # Range parameter
        range_parm = record.get('range', None)
        if range_parm:
            opr = "range"
            opr_args = []
            if range_parm.find("min") > -1:
                opr_args.append("min")
            if range_parm.find("max") > -1:
                opr_args.append("max")

        if record.get('usage', None):
            # see if any usage params are sent to field
            opr = record.usage.lower().split(':')
            opr, opr_args = opr[0], opr[1:]

        if opr == "range":  # range search
            if 'min' in opr_args:
                lo = min(record.keys)
            else:
                lo = None
            if 'max' in opr_args:
                hi = max(record.keys)
            else:
                hi = None
            if hi:
                setlist = index.values(lo, hi)
            else:
                setlist = index.values(lo)

            # If we only use one key, intersect and return immediately
            if len(setlist) == 1:
                result = setlist[0]
                if isinstance(result, int):
                    result = IISet((result,))
                if not_parm:
                    exclude = self._apply_not(not_parm, resultset)
                    result = difference(result, exclude)
                return result, (self.id,)

            if operator == 'or':
                tmp = []
                for s in setlist:
                    if isinstance(s, int):
                        s = IISet((s,))
                    tmp.append(s)
                r = multiunion(tmp)
            else:
                # For intersection, sort with smallest data set first
                tmp = []
                for s in setlist:
                    if isinstance(s, int):
                        s = IISet((s,))
                    tmp.append(s)
                if len(tmp) > 2:
                    setlist = sorted(tmp, key=len)
                else:
                    setlist = tmp
                r = resultset
                for s in setlist:
                    # the result is bound by the resultset
                    r = intersection(r, s)

        else:  # not a range search
            # Filter duplicates
            setlist = []
            for k in record.keys:
                if k is None:
                    raise TypeError('None cannot be in an index.')
                s = index.get(k, None)
                # If None, try to bail early
                if s is None:
                    if operator == 'or':
                        # If union, we can't possibly get a bigger result
                        continue
                    # If intersection, we can't possibly get a smaller result
                    return IISet(), (self.id,)
                elif isinstance(s, int):
                    s = IISet((s,))
                setlist.append(s)

            # If we only use one key return immediately
            if len(setlist) == 1:
                result = setlist[0]
                if isinstance(result, int):
                    result = IISet((result,))
                if not_parm:
                    exclude = self._apply_not(not_parm, resultset)
                    result = difference(result, exclude)
                return result, (self.id,)

            if operator == 'or':
                # If we already get a small result set passed in, intersecting
                # the various indexes with it and doing the union later is
                # faster than creating a multiunion first.
                if resultset is not None and len(resultset) < 200:
                    smalllist = []
                    for s in setlist:
                        smalllist.append(intersection(resultset, s))
                    r = multiunion(smalllist)
                else:
                    r = multiunion(setlist)
            else:
                # For intersection, sort with smallest data set first
                if len(setlist) > 2:
                    setlist = sorted(setlist, key=len)
                r = resultset
                for s in setlist:
                    r = intersection(r, s)

        if isinstance(r, int):
            r = IISet((r, ))
        if r is None:
            return IISet(), (self.id,)
        if not_parm:
            exclude = self._apply_not(not_parm, resultset)
            r = difference(r, exclude)
        return r, (self.id,)

    def hasUniqueValuesFor(self, name):
        """has unique values for column name"""
        if name == self.id:
            return 1
        return 0

    def getIndexSourceNames(self):
        """ return sequence of indexed attributes """
        # BBB:  older indexes didn't have 'indexed_attrs'
        return getattr(self, 'indexed_attrs', [self.id])

    def getIndexQueryNames(self):
        """Indicate that this index applies to queries for the index's name."""
        return (self.id,)

    def uniqueValues(self, name=None, withLengths=0):
        """returns the unique values for name

        if withLengths is true, returns a sequence of
        tuples of (value, length)
        """
        if name is None:
            name = self.id
        elif name != self.id:
            raise StopIteration

        if not withLengths:
            for key in self._index.keys():
                yield key
        else:
            for key, value in self._index.items():
                if isinstance(value, int):
                    yield (key, 1)
                else:
                    yield (key, len(value))

    def keyForDocument(self, id):
        # This method is superseded by documentToKeyMap
        return self._unindex[id]

    def documentToKeyMap(self):
        return self._unindex

    def items(self):
        items = []
        for k, v in self._index.items():
            if isinstance(v, int):
                v = IISet((v,))
            items.append((k, v))
        return items
Example #32
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)
Example #33
0
class MemberDataTool(UniqueObject, SimpleItem, PropertyManager,
                     ActionProviderBase):
    """ This tool wraps user objects, making them act as Member objects.
    """

    __implements__ = (IMemberDataTool, ActionProviderBase.__implements__)

    id = 'portal_memberdata'
    meta_type = 'CMF Member Data Tool'
    _actions = ()

    _v_temps = None
    _properties = ()

    security = ClassSecurityInfo()

    manage_options = (ActionProviderBase.manage_options +
                      ({
                          'label': 'Overview',
                          'action': 'manage_overview'
                      }, {
                          'label': 'Contents',
                          'action': 'manage_showContents'
                      }) + PropertyManager.manage_options +
                      SimpleItem.manage_options)

    #
    #   ZMI methods
    #
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = DTMLFile('explainMemberDataTool', _dtmldir)

    security.declareProtected(ViewManagementScreens, 'manage_showContents')
    manage_showContents = DTMLFile('memberdataContents', _dtmldir)

    def __init__(self):
        self._members = OOBTree()
        # Create the default properties.
        self._setProperty('email', '', 'string')
        self._setProperty('portal_skin', '', 'string')
        self._setProperty('listed', '', 'boolean')
        self._setProperty('login_time', '2000/01/01', 'date')
        self._setProperty('last_login_time', '2000/01/01', 'date')

    #
    #   'portal_memberdata' interface methods
    #
    security.declarePrivate('getMemberDataContents')

    def getMemberDataContents(self):
        '''
        Return the number of members stored in the _members
        BTree and some other useful info
        '''
        membertool = getToolByName(self, 'portal_membership')
        members = self._members
        user_list = membertool.listMemberIds()
        member_list = members.keys()
        member_count = len(members)
        orphan_count = 0

        for member in member_list:
            if member not in user_list:
                orphan_count = orphan_count + 1

        return [{'member_count': member_count, 'orphan_count': orphan_count}]

    security.declarePrivate('searchMemberData')

    def searchMemberData(self, search_param, search_term, attributes=()):
        """ Search members. """
        res = []

        if not search_param:
            return res

        membership = getToolByName(self, 'portal_membership')

        if len(attributes) == 0:
            attributes = ('id', 'email')

        if search_param == 'username':
            search_param = 'id'

        for user_id in self._members.keys():
            u = membership.getMemberById(user_id)

            if u is not None:
                memberProperty = u.getProperty
                searched = memberProperty(search_param, None)

                if searched is not None and searched.find(search_term) != -1:
                    user_data = {}

                    for desired in attributes:
                        if desired == 'id':
                            user_data['username'] = memberProperty(desired, '')
                        else:
                            user_data[desired] = memberProperty(desired, '')

                    res.append(user_data)

        return res

    security.declarePrivate('searchMemberDataContents')

    def searchMemberDataContents(self, search_param, search_term):
        """ Search members. This method will be deprecated soon. """
        res = []

        if search_param == 'username':
            search_param = 'id'

        mtool = getToolByName(self, 'portal_membership')

        for member_id in self._members.keys():

            user_wrapper = mtool.getMemberById(member_id)

            if user_wrapper is not None:
                memberProperty = user_wrapper.getProperty
                searched = memberProperty(search_param, None)

                if searched is not None and searched.find(search_term) != -1:

                    res.append({
                        'username': memberProperty('id'),
                        'email': memberProperty('email', '')
                    })
        return res

    security.declarePrivate('pruneMemberDataContents')

    def pruneMemberDataContents(self):
        """ Delete data contents of all members not listet in acl_users.
        """
        membertool = getToolByName(self, 'portal_membership')
        members = self._members
        user_list = membertool.listMemberIds()

        for tuple in members.items():
            member_name = tuple[0]
            member_obj = tuple[1]
            if member_name not in user_list:
                del members[member_name]

    security.declarePrivate('wrapUser')

    def wrapUser(self, u):
        '''
        If possible, returns the Member object that corresponds
        to the given User object.
        '''
        id = u.getId()
        members = self._members
        if not members.has_key(id):
            # Get a temporary member that might be
            # registered later via registerMemberData().
            temps = self._v_temps
            if temps is not None and temps.has_key(id):
                m = temps[id]
            else:
                base = aq_base(self)
                m = MemberData(base, id)
                if temps is None:
                    self._v_temps = {id: m}
                    if hasattr(self, 'REQUEST'):
                        # No REQUEST during tests.
                        self.REQUEST._hold(CleanupTemp(self))
                else:
                    temps[id] = m
        else:
            m = members[id]
        # Return a wrapper with self as containment and
        # the user as context.
        return m.__of__(self).__of__(u)

    security.declarePrivate('registerMemberData')

    def registerMemberData(self, m, id):
        """ Add the given member data to the _members btree.
        """
        self._members[id] = aq_base(m)

    security.declarePrivate('deleteMemberData')

    def deleteMemberData(self, member_id):
        """ Delete member data of specified member.
        """
        members = self._members
        if members.has_key(member_id):
            del members[member_id]
            return 1
        else:
            return 0
Example #34
0
class Package(SubscriptableBaseModel):
    pypi_url = 'http://pypi.python.org/pypi/{}/json'
    subobjects_attr = 'releases'

    def __init__(self, name):
        self.__name__ = name
        self.name = name
        self.releases = OOBTree()

    def __iter__(self):
        return (self.releases[release] for release in self.releases)

    def __getitem__(self, release_name_or_index):
        if isinstance(release_name_or_index, int):
            return next(
                itertools.islice(self.__iter__(), release_name_or_index,
                                 release_name_or_index + 1))
        return self.releases[release_name_or_index]

    def __setitem__(self, key, value):
        key = format_key(key)
        self.releases[key] = value
        self.releases[key].__parent__ = self

    def __delitem__(self, key):
        key = format_key(key)
        del (self.releases[key])

    @classmethod
    @cache_region('pypi', 'get_last_remote_filename')
    def get_last_remote_version(cls, proxy, package_name):
        logger.debug('Not in cache')
        if not proxy:
            return None
        try:
            result = requests.get(
                'http://pypi.python.org/pypi/{}/json'.format(package_name))
            if not result.status_code == 200:
                return None
            result = json.loads(result.content.decode('utf-8'))
            return result['info']['version']
        except ConnectionError:
            pass
        return None

    def repository_is_up_to_date(self, last_remote_release):
        if not last_remote_release:
            return True
        remote_version = parse_version(last_remote_release)

        local_versions = [
            release.version for release in self.releases.values()
        ]
        for version in local_versions:
            if parse_version(version) >= remote_version:
                return True
        return False

    @classmethod
    def by_name(cls, name, request):
        root = repository_root_factory(request)
        return root[name] if name in root else None

    def get_last_release(self):
        if not len(self.releases.items()):
            return None
        max_version = max(
            [parse_version(version) for version in self.releases.keys()])
        for version, release in self.releases.items():
            if parse_version(version) == max_version:
                return release

    @property
    def metadata(self):
        last_release = self.get_last_release()
        if last_release:
            return last_release.metadata
        else:
            return {}
Example #35
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]
Example #36
0
class TopicIndex(Persistent, SimpleItem):
    """A TopicIndex maintains a set of FilteredSet objects.

    Every FilteredSet object consists of an expression and and IISet with all
    Ids of indexed objects that eval with this expression to 1.
    """
    implements(ITopicIndex, IPluggableIndex)

    meta_type = "TopicIndex"
    query_options = ('query', 'operator')

    manage_options = ({'label': 'FilteredSets', 'action': 'manage_main'}, )

    def __init__(self, id, caller=None):
        self.id = id
        self.filteredSets = OOBTree()
        self.operators = ('or', 'and')
        self.defaultOperator = 'or'

    def getId(self):
        return self.id

    def clear(self):
        for fs in self.filteredSets.values():
            fs.clear()

    def index_object(self, docid, obj, threshold=100):
        """ hook for (Z)Catalog """
        for fid, filteredSet in self.filteredSets.items():
            filteredSet.index_object(docid, obj)
        return 1

    def unindex_object(self, docid):
        """ hook for (Z)Catalog """
        for fs in self.filteredSets.values():
            try:
                fs.unindex_object(docid)
            except KeyError:
                LOG.debug('Attempt to unindex document'
                          ' with id %s failed' % docid)
        return 1

    def numObjects(self):
        """Return the number of indexed objects."""
        return "n/a"

    def indexSize(self):
        """Return the size of the index in terms of distinct values."""
        return "n/a"

    def search(self, filter_id):
        f = self.filteredSets.get(filter_id, None)
        if f is not None:
            return f.getIds()

    def _apply_index(self, request):
        """hook for (Z)Catalog
        'request' --  mapping type (usually {"topic": "..." }
        """
        record = parseIndexRequest(request, self.id, self.query_options)
        if record.keys is None:
            return None

        operator = record.get('operator', self.defaultOperator).lower()
        if operator == 'or':
            set_func = union
        else:
            set_func = intersection

        res = None
        for filter_id in record.keys:
            rows = self.search(filter_id)
            res = set_func(res, rows)

        if res:
            return res, (self.id, )
        else:
            return IITreeSet(), (self.id, )

    def uniqueValues(self, name=None, withLength=0):
        """ needed to be consistent with the interface """
        return self.filteredSets.keys()

    def getEntryForObject(self, docid, default=_marker):
        """ Takes a document ID and returns all the information we have
            on that specific object.
        """
        return self.filteredSets.keys()

    def addFilteredSet(self, filter_id, typeFilteredSet, expr):
        # Add a FilteredSet object.
        if filter_id in self.filteredSets:
            raise KeyError('A FilteredSet with this name already exists: %s' %
                           filter_id)
        self.filteredSets[filter_id] = factory(
            filter_id,
            typeFilteredSet,
            expr,
        )

    def delFilteredSet(self, filter_id):
        # Delete the FilteredSet object specified by 'filter_id'.
        if filter_id not in self.filteredSets:
            raise KeyError('no such FilteredSet:  %s' % filter_id)
        del self.filteredSets[filter_id]

    def clearFilteredSet(self, filter_id):
        # Clear the FilteredSet object specified by 'filter_id'.
        f = self.filteredSets.get(filter_id, None)
        if f is None:
            raise KeyError('no such FilteredSet: %s' % filter_id)
        f.clear()

    def manage_addFilteredSet(self, filter_id, typeFilteredSet, expr, URL1, \
            REQUEST=None, RESPONSE=None):
        """ add a new filtered set """

        if len(filter_id) == 0:
            raise RuntimeError('Length of ID too short')
        if len(expr) == 0:
            raise RuntimeError('Length of expression too short')

        self.addFilteredSet(filter_id, typeFilteredSet, expr)

        if RESPONSE:
            RESPONSE.redirect(URL1 + '/manage_workspace?'
                              'manage_tabs_message=FilteredSet%20added')

    def manage_delFilteredSet(self,
                              filter_ids=[],
                              URL1=None,
                              REQUEST=None,
                              RESPONSE=None):
        """ delete a list of FilteredSets"""

        for filter_id in filter_ids:
            self.delFilteredSet(filter_id)

        if RESPONSE:
            RESPONSE.redirect(URL1 + '/manage_workspace?'
                              'manage_tabs_message=FilteredSet(s)%20deleted')

    def manage_saveFilteredSet(self,
                               filter_id,
                               expr,
                               URL1=None,
                               REQUEST=None,
                               RESPONSE=None):
        """ save expression for a FilteredSet """

        self.filteredSets[filter_id].setExpression(expr)

        if RESPONSE:
            RESPONSE.redirect(URL1 + '/manage_workspace?'
                              'manage_tabs_message=FilteredSet(s)%20updated')

    def getIndexSourceNames(self):
        """ return names of indexed attributes """
        return ('n/a', )

    def getIndexQueryNames(self):
        return (self.id, )

    def manage_clearFilteredSet(self,
                                filter_ids=[],
                                URL1=None,
                                REQUEST=None,
                                RESPONSE=None):
        """  clear a list of FilteredSets"""

        for filter_id in filter_ids:
            self.clearFilteredSet(filter_id)

        if RESPONSE:
            RESPONSE.redirect(URL1 + '/manage_workspace?'
                              'manage_tabs_message=FilteredSet(s)%20cleared')

    manage = manage_main = DTMLFile('dtml/manageTopicIndex', globals())
    manage_main._setName('manage_main')
    editFilteredSet = DTMLFile('dtml/editFilteredSet', globals())
        elif len(str(value)) > 1:
            for j in range(len(value)):
                list_rid.append(value[j])
        '''
        value.append(list2[j])
        #list_rid.append(list2[i])
        k.insert(list6[j], value)
'''
t.insert('a', location1)
t.insert('c', location2)
t.insert('d', location3)
t.insert('b', location4)
#t.insert(('y', 3), 10)

'''
s = t.keys()
d = t.values()

#t.__delitem__('ai')

#t.__setitem__(location1, 'y')
'''
if t.has_key(("zi", 3)):
    print("today")
else:
    print("yesterday")

list3 = []
list3 = t.get(("ai", 1), -1)

Example #38
0
class BTreeFolder2Base(Persistent):
    """Base for BTree-based folders.
    """

    security = ClassSecurityInfo()

    manage_options = (({
        'label': 'Contents',
        'action': 'manage_main'
    }, ) + Folder.manage_options[1:])

    security.declareProtected(view_management_screens, 'manage_main')
    manage_main = DTMLFile('contents', globals())

    _tree = None  # OOBTree: { id -> object }
    _count = None  # A BTrees.Length
    _v_nextid = 0  # The integer component of the next generated ID
    _mt_index = None  # OOBTree: { meta_type -> OIBTree: { id -> 1 } }
    title = ''

    # superValues() looks for the _objects attribute, but the implementation
    # would be inefficient, so superValues() support is disabled.
    _objects = ()

    def __init__(self, id=None):
        if id is not None:
            self.id = id
        self._initBTrees()

    def _initBTrees(self):
        self._tree = OOBTree()
        self._count = Length()
        self._mt_index = OOBTree()

    def _populateFromFolder(self, source):
        """Fill this folder with the contents of another folder.
        """
        for name in source.objectIds():
            value = source._getOb(name, None)
            if value is not None:
                self._setOb(name, aq_base(value))

    security.declareProtected(view_management_screens, 'manage_fixCount')

    def manage_fixCount(self):
        """Calls self._fixCount() and reports the result as text.
        """
        old, new = self._fixCount()
        path = '/'.join(self.getPhysicalPath())
        if old == new:
            return "No count mismatch detected in BTreeFolder2 at %s." % path
        else:
            return ("Fixed count mismatch in BTreeFolder2 at %s. "
                    "Count was %d; corrected to %d" % (path, old, new))

    def _fixCount(self):
        """Checks if the value of self._count disagrees with
        len(self.objectIds()). If so, corrects self._count. Returns the
        old and new count values. If old==new, no correction was
        performed.
        """
        old = self._count()
        new = len(self.objectIds())
        if old != new:
            self._count.set(new)
        return old, new

    security.declareProtected(view_management_screens, 'manage_cleanup')

    def manage_cleanup(self):
        """Calls self._cleanup() and reports the result as text.
        """
        v = self._cleanup()
        path = '/'.join(self.getPhysicalPath())
        if v:
            return "No damage detected in BTreeFolder2 at %s." % path
        else:
            return ("Fixed BTreeFolder2 at %s.  "
                    "See the log for more details." % path)

    def _cleanup(self):
        """Cleans up errors in the BTrees.

        Certain ZODB bugs have caused BTrees to become slightly insane.
        Fortunately, there is a way to clean up damaged BTrees that
        always seems to work: make a new BTree containing the items()
        of the old one.

        Returns 1 if no damage was detected, or 0 if damage was
        detected and fixed.
        """
        from BTrees.check import check
        path = '/'.join(self.getPhysicalPath())
        try:
            check(self._tree)
            for key in self._tree.keys():
                if key not in self._tree:
                    raise AssertionError("Missing value for key: %s" %
                                         repr(key))
            check(self._mt_index)
            for key, value in self._mt_index.items():
                if (key not in self._mt_index
                        or self._mt_index[key] is not value):
                    raise AssertionError(
                        "Missing or incorrect meta_type index: %s" % repr(key))
                check(value)
                for k in value.keys():
                    if k not in value:
                        raise AssertionError(
                            "Missing values for meta_type index: %s" %
                            repr(key))
            return 1
        except AssertionError:
            LOG.warn('Detected damage to %s. Fixing now.' % path,
                     exc_info=sys.exc_info())
            try:
                self._tree = OOBTree(self._tree)
                mt_index = OOBTree()
                for key, value in self._mt_index.items():
                    mt_index[key] = OIBTree(value)
                self._mt_index = mt_index
            except:
                LOG.error('Failed to fix %s.' % path, exc_info=sys.exc_info())
                raise
            else:
                LOG.info('Fixed %s.' % path)
            return 0

    def _getOb(self, id, default=_marker):
        """Return the named object from the folder.
        """
        tree = self._tree
        if default is _marker:
            ob = tree[id]
            return ob.__of__(self)
        else:
            ob = tree.get(id, _marker)
            if ob is _marker:
                return default
            else:
                return ob.__of__(self)

    security.declareProtected(access_contents_information, 'get')

    def get(self, name, default=None):
        return self._getOb(name, default)

    def __getitem__(self, name):
        return self._getOb(name)

    def __getattr__(self, name):
        # Boo hoo hoo!  Zope 2 prefers implicit acquisition over traversal
        # to subitems, and __bobo_traverse__ hooks don't work with
        # restrictedTraverse() unless __getattr__() is also present.
        # Oh well.
        res = self._tree.get(name)
        if res is None:
            raise AttributeError(name)
        return res

    def _setOb(self, id, object):
        """Store the named object in the folder.
        """
        tree = self._tree
        if id in tree:
            raise KeyError('There is already an item named "%s".' % id)
        tree[id] = object
        self._count.change(1)
        # Update the meta type index.
        mti = self._mt_index
        meta_type = getattr(object, 'meta_type', None)
        if meta_type is not None:
            ids = mti.get(meta_type, None)
            if ids is None:
                ids = OIBTree()
                mti[meta_type] = ids
            ids[id] = 1

    def _delOb(self, id):
        """Remove the named object from the folder.
        """
        tree = self._tree
        meta_type = getattr(tree[id], 'meta_type', None)
        del tree[id]
        self._count.change(-1)
        # Update the meta type index.
        if meta_type is not None:
            mti = self._mt_index
            ids = mti.get(meta_type, None)
            if ids is not None and id in ids:
                del ids[id]
                if not ids:
                    # Removed the last object of this meta_type.
                    # Prune the index.
                    del mti[meta_type]

    security.declareProtected(view_management_screens, 'getBatchObjectListing')

    def getBatchObjectListing(self, REQUEST=None):
        """Return a structure for a page template to show the list of objects.
        """
        if REQUEST is None:
            REQUEST = {}
        pref_rows = int(REQUEST.get('dtpref_rows', 20))
        b_start = int(REQUEST.get('b_start', 1))
        b_count = int(REQUEST.get('b_count', 1000))
        b_end = b_start + b_count - 1
        url = self.absolute_url() + '/manage_main'
        idlist = self.objectIds()  # Pre-sorted.
        count = self.objectCount()

        if b_end < count:
            next_url = url + '?b_start=%d' % (b_start + b_count)
        else:
            b_end = count
            next_url = ''

        if b_start > 1:
            prev_url = url + '?b_start=%d' % max(b_start - b_count, 1)
        else:
            prev_url = ''

        formatted = []
        formatted.append(listtext0 % pref_rows)
        for i in range(b_start - 1, b_end):
            optID = escape(idlist[i])
            formatted.append(listtext1 % (escape(optID, quote=1), optID))
        formatted.append(listtext2)
        return {
            'b_start': b_start,
            'b_end': b_end,
            'prev_batch_url': prev_url,
            'next_batch_url': next_url,
            'formatted_list': ''.join(formatted)
        }

    security.declareProtected(view_management_screens,
                              'manage_object_workspace')

    def manage_object_workspace(self, ids=(), REQUEST=None):
        '''Redirects to the workspace of the first object in
        the list.'''
        if ids and REQUEST is not None:
            REQUEST.RESPONSE.redirect('%s/%s/manage_workspace' %
                                      (self.absolute_url(), quote(ids[0])))
        else:
            return self.manage_main(self, REQUEST)

    security.declareProtected(access_contents_information, 'tpValues')

    def tpValues(self):
        """Ensures the items don't show up in the left pane.
        """
        return ()

    security.declareProtected(access_contents_information, 'objectCount')

    def objectCount(self):
        """Returns the number of items in the folder."""
        return self._count()

    def __len__(self):
        return self.objectCount()

    def __nonzero__(self):
        return True

    security.declareProtected(access_contents_information, 'has_key')

    def has_key(self, id):
        """Indicates whether the folder has an item by ID.
        """
        return id in self._tree

    # backward compatibility
    hasObject = has_key

    security.declareProtected(access_contents_information, 'objectIds')

    def objectIds(self, spec=None):
        # Returns a list of subobject ids of the current object.
        # If 'spec' is specified, returns objects whose meta_type
        # matches 'spec'.

        if spec is None:
            return self._tree.keys()

        if isinstance(spec, str):
            spec = [spec]

        set = None
        mti = self._mt_index
        for meta_type in spec:
            ids = mti.get(meta_type, None)
            if ids is not None:
                set = union(set, ids)
        if set is None:
            return ()
        else:
            return set.keys()

    security.declareProtected(access_contents_information, 'keys')

    def keys(self):
        return self._tree.keys()

    def __contains__(self, name):
        return name in self._tree

    def __iter__(self):
        return iter(self.objectIds())

    security.declareProtected(access_contents_information, 'objectValues')

    def objectValues(self, spec=None):
        # Returns a list of actual subobjects of the current object.
        # If 'spec' is specified, returns only objects whose meta_type
        # match 'spec'.
        if spec is None:
            return LazyMap(self._getOb, self._tree.keys())
        return LazyMap(self._getOb, self.objectIds(spec))

    security.declareProtected(access_contents_information, 'values')

    def values(self):
        return LazyMap(self._getOb, self._tree.keys())

    security.declareProtected(access_contents_information, 'objectItems')

    def objectItems(self, spec=None):
        # Returns a list of (id, subobject) tuples of the current object.
        # If 'spec' is specified, returns only objects whose meta_type match
        # 'spec'
        if spec is None:
            return LazyMap(lambda id, _getOb=self._getOb: (id, _getOb(id)),
                           self._tree.keys())
        return LazyMap(lambda id, _getOb=self._getOb: (id, _getOb(id)),
                       self.objectIds(spec))

    security.declareProtected(access_contents_information, 'items')

    def items(self):
        return LazyMap(lambda id, _getOb=self._getOb: (id, _getOb(id)),
                       self._tree.keys())

    security.declareProtected(access_contents_information, 'objectMap')

    def objectMap(self):
        # Returns a tuple of mappings containing subobject meta-data.
        return LazyMap(
            lambda (k, v): {
                'id': k,
                'meta_type': getattr(v, 'meta_type', None)
            }, self._tree.items(), self._count())

    security.declareProtected(access_contents_information, 'objectIds_d')

    def objectIds_d(self, t=None):
        ids = self.objectIds(t)
        res = {}
        for id in ids:
            res[id] = 1
        return res

    security.declareProtected(access_contents_information, 'objectMap_d')

    def objectMap_d(self, t=None):
        return self.objectMap()

    def _checkId(self, id, allow_dup=0):
        if not allow_dup and id in self:
            raise BadRequestException('The id "%s" is invalid--'
                                      'it is already in use.' % id)

    def _setObject(self,
                   id,
                   object,
                   roles=None,
                   user=None,
                   set_owner=1,
                   suppress_events=False):
        ob = object  # better name, keep original function signature
        v = self._checkId(id)
        if v is not None:
            id = v

        # If an object by the given id already exists, remove it.
        if id in self:
            self._delObject(id)

        if not suppress_events:
            notify(ObjectWillBeAddedEvent(ob, self, id))

        self._setOb(id, ob)
        ob = self._getOb(id)

        if set_owner:
            # TODO: eventify manage_fixupOwnershipAfterAdd
            # This will be called for a copy/clone, or a normal _setObject.
            ob.manage_fixupOwnershipAfterAdd()

            # Try to give user the local role "Owner", but only if
            # no local roles have been set on the object yet.
            if getattr(ob, '__ac_local_roles__', _marker) is None:
                user = getSecurityManager().getUser()
                if user is not None:
                    userid = user.getId()
                    if userid is not None:
                        ob.manage_setLocalRoles(userid, ['Owner'])

        if not suppress_events:
            notify(ObjectAddedEvent(ob, self, id))
            notifyContainerModified(self)

        compatibilityCall('manage_afterAdd', ob, ob, self)

        return id

    def __setitem__(self, key, value):
        return self._setObject(key, value)

    def _delObject(self, id, dp=1, suppress_events=False):
        ob = self._getOb(id)

        compatibilityCall('manage_beforeDelete', ob, ob, self)

        if not suppress_events:
            notify(ObjectWillBeRemovedEvent(ob, self, id))

        self._delOb(id)

        if not suppress_events:
            notify(ObjectRemovedEvent(ob, self, id))
            notifyContainerModified(self)

    def __delitem__(self, name):
        return self._delObject(id=name)

    # Utility for generating unique IDs.

    security.declareProtected(access_contents_information, 'generateId')

    def generateId(self, prefix='item', suffix='', rand_ceiling=999999999):
        """Returns an ID not used yet by this folder.

        The ID is unlikely to collide with other threads and clients.
        The IDs are sequential to optimize access to objects
        that are likely to have some relation.
        """
        tree = self._tree
        n = self._v_nextid
        attempt = 0
        while 1:
            if n % 4000 != 0 and n <= rand_ceiling:
                id = '%s%d%s' % (prefix, n, suffix)
                if id not in tree:
                    break
            n = randint(1, rand_ceiling)
            attempt = attempt + 1
            if attempt > MAX_UNIQUEID_ATTEMPTS:
                # Prevent denial of service
                raise ExhaustedUniqueIdsError
        self._v_nextid = n + 1
        return id
Example #39
0
class PermutermIndex:
    def __init__(self, invertedIndex):
        self._invertedIndex = invertedIndex
        self._btree = BTree()
        self.build()

    def build(self):
        """ 
            Reads words in invertedIndex and builds and permutermIndex

            Arguments:
            None

            Returns:
            None
        """
        if os.path.isfile("PermutermIndex"):
            print("Loading the Permuterm Index from file")
            with open("PermutermIndex", "rb") as file:
                self._btree = pickle.load(file)
        else:
            print("Building the Permuterm Index")

            words = self._invertedIndex.getKeys()

            for word in words:
                wordsRotated = rotateWord(word)
                for wordR in wordsRotated:
                    self._btree.insert(wordR, word)

            sys.setrecursionlimit(100000)
            with open("PermutermIndex", "wb") as file:
                print("Saving the Permuterm Index to file")
                pickle.dump(self._btree, file)

    def search(self, query):
        """
            Searches the permuterm index for the wildcard query

            Arguments:
            query - wildcard query to search the permuterm index

            Returns:
            List of words matching the query
        """
        queryMin, queryMax = rotateQuery(query)
        if queryMin == "":
            return []

        matches = set()
        for word in self._btree.keys(queryMin, queryMax):
            if word == queryMax:
                break
            actualWord = self._btree.get(word)
            matches.add(actualWord)

        matches = list(matches)
        return matches

    def getDocuments(self, termList, queryType=0, queryMetadata=defaultdict(int)):
        """
            Finds the documents containing the terms

            Arguments:
            termList - List of query terms

            Returns:
            List of document numbers
        """
        if queryType == 2:
            queryType = 0
        elif queryType == 3:
            queryType = 1

        allTerms = []
        for term in termList:
            if "*" in term:
                res = self.search(term)
            else:
                res = [term]
            if res != []:
                allTerms.append(res)

        termList = list(itertools.product(*allTerms))
        docList = [(self._invertedIndex.getDocuments(doc, queryType, queryMetadata), doc)
                   for doc in termList]
        docList = [doc for doc in docList if doc[0] != []]
        return docList
Example #40
0
class PathIndex(Persistent, SimpleItem):
    """Index for paths returned by getPhysicalPath.

    A path index stores all path components of the physical path of an object.

    Internal datastructure:

    - a physical path of an object is split into its components

    - every component is kept as a  key of a OOBTree in self._indexes

    - the value is a mapping 'level of the path component' to
      'all docids with this path component on this level'
    """
    implements(IPathIndex, IUniqueValueIndex, ISortIndex)

    meta_type = "PathIndex"
    query_options = ('query', 'level', 'operator')

    manage_options = ({'label': 'Settings', 'action': 'manage_main'}, )

    def __init__(self, id, caller=None):
        self.id = id
        self.operators = ('or', 'and')
        self.useOperator = 'or'
        self.clear()

    def __len__(self):
        return self._length()

    # IPluggableIndex implementation

    def getEntryForObject(self, docid, default=None):
        """ See IPluggableIndex.
        """
        try:
            return self._unindex[docid]
        except KeyError:
            return default

    def getIndexSourceNames(self):
        """ See IPluggableIndex.
        """
        return (
            self.id,
            'getPhysicalPath',
        )

    def getIndexQueryNames(self):
        return (self.id, )

    def index_object(self, docid, obj, threshold=100):
        """ See IPluggableIndex.
        """
        f = getattr(obj, self.id, None)

        if f is not None:
            if safe_callable(f):
                try:
                    path = f()
                except AttributeError:
                    return 0
            else:
                path = f

            if not isinstance(path, (str, tuple)):
                raise TypeError(
                    'path value must be string or tuple of strings')
        else:
            try:
                path = obj.getPhysicalPath()
            except AttributeError:
                return 0

        if isinstance(path, (list, tuple)):
            path = '/' + '/'.join(path[1:])

        comps = filter(None, path.split('/'))

        old_value = self._unindex.get(docid, None)
        if old_value == path:
            return 0

        if old_value is None:
            self._length.change(1)

        for i in range(len(comps)):
            self.insertEntry(comps[i], docid, i)
        self._unindex[docid] = path
        return 1

    def unindex_object(self, docid):
        """ See IPluggableIndex.
        """
        if docid not in self._unindex:
            LOG.debug('Attempt to unindex nonexistent document with id %s' %
                      docid)
            return

        comps = self._unindex[docid].split('/')

        for level in range(len(comps[1:])):
            comp = comps[level + 1]
            try:
                self._index[comp][level].remove(docid)
                if not self._index[comp][level]:
                    del self._index[comp][level]

                if not self._index[comp]:
                    del self._index[comp]
            except KeyError:
                LOG.debug('Attempt to unindex document with id %s failed' %
                          docid)

        self._length.change(-1)
        del self._unindex[docid]

    def _apply_index(self, request):
        """ See IPluggableIndex.

        o Unpacks args from catalog and mapps onto '_search'.
        """
        record = parseIndexRequest(request, self.id, self.query_options)
        if record.keys is None:
            return None

        level = record.get("level", 0)
        operator = record.get('operator', self.useOperator).lower()

        # depending on the operator we use intersection of union
        if operator == "or":
            set_func = union
        else:
            set_func = intersection

        res = None
        for k in record.keys:
            rows = self._search(k, level)
            res = set_func(res, rows)

        if res:
            return res, (self.id, )
        else:
            return IISet(), (self.id, )

    def numObjects(self):
        """ See IPluggableIndex.
        """
        return len(self._unindex)

    def indexSize(self):
        """ See IPluggableIndex.
        """
        return len(self)

    def clear(self):
        """ See IPluggableIndex.
        """
        self._depth = 0
        self._index = OOBTree()
        self._unindex = IOBTree()
        self._length = Length(0)

    # IUniqueValueIndex implementation

    def hasUniqueValuesFor(self, name):
        """ See IUniqueValueIndex.
        """
        return name == self.id

    def uniqueValues(self, name=None, withLengths=0):
        """  See IUniqueValueIndex.
        """
        if name in (None, self.id, 'getPhysicalPath'):
            if withLengths:
                for key in self._index:
                    yield (key, len(self._search(key, -1)))
            else:
                for key in self._index.keys():
                    yield key

    # ISortIndex implementation

    def keyForDocument(self, documentId):
        """ See ISortIndex.
        """
        return self._unindex.get(documentId)

    def documentToKeyMap(self):
        """ See ISortIndex.
        """
        return self._unindex

    # IPathIndex implementation.

    def insertEntry(self, comp, id, level):
        """ See IPathIndex
        """
        tree = self._index.get(comp, None)
        if tree is None:
            self._index[comp] = tree = IOBTree()

        tree2 = tree.get(level, None)
        if tree2 is None:
            tree[level] = tree2 = IITreeSet()

        tree2.insert(id)
        if level > self._depth:
            self._depth = level

    # Helper methods

    def _search(self, path, default_level=0):
        """ Perform the actual search.

        ``path``
            a string representing a relative URL, or a part of a relative URL,
            or a tuple ``(path, level)``.  In the first two cases, use
            ``default_level`` as the level for the search.

        ``default_level``
            the level to use for non-tuple queries.

        ``level >= 0`` =>  match ``path`` only at the given level.

        ``level <  0`` =>  match ``path`` at *any* level
        """
        if isinstance(path, str):
            level = default_level
        else:
            level = int(path[1])
            path = path[0]

        if level < 0:
            # Search at every level, return the union of all results
            return multiunion([
                self._search(path, level) for level in xrange(self._depth + 1)
            ])

        comps = filter(None, path.split('/'))

        if level + len(comps) - 1 > self._depth:
            # Our search is for a path longer than anything in the index
            return IISet()

        if len(comps) == 0:
            return IISet(self._unindex.keys())

        results = None
        for i, comp in reversed(list(enumerate(comps))):
            tree = self._index.get(comp, None)
            if tree is None:
                return IISet()
            tree2 = tree.get(level + i, None)
            if tree2 is None:
                return IISet()
            results = intersection(results, tree2)
        return results

    manage = manage_main = DTMLFile('dtml/managePathIndex', globals())
    manage_main._setName('manage_main')
Example #41
0
class MemberDataTool(UniqueObject, SimpleItem, PropertyManager):
    """ This tool wraps user objects, making them act as Member objects.
    """

    implements(IMemberDataTool)

    id = 'portal_memberdata'
    meta_type = 'CMF Member Data Tool'
    _properties = ()

    security = ClassSecurityInfo()

    manage_options = (({
        'label': 'Overview',
        'action': 'manage_overview'
    }, {
        'label': 'Contents',
        'action': 'manage_showContents'
    }) + PropertyManager.manage_options + SimpleItem.manage_options)

    #
    #   ZMI methods
    #
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = DTMLFile('explainMemberDataTool', _dtmldir)

    security.declareProtected(ViewManagementScreens, 'manage_showContents')
    manage_showContents = DTMLFile('memberdataContents', _dtmldir)

    def __init__(self):
        self._members = OOBTree()
        # Create the default properties.
        self._setProperty('email', '', 'string')
        self._setProperty('portal_skin', '', 'string')
        self._setProperty('listed', '', 'boolean')
        self._setProperty('login_time', '2000/01/01', 'date')
        self._setProperty('last_login_time', '2000/01/01', 'date')

    #
    #   'portal_memberdata' interface methods
    #
    security.declarePrivate('getMemberDataContents')

    def getMemberDataContents(self):
        '''
        Return the number of members stored in the _members
        BTree and some other useful info
        '''
        # XXX: this method violates the rules for tools/utilities:
        # it depends on a non-utility tool
        membertool = getToolByName(self, 'portal_membership')
        members = self._members
        user_list = membertool.listMemberIds()
        member_list = members.keys()
        member_count = len(members)
        orphan_count = 0

        for member in member_list:
            if member not in user_list:
                orphan_count = orphan_count + 1

        return [{'member_count': member_count, 'orphan_count': orphan_count}]

    security.declarePrivate('searchMemberData')

    def searchMemberData(self, search_param, search_term, attributes=()):
        """ Search members. """
        # XXX: this method violates the rules for tools/utilities:
        # it depends on a non-utility tool
        res = []

        if not search_param:
            return res

        membership = getToolByName(self, 'portal_membership')

        if len(attributes) == 0:
            attributes = ('id', 'email')

        if search_param == 'username':
            search_param = 'id'

        for user_id in self._members.keys():
            u = membership.getMemberById(user_id)

            if u is not None:
                memberProperty = u.getProperty
                searched = memberProperty(search_param, None)

                if searched is not None and searched.find(search_term) != -1:
                    user_data = {}

                    for desired in attributes:
                        if desired == 'id':
                            user_data['username'] = memberProperty(desired, '')
                        else:
                            user_data[desired] = memberProperty(desired, '')

                    res.append(user_data)

        return res

    security.declarePrivate('searchMemberDataContents')

    def searchMemberDataContents(self, search_param, search_term):
        """ Search members. This method will be deprecated soon. """
        # XXX: this method violates the rules for tools/utilities:
        # it depends on a non-utility tool
        res = []

        if search_param == 'username':
            search_param = 'id'

        mtool = getToolByName(self, 'portal_membership')

        for member_id in self._members.keys():

            user_wrapper = mtool.getMemberById(member_id)

            if user_wrapper is not None:
                memberProperty = user_wrapper.getProperty
                searched = memberProperty(search_param, None)

                if searched is not None and searched.find(search_term) != -1:

                    res.append({
                        'username': memberProperty('id'),
                        'email': memberProperty('email', '')
                    })
        return res

    security.declarePrivate('pruneMemberDataContents')

    def pruneMemberDataContents(self):
        """ Delete data contents of all members not listet in acl_users.
        """
        # XXX: this method violates the rules for tools/utilities:
        # it depends on a non-utility tool
        membertool = getToolByName(self, 'portal_membership')
        members = self._members
        user_list = membertool.listMemberIds()

        for member_id in list(members.keys()):
            if member_id not in user_list:
                del members[member_id]

    security.declarePrivate('wrapUser')

    def wrapUser(self, u):
        '''
        If possible, returns the Member object that corresponds
        to the given User object.
        '''
        id = u.getId()
        members = self._members
        if not id in members:
            member_factory = queryUtility(IFactory, u'MemberData')
            if member_factory is None:
                member_factory = MemberData
            base = aq_base(self)
            members[id] = member_factory(base, id)
        # Return a wrapper with self as containment and
        # the user as context.
        return members[id].__of__(self).__of__(u)

    security.declarePrivate('registerMemberData')

    def registerMemberData(self, m, id):
        """ Add the given member data to the _members btree.
        """
        self._members[id] = aq_base(m)

    security.declarePrivate('deleteMemberData')

    def deleteMemberData(self, member_id):
        """ Delete member data of specified member.
        """
        members = self._members
        if members.has_key(member_id):
            del members[member_id]
            return 1
        else:
            return 0
class ZODBUserManager( BasePlugin, Cacheable ):

    """ PAS plugin for managing users in the ZODB.
    """

    meta_type = 'ZODB User Manager'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title

        self._user_passwords = OOBTree()
        self._login_to_userid = OOBTree()
        self._userid_to_login = OOBTree()

    #
    #   IAuthenticationPlugin implementation
    #
    security.declarePrivate( 'authenticateCredentials' )
    def authenticateCredentials( self, credentials ):

        """ See IAuthenticationPlugin.

        o We expect the credentials to be those returned by
          ILoginPasswordExtractionPlugin.
        """
        login = credentials.get( 'login' )
        password = credentials.get( 'password' )

        if login is None or password is None:
            return None

        # Do we have a link between login and userid?  Do NOT fall
        # back to using the login as userid when there is no match, as
        # that gives a high chance of seeming to log in successfully,
        # but in reality failing.
        userid = self._login_to_userid.get(login)
        if userid is None:
            # Someone may be logging in with a userid instead of a
            # login name and the two are not the same.  We could try
            # turning those around, but really we should just fail.
            #
            # userid = login
            # login = self._userid_to_login.get(userid)
            # if login is None:
            #     return None
            return None

        reference = self._user_passwords.get(userid)

        if reference is None:
            return None

        if AuthEncoding.is_encrypted( reference ):
            if AuthEncoding.pw_validate( reference, password ):
                return userid, login

        # Support previous naive behavior
        digested = sha( password ).hexdigest()

        if reference == digested:
            return userid, login

        return None

    #
    #   IUserEnumerationPlugin implementation
    #
    security.declarePrivate( 'enumerateUsers' )
    def enumerateUsers( self
                      , id=None
                      , login=None
                      , exact_match=False
                      , sort_by=None
                      , max_results=None
                      , **kw
                      ):

        """ See IUserEnumerationPlugin.
        """
        user_info = []
        user_ids = []
        plugin_id = self.getId()
        view_name = createViewName('enumerateUsers', id or login)


        if isinstance( id, basestring ):
            id = [ id ]

        if isinstance( login, basestring ):
            login = [ login ]

        # Look in the cache first...
        keywords = copy.deepcopy(kw)
        keywords.update( { 'id' : id
                         , 'login' : login
                         , 'exact_match' : exact_match
                         , 'sort_by' : sort_by
                         , 'max_results' : max_results
                         }
                       )
        cached_info = self.ZCacheable_get( view_name=view_name
                                         , keywords=keywords
                                         , default=None
                                         )
        if cached_info is not None:
            return tuple(cached_info)

        terms = id or login

        if exact_match:
            if terms:

                if id:
                    # if we're doing an exact match based on id, it
                    # absolutely will have been qualified (if we have a
                    # prefix), so we can ignore any that don't begin with
                    # our prefix
                    id = [ x for x in id if x.startswith(self.prefix) ]
                    user_ids.extend( [ x[len(self.prefix):] for x in id ] )
                elif login:
                    user_ids.extend( [ self._login_to_userid.get( x )
                                       for x in login ] )

                # we're claiming an exact match search, if we still don't
                # have anything, better bail.
                if not user_ids:
                    return ()
            else:
                # insane - exact match with neither login nor id
                return ()

        if user_ids:
            user_filter = None

        else:   # Searching
            user_ids = self.listUserIds()
            user_filter = _ZODBUserFilter( id, login, **kw )

        for user_id in user_ids:

            if self._userid_to_login.get( user_id ):
                e_url = '%s/manage_users' % self.getId()
                qs = 'user_id=%s' % user_id

                info = { 'id' : self.prefix + user_id
                       , 'login' : self._userid_to_login[ user_id ]
                       , 'pluginid' : plugin_id
                       , 'editurl' : '%s?%s' % (e_url, qs)
                       }

                if not user_filter or user_filter( info ):
                    user_info.append( info )

        # Put the computed value into the cache
        self.ZCacheable_set(user_info, view_name=view_name, keywords=keywords)

        return tuple( user_info )

    #
    #   IUserAdderPlugin implementation
    #
    security.declarePrivate( 'doAddUser' )
    def doAddUser( self, login, password ):
        try:
            self.addUser( login, login, password )
        except KeyError:
            return False
        return True

    #
    #   (notional)IZODBUserManager interface
    #
    security.declareProtected( ManageUsers, 'listUserIds' )
    def listUserIds( self ):

        """ -> ( user_id_1, ... user_id_n )
        """
        return self._user_passwords.keys()

    security.declareProtected( ManageUsers, 'getUserInfo' )
    def getUserInfo( self, user_id ):

        """ user_id -> {}
        """
        return { 'user_id' : user_id
               , 'login_name' : self._userid_to_login[ user_id ]
               , 'pluginid' : self.getId()
               }

    security.declareProtected( ManageUsers, 'listUserInfo' )
    def listUserInfo( self ):

        """ -> ( {}, ...{} )

        o Return one mapping per user, with the following keys:

          - 'user_id'
          - 'login_name'
        """
        return [ self.getUserInfo( x ) for x in self._user_passwords.keys() ]

    security.declareProtected( ManageUsers, 'getUserIdForLogin' )
    def getUserIdForLogin( self, login_name ):

        """ login_name -> user_id

        o Raise KeyError if no user exists for the login name.
        """
        return self._login_to_userid[ login_name ]

    security.declareProtected( ManageUsers, 'getLoginForUserId' )
    def getLoginForUserId( self, user_id ):

        """ user_id -> login_name

        o Raise KeyError if no user exists for that ID.
        """
        return self._userid_to_login[ user_id ]

    security.declarePrivate( 'addUser' )
    def addUser( self, user_id, login_name, password ):

        if self._user_passwords.get( user_id ) is not None:
            raise KeyError, 'Duplicate user ID: %s' % user_id

        if self._login_to_userid.get( login_name ) is not None:
            raise KeyError, 'Duplicate login name: %s' % login_name

        self._user_passwords[ user_id ] = self._pw_encrypt( password)
        self._login_to_userid[ login_name ] = user_id
        self._userid_to_login[ user_id ] = login_name

        # enumerateUsers return value has changed
        view_name = createViewName('enumerateUsers')
        self.ZCacheable_invalidate(view_name=view_name)

    security.declarePrivate('updateUser')
    def updateUser(self, user_id, login_name):

        # The following raises a KeyError if the user_id is invalid
        old_login = self.getLoginForUserId(user_id)

        if old_login != login_name:

            if self._login_to_userid.get(login_name) is not None:
                raise ValueError('Login name not available: %s' % login_name)

            del self._login_to_userid[old_login]
            self._login_to_userid[login_name] = user_id
            self._userid_to_login[user_id] = login_name
        # Signal success.
        return True

    security.declarePrivate('updateEveryLoginName')
    def updateEveryLoginName(self, quit_on_first_error=True):
        # Update all login names to their canonical value.  This
        # should be done after changing the login_transform property
        # of pas.  You can set quit_on_first_error to False to report
        # all errors before quitting with an error.  This can be
        # useful if you want to know how many problems there are, if
        # any.
        pas = self._getPAS()
        transform = pas._get_login_transform_method()
        if not transform:
            logger.warn("PAS has a non-existing, empty or wrong "
                        "login_transform property.")
            return

        # Make a fresh mapping, as we do not want to add or remove
        # items to the original mapping while we are iterating over
        # it.
        new_login_to_userid = OOBTree()
        errors = []
        for old_login_name, user_id in self._login_to_userid.items():
            new_login_name = transform(old_login_name)
            if new_login_name in new_login_to_userid:
                logger.error("User id %s: login name %r already taken.",
                             user_id, new_login_name)
                errors.append(new_login_name)
                if quit_on_first_error:
                    break
            new_login_to_userid[new_login_name] = user_id
            if new_login_name != old_login_name:
                self._userid_to_login[user_id] = new_login_name
                # Also, remove from the cache
                view_name = createViewName('enumerateUsers', user_id)
                self.ZCacheable_invalidate(view_name=view_name)
                logger.debug("User id %s: changed login name from %r to %r.",
                             user_id, old_login_name, new_login_name)

        # If there were errors, we do not want to save any changes.
        if errors:
            logger.error("There were %d errors when updating login names. "
                         "quit_on_first_error was %r", len(errors),
                         quit_on_first_error)
            # Make sure the exception we raise is not swallowed.
            self._dont_swallow_my_exceptions = True
            raise ValueError("Transformed login names are not unique: %s." %
                             ', '.join(errors))

        # Make sure we did not lose any users.
        assert(len(self._login_to_userid.keys())
               == len(new_login_to_userid.keys()))
        # Empty the main cache.
        view_name = createViewName('enumerateUsers')
        self.ZCacheable_invalidate(view_name=view_name)
        # Store the new login mapping.
        self._login_to_userid = new_login_to_userid

    security.declarePrivate( 'removeUser' )
    def removeUser( self, user_id ):

        if self._user_passwords.get( user_id ) is None:
            raise KeyError, 'Invalid user ID: %s' % user_id

        login_name = self._userid_to_login[ user_id ]

        del self._user_passwords[ user_id ]
        del self._login_to_userid[ login_name ]
        del self._userid_to_login[ user_id ]

        # Also, remove from the cache
        view_name = createViewName('enumerateUsers')
        self.ZCacheable_invalidate(view_name=view_name)
        view_name = createViewName('enumerateUsers', user_id)
        self.ZCacheable_invalidate(view_name=view_name)

    security.declarePrivate( 'updateUserPassword' )
    def updateUserPassword( self, user_id, password ):

        if self._user_passwords.get( user_id ) is None:
            raise KeyError, 'Invalid user ID: %s' % user_id

        if password:
            self._user_passwords[ user_id ] = self._pw_encrypt( password )

    security.declarePrivate( '_pw_encrypt' )
    def _pw_encrypt( self, password ):
        """Returns the AuthEncoding encrypted password

        If 'password' is already encrypted, it is returned
        as is and not encrypted again.
        """
        if AuthEncoding.is_encrypted( password ):
            return password
        return AuthEncoding.pw_encrypt( password )

    #
    #   ZMI
    #
    manage_options = ( ( { 'label': 'Users',
                           'action': 'manage_users', }
                         ,
                       )
                     + BasePlugin.manage_options
                     + Cacheable.manage_options
                     )

    security.declarePublic( 'manage_widgets' )
    manage_widgets = PageTemplateFile( 'www/zuWidgets'
                                     , globals()
                                     , __name__='manage_widgets'
                                     )

    security.declareProtected( ManageUsers, 'manage_users' )
    manage_users = PageTemplateFile( 'www/zuUsers'
                                   , globals()
                                   , __name__='manage_users'
                                   )

    security.declareProtected( ManageUsers, 'manage_addUser' )
    @csrf_only
    @postonly
    def manage_addUser( self
                      , user_id
                      , login_name
                      , password
                      , confirm
                      , RESPONSE=None
                      , REQUEST=None
                      ):
        """ Add a user via the ZMI.
        """
        if password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:

            if not login_name:
                login_name = user_id

            # XXX:  validate 'user_id', 'login_name' against policies?

            self.addUser( user_id, login_name, password )

            message = 'User+added'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageUsers, 'manage_updateUserPassword' )
    @csrf_only
    @postonly
    def manage_updateUserPassword( self
                                 , user_id
                                 , password
                                 , confirm
                                 , RESPONSE=None
                                 , REQUEST=None
                                 ):
        """ Update a user's login name / password via the ZMI.
        """
        if password and password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:

            self.updateUserPassword( user_id, password )

            message = 'password+updated'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageUsers, 'manage_updateUser' )
    @csrf_only
    @postonly
    def manage_updateUser(self
                         , user_id
                         , login_name
                         , RESPONSE=None
                         , REQUEST=None
                         ):
        """ Update a user's login name via the ZMI.
        """
        if not login_name:
            login_name = user_id

        # XXX:  validate 'user_id', 'login_name' against policies?

        self.updateUser(user_id, login_name)

        message = 'Login+name+updated'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageUsers, 'manage_removeUsers' )
    @csrf_only
    @postonly
    def manage_removeUsers( self
                          , user_ids
                          , RESPONSE=None
                          , REQUEST=None
                          ):
        """ Remove one or more users via the ZMI.
        """
        user_ids = filter( None, user_ids )

        if not user_ids:
            message = 'no+users+selected'

        else:

            for user_id in user_ids:
                self.removeUser( user_id )

            message = 'Users+removed'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    #
    #   Allow users to change their own login name and password.
    #
    security.declareProtected( SetOwnPassword, 'getOwnUserInfo' )
    def getOwnUserInfo( self ):

        """ Return current user's info.
        """
        user_id = getSecurityManager().getUser().getId()

        return self.getUserInfo( user_id )

    security.declareProtected( SetOwnPassword, 'manage_updatePasswordForm' )
    manage_updatePasswordForm = PageTemplateFile( 'www/zuPasswd'
                                   , globals()
                                   , __name__='manage_updatePasswordForm'
                                   )

    security.declareProtected( SetOwnPassword, 'manage_updatePassword' )
    @csrf_only
    @postonly
    def manage_updatePassword( self
                             , login_name
                             , password
                             , confirm
                             , RESPONSE=None
                             , REQUEST=None
                             ):
        """ Update the current user's password and login name.
        """
        user_id = getSecurityManager().getUser().getId()
        if password != confirm:
            message = 'password+and+confirm+do+not+match'

        else:

            if not login_name:
                login_name = user_id

            # XXX:  validate 'user_id', 'login_name' against policies?
            self.updateUser( user_id, login_name )
            self.updateUserPassword( user_id, password )

            message = 'password+updated'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_updatePasswordForm'
                               '?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )
Example #43
0
class ReplicationTarget(CremeSimpleItem):

    meta_type = 'ReplicationTarget'

    _properties = (
        {'id':'url', 'type':'string', 'mode':'wd'},
        {'id':'username', 'type':'string', 'mode':'wd'},
        {'id':'password', 'type':'string', 'mode':'wd'},
    )

    def __init__(self, **kw):
        self._transactions = OOBTree()
        CremeSimpleItem.__init__(self, **kw)

    def log(self, entry_dict):
        t_id = self._p_jar._storage._serial
        if not self._transactions.has_key(t_id):
            self._transactions[t_id] = ReplogTransaction(t_id)
        transaction = self._transactions[t_id]
        transaction.add_entry(entry_dict)


    def replicate(self):
        """ replicate log to targets """
        keys = OOSet(self._transactions.keys())
        for key in keys:
            transaction = self._transactions[key]
            try:
                transaction.replicate(self)
                del self._transactions[key]
            except:
                from sys import exc_info
                import traceback
                info = exc_info()
                zlog('Replication',
                     'Could not replicate transaction %s to %s'%(
                        oid2str(transaction.id), self.id)) 
                break

    def html(self, suppress_entries=0):
        """ html log for viewing transactions in the ZMI """
        out = []
        keys = OOSet(self._transactions.keys())
        for t_id in keys:
            t = self._transactions[t_id]
            out.append('''
<h4>Transaction id: %s</h4>
<p>
<em>User:</em> %s<br/>
<em>Description:</em> %s<br/>
</p>
''' % (oid2str(t.id), t.user, t.description))
            if suppress_entries:
                continue
            for entry_id in t._entries.keys():
                entry = t._entries[entry_id]
                out.append('''
<p>
<em>id:</em> %(id)s<br/>
<em>obj:</em> %(path)s<br/>
<em>method:</em> %(method)s<br/>
<em>args:</em> %(args)s<br/>
</p>
''' % entry)
        out = '<hr>'.join(out)
        return '<html><body>%s</body></html>' % out
Example #44
0
class TopicIndex(Persistent, SimpleItem):

    """A TopicIndex maintains a set of FilteredSet objects.

    Every FilteredSet object consists of an expression and and IISet with all
    Ids of indexed objects that eval with this expression to 1.
    """
    implements(ITopicIndex, IPluggableIndex)

    meta_type="TopicIndex"
    query_options = ('query', 'operator')

    manage_options= (
        {'label': 'FilteredSets', 'action': 'manage_main'},
    )

    def __init__(self,id,caller=None):
        self.id = id
        self.filteredSets  = OOBTree()
        self.operators = ('or','and')
        self.defaultOperator = 'or'

    def getId(self):
        return self.id

    def clear(self):
        for fs in self.filteredSets.values():
            fs.clear()

    def index_object(self, docid, obj ,threshold=100):
        """ hook for (Z)Catalog """
        for fid, filteredSet in self.filteredSets.items():
            filteredSet.index_object(docid,obj)
        return 1

    def unindex_object(self,docid):
        """ hook for (Z)Catalog """

        for fs in self.filteredSets.values():
            try:
                fs.unindex_object(docid)
            except KeyError:
                LOG.debug('Attempt to unindex document'
                          ' with id %s failed' % docid)
        return 1

    def numObjects(self):
        """Return the number of indexed objects."""
        return "n/a"

    def indexSize(self):
        """Return the size of the index in terms of distinct values."""
        return "n/a"

    def search(self,filter_id):
        if self.filteredSets.has_key(filter_id):
            return self.filteredSets[filter_id].getIds()

    def _apply_index(self, request):
        """ hook for (Z)Catalog
            'request' --  mapping type (usually {"topic": "..." }
        """
        record = parseIndexRequest(request, self.id, self.query_options)
        if record.keys is None:
            return None

        operator = record.get('operator', self.defaultOperator).lower()
        if operator == 'or':  set_func = union
        else: set_func = intersection

        res = None
        for filter_id in record.keys:
            rows = self.search(filter_id)
            res = set_func(res,rows)

        if res:
            return res, (self.id,)
        else:
            return IITreeSet(), (self.id,)

    def uniqueValues(self,name=None, withLength=0):
        """ needed to be consistent with the interface """
        return self.filteredSets.keys()

    def getEntryForObject(self,docid, default=_marker):
        """ Takes a document ID and returns all the information we have
            on that specific object.
        """
        return self.filteredSets.keys()

    def addFilteredSet(self, filter_id, typeFilteredSet, expr):
        # Add a FilteredSet object.
        if self.filteredSets.has_key(filter_id):
            raise KeyError,\
                'A FilteredSet with this name already exists: %s' % filter_id
        self.filteredSets[filter_id] = factory(filter_id,
                                               typeFilteredSet,
                                               expr,
                                              )

    def delFilteredSet(self, filter_id):
        # Delete the FilteredSet object specified by 'filter_id'.
        if not self.filteredSets.has_key(filter_id):
            raise KeyError,\
                'no such FilteredSet:  %s' % filter_id
        del self.filteredSets[filter_id]

    def clearFilteredSet(self, filter_id):
        # Clear the FilteredSet object specified by 'filter_id'.
        if not self.filteredSets.has_key(filter_id):
            raise KeyError,\
                'no such FilteredSet:  %s' % filter_id
        self.filteredSets[filter_id].clear()

    def manage_addFilteredSet(self, filter_id, typeFilteredSet, expr, URL1, \
            REQUEST=None,RESPONSE=None):
        """ add a new filtered set """

        if len(filter_id) == 0: raise RuntimeError,'Length of ID too short'
        if len(expr) == 0: raise RuntimeError,'Length of expression too short'

        self.addFilteredSet(filter_id, typeFilteredSet, expr)

        if RESPONSE:
            RESPONSE.redirect(URL1+'/manage_workspace?'
            'manage_tabs_message=FilteredSet%20added')

    def manage_delFilteredSet(self, filter_ids=[], URL1=None, \
            REQUEST=None,RESPONSE=None):
        """ delete a list of FilteredSets"""

        for filter_id in filter_ids:
            self.delFilteredSet(filter_id)

        if RESPONSE:
            RESPONSE.redirect(URL1+'/manage_workspace?'
            'manage_tabs_message=FilteredSet(s)%20deleted')

    def manage_saveFilteredSet(self,filter_id, expr, URL1=None,\
            REQUEST=None,RESPONSE=None):
        """ save expression for a FilteredSet """

        self.filteredSets[filter_id].setExpression(expr)

        if RESPONSE:
            RESPONSE.redirect(URL1+'/manage_workspace?'
            'manage_tabs_message=FilteredSet(s)%20updated')

    def getIndexSourceNames(self):
        """ return names of indexed attributes """
        return ('n/a',)

    def getIndexQueryNames(self):
        return (self.id,)

    def manage_clearFilteredSet(self, filter_ids=[], URL1=None, \
            REQUEST=None,RESPONSE=None):
        """  clear a list of FilteredSets"""

        for filter_id in filter_ids:
            self.clearFilteredSet(filter_id)

        if RESPONSE:
            RESPONSE.redirect(URL1+'/manage_workspace?'
             'manage_tabs_message=FilteredSet(s)%20cleared')

    manage = manage_main = DTMLFile('dtml/manageTopicIndex',globals())
    manage_main._setName('manage_main')
    editFilteredSet = DTMLFile('dtml/editFilteredSet',globals())
Example #45
0
class BTreeFolder2Base (Persistent):
    """Base for BTree-based folders.
    """

    security = ClassSecurityInfo()

    manage_options=(
        ({'label':'Contents', 'action':'manage_main',},
         ) + Folder.manage_options[1:]
        )

    security.declareProtected(view_management_screens,
                              'manage_main')
    manage_main = DTMLFile('contents', globals())

    _tree = None      # OOBTree: { id -> object }
    _count = None     # A BTrees.Length
    _v_nextid = 0     # The integer component of the next generated ID
    _mt_index = None  # OOBTree: { meta_type -> OIBTree: { id -> 1 } }
    title = ''


    def __init__(self, id=None):
        if id is not None:
            self.id = id
        self._initBTrees()

    def _initBTrees(self):
        self._tree = OOBTree()
        self._count = Length()
        self._mt_index = OOBTree()


    def _populateFromFolder(self, source):
        """Fill this folder with the contents of another folder.
        """
        for name in source.objectIds():
            value = source._getOb(name, None)
            if value is not None:
                self._setOb(name, aq_base(value))


    security.declareProtected(view_management_screens, 'manage_fixCount')
    def manage_fixCount(self):
        """Calls self._fixCount() and reports the result as text.
        """
        old, new = self._fixCount()
        path = '/'.join(self.getPhysicalPath())
        if old == new:
            return "No count mismatch detected in BTreeFolder2 at %s." % path
        else:
            return ("Fixed count mismatch in BTreeFolder2 at %s. "
                    "Count was %d; corrected to %d" % (path, old, new))


    def _fixCount(self):
        """Checks if the value of self._count disagrees with
        len(self.objectIds()). If so, corrects self._count. Returns the
        old and new count values. If old==new, no correction was
        performed.
        """
        old = self._count()
        new = len(self.objectIds())
        if old != new:
            self._count.set(new)
        return old, new


    security.declareProtected(view_management_screens, 'manage_cleanup')
    def manage_cleanup(self):
        """Calls self._cleanup() and reports the result as text.
        """
        v = self._cleanup()
        path = '/'.join(self.getPhysicalPath())
        if v:
            return "No damage detected in BTreeFolder2 at %s." % path
        else:
            return ("Fixed BTreeFolder2 at %s.  "
                    "See the log for more details." % path)


    def _cleanup(self):
        """Cleans up errors in the BTrees.

        Certain ZODB bugs have caused BTrees to become slightly insane.
        Fortunately, there is a way to clean up damaged BTrees that
        always seems to work: make a new BTree containing the items()
        of the old one.

        Returns 1 if no damage was detected, or 0 if damage was
        detected and fixed.
        """
        from BTrees.check import check
        path = '/'.join(self.getPhysicalPath())
        try:
            check(self._tree)
            for key in self._tree.keys():
                if not self._tree.has_key(key):
                    raise AssertionError(
                        "Missing value for key: %s" % repr(key))
            check(self._mt_index)
            for key, value in self._mt_index.items():
                if (not self._mt_index.has_key(key)
                    or self._mt_index[key] is not value):
                    raise AssertionError(
                        "Missing or incorrect meta_type index: %s"
                        % repr(key))
                check(value)
                for k in value.keys():
                    if not value.has_key(k):
                        raise AssertionError(
                            "Missing values for meta_type index: %s"
                            % repr(key))
            return 1
        except AssertionError:
            LOG.warn( 'Detected damage to %s. Fixing now.' % path,
                exc_info=True)
            try:
                self._tree = OOBTree(self._tree)
                mt_index = OOBTree()
                for key, value in self._mt_index.items():
                    mt_index[key] = OIBTree(value)
                self._mt_index = mt_index
            except:
                LOG.error('Failed to fix %s.' % path,
                    exc_info=True)
                raise
            else:
                LOG.info('Fixed %s.' % path)
            return 0


    def _getOb(self, id, default=_marker):
        """Return the named object from the folder.
        """
        tree = self._tree
        if default is _marker:
            ob = tree[id]
            return ob.__of__(self)
        else:
            ob = tree.get(id, _marker)
            if ob is _marker:
                return default
            else:
                return ob.__of__(self)


    def _setOb(self, id, object):
        """Store the named object in the folder.
        """
        tree = self._tree
        if tree.has_key(id):
            raise KeyError('There is already an item named "%s".' % id)
        tree[id] = object
        self._count.change(1)
        # Update the meta type index.
        mti = self._mt_index
        meta_type = getattr(object, 'meta_type', None)
        if meta_type is not None:
            ids = mti.get(meta_type, None)
            if ids is None:
                ids = OIBTree()
                mti[meta_type] = ids
            ids[id] = 1


    def _delOb(self, id):
        """Remove the named object from the folder.
        """
        tree = self._tree
        meta_type = getattr(tree[id], 'meta_type', None)
        del tree[id]
        self._count.change(-1)
        # Update the meta type index.
        if meta_type is not None:
            mti = self._mt_index
            ids = mti.get(meta_type, None)
            if ids is not None and ids.has_key(id):
                del ids[id]
                if not ids:
                    # Removed the last object of this meta_type.
                    # Prune the index.
                    del mti[meta_type]


    security.declareProtected(view_management_screens, 'getBatchObjectListing')
    def getBatchObjectListing(self, REQUEST=None):
        """Return a structure for a page template to show the list of objects.
        """
        if REQUEST is None:
            REQUEST = {}
        pref_rows = int(REQUEST.get('dtpref_rows', 20))
        b_start = int(REQUEST.get('b_start', 1))
        b_count = int(REQUEST.get('b_count', 1000))
        b_end = b_start + b_count - 1
        url = self.absolute_url() + '/manage_main'
        idlist = self.objectIds()  # Pre-sorted.
        count = self.objectCount()

        if b_end < count:
            next_url = url + '?b_start=%d' % (b_start + b_count)
        else:
            b_end = count
            next_url = ''

        if b_start > 1:
            prev_url = url + '?b_start=%d' % max(b_start - b_count, 1)
        else:
            prev_url = ''

        formatted = []
        formatted.append(listtext0 % pref_rows)
        for i in range(b_start - 1, b_end):
            optID = escape(idlist[i])
            formatted.append(listtext1 % (escape(optID, quote=1), optID))
        formatted.append(listtext2)
        return {'b_start': b_start, 'b_end': b_end,
                'prev_batch_url': prev_url,
                'next_batch_url': next_url,
                'formatted_list': ''.join(formatted)}


    security.declareProtected(view_management_screens,
                              'manage_object_workspace')
    def manage_object_workspace(self, ids=(), REQUEST=None):
        '''Redirects to the workspace of the first object in
        the list.'''
        if ids and REQUEST is not None:
            REQUEST.RESPONSE.redirect(
                '%s/%s/manage_workspace' % (
                self.absolute_url(), quote(ids[0])))
        else:
            return self.manage_main(self, REQUEST)


    security.declareProtected(access_contents_information,
                              'tpValues')
    def tpValues(self):
        """Ensures the items don't show up in the left pane.
        """
        return ()


    security.declareProtected(access_contents_information,
                              'objectCount')
    def objectCount(self):
        """Returns the number of items in the folder."""
        return self._count()


    security.declareProtected(access_contents_information, 'has_key')
    def has_key(self, id):
        """Indicates whether the folder has an item by ID.
        """
        return self._tree.has_key(id)


    security.declareProtected(access_contents_information,
                              'objectIds')
    def objectIds(self, spec=None):
        # Returns a list of subobject ids of the current object.
        # If 'spec' is specified, returns objects whose meta_type
        # matches 'spec'.
        if spec is not None:
            if isinstance(spec, StringType):
                spec = [spec]
            mti = self._mt_index
            set = None
            for meta_type in spec:
                ids = mti.get(meta_type, None)
                if ids is not None:
                    set = union(set, ids)
            if set is None:
                return ()
            else:
                return set.keys()
        else:
            return self._tree.keys()


    security.declareProtected(access_contents_information,
                              'objectValues')
    def objectValues(self, spec=None):
        # Returns a list of actual subobjects of the current object.
        # If 'spec' is specified, returns only objects whose meta_type
        # match 'spec'.
        return LazyMap(self._getOb, self.objectIds(spec))


    security.declareProtected(access_contents_information,
                              'objectItems')
    def objectItems(self, spec=None):
        # Returns a list of (id, subobject) tuples of the current object.
        # If 'spec' is specified, returns only objects whose meta_type match
        # 'spec'
        return LazyMap(lambda id, _getOb=self._getOb: (id, _getOb(id)),
                       self.objectIds(spec))


    security.declareProtected(access_contents_information,
                              'objectMap')
    def objectMap(self):
        # Returns a tuple of mappings containing subobject meta-data.
        return LazyMap(lambda (k, v):
                       {'id': k, 'meta_type': getattr(v, 'meta_type', None)},
                       self._tree.items(), self._count())

    # superValues() looks for the _objects attribute, but the implementation
    # would be inefficient, so superValues() support is disabled.
    _objects = ()


    security.declareProtected(access_contents_information,
                              'objectIds_d')
    def objectIds_d(self, t=None):
        ids = self.objectIds(t)
        res = {}
        for id in ids:
            res[id] = 1
        return res


    security.declareProtected(access_contents_information,
                              'objectMap_d')
    def objectMap_d(self, t=None):
        return self.objectMap()


    def _checkId(self, id, allow_dup=0):
        if not allow_dup and self.has_key(id):
            raise BadRequestException, ('The id "%s" is invalid--'
                                        'it is already in use.' % id)


    def _setObject(self, id, object, roles=None, user=None, set_owner=1,
                   suppress_events=False):
        ob = object # better name, keep original function signature
        v = self._checkId(id)
        if v is not None:
            id = v

        # If an object by the given id already exists, remove it.
        if self.has_key(id):
            self._delObject(id)

        if not suppress_events:
            notify(ObjectWillBeAddedEvent(ob, self, id))

        self._setOb(id, ob)
        ob = self._getOb(id)

        if set_owner:
            # TODO: eventify manage_fixupOwnershipAfterAdd
            # This will be called for a copy/clone, or a normal _setObject.
            ob.manage_fixupOwnershipAfterAdd()

            # Try to give user the local role "Owner", but only if
            # no local roles have been set on the object yet.
            if getattr(ob, '__ac_local_roles__', _marker) is None:
                user = getSecurityManager().getUser()
                if user is not None:
                    userid = user.getId()
                    if userid is not None:
                        ob.manage_setLocalRoles(userid, ['Owner'])

        if not suppress_events:
            notify(ObjectAddedEvent(ob, self, id))
            notifyContainerModified(self)

        OFS.subscribers.compatibilityCall('manage_afterAdd', ob, ob, self)

        return id


    def _delObject(self, id, dp=1, suppress_events=False):
        ob = self._getOb(id)

        OFS.subscribers.compatibilityCall('manage_beforeDelete', ob, ob, self)

        if not suppress_events:
            notify(ObjectWillBeRemovedEvent(ob, self, id))

        self._delOb(id)

        if not suppress_events:
            notify(ObjectRemovedEvent(ob, self, id))
            notifyContainerModified(self)


    # Aliases for mapping-like access.
    __len__ = objectCount
    keys = objectIds
    values = objectValues
    items = objectItems

    # backward compatibility
    hasObject = has_key

    security.declareProtected(access_contents_information, 'get')
    def get(self, name, default=None):
        return self._getOb(name, default)


    # Utility for generating unique IDs.

    security.declareProtected(access_contents_information, 'generateId')
    def generateId(self, prefix='item', suffix='', rand_ceiling=999999999):
        """Returns an ID not used yet by this folder.

        The ID is unlikely to collide with other threads and clients.
        The IDs are sequential to optimize access to objects
        that are likely to have some relation.
        """
        tree = self._tree
        n = self._v_nextid
        attempt = 0
        while 1:
            if n % 4000 != 0 and n <= rand_ceiling:
                id = '%s%d%s' % (prefix, n, suffix)
                if not tree.has_key(id):
                    break
            n = randint(1, rand_ceiling)
            attempt = attempt + 1
            if attempt > MAX_UNIQUEID_ATTEMPTS:
                # Prevent denial of service
                raise ExhaustedUniqueIdsError
        self._v_nextid = n + 1
        return id

    def __getattr__(self, name):
        # Boo hoo hoo!  Zope 2 prefers implicit acquisition over traversal
        # to subitems, and __bobo_traverse__ hooks don't work with
        # restrictedTraverse() unless __getattr__() is also present.
        # Oh well.
        res = self._tree.get(name)
        if res is None:
            raise AttributeError, name
        return res
class ToManyContRelationship(ToManyRelationshipBase):
    """
    ToManyContRelationship is the ToMany side of a realtionship that
    contains its related objects (like the normal Zope ObjectManager)
    """

    meta_type = "ToManyContRelationship"

    security = ClassSecurityInfo()


    def __init__(self, id):
        """set our instance values"""
        self.id = id
        self._objects = OOBTree()


    def _safeOfObjects(self):
        """
        Try to safely return ZenPack objects rather than
        causing imports to fail.
        """
        objs = []
        for ob in self._objects.values():
            try:
                objs.append(ob.__of__(self))
            except AttributeError:
                log.info("Ignoring unresolvable object '%s'", str(ob))
        return objs

    def __call__(self):
        """when we are called return our related object in our aq context"""
        return self._safeOfObjects()


    def __getattr__(self, name):
        """look in the two object stores for related objects"""
        if '_objects' in self.__dict__:
            objects = self._objects
            if objects.has_key(name): return objects[name]
        raise AttributeError( "Unable to find the attribute '%s'" % name )


    def __hasattr__(self, name):
        """check to see if we have an object by an id
        this will fail if passed a short id and object is stored
        with fullid (ie: it is related not contained)
        use hasobject to get around this issue"""
        return self._objects.has_key(name)


    def hasobject(self, obj):
        "check to see if we have this object"
        return self._objects.get(obj.id) == obj


    def addRelation(self, obj):
        """Override base to run manage_afterAdd like ObjectManager"""
        if self._objects.has_key(obj.getId()):
            log.debug("obj %s already exists on %s", obj.getPrimaryId(),
                        self.getPrimaryId())

        notify(ObjectWillBeAddedEvent(obj, self, obj.getId()))
        ToManyRelationshipBase.addRelation(self, obj)
        obj = obj.__of__(self)
        o = self._getOb(obj.id)
        notify(ObjectAddedEvent(o, self, obj.getId()))


    def _setObject(self,id,object,roles=None,user=None,set_owner=1):
        """ObjectManager interface to add contained object."""
        unused(user, roles, set_owner)
        object.__primary_parent__ = aq_base(self)
        self.addRelation(object)
        return object.getId()


    def manage_afterAdd(self, item, container):
        # Don't do recursion anymore, a subscriber does that.
        pass
    manage_afterAdd.__five_method__ = True

    def manage_afterClone(self, item):
        # Don't do recursion anymore, a subscriber does that.
        pass
    manage_afterClone.__five_method__ = True

    def manage_beforeDelete(self, item, container):
        # Don't do recursion anymore, a subscriber does that.
        pass
    manage_beforeDelete.__five_method__ = True

    def _add(self,obj):
        """add an object to one side of a ToManyContRelationship.
        """
        id = obj.id
        if self._objects.has_key(id):
            raise RelationshipExistsError
        v=checkValidId(self, id)
        if v is not None: id=v
        self._objects[id] = aq_base(obj)
        obj = aq_base(obj).__of__(self)


    def _remove(self, obj=None, suppress_events=False):
        """remove object from our side of a relationship"""
        if obj: objs = [obj]
        else: objs = self.objectValuesAll()
        if not suppress_events:
            for robj in objs:
                notify(ObjectWillBeRemovedEvent(robj, self, robj.getId()))
        if obj:
            id = obj.id
            if not self._objects.has_key(id):
                raise ObjectNotFound(
                    "object %s not found on %s" % (
                    obj.getPrimaryId(), self.getPrimaryId()))
            del self._objects[id]
        else:
            self._objects = OOBTree()
            self.__primary_parent__._p_changed = True
        if not suppress_events:
            for robj in objs:
                notify(ObjectRemovedEvent(robj, self, robj.getId()))


    def _remoteRemove(self, obj=None):
        """remove an object from the far side of this relationship
        if no object is passed in remove all objects"""
        if obj:
            if not self._objects.has_key(obj.id):
                raise ObjectNotFound("object %s not found on %s" % (
                    obj.getPrimaryId(), self.getPrimaryId()))
            objs = [obj]
        else: objs = self.objectValuesAll()
        remoteName = self.remoteName()
        for obj in objs:
            rel = getattr(obj, remoteName)
            try:
                rel._remove(self.__primary_parent__)
            except ObjectNotFound:
                message = log_tb(sys.exc_info())
                log.error('Remote remove failed. Run "zenchkrels -r -x1". ' + message)
                continue


    def _getOb(self, id, default=zenmarker):
        """look up in our local store and wrap in our aq_chain"""
        if self._objects.has_key(id):
            return self._objects[id].__of__(self)
        elif default == zenmarker:
            raise AttributeError( "Unable to find %s" % id )
        return default


    security.declareProtected('View', 'objectIds')
    def objectIds(self, spec=None):
        """only return contained objects"""
        if spec:
            if isinstance(spec,basestring): spec=[spec]
            return [obj.id for obj in self._objects.values() \
                        if obj.meta_type in spec]
        return [ k for k in self._objects.keys() ]
    objectIdsAll = objectIds


    security.declareProtected('View', 'objectValues')
    def objectValues(self, spec=None):
        """override to only return owned objects for many to many rel"""
        if spec:
            if isinstance(spec,basestring): spec=[spec]
            return [ob.__of__(self) for ob in self._objects.values() \
                        if ob.meta_type in spec]
        return self._safeOfObjects()
    security.declareProtected('View', 'objectValuesAll')
    objectValuesAll = objectValues


    def objectValuesGen(self):
        """Generator that returns all related objects."""
        return (obj.__of__(self) for obj in self._objects.values())


    def objectItems(self, spec=None):
        """over ride to only return owned objects for many to many rel"""
        if spec:
            if isinstance(spec,basestring): spec=[spec]
            return [(key,value.__of__(self)) \
                for (key,value) in self._objects.items() \
                    if value.meta_type in spec]
        return [(key,value.__of__(self)) \
                    for (key,value) in self._objects.items()]
    objectItemsAll = objectItems


#FIXME - need to make this work
#    def all_meta_types(self, interfaces=None):
#        mts = []
#        for mt in ToManyRelationshipBase.all_meta_types(self, interfaces):
#            if (mt.has_key('instance') and mt['instance']):
#                for cl in self.sub_classes:
#                    if checkClass(mt['instance'], cl):
#                        mts.append(mt)
#        return mts


    def _getCopy(self, container):
        """
        make new relation add copies of contained objs
        and refs if the relation is a many to many
        """
        rel = self.__class__(self.id)
        rel.__primary_parent__ = container
        rel = rel.__of__(container)
        norelcopy = getattr(self, 'zNoRelationshipCopy', [])
        if self.id in norelcopy: return rel
        for oobj in self.objectValuesAll():
            cobj = oobj._getCopy(rel)
            rel._setObject(cobj.id, cobj)
        return rel

    def checkValidId(self, id):
        """
        Is this a valid id for this container?
        """
        try:
            checkValidId(self, id)
        except:
            raise
        else:
            return True

    def exportXml(self, ofile, ignorerels=[]):
        """Return an xml representation of a ToManyContRelationship
        <tomanycont id='interfaces'>
            <object id='hme0'
                module='Products.Confmon.IpInterface' class='IpInterface'>
                <property></property> etc....
            </object>
        </tomanycont>
        """
        if self.countObjects() == 0: return
        ofile.write("<tomanycont id='%s'>\n" % self.id)
        for obj in self.objectValues():
            obj.exportXml(ofile, ignorerels)
        ofile.write("</tomanycont>\n")


    def checkRelation(self, repair=False):
        """Check to make sure that relationship bidirectionality is ok.
        """
        if len(self._objects):
            log.debug("checking relation: %s", self.id)
        else:
            return

        # look for objects that don't point back to us
        # or who should no longer exist in the database
        remoteName = self.remoteName()
        parentObject = self.getPrimaryParent()
        for obj in self._objects.values():
            if not hasattr(obj, remoteName):
                path = parentObject.getPrimaryUrlPath()
                if repair:
                    log.warn("Deleting %s object '%s' relation '%s' (missing remote relation '%s')",
                             path, obj, self.id, remoteName)
                    self._remove(obj, True)
                    continue
                else:
                    msg = "%s object '%s' relation '%s' missing remote relation '%s'" % (
                             path, obj, self.id, remoteName)
                    raise AttributeError(msg)

            rrel = getattr(obj, remoteName)
            if not rrel.hasobject(parentObject):
                log.error("remote relation %s doesn't point back to %s",
                                rrel.getPrimaryId(), self.getPrimaryId())
                if repair:
                    log.warn("reconnecting relation %s to relation %s",
                            rrel.getPrimaryId(),self.getPrimaryId())
                    rrel._add(parentObject)
Example #47
0
class MemberDataTool(UniqueObject, SimpleItem, PropertyManager,
                     ActionProviderBase):

    """ This tool wraps user objects, making them act as Member objects.
    """

    implements(IMemberDataTool)
    __implements__ = (z2IMemberDataTool, ActionProviderBase.__implements__)

    id = 'portal_memberdata'
    meta_type = 'CMF Member Data Tool'
    _v_temps = None
    _properties = ()

    security = ClassSecurityInfo()

    manage_options=( ActionProviderBase.manage_options +
                     ({ 'label' : 'Overview'
                       , 'action' : 'manage_overview'
                       }
                     , { 'label' : 'Contents'
                       , 'action' : 'manage_showContents'
                       }
                     )
                   + PropertyManager.manage_options
                   + SimpleItem.manage_options
                   )

    #
    #   ZMI methods
    #
    security.declareProtected(ManagePortal, 'manage_overview')
    manage_overview = DTMLResource( 'dtml/explainMemberDataTool', globals() )

    security.declareProtected(ViewManagementScreens, 'manage_showContents')
    manage_showContents = DTMLResource( 'dtml/memberdataContents', globals() )


    def __init__(self):
        self._members = OOBTree()
        # Create the default properties.
        self._setProperty('email', '', 'string')
        self._setProperty('portal_skin', '', 'string')
        self._setProperty('listed', '', 'boolean')
        self._setProperty('login_time', '2000/01/01', 'date')
        self._setProperty('last_login_time', '2000/01/01', 'date')

    #
    #   'portal_memberdata' interface methods
    #
    security.declarePrivate('getMemberDataContents')
    def getMemberDataContents(self):
        '''
        Return the number of members stored in the _members
        BTree and some other useful info
        '''
        membertool   = getToolByName(self, 'portal_membership')
        members      = self._members
        user_list    = membertool.listMemberIds()
        member_list  = members.keys()
        member_count = len(members)
        orphan_count = 0

        for member in member_list:
            if member not in user_list:
                orphan_count = orphan_count + 1

        return [{ 'member_count' : member_count,
                  'orphan_count' : orphan_count }]

    security.declarePrivate('searchMemberData')
    def searchMemberData(self, search_param, search_term, attributes=()):
        """ Search members. """
        res = []

        if not search_param:
            return res

        membership = getToolByName(self, 'portal_membership')

        if len(attributes) == 0:
            attributes = ('id', 'email')

        if search_param == 'username':
            search_param = 'id'

        for user_id in self._members.keys():
            u = membership.getMemberById(user_id)

            if u is not None:
                memberProperty = u.getProperty
                searched = memberProperty(search_param, None)

                if searched is not None and searched.find(search_term) != -1:
                    user_data = {}

                    for desired in attributes:
                        if desired == 'id':
                            user_data['username'] = memberProperty(desired, '')
                        else:
                            user_data[desired] = memberProperty(desired, '')

                    res.append(user_data)

        return res

    security.declarePrivate( 'searchMemberDataContents' )
    def searchMemberDataContents( self, search_param, search_term ):
        """ Search members. This method will be deprecated soon. """
        res = []

        if search_param == 'username':
            search_param = 'id'

        mtool   = getToolByName(self, 'portal_membership')

        for member_id in self._members.keys():

            user_wrapper = mtool.getMemberById( member_id )

            if user_wrapper is not None:
                memberProperty = user_wrapper.getProperty
                searched = memberProperty( search_param, None )

                if searched is not None and searched.find(search_term) != -1:

                    res.append( { 'username': memberProperty( 'id' )
                                , 'email' : memberProperty( 'email', '' )
                                }
                            )
        return res

    security.declarePrivate('pruneMemberDataContents')
    def pruneMemberDataContents(self):
        """ Delete data contents of all members not listet in acl_users.
        """
        membertool= getToolByName(self, 'portal_membership')
        members = self._members
        user_list = membertool.listMemberIds()

        for member_id in list(members.keys()):
            if member_id not in user_list:
                del members[member_id]

    security.declarePrivate('wrapUser')
    def wrapUser(self, u):
        '''
        If possible, returns the Member object that corresponds
        to the given User object.
        '''
        id = u.getId()
        members = self._members
        if not members.has_key(id):
            # Get a temporary member that might be
            # registered later via registerMemberData().
            temps = self._v_temps
            if temps is not None and temps.has_key(id):
                m = temps[id]
            else:
                base = aq_base(self)
                m = MemberData(base, id)
                if temps is None:
                    self._v_temps = {id:m}
                    if hasattr(self, 'REQUEST'):
                        # No REQUEST during tests.
                        self.REQUEST._hold(CleanupTemp(self))
                else:
                    temps[id] = m
        else:
            m = members[id]
        # Return a wrapper with self as containment and
        # the user as context.
        return m.__of__(self).__of__(u)

    security.declarePrivate('registerMemberData')
    def registerMemberData(self, m, id):
        """ Add the given member data to the _members btree.
        """
        self._members[id] = aq_base(m)

    security.declarePrivate('deleteMemberData')
    def deleteMemberData(self, member_id):
        """ Delete member data of specified member.
        """
        members = self._members
        if members.has_key(member_id):
            del members[member_id]
            return 1
        else:
            return 0
Example #48
0
class Index:

    def __init__(self, table, col_idx, idx_type, transform_criteria=None):
        self.index = None
        self.type = idx_type
        self.table = table
        self.transform_criteria = transform_criteria
        # table.indexes[col_idx]=idx_type

        if idx_type == "Hash":
            self.create_hash_index(col_idx)
        elif idx_type == "Hash_Transform":
            self.create_transform_hash_index(col_idx)
        else:
            self.index = OOBTree()
            for i, row in enumerate(self.table.rows):
                k = row[col_idx]
                if k in self.index:
                    self.index[k].append((i, col_idx))
                else:
                    self.index[k] = [(i, col_idx)]

    def create_hash_index(self, col_idx):
        self.index = {}
        if self.table.is_col_numeric(col_idx):
            for i, row in enumerate(self.table.rows):
                k = float(row[col_idx])
                if k in self.index.keys():
                    self.index[k].append((i, col_idx))
                else:
                    self.index[k] = [(i, col_idx)]
    
        else:  # indexed column is not numberic ; keys should be strings
            for i, row in enumerate(self.table.rows):
                k = row[col_idx]
                if k in self.index.keys():
                    self.index[k].append((i, col_idx))
                else:
                    self.index[k] = [(i, col_idx)]

    def create_transform_hash_index(self, col_idx):
        # assuming transformed coclumns are numeric
        self.index = {}
        arithop = self.transform_criteria[0]
        constant = self.transform_criteria[1]
        for i, row in enumerate(self.table.rows):
            key = row[col_idx]
            transformed_key = arithop(float(key), float(constant))
            if transformed_key in self.index.keys():
                self.index[transformed_key].append((i, col_idx))
            else:
                self.index[transformed_key] = [(i, col_idx)]

    def get_pos(self, key):
        if key in self.index.keys():
            return self.index[key]
        else:
            return None

    def print(self, f=None):
        for k, v in self.index.items():
            print("%-10s -> %s" % (k, v), file=f)
Example #49
0
class LookupTable(base.Base):
    "LookupTable class"

    meta_type = "LookupTable"
    security = ClassSecurityInfo()
    records = None
    recordsLength = None

    drawDict = base.Base.drawDict.copy()
    drawDict['drawTable'] = 'drawTable'

    security.declareProtected('View management screens', 'edit')
    def edit(self, *args, **kw):
        "Inline edit short object"
        format = "<p>Currently there are %s records</p><div>%s</div>"
        if self.records is None:
            self.records = OOBTree()
        lenRecords = self.recordsLength() if self.recordsLength is not None else 0
        return format % (lenRecords, self.create_button('clear', "Clear"))

    security.declarePrivate('processRecorderChanges')
    def processRecorderChanges(self, form):
        "process the recorder changes"
        clear = form.pop('clear', None)
        if clear is not None:
            self.clear()

    security.declarePrivate('after_manage_edit')
    def before_manage_edit(self, form):
        "process the edits"
        self.processRecorderChanges(form)

    security.declareProtected('View management screens', "drawTable")
    def drawTable(self):
        "Render page"
        temp = []
        format = '<p>%s:%s</p>'
        if self.records is not None:
            for key,value in self.records.items():
                temp.append(format % (repr(key), repr(value)))
        return ''.join(temp)

    security.declareProtected('Python Record Modification', 'insert')
    def insert(self, key, value):
        "modify this key and value into the OOBTree"
        if self.records is None:
            self.records = OOBTree()
        if self.recordsLength is None:
            self.setObject('recordsLength' ,BTrees.Length.Length())
        
        if key not in self.records:
            self.recordsLength.change(1)
        self.records.insert(key,value)

    security.declareProtected('Python Record Modification', 'add')
    def add(self, key, value):
        "this this key and value into the OOBTree"
        if self.records is None:
            self.records = OOBTree()
        if self.recordsLength is None:
            self.setObject('recordsLength' ,BTrees.Length.Length())
        
        if key not in self.records:
            self.recordsLength.change(1)
        self.records[key] = value

    security.declareProtected('Python Record Access', 'items')
    def items(self, min=None, max=None):
        "return the items in this OOBTree"
        if self.records is None:
            return []
        return self.records.items(min, max)

    security.declareProtected('Python Record Access', 'values')
    def values(self, min=None, max=None):
        "return the values of this OOBTree"
        if self.records is None:
            return []
        return self.records.values(min, max)

    security.declareProtected('Python Record Modification', 'update')
    def update(self, collection):
        "update our OOBTree with the data in collection"
        if self.records is None:
            self.records = OOBTree()
        if self.recordsLength is None:
            self.setObject('recordsLength' ,BTrees.Length.Length())
        
        records = self.records
        change = self.recordsLength.change
        for key,value in collection.items():
            if key not in records:
                change(1)
            records[key] = value

    security.declareProtected('Python Record Access', 'keys')
    def keys(self, min=None, max=None):
        "return the keys of this OOBTree"
        if self.records is None:
            return []
        return self.records.keys(min,max)

    security.declareProtected('Python Record Modification', '__delitem__')
    def __delitem__(self, key):
        "delete this key from the OOBTree"
        if self.records is not None:
            del self.records[key]
            self.recordsLength.change(-1)

    security.declareProtected('Python Record Modification', 'remove')
    def remove(self, key):
        "delete this key from the OOBTree"
        if self.records is not None:
            del self.records[key]
            self.recordsLength.change(-1)

    security.declareProtected('Python Record Modification', '__setitem__')
    def __setitem__(self, key, value):
        "set this key and value in the OOBTree"
        if self.records is None:
            self.records = OOBTree()
        self.records[key] = value

    security.declareProtected('Python Record Access', '__getitem__')
    def __getitem__(self, index):
        "get this item from the OOBTree"
        if self.records is not None:
            return self.records[index]
        raise KeyError, index

    security.declareProtected('Python Record Access', 'get')
    def get(self, key, default=None):
        "get this item from the OOBTree"
        if self.records is not None:
            return self.records.get(key,default)
        return default

    security.declareProtected('Python Record Access', 'has_key')
    def has_key(self, key):
        "see if we have this key in the OOBTree"
        if self.records is not None:
            return self.records.has_key(key)
        return False

    security.declareProtected('Python Record Modification', 'clear')
    def clear(self):
        "clear the OOBTree"
        self.setObject('records', None)
        self.setObject('recordsLength', None)
        
    security.declarePrivate("PrincipiaSearchSource")
    def PrincipiaSearchSource(self):
        "This is the basic search function"
        return ''
      
    security.declarePrivate('classUpgrader')
    def classUpgrader(self):
        "upgrade this class"
        self.createBTreeLength() 
      
    security.declarePrivate('createBTreeLength')
    def createBTreeLength(self):
        "remove Filters that are not being used"
        if self.records is not None:
            length = BTrees.Length.Length()
            length.set(len(self.records))
            self.setObject('recordsLength', length)
    createBTreeLength = utility.upgradeLimit(createBTreeLength, 165)
Example #50
0
class UnIndex(SimpleItem):
    """Simple forward and reverse index.
    """

    zmi_icon = 'fas fa-info-circle'
    _counter = None
    operators = ('or', 'and')
    useOperator = 'or'
    query_options = ()

    def __init__(self, id, ignore_ex=None, call_methods=None,
                 extra=None, caller=None):
        """Create an unindex

        UnIndexes are indexes that contain two index components, the
        forward index (like plain index objects) and an inverted
        index.  The inverted index is so that objects can be unindexed
        even when the old value of the object is not known.

        e.g.

        self._index = {datum:[documentId1, documentId2]}
        self._unindex = {documentId:datum}

        The arguments are:

          'id' -- the name of the item attribute to index.  This is
          either an attribute name or a record key.

          'ignore_ex' -- should be set to true if you want the index
          to ignore exceptions raised while indexing instead of
          propagating them.

          'call_methods' -- should be set to true if you want the index
          to call the attribute 'id' (note: 'id' should be callable!)
          You will also need to pass in an object in the index and
          uninded methods for this to work.

          'extra' -- a mapping object that keeps additional
          index-related parameters - subitem 'indexed_attrs'
          can be string with comma separated attribute names or
          a list

          'caller' -- reference to the calling object (usually
          a (Z)Catalog instance
        """

        def _get(o, k, default):
            """ return a value for a given key of a dict/record 'o' """
            if isinstance(o, dict):
                return o.get(k, default)
            else:
                return getattr(o, k, default)

        self.id = id
        self.ignore_ex = ignore_ex  # currently unimplemented
        self.call_methods = call_methods

        # allow index to index multiple attributes
        ia = _get(extra, 'indexed_attrs', id)
        if isinstance(ia, str):
            self.indexed_attrs = ia.split(',')
        else:
            self.indexed_attrs = list(ia)
        self.indexed_attrs = [
            attr.strip() for attr in self.indexed_attrs if attr]
        if not self.indexed_attrs:
            self.indexed_attrs = [id]

        self.clear()

    def __len__(self):
        return self._length()

    def getId(self):
        return self.id

    def clear(self):
        self._length = Length()
        self._index = OOBTree()
        self._unindex = IOBTree()

        if self._counter is None:
            self._counter = Length()
        else:
            self._increment_counter()

    def __nonzero__(self):
        return not not self._unindex

    def histogram(self):
        """Return a mapping which provides a histogram of the number of
        elements found at each point in the index.
        """
        histogram = {}
        for item in self._index.items():
            if isinstance(item, int):
                entry = 1  # "set" length is 1
            else:
                key, value = item
                entry = len(value)
            histogram[entry] = histogram.get(entry, 0) + 1
        return histogram

    def referencedObjects(self):
        """Generate a list of IDs for which we have referenced objects."""
        return self._unindex.keys()

    def getEntryForObject(self, documentId, default=_marker):
        """Takes a document ID and returns all the information we have
        on that specific object.
        """
        if default is _marker:
            return self._unindex.get(documentId)
        return self._unindex.get(documentId, default)

    def removeForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and remove any reference to documentId
        in its entry in the index.
        """
        indexRow = self._index.get(entry, _marker)
        if indexRow is not _marker:
            try:
                indexRow.remove(documentId)
                if not indexRow:
                    del self._index[entry]
                    self._length.change(-1)
            except ConflictError:
                raise
            except AttributeError:
                # index row is an int
                try:
                    del self._index[entry]
                except KeyError:
                    # swallow KeyError because it was probably
                    # removed and then _length AttributeError raised
                    pass
                if isinstance(self.__len__, Length):
                    self._length = self.__len__
                    del self.__len__
                self._length.change(-1)
            except Exception:
                LOG.error('%(context)s: unindex_object could not remove '
                          'documentId %(doc_id)s from index %(index)r.  This '
                          'should not happen.', dict(
                              context=self.__class__.__name__,
                              doc_id=documentId,
                              index=self.id),
                          exc_info=sys.exc_info())
        else:
            LOG.error('%(context)s: unindex_object tried to '
                      'retrieve set %(entry)r from index %(index)r '
                      'but couldn\'t.  This should not happen.', dict(
                          context=self.__class__.__name__,
                          entry=entry,
                          index=self.id))

    def insertForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and put it in the correct place
        in the forward index.

        This will also deal with creating the entire row if necessary.
        """
        indexRow = self._index.get(entry, _marker)

        # Make sure there's actually a row there already. If not, create
        # a set and stuff it in first.
        if indexRow is _marker:
            # We always use a set to avoid getting conflict errors on
            # multiple threads adding a new row at the same time
            self._index[entry] = IITreeSet((documentId, ))
            self._length.change(1)
        else:
            try:
                indexRow.insert(documentId)
            except AttributeError:
                # Inline migration: index row with one element was an int at
                # first (before Zope 2.13).
                indexRow = IITreeSet((indexRow, documentId))
                self._index[entry] = indexRow

    def index_object(self, documentId, obj, threshold=None):
        """ wrapper to handle indexing of multiple attributes """

        fields = self.getIndexSourceNames()
        res = 0
        for attr in fields:
            res += self._index_object(documentId, obj, threshold, attr)

        if res > 0:
            self._increment_counter()

        return res > 0

    def _index_object(self, documentId, obj, threshold=None, attr=''):
        """ index and object 'obj' with integer id 'documentId'"""
        returnStatus = 0

        # First we need to see if there's anything interesting to look at
        datum = self._get_object_datum(obj, attr)
        if datum is None:
            # Prevent None from being indexed. None doesn't have a valid
            # ordering definition compared to any other object.
            # BTrees 4.0+ will throw a TypeError
            # "object has default comparison" and won't let it be indexed.
            return 0

        # We don't want to do anything that we don't have to here, so we'll
        # check to see if the new and existing information is the same.
        oldDatum = self._unindex.get(documentId, _marker)
        if datum != oldDatum:
            if oldDatum is not _marker:
                self.removeForwardIndexEntry(oldDatum, documentId)
                if datum is _marker:
                    try:
                        del self._unindex[documentId]
                    except ConflictError:
                        raise
                    except Exception:
                        LOG.error('Should not happen: oldDatum was there, '
                                  'now its not, for document: %s', documentId)

            if datum is not _marker:
                self.insertForwardIndexEntry(datum, documentId)
                self._unindex[documentId] = datum

            returnStatus = 1

        return returnStatus

    def _get_object_datum(self, obj, attr):
        # self.id is the name of the index, which is also the name of the
        # attribute we're interested in.  If the attribute is callable,
        # we'll do so.
        try:
            datum = getattr(obj, attr)
            if safe_callable(datum):
                datum = datum()
        except (AttributeError, TypeError):
            datum = _marker
        return datum

    def _increment_counter(self):
        if self._counter is None:
            self._counter = Length()
        self._counter.change(1)

    def getCounter(self):
        """Return a counter which is increased on index changes"""
        return self._counter is not None and self._counter() or 0

    def numObjects(self):
        """Return the number of indexed objects."""
        return len(self._unindex)

    def indexSize(self):
        """Return the size of the index in terms of distinct values."""
        return len(self)

    def unindex_object(self, documentId):
        """ Unindex the object with integer id 'documentId' and don't
        raise an exception if we fail
        """
        unindexRecord = self._unindex.get(documentId, _marker)
        if unindexRecord is _marker:
            return None

        self._increment_counter()

        self.removeForwardIndexEntry(unindexRecord, documentId)
        try:
            del self._unindex[documentId]
        except ConflictError:
            raise
        except Exception:
            LOG.debug('Attempt to unindex nonexistent document'
                      ' with id %s', documentId, exc_info=True)

    def _apply_not(self, not_parm, resultset=None):
        index = self._index
        setlist = []
        for k in not_parm:
            s = index.get(k, None)
            if s is None:
                continue
            elif isinstance(s, int):
                s = IISet((s, ))
            setlist.append(s)
        return multiunion(setlist)

    def _convert(self, value, default=None):
        return value

    def getRequestCache(self):
        """returns dict for caching per request for interim results
        of an index search. Returns 'None' if no REQUEST attribute
        is available"""

        cache = None
        REQUEST = aq_get(self, 'REQUEST', None)
        if REQUEST is not None:
            catalog = aq_parent(aq_parent(aq_inner(self)))
            if catalog is not None:
                # unique catalog identifier
                key = '_catalogcache_{0}_{1}'.format(
                    catalog.getId(), id(catalog))
                cache = REQUEST.get(key, None)
                if cache is None:
                    cache = REQUEST[key] = RequestCache()

        return cache

    def getRequestCacheKey(self, record, resultset=None):
        """returns an unique key of a search record"""
        params = []

        # record operator (or, and)
        params.append(('operator', record.operator))

        # not / exclude operator
        not_value = record.get('not', None)
        if not_value is not None:
            not_value = frozenset(not_value)
            params.append(('not', not_value))

        # record options
        for op in ['range', 'usage']:
            op_value = record.get(op, None)
            if op_value is not None:
                params.append((op, op_value))

        # record keys
        rec_keys = frozenset(record.keys)
        params.append(('keys', rec_keys))

        # build record identifier
        rid = frozenset(params)

        # unique index identifier
        iid = '_{0}_{1}_{2}'.format(self.__class__.__name__,
                                    self.id, self.getCounter())
        return (iid, rid)

    def _apply_index(self, request, resultset=None):
        """Apply the index to query parameters given in the request arg.

        If the query does not match the index, return None, otherwise
        return a tuple of (result, used_attributes), where used_attributes
        is again a tuple with the names of all used data fields.
        """
        record = IndexQuery(request, self.id, self.query_options,
                            self.operators, self.useOperator)
        if record.keys is None:
            return None
        return (self.query_index(record, resultset=resultset), (self.id, ))

    def query_index(self, record, resultset=None):
        """Search the index with the given IndexQuery object.

        If the query has a key which matches the 'id' of
        the index instance, one of a few things can happen:

          - if the value is a string, turn the value into
            a single-element sequence, and proceed.

          - if the value is a sequence, return a union search.

          - If the value is a dict and contains a key of the form
            '<index>_operator' this overrides the default method
            ('or') to combine search results. Valid values are 'or'
            and 'and'.
        """
        index = self._index
        r = None
        opr = None

        # not / exclude parameter
        not_parm = record.get('not', None)

        operator = record.operator

        cachekey = None
        cache = self.getRequestCache()
        if cache is not None:
            cachekey = self.getRequestCacheKey(record)
            if cachekey is not None:
                cached = None
                if operator == 'or':
                    cached = cache.get(cachekey, None)
                else:
                    cached_setlist = cache.get(cachekey, None)
                    if cached_setlist is not None:
                        r = resultset
                        for s in cached_setlist:
                            # the result is bound by the resultset
                            r = intersection(r, s)
                            # If intersection, we can't possibly get a
                            # smaller result
                            if not r:
                                break
                        cached = r

                if cached is not None:
                    if isinstance(cached, int):
                        cached = IISet((cached, ))

                    if not_parm:
                        not_parm = list(map(self._convert, not_parm))
                        exclude = self._apply_not(not_parm, resultset)
                        cached = difference(cached, exclude)

                    return cached

        if not record.keys and not_parm:
            # convert into indexed format
            not_parm = list(map(self._convert, not_parm))
            # we have only a 'not' query
            record.keys = [k for k in index.keys() if k not in not_parm]
        else:
            # convert query arguments into indexed format
            record.keys = list(map(self._convert, record.keys))

        # Range parameter
        range_parm = record.get('range', None)
        if range_parm:
            opr = 'range'
            opr_args = []
            if range_parm.find('min') > -1:
                opr_args.append('min')
            if range_parm.find('max') > -1:
                opr_args.append('max')

        if record.get('usage', None):
            # see if any usage params are sent to field
            opr = record.usage.lower().split(':')
            opr, opr_args = opr[0], opr[1:]

        if opr == 'range':  # range search
            if 'min' in opr_args:
                lo = min(record.keys)
            else:
                lo = None
            if 'max' in opr_args:
                hi = max(record.keys)
            else:
                hi = None
            if hi:
                setlist = index.values(lo, hi)
            else:
                setlist = index.values(lo)

            # If we only use one key, intersect and return immediately
            if len(setlist) == 1:
                result = setlist[0]
                if isinstance(result, int):
                    result = IISet((result,))

                if cachekey is not None:
                    if operator == 'or':
                        cache[cachekey] = result
                    else:
                        cache[cachekey] = [result]

                if not_parm:
                    exclude = self._apply_not(not_parm, resultset)
                    result = difference(result, exclude)
                return result

            if operator == 'or':
                tmp = []
                for s in setlist:
                    if isinstance(s, int):
                        s = IISet((s,))
                    tmp.append(s)
                r = multiunion(tmp)

                if cachekey is not None:
                    cache[cachekey] = r
            else:
                # For intersection, sort with smallest data set first
                tmp = []
                for s in setlist:
                    if isinstance(s, int):
                        s = IISet((s,))
                    tmp.append(s)
                if len(tmp) > 2:
                    setlist = sorted(tmp, key=len)
                else:
                    setlist = tmp

                # 'r' is not invariant of resultset. Thus, we
                # have to remember 'setlist'
                if cachekey is not None:
                    cache[cachekey] = setlist

                r = resultset
                for s in setlist:
                    # the result is bound by the resultset
                    r = intersection(r, s)
                    # If intersection, we can't possibly get a smaller result
                    if not r:
                        break

        else:  # not a range search
            # Filter duplicates
            setlist = []
            for k in record.keys:
                if k is None:
                    # Prevent None from being looked up. None doesn't
                    # have a valid ordering definition compared to any
                    # other object. BTrees 4.0+ will throw a TypeError
                    # "object has default comparison".
                    continue
                try:
                    s = index.get(k, None)
                except TypeError:
                    # key is not valid for this Btree so the value is None
                    LOG.error(
                        '%(context)s: query_index tried '
                        'to look up key %(key)r from index %(index)r '
                        'but key was of the wrong type.', dict(
                            context=self.__class__.__name__,
                            key=k,
                            index=self.id,
                        )
                    )
                    s = None
                # If None, try to bail early
                if s is None:
                    if operator == 'or':
                        # If union, we can possibly get a bigger result
                        continue
                    # If intersection, we can't possibly get a smaller result
                    if cachekey is not None:
                        # If operator is 'and', we have to cache a list of
                        # IISet objects
                        cache[cachekey] = [IISet()]
                    return IISet()
                elif isinstance(s, int):
                    s = IISet((s,))
                setlist.append(s)

            # If we only use one key return immediately
            if len(setlist) == 1:
                result = setlist[0]
                if isinstance(result, int):
                    result = IISet((result,))

                if cachekey is not None:
                    if operator == 'or':
                        cache[cachekey] = result
                    else:
                        cache[cachekey] = [result]

                if not_parm:
                    exclude = self._apply_not(not_parm, resultset)
                    result = difference(result, exclude)
                return result

            if operator == 'or':
                # If we already get a small result set passed in, intersecting
                # the various indexes with it and doing the union later is
                # faster than creating a multiunion first.

                if resultset is not None and len(resultset) < 200:
                    smalllist = []
                    for s in setlist:
                        smalllist.append(intersection(resultset, s))
                    r = multiunion(smalllist)

                    # 'r' is not invariant of resultset.  Thus, we
                    # have to remember the union of 'setlist'. But
                    # this is maybe a performance killer. So we do not cache.
                    # if cachekey is not None:
                    #    cache[cachekey] = multiunion(setlist)

                else:
                    r = multiunion(setlist)
                    if cachekey is not None:
                        cache[cachekey] = r
            else:
                # For intersection, sort with smallest data set first
                if len(setlist) > 2:
                    setlist = sorted(setlist, key=len)

                # 'r' is not invariant of resultset. Thus, we
                # have to remember the union of 'setlist'
                if cachekey is not None:
                    cache[cachekey] = setlist

                r = resultset
                for s in setlist:
                    r = intersection(r, s)
                    # If intersection, we can't possibly get a smaller result
                    if not r:
                        break

        if isinstance(r, int):
            r = IISet((r, ))
        if r is None:
            return IISet()
        if not_parm:
            exclude = self._apply_not(not_parm, resultset)
            r = difference(r, exclude)
        return r

    def hasUniqueValuesFor(self, name):
        """has unique values for column name"""
        if name == self.id:
            return 1
        return 0

    def getIndexSourceNames(self):
        """Return sequence of indexed attributes."""
        return getattr(self, 'indexed_attrs', [self.id])

    def getIndexQueryNames(self):
        """Indicate that this index applies to queries for the index's name."""
        return (self.id,)

    def uniqueValues(self, name=None, withLengths=0):
        """returns the unique values for name

        if withLengths is true, returns a sequence of
        tuples of (value, length)
        """
        if name is None:
            name = self.id
        elif name != self.id:
            return

        if not withLengths:
            for key in self._index.keys():
                yield key
        else:
            for key, value in self._index.items():
                if isinstance(value, int):
                    yield (key, 1)
                else:
                    yield (key, len(value))

    def keyForDocument(self, id):
        # This method is superseded by documentToKeyMap
        return self._unindex[id]

    def documentToKeyMap(self):
        return self._unindex

    def items(self):
        items = []
        for k, v in self._index.items():
            if isinstance(v, int):
                v = IISet((v,))
            items.append((k, v))
        return items
Example #51
0
class Folder(Persistent, Contained):
    """The standard Zope Folder implementation."""

    implements(IContentContainer)

    def __init__(self):
        self.data = OOBTree()

    def keys(self):
        """Return a sequence-like object containing the names
           associated with the objects that appear in the folder
        """
        return self.data.keys()

    def __iter__(self):
        return iter(self.data.keys())

    def values(self):
        """Return a sequence-like object containing the objects that
           appear in the folder.
        """
        return self.data.values()

    def items(self):
        """Return a sequence-like object containing tuples of the form
           (name, object) for the objects that appear in the folder.
        """
        return self.data.items()

    def __getitem__(self, name):
        """Return the named object, or raise ``KeyError`` if the object
           is not found.
        """
        return self.data[name]

    def get(self, name, default=None):
        """Return the named object, or the value of the `default`
           argument if the object is not found.
        """
        return self.data.get(name, default)

    def __contains__(self, name):
        """Return true if the named object appears in the folder."""
        return self.data.has_key(name)

    def __len__(self):
        """Return the number of objects in the folder."""
        return len(self.data)

    def __setitem__(self, name, object):
        """Add the given object to the folder under the given name."""

        if not (isinstance(name, str) or isinstance(name, unicode)):
            raise TypeError("Name must be a string rather than a %s" %
                            name.__class__.__name__)
        try:
            unicode(name)
        except UnicodeError:
            raise TypeError("Non-unicode names must be 7-bit-ascii only")
        if not name:
            raise TypeError("Name must not be empty")

        if name in self.data:
            raise KeyError("name, %s, is already in use" % name)

        setitem(self, self.data.__setitem__, name, object)

    def __delitem__(self, name):
        """Delete the named object from the folder. Raises a KeyError
           if the object is not found."""
        uncontained(self.data[name], self, name)
        del self.data[name]
Example #52
0
class DataBucketStream(Document):
    """
  Represents data stored in many small files inside a "stream".
  Each file is "addressed" by its key similar to dict.
  """

    meta_type = 'ERP5 Data Bucket Stream'
    portal_type = 'Data Bucket Stream'
    add_permission = Permissions.AddPortalContent

    # Declarative security
    security = ClassSecurityInfo()
    security.declareObjectProtected(Permissions.AccessContentsInformation)

    # Declarative properties
    property_sheets = (PropertySheet.CategoryCore, PropertySheet.SortIndex)

    def __init__(self, id, **kw):
        self.initBucketTree()
        self.initIndexTree()
        Document.__init__(self, id, **kw)

    def __len__(self):
        return len(self._tree)

    def initBucketTree(self):
        """
      Initialize the Bucket Tree
    """
        self._tree = OOBTree()

    def initIndexTree(self):
        """
      Initialize the Index Tree
    """
        self._long_index_tree = LOBTree()

    def getMaxKey(self, key=None):
        """
    Return the maximum key
    """
        try:
            return self._tree.maxKey(key)
        except ValueError:
            return None

    def getMaxIndex(self, index=None):
        """
    Return the maximum index
    """
        try:
            return self._long_index_tree.maxKey(index)
        except ValueError:
            return None

    def getMinKey(self, key=None):
        """
    Return the minimum key
    """
        try:
            return self._tree.minKey(key)
        except ValueError:
            return None

    def getMinIndex(self, index=None):
        """
    Return the minimum key
    """
        try:
            return self._long_index_tree.minKey(index)
        except ValueError:
            return None

    def _getOb(self, id, *args, **kw):
        return None

    def getBucketByKey(self, key=None):
        """
      Get one bucket
    """
        return self._tree[key].value

    def getBucketByIndex(self, index=None):
        """
      Get one bucket
    """
        key = self._long_index_tree[index]
        return self.getBucketByKey(key).value

    def getBucket(self, key):
        log('DeprecationWarning: Please use getBucketByKey')
        return self.getBucketByKey(key)

    def hasBucketKey(self, key):
        """
      Wether bucket with such key exists
    """
        return key in self._tree

    def hasBucketIndex(self, index):
        """
      Wether bucket with such index exists
    """
        return self._long_index_tree.has_key(index)

    def insertBucket(self, key, value):
        """
      Insert one bucket
    """
        try:
            count = self._long_index_tree.maxKey() + 1
        except ValueError:
            count = 0
        except AttributeError:
            pass
        try:
            self._long_index_tree.insert(count, key)
        except AttributeError:
            pass
        value = PersistentString(value)
        is_new_key = self._tree.insert(key, value)
        if not is_new_key:
            self.log("Reingestion of same key")
            self._tree[key] = value

    def getBucketKeySequenceByKey(self,
                                  start_key=None,
                                  stop_key=None,
                                  count=None,
                                  exclude_start_key=False,
                                  exclude_stop_key=False):
        """
      Get a lazy sequence of bucket keys
    """
        sequence = self._tree.keys(min=start_key,
                                   max=stop_key,
                                   excludemin=exclude_start_key,
                                   excludemax=exclude_stop_key)
        if count is None:
            return sequence
        return sequence[:count]

    def getBucketKeySequenceByIndex(self,
                                    start_index=None,
                                    stop_index=None,
                                    count=None,
                                    exclude_start_index=False,
                                    exclude_stop_index=False):
        """
      Get a lazy sequence of bucket keys
    """
        sequence = self._long_index_tree.values(min=start_index,
                                                max=stop_index,
                                                excludemin=exclude_start_index,
                                                excludemax=exclude_stop_index)
        if count is None:
            return sequence
        return sequence[:count]

    def getBucketKeySequence(self, start_key=None, count=None):
        log('DeprecationWarning: Please use getBucketKeySequenceByKey')
        return self.getBucketKeySequenceByKey(start_key=start_key, count=count)

    def getBucketIndexKeySequenceByIndex(self,
                                         start_index=None,
                                         stop_index=None,
                                         count=None,
                                         exclude_start_index=False,
                                         exclude_stop_index=False):
        """
      Get a lazy sequence of bucket keys
    """
        sequence = self._long_index_tree.items(min=start_index,
                                               max=stop_index,
                                               excludemin=exclude_start_index,
                                               excludemax=exclude_stop_index)
        if count is not None:
            sequence = sequence[:count]
        return IndexKeySequence(self, sequence)

    def getBucketIndexSequenceByIndex(self,
                                      start_index=None,
                                      stop_index=None,
                                      count=None,
                                      exclude_start_index=False,
                                      exclude_stop_index=False):
        """
      Get a lazy sequence of bucket keys
    """
        sequence = self._long_index_tree.keys(min=start_index,
                                              max=stop_index,
                                              excludemin=exclude_start_index,
                                              excludemax=exclude_stop_index)
        if count is None:
            return sequence
        return sequence[:count]

    def getBucketValueSequenceByKey(self,
                                    start_key=None,
                                    stop_key=None,
                                    count=None,
                                    exclude_start_key=False,
                                    exclude_stop_key=False):
        """
      Get a lazy sequence of bucket values
    """
        sequence = self._tree.values(min=start_key,
                                     max=stop_key,
                                     excludemin=exclude_start_key,
                                     excludemax=exclude_stop_key)
        if count is None:
            return sequence
        return sequence[:count]

    def getBucketValueSequenceByIndex(self,
                                      start_index=None,
                                      stop_index=None,
                                      count=None,
                                      exclude_start_index=False,
                                      exclude_stop_index=False):
        """
      Get a lazy sequence of bucket values
    """
        sequence = self._long_index_tree.values(min=start_index,
                                                max=stop_index,
                                                excludemin=exclude_start_index,
                                                excludemax=exclude_stop_index)
        if count is not None:
            sequence = sequence[:count]
        return IndexValueSequence(self, sequence)

    def getBucketValueSequence(self, start_key=None, count=None):
        log('DeprecationWarning: Please use getBucketValueSequenceByKey')
        return self.getBucketValueSequenceByKey(start_key=start_key,
                                                count=count)

    def getBucketKeyItemSequenceByKey(self,
                                      start_key=None,
                                      stop_key=None,
                                      count=None,
                                      exclude_start_key=False,
                                      exclude_stop_key=False):
        """
      Get a lazy sequence of bucket items
    """
        sequence = self._tree.items(min=start_key,
                                    max=stop_key,
                                    excludemin=exclude_start_key,
                                    excludemax=exclude_stop_key)
        if count is None:
            return sequence
        return sequence[:count]

    def getBucketItemSequence(self,
                              start_key=None,
                              count=None,
                              exclude_start_key=False):
        log('DeprecationWarning: Please use getBucketKeyItemSequenceByKey')
        return self.getBucketKeyItemSequenceByKey(
            start_key=start_key,
            count=count,
            exclude_start_key=exclude_start_key)

    def getBucketIndexItemSequenceByIndex(self,
                                          start_index=None,
                                          stop_index=None,
                                          count=None,
                                          exclude_start_index=False,
                                          exclude_stop_index=False):
        """
      Get a lazy sequence of bucket items
    """
        sequence = self._long_index_tree.items(min=start_index,
                                               max=stop_index,
                                               excludemin=exclude_start_index,
                                               excludemax=exclude_stop_index)
        if count is not None:
            sequence = sequence[:count]
        return IndexItemSequence(self, sequence)

    def getBucketIndexKeyItemSequenceByIndex(self,
                                             start_index=None,
                                             stop_index=None,
                                             count=None,
                                             exclude_start_index=False,
                                             exclude_stop_index=False):
        """
      Get a lazy sequence of bucket items
    """
        sequence = self._long_index_tree.items(min=start_index,
                                               max=stop_index,
                                               excludemin=exclude_start_index,
                                               excludemax=exclude_stop_index)
        if count is not None:
            sequence = sequence[:count]
        return IndexKeyItemSequence(self, sequence)

    def getItemList(self):
        """
      Return a list of all key, value pairs
    """
        return [item for item in self._tree.items()]

    def getKeyList(self):
        """
      Return a list of all keys
    """
        return [key for key in self._tree.keys()]

    def getIndexList(self):
        """
      Return a list of all indexes
    """
        return [key for key in self._long_index_tree.keys()]

    def getIndexKeyTupleList(self):
        """
      Return a list of all indexes
    """
        return [key for key in self._long_index_tree.items()]

    def getMd5sum(self, key):
        """
      Get hexdigest of bucket.
    """
        h = hashlib.md5()
        h.update(self.getBucketByKey(key))
        return h.hexdigest()

    def delBucketByKey(self, key):
        """
      Remove the bucket.
    """
        del self._tree[key]
        for index, my_key in list(self.getBucketIndexKeySequenceByIndex()):
            if my_key == key:
                del self._long_index_tree[index]

    def delBucketByIndex(self, index):
        """
      Remove the bucket.
    """
        key = self._long_index_tree[index]
        del self._tree[key]
        del self._long_index_tree[index]

    def rebuildIndexTreeByKeyOrder(self):
        """
        Clear and rebuild the index tree by order of keys
    """
        self.initIndexTree()
        for count, key in enumerate(self.getBucketKeySequenceByKey()):
            self._long_index_tree.insert(count, key)
Example #53
0
class ZODBGroupManager( BasePlugin ):

    """ PAS plugin for managing groups, and groups of groups in the ZODB
    """
    __implements__ = ( IGroupEnumerationPlugin
                     , IGroupsPlugin
                     )

    meta_type = 'ZODB Group Manager'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title
        self._groups = OOBTree()
        self._principal_groups = OOBTree()

    #
    #   IGroupEnumerationPlugin implementation
    #
    security.declarePrivate( 'enumerateGroups' )
    def enumerateGroups( self
                        , id=None
                        , title=None
                        , exact_match=False
                        , sort_by=None
                        , max_results=None
                        , **kw
                        ):

        """ See IGroupEnumerationPlugin.
        """
        group_info = []
        group_ids = []
        plugin_id = self.getId()

        if isinstance( id, str ):
            id = [ id ]

        if isinstance( title, str ):
            title = [ title ]

        if exact_match and ( id or title ):

            if id:
                group_ids.extend( id )
            elif title:
                group_ids.extend( title )

        if group_ids:
            group_filter = None

        else:   # Searching
            group_ids = self.listGroupIds()
            group_filter = _ZODBGroupFilter( id, title, **kw )

        for group_id in group_ids:

            if self._groups.get( group_id, None ):
                e_url = '%s/manage_groups' % self.getId()
                p_qs = 'group_id=%s' % group_id
                m_qs = 'group_id=%s&assign=1' % group_id

                info = {}
                info.update( self._groups[ group_id ] )
                
                info[ 'pluginid' ] = plugin_id
                info[ 'properties_url' ] = '%s?%s' % ( e_url, p_qs )
                info[ 'members_url' ] = '%s?%s' % ( e_url, m_qs )
                
                if not group_filter or group_filter( info ):
                    group_info.append( info )

        return tuple( group_info )

    #
    #   IGroupsPlugin implementation
    #
    security.declarePrivate( 'getGroupsForPrincipal' )
    def getGroupsForPrincipal( self, principal, request=None ):

        """ See IGroupsPlugin.
        """
        return tuple( self._principal_groups.get( principal.getId(), () ) )

    #
    #   (notional)IZODBGroupManager interface
    #
    security.declareProtected( ManageGroups, 'listGroupIds' )
    def listGroupIds( self ):

        """ -> ( group_id_1, ... group_id_n )
        """
        return self._groups.keys()

    security.declareProtected( ManageGroups, 'listGroupInfo' )
    def listGroupInfo( self ):

        """ -> ( {}, ...{} )

        o Return one mapping per group, with the following keys:

          - 'id' 
        """
        return self._groups.values()

    security.declareProtected( ManageGroups, 'getGroupInfo' )
    def getGroupInfo( self, group_id ):

        """ group_id -> {}
        """
        return self._groups[ group_id ]

    security.declarePrivate( 'addGroup' )
    def addGroup( self, group_id, title=None, description=None ):

        """ Add 'group_id' to the list of groups managed by this object.

        o Raise KeyError on duplicate.
        """
        if self._groups.get( group_id ) is not None:
            raise KeyError, 'Duplicate group ID: %s' % group_id

        self._groups[ group_id ] = { 'id' : group_id
                                   , 'title' : title
                                   , 'description' : description
                                   }

    security.declarePrivate( 'updateGroup' )
    def updateGroup( self, group_id, title, description ):

        """ Update properties for 'group_id'

        o Raise KeyError if group_id doesn't already exist.
        """
        self._groups[ group_id ].update({ 'title' : title
                                        , 'description' : description
                                        })
        self._groups[ group_id ] = self._groups[ group_id ]

    security.declarePrivate( 'removeGroup' )
    def removeGroup( self, group_id ):

        """ Remove 'role_id' from the list of roles managed by this
            object, removing assigned members from it before doing so.

        o Raise KeyError if 'group_id' doesn't already exist.
        """
        for principal_id in self._principal_groups.keys():
            self.removePrincipalFromGroup( principal_id, group_id )
        del self._groups[ group_id ]

    #
    #   Group assignment API
    #
    security.declareProtected( ManageGroups, 'listAvailablePrincipals' )
    def listAvailablePrincipals( self, group_id, search_name ):

        """ Return a list of principal IDs to that can belong to the group.

        o If supplied, 'search_name' constrains the principal IDs;  if not,
          return empty list.

        o Omit principals with existing assignments.
        """
        result = []

        if search_name:  # don't bother searching if no criteria

            parent = aq_parent( self )

            for info in parent.searchPrincipals( max_results=20
                                               , sort_by='id'
                                               , name=search_name
                                               , exact_match=False
                                               ):
                id = info[ 'id' ]
                title = info.get( 'title', id )
                if ( group_id not in self._principal_groups.get( id, () )
                 and group_id != id ):
                    result.append( ( id, title ) )
        
        return result

    security.declareProtected( ManageGroups, 'listAssignedPrincipals' )
    def listAssignedPrincipals( self, group_id ):

        """ Return a list of principal IDs belonging to a group.
        """
        result = []

        for k, v in self._principal_groups.items():
            if group_id in v:
                # should be one and only one mapping to 'k'

                parent = aq_parent( self )
                info = parent.searchPrincipals( id=k, exact_match=True )
                assert( len( info ) == 1 )
                result.append( ( k, info[0].get( 'title', k ) ) )
        
        return result

    security.declareProtected( ManageGroups, 'addPrincipalToGroup' )
    def addPrincipalToGroup( self, principal_id, group_id ):

        """ Add a principal to a group.

        o Return a boolean indicating whether a new assignment was created.

        o Raise KeyError if 'group_id' is unknown.
        """
        group_info = self._groups[ group_id ] # raise KeyError if unknown!

        current = self._principal_groups.get( principal_id, () )
        already = group_id in current

        if not already:
            new = current + ( group_id, )
            self._principal_groups[ principal_id ] = new

        return not already

    security.declareProtected( ManageGroups, 'removePrincipalFromGroup' )
    def removePrincipalFromGroup( self, principal_id, group_id ):

        """ Remove a prinicpal from from a group.

        o Return a boolean indicating whether the principal was already 
          a member of the group.

        o Raise KeyError if 'group_id' is unknown.

        o Ignore requests to remove a principal if not already a member
          of the group.
        """
        group_info = self._groups[ group_id ] # raise KeyError if unknown!

        current = self._principal_groups.get( principal_id, () )
        new = tuple( [ x for x in current if x != group_id ] )
        already = current != new

        if already:
            self._principal_groups[ principal_id ] = new

        return already

    #
    #   ZMI
    #
    manage_options = ( ( { 'label': 'Groups', 
                           'action': 'manage_groups', }
                         ,
                       )
                     + BasePlugin.manage_options
                     )

    security.declarePublic( 'manage_widgets' )
    manage_widgets = PageTemplateFile( 'www/zuWidgets'
                                     , globals()
                                     , __name__='manage_widgets'
                                     )

    security.declareProtected( ManageGroups, 'manage_groups' )
    manage_groups = PageTemplateFile( 'www/zgGroups'
                                    , globals()
                                    , __name__='manage_groups'
                                    )

    security.declareProtected( ManageGroups, 'manage_twoLists' )
    manage_twoLists = PageTemplateFile( '../www/two_lists'
                                      , globals()
                                      , __name__='manage_twoLists'
                                      )

    security.declareProtected( ManageGroups, 'manage_addGroup' )
    def manage_addGroup( self
                       , group_id
                       , title=None
                       , description=None
                       , RESPONSE=None
                       ):
        """ Add a group via the ZMI.
        """
        self.addGroup( group_id, title, description )

        message = 'Group+added'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageGroups, 'manage_updateGroup' )
    def manage_updateGroup( self
                          , group_id
                          , title
                          , description
                          , RESPONSE=None
                          ):
        """ Update a group via the ZMI.
        """
        self.updateGroup( group_id, title, description )

        message = 'Group+updated'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageGroups, 'manage_removeGroups' )
    def manage_removeGroups( self
                           , group_ids
                           , RESPONSE=None
                           ):
        """ Remove one or more groups via the ZMI.
        """
        group_ids = filter( None, group_ids )

        if not group_ids:
            message = 'no+groups+selected'

        else:
        
            for group_id in group_ids:
                self.removeGroup( group_id )

            message = 'Groups+removed'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageGroups, 'manage_addPrincipalsToGroup' )
    def manage_addPrincipalsToGroup( self
                                   , group_id
                                   , principal_ids
                                   , RESPONSE=None
                                   ):
        """ Add one or more principals to a group via the ZMI.
        """
        assigned = []

        for principal_id in principal_ids:
            if self.addPrincipalToGroup( principal_id, group_id ):
                assigned.append( principal_id )

        if not assigned:
            message = 'Principals+already+members+of+%s' % group_id
        else:
            message = '%s+added+to+%s' % ( '+'.join( assigned )
                                         , group_id
                                         )

        if RESPONSE is not None:
            RESPONSE.redirect( ( '%s/manage_groups?group_id=%s&assign=1'
                               + '&manage_tabs_message=%s'
                               ) % ( self.absolute_url(), group_id, message )
                             )

    security.declareProtected( ManageGroups
                             , 'manage_removePrincipalsFromGroup' 
                             )
    def manage_removePrincipalsFromGroup( self
                                        , group_id
                                        , principal_ids
                                        , RESPONSE=None
                                        ):
        """ Remove one or more principals from a group via the ZMI.
        """
        removed = []
        
        for principal_id in principal_ids:
            if self.removePrincipalFromGroup( principal_id, group_id ):
                removed.append( principal_id )

        if not removed:
            message = 'Principals+not+in+group+%s' % group_id
        else:
            message = 'Principals+%s+removed+from+%s' % ( '+'.join( removed )
                                                        , group_id
                                                        )

        if RESPONSE is not None:
            RESPONSE.redirect( ( '%s/manage_groups?group_id=%s&assign=1'
                               + '&manage_tabs_message=%s'
                               ) % ( self.absolute_url(), group_id, message )
                             )
Example #54
0
class TopicIndex(Persistent, SimpleItem):
    """A TopicIndex maintains a set of FilteredSet objects.

    Every FilteredSet object consists of an expression and and IISet with all
    Ids of indexed objects that eval with this expression to 1.
    """

    meta_type = 'TopicIndex'
    zmi_icon = 'fas fa-info-circle'
    operators = ('or', 'and')
    useOperator = 'or'
    query_options = ('query', 'operator')

    manage_options = (
        {'label': 'FilteredSets', 'action': 'manage_main'},
    )

    def __init__(self, id, caller=None):
        self.id = id
        self.filteredSets = OOBTree()

    def getId(self):
        return self.id

    def clear(self):
        for fs in self.filteredSets.values():
            fs.clear()

    def index_object(self, docid, obj, threshold=100):
        """ hook for (Z)Catalog """
        for fid, filteredSet in self.filteredSets.items():
            filteredSet.index_object(docid, obj)
        return 1

    def unindex_object(self, docid):
        """ hook for (Z)Catalog """
        for fs in self.filteredSets.values():
            try:
                fs.unindex_object(docid)
            except KeyError:
                LOG.debug('Attempt to unindex document'
                          ' with id %s failed', docid)
        return 1

    def numObjects(self):
        """Return the number of indexed objects."""
        setlist = []
        for fs in self.filteredSets.values():
            setlist.append(fs.getIds())
        return len(multiunion(setlist))

    def indexSize(self):
        """Return the size of the index in terms of distinct values."""
        return len(self.filteredSets)

    def search(self, filter_id):
        f = self.filteredSets.get(filter_id, None)
        if f is not None:
            return f.getIds()

    def _apply_index(self, request):
        record = IndexQuery(request, self.id, self.query_options,
                            self.operators, self.useOperator)
        if record.keys is None:
            return None
        return (self.query_index(record), (self.id, ))

    def query_index(self, record, resultset=None):
        """Hook for (Z)Catalog
        'record' --  mapping type (usually {"topic": "..." }
        """
        operator = record.operator
        if operator == 'or':
            set_func = union
        else:
            set_func = intersection

        res = None
        for filter_id in record.keys:
            rows = self.search(filter_id)
            res = set_func(res, rows)

        if res:
            return res
        return IITreeSet()

    def hasUniqueValuesFor(self, name):
        """has unique values for column name"""
        if name == self.id:
            return 1
        return 0

    def uniqueValues(self, name=None, withLength=0):
        """Return an iterable/sequence of unique values for name.

        If 'withLengths' is true, returns a iterable/sequence of tuples of
        (value, length).
        """

        if name is None:
            name = self.id
        elif name != self.id:
            return

        if not withLength:
            for key in self.filteredSets.keys():
                yield key
        else:
            for key, value in self.filteredSets.items():
                yield (key, len(value.getIds()))

    def getEntryForObject(self, docid, default=None):
        """ Takes a document ID and returns all the information we have
            on that specific object.
        """
        res = []
        for fs in self.filteredSets.values():
            ids = fs.getIds()
            if docid in ids:
                res.append(fs.getId())

        return res or default

    def addFilteredSet(self, filter_id, typeFilteredSet, expr):
        # Add a FilteredSet object.
        if filter_id in self.filteredSets:
            raise KeyError(('A FilteredSet with this name already '
                            'exists: {0}'.format(filter_id)))
        self.filteredSets[filter_id] = factory(
            filter_id, typeFilteredSet, expr)

    def delFilteredSet(self, filter_id):
        # Delete the FilteredSet object specified by 'filter_id'.
        if filter_id not in self.filteredSets:
            raise KeyError(
                'no such FilteredSet:  {0}'.format(filter_id))
        del self.filteredSets[filter_id]

    def clearFilteredSet(self, filter_id):
        # Clear the FilteredSet object specified by 'filter_id'.
        f = self.filteredSets.get(filter_id, None)
        if f is None:
            raise KeyError('no such FilteredSet: {0}'.format(filter_id))
        f.clear()

    def manage_addFilteredSet(self, filter_id, typeFilteredSet, expr, URL1,
                              REQUEST=None, RESPONSE=None):
        """ add a new filtered set """

        if len(filter_id) == 0:
            raise RuntimeError('Length of ID too short')
        if len(expr) == 0:
            raise RuntimeError('Length of expression too short')

        self.addFilteredSet(filter_id, typeFilteredSet, expr)

        if RESPONSE:
            RESPONSE.redirect(URL1 + (
                '/manage_workspace?'
                'manage_tabs_message=FilteredSet%20added'))

    def manage_delFilteredSet(self, filter_ids=[], URL1=None,
                              REQUEST=None, RESPONSE=None):
        """ delete a list of FilteredSets"""

        for filter_id in filter_ids:
            self.delFilteredSet(filter_id)

        if RESPONSE:
            RESPONSE.redirect(URL1 + (
                '/manage_workspace?'
                'manage_tabs_message=FilteredSet(s)%20deleted'))

    def manage_saveFilteredSet(self, filter_id, expr, URL1=None,
                               REQUEST=None, RESPONSE=None):
        """ save expression for a FilteredSet """

        self.filteredSets[filter_id].setExpression(expr)

        if RESPONSE:
            RESPONSE.redirect(URL1 + (
                '/manage_workspace?'
                'manage_tabs_message=FilteredSet(s)%20updated'))

    def getIndexSourceNames(self):
        """ return names of indexed attributes """
        return ('n/a', )

    def getIndexQueryNames(self):
        return (self.id,)

    def manage_clearFilteredSet(self, filter_ids=[], URL1=None,
                                REQUEST=None, RESPONSE=None):
        """  clear a list of FilteredSets"""

        for filter_id in filter_ids:
            self.clearFilteredSet(filter_id)

        if RESPONSE:
            RESPONSE.redirect(URL1 + (
                '/manage_workspace?'
                'manage_tabs_message=FilteredSet(s)%20cleared'))

    manage = manage_main = DTMLFile('dtml/manageTopicIndex', globals())
    manage_main._setName('manage_main')
    editFilteredSet = DTMLFile('dtml/editFilteredSet', globals())
class UnIndex(SimpleItem):
    """Simple forward and reverse index.
    """

    zmi_icon = 'fas fa-info-circle'
    _counter = None
    operators = ('or', 'and')
    useOperator = 'or'
    query_options = ()

    def __init__(self, id, ignore_ex=None, call_methods=None,
                 extra=None, caller=None):
        """Create an unindex

        UnIndexes are indexes that contain two index components, the
        forward index (like plain index objects) and an inverted
        index.  The inverted index is so that objects can be unindexed
        even when the old value of the object is not known.

        e.g.

        self._index = {datum:[documentId1, documentId2]}
        self._unindex = {documentId:datum}

        The arguments are:

          'id' -- the name of the item attribute to index.  This is
          either an attribute name or a record key.

          'ignore_ex' -- should be set to true if you want the index
          to ignore exceptions raised while indexing instead of
          propagating them.

          'call_methods' -- should be set to true if you want the index
          to call the attribute 'id' (note: 'id' should be callable!)
          You will also need to pass in an object in the index and
          uninded methods for this to work.

          'extra' -- a mapping object that keeps additional
          index-related parameters - subitem 'indexed_attrs'
          can be string with comma separated attribute names or
          a list

          'caller' -- reference to the calling object (usually
          a (Z)Catalog instance
        """

        def _get(o, k, default):
            """ return a value for a given key of a dict/record 'o' """
            if isinstance(o, dict):
                return o.get(k, default)
            else:
                return getattr(o, k, default)

        self.id = id
        self.ignore_ex = ignore_ex  # currently unimplemented
        self.call_methods = call_methods

        # allow index to index multiple attributes
        ia = _get(extra, 'indexed_attrs', id)
        if isinstance(ia, str):
            self.indexed_attrs = ia.split(',')
        else:
            self.indexed_attrs = list(ia)
        self.indexed_attrs = [
            attr.strip() for attr in self.indexed_attrs if attr]
        if not self.indexed_attrs:
            self.indexed_attrs = [id]

        self.clear()

    def __len__(self):
        return self._length()

    def getId(self):
        return self.id

    def clear(self):
        self._length = Length()
        self._index = OOBTree()
        self._unindex = IOBTree()

        if self._counter is None:
            self._counter = Length()
        else:
            self._increment_counter()

    def __nonzero__(self):
        return not not self._unindex

    def histogram(self):
        """Return a mapping which provides a histogram of the number of
        elements found at each point in the index.
        """
        histogram = {}
        for item in self._index.items():
            if isinstance(item, int):
                entry = 1  # "set" length is 1
            else:
                key, value = item
                entry = len(value)
            histogram[entry] = histogram.get(entry, 0) + 1
        return histogram

    def referencedObjects(self):
        """Generate a list of IDs for which we have referenced objects."""
        return self._unindex.keys()

    def getEntryForObject(self, documentId, default=_marker):
        """Takes a document ID and returns all the information we have
        on that specific object.
        """
        if default is _marker:
            return self._unindex.get(documentId)
        return self._unindex.get(documentId, default)

    def removeForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and remove any reference to documentId
        in its entry in the index.
        """
        indexRow = self._index.get(entry, _marker)
        if indexRow is not _marker:
            try:
                indexRow.remove(documentId)
                if not indexRow:
                    del self._index[entry]
                    self._length.change(-1)
            except ConflictError:
                raise
            except AttributeError:
                # index row is an int
                try:
                    del self._index[entry]
                except KeyError:
                    # swallow KeyError because it was probably
                    # removed and then _length AttributeError raised
                    pass
                if isinstance(self.__len__, Length):
                    self._length = self.__len__
                    del self.__len__
                self._length.change(-1)
            except Exception:
                LOG.error('%(context)s: unindex_object could not remove '
                          'documentId %(doc_id)s from index %(index)r.  This '
                          'should not happen.', dict(
                              context=self.__class__.__name__,
                              doc_id=documentId,
                              index=self.id),
                          exc_info=sys.exc_info())
        else:
            LOG.error('%(context)s: unindex_object tried to '
                      'retrieve set %(entry)r from index %(index)r '
                      'but couldn\'t.  This should not happen.', dict(
                          context=self.__class__.__name__,
                          entry=entry,
                          index=self.id))

    def insertForwardIndexEntry(self, entry, documentId):
        """Take the entry provided and put it in the correct place
        in the forward index.

        This will also deal with creating the entire row if necessary.
        """
        indexRow = self._index.get(entry, _marker)

        # Make sure there's actually a row there already. If not, create
        # a set and stuff it in first.
        if indexRow is _marker:
            # We always use a set to avoid getting conflict errors on
            # multiple threads adding a new row at the same time
            self._index[entry] = IITreeSet((documentId, ))
            self._length.change(1)
        else:
            try:
                indexRow.insert(documentId)
            except AttributeError:
                # Inline migration: index row with one element was an int at
                # first (before Zope 2.13).
                indexRow = IITreeSet((indexRow, documentId))
                self._index[entry] = indexRow

    def index_object(self, documentId, obj, threshold=None):
        """ wrapper to handle indexing of multiple attributes """

        fields = self.getIndexSourceNames()
        res = 0
        for attr in fields:
            res += self._index_object(documentId, obj, threshold, attr)

        if res > 0:
            self._increment_counter()

        return res > 0

    def _index_object(self, documentId, obj, threshold=None, attr=''):
        """ index and object 'obj' with integer id 'documentId'"""
        returnStatus = 0

        # First we need to see if there's anything interesting to look at
        datum = self._get_object_datum(obj, attr)
        if datum is None:
            # Prevent None from being indexed. None doesn't have a valid
            # ordering definition compared to any other object.
            # BTrees 4.0+ will throw a TypeError
            # "object has default comparison" and won't let it be indexed.
            return 0

        datum = self._convert(datum, default=_marker)

        # We don't want to do anything that we don't have to here, so we'll
        # check to see if the new and existing information is the same.
        oldDatum = self._unindex.get(documentId, _marker)
        if datum != oldDatum:
            if oldDatum is not _marker:
                self.removeForwardIndexEntry(oldDatum, documentId)
                if datum is _marker:
                    try:
                        del self._unindex[documentId]
                    except ConflictError:
                        raise
                    except Exception:
                        LOG.error('%(context)s: oldDatum was there, '
                                  'now it\'s not for documentId %(doc_id)s '
                                  'from index %(index)r.  This '
                                  'should not happen.', dict(
                                      context=self.__class__.__name__,
                                      doc_id=documentId,
                                      index=self.id),
                                  exc_info=sys.exc_info())

            if datum is not _marker:
                self.insertForwardIndexEntry(datum, documentId)
                self._unindex[documentId] = datum

            returnStatus = 1

        return returnStatus

    def _get_object_datum(self, obj, attr):
        # self.id is the name of the index, which is also the name of the
        # attribute we're interested in.  If the attribute is callable,
        # we'll do so.
        try:
            datum = getattr(obj, attr)
            if safe_callable(datum):
                datum = datum()
        except (AttributeError, TypeError):
            datum = _marker
        return datum

    def _increment_counter(self):
        if self._counter is None:
            self._counter = Length()
        self._counter.change(1)

    def getCounter(self):
        """Return a counter which is increased on index changes"""
        return self._counter is not None and self._counter() or 0

    def numObjects(self):
        """Return the number of indexed objects."""
        return len(self._unindex)

    def indexSize(self):
        """Return the size of the index in terms of distinct values."""
        return len(self)

    def unindex_object(self, documentId):
        """ Unindex the object with integer id 'documentId' and don't
        raise an exception if we fail
        """
        unindexRecord = self._unindex.get(documentId, _marker)
        if unindexRecord is _marker:
            return None

        self._increment_counter()

        self.removeForwardIndexEntry(unindexRecord, documentId)
        try:
            del self._unindex[documentId]
        except ConflictError:
            raise
        except Exception:
            LOG.debug('%(context)s: attempt to unindex nonexistent '
                      'documentId %(doc_id)s from index %(index)r. This '
                      'should not happen.', dict(
                          context=self.__class__.__name__,
                          doc_id=documentId,
                          index=self.id),
                      exc_info=True)

    def _apply_not(self, not_parm, resultset=None):
        index = self._index
        setlist = []
        for k in not_parm:
            s = index.get(k, None)
            if s is None:
                continue
            elif isinstance(s, int):
                s = IISet((s, ))
            setlist.append(s)
        return multiunion(setlist)

    def _convert(self, value, default=None):
        return value

    def getRequestCache(self):
        """returns dict for caching per request for interim results
        of an index search. Returns 'None' if no REQUEST attribute
        is available"""

        cache = None
        REQUEST = aq_get(self, 'REQUEST', None)
        if REQUEST is not None:
            catalog = aq_parent(aq_parent(aq_inner(self)))
            if catalog is not None:
                # unique catalog identifier
                key = '_catalogcache_{0}_{1}'.format(
                    catalog.getId(), id(catalog))
                cache = REQUEST.get(key, None)
                if cache is None:
                    cache = REQUEST[key] = RequestCache()

        return cache

    def getRequestCacheKey(self, record, resultset=None):
        """returns an unique key of a search record"""
        params = []

        # record operator (or, and)
        params.append(('operator', record.operator))

        # not / exclude operator
        not_value = record.get('not', None)
        if not_value is not None:
            not_value = frozenset(not_value)
            params.append(('not', not_value))

        # record options
        for op in ['range', 'usage']:
            op_value = record.get(op, None)
            if op_value is not None:
                params.append((op, op_value))

        # record keys
        rec_keys = frozenset(record.keys)
        params.append(('keys', rec_keys))

        # build record identifier
        rid = frozenset(params)

        # unique index identifier
        iid = '_{0}_{1}_{2}'.format(self.__class__.__name__,
                                    self.id, self.getCounter())
        return (iid, rid)

    def _apply_index(self, request, resultset=None):
        """Apply the index to query parameters given in the request arg.

        If the query does not match the index, return None, otherwise
        return a tuple of (result, used_attributes), where used_attributes
        is again a tuple with the names of all used data fields.

        If not `None`, the resultset argument
        indicates that the search result is relevant only on this set,
        i.e. everything outside resultset is of no importance.
        The index can use this information for optimizations.
        """
        record = IndexQuery(request, self.id, self.query_options,
                            self.operators, self.useOperator)
        if record.keys is None:
            return None
        return (self.query_index(record, resultset=resultset), (self.id, ))

    def query_index(self, record, resultset=None):
        """Search the index with the given IndexQuery object.

        If not `None`, the resultset argument
        indicates that the search result is relevant only on this set,
        i.e. everything outside resultset is of no importance.
        The index can use this information for optimizations.
        """
        index = self._index
        r = None
        opr = None

        # not / exclude parameter
        not_parm = record.get('not', None)

        operator = record.operator

        cachekey = None
        cache = self.getRequestCache()
        if cache is not None:
            cachekey = self.getRequestCacheKey(record)
            if cachekey is not None:
                cached = None
                if operator == 'or':
                    cached = cache.get(cachekey, None)
                else:
                    cached_setlist = cache.get(cachekey, None)
                    if cached_setlist is not None:
                        r = resultset
                        for s in cached_setlist:
                            # the result is bound by the resultset
                            r = intersection(r, s)
                            # If intersection, we can't possibly get a
                            # smaller result
                            if not r:
                                break
                        cached = r

                if cached is not None:
                    if isinstance(cached, int):
                        cached = IISet((cached, ))

                    if not_parm:
                        not_parm = list(map(self._convert, not_parm))
                        exclude = self._apply_not(not_parm, resultset)
                        cached = difference(cached, exclude)

                    return cached

        if not record.keys and not_parm:
            # convert into indexed format
            not_parm = list(map(self._convert, not_parm))
            # we have only a 'not' query
            record.keys = [k for k in index.keys() if k not in not_parm]
        else:
            # convert query arguments into indexed format
            record.keys = list(map(self._convert, record.keys))

        # Range parameter
        range_parm = record.get('range', None)
        if range_parm:
            opr = 'range'
            opr_args = []
            if range_parm.find('min') > -1:
                opr_args.append('min')
            if range_parm.find('max') > -1:
                opr_args.append('max')

        if record.get('usage', None):
            # see if any usage params are sent to field
            opr = record.usage.lower().split(':')
            opr, opr_args = opr[0], opr[1:]

        if opr == 'range':  # range search
            if 'min' in opr_args:
                lo = min(record.keys)
            else:
                lo = None
            if 'max' in opr_args:
                hi = max(record.keys)
            else:
                hi = None
            if hi:
                setlist = index.values(lo, hi)
            else:
                setlist = index.values(lo)

            # If we only use one key, intersect and return immediately
            if len(setlist) == 1:
                result = setlist[0]
                if isinstance(result, int):
                    result = IISet((result,))

                if cachekey is not None:
                    if operator == 'or':
                        cache[cachekey] = result
                    else:
                        cache[cachekey] = [result]

                if not_parm:
                    exclude = self._apply_not(not_parm, resultset)
                    result = difference(result, exclude)
                return result

            if operator == 'or':
                tmp = []
                for s in setlist:
                    if isinstance(s, int):
                        s = IISet((s,))
                    tmp.append(s)
                r = multiunion(tmp)

                if cachekey is not None:
                    cache[cachekey] = r
            else:
                # For intersection, sort with smallest data set first
                tmp = []
                for s in setlist:
                    if isinstance(s, int):
                        s = IISet((s,))
                    tmp.append(s)
                if len(tmp) > 2:
                    setlist = sorted(tmp, key=len)
                else:
                    setlist = tmp

                # 'r' is not invariant of resultset. Thus, we
                # have to remember 'setlist'
                if cachekey is not None:
                    cache[cachekey] = setlist

                r = resultset
                for s in setlist:
                    # the result is bound by the resultset
                    r = intersection(r, s)
                    # If intersection, we can't possibly get a smaller result
                    if not r:
                        break

        else:  # not a range search
            # Filter duplicates
            setlist = []
            for k in record.keys:
                if k is None:
                    # Prevent None from being looked up. None doesn't
                    # have a valid ordering definition compared to any
                    # other object. BTrees 4.0+ will throw a TypeError
                    # "object has default comparison".
                    continue
                try:
                    s = index.get(k, None)
                except TypeError:
                    # key is not valid for this Btree so the value is None
                    LOG.error(
                        '%(context)s: query_index tried '
                        'to look up key %(key)r from index %(index)r '
                        'but key was of the wrong type.', dict(
                            context=self.__class__.__name__,
                            key=k,
                            index=self.id,
                        )
                    )
                    s = None
                # If None, try to bail early
                if s is None:
                    if operator == 'or':
                        # If union, we can possibly get a bigger result
                        continue
                    # If intersection, we can't possibly get a smaller result
                    if cachekey is not None:
                        # If operator is 'and', we have to cache a list of
                        # IISet objects
                        cache[cachekey] = [IISet()]
                    return IISet()
                elif isinstance(s, int):
                    s = IISet((s,))
                setlist.append(s)

            # If we only use one key return immediately
            if len(setlist) == 1:
                result = setlist[0]
                if isinstance(result, int):
                    result = IISet((result,))

                if cachekey is not None:
                    if operator == 'or':
                        cache[cachekey] = result
                    else:
                        cache[cachekey] = [result]

                if not_parm:
                    exclude = self._apply_not(not_parm, resultset)
                    result = difference(result, exclude)
                return result

            if operator == 'or':
                # If we already get a small result set passed in, intersecting
                # the various indexes with it and doing the union later is
                # faster than creating a multiunion first.

                if resultset is not None and len(resultset) < 200:
                    smalllist = []
                    for s in setlist:
                        smalllist.append(intersection(resultset, s))
                    r = multiunion(smalllist)

                    # 'r' is not invariant of resultset.  Thus, we
                    # have to remember the union of 'setlist'. But
                    # this is maybe a performance killer. So we do not cache.
                    # if cachekey is not None:
                    #    cache[cachekey] = multiunion(setlist)

                else:
                    r = multiunion(setlist)
                    if cachekey is not None:
                        cache[cachekey] = r
            else:
                # For intersection, sort with smallest data set first
                if len(setlist) > 2:
                    setlist = sorted(setlist, key=len)

                # 'r' is not invariant of resultset. Thus, we
                # have to remember the union of 'setlist'
                if cachekey is not None:
                    cache[cachekey] = setlist

                r = resultset
                for s in setlist:
                    r = intersection(r, s)
                    # If intersection, we can't possibly get a smaller result
                    if not r:
                        break

        if isinstance(r, int):
            r = IISet((r, ))
        if r is None:
            return IISet()
        if not_parm:
            exclude = self._apply_not(not_parm, resultset)
            r = difference(r, exclude)
        return r

    def hasUniqueValuesFor(self, name):
        """has unique values for column name"""
        if name == self.id:
            return 1
        return 0

    def getIndexSourceNames(self):
        """Return sequence of indexed attributes."""
        return getattr(self, 'indexed_attrs', [self.id])

    def getIndexQueryNames(self):
        """Indicate that this index applies to queries for the index's name."""
        return (self.id,)

    def uniqueValues(self, name=None, withLengths=0):
        """returns the unique values for name

        if withLengths is true, returns a sequence of
        tuples of (value, length)
        """
        if name is None:
            name = self.id
        elif name != self.id:
            return

        if not withLengths:
            for key in self._index.keys():
                yield key
        else:
            for key, value in self._index.items():
                if isinstance(value, int):
                    yield (key, 1)
                else:
                    yield (key, len(value))

    def keyForDocument(self, id):
        # This method is superseded by documentToKeyMap
        return self._unindex[id]

    def documentToKeyMap(self):
        return self._unindex

    def items(self):
        items = []
        for k, v in self._index.items():
            if isinstance(v, int):
                v = IISet((v,))
            items.append((k, v))
        return items
Example #56
0
class ZODBGroupManager( BasePlugin ):

    """ PAS plugin for managing groups, and groups of groups in the ZODB
    """
    meta_type = 'ZODB Group Manager'

    security = ClassSecurityInfo()

    def __init__(self, id, title=None):

        self._id = self.id = id
        self.title = title
        self._groups = OOBTree()
        self._principal_groups = OOBTree()

    #
    #   IGroupEnumerationPlugin implementation
    #
    security.declarePrivate( 'enumerateGroups' )
    def enumerateGroups( self
                        , id=None
                        , title=None
                        , exact_match=False
                        , sort_by=None
                        , max_results=None
                        , **kw
                        ):

        """ See IGroupEnumerationPlugin.
        """
        group_info = []
        group_ids = []
        plugin_id = self.getId()

        if isinstance( id, str ):
            id = [ id ]

        if isinstance( title, str ):
            title = [ title ]

        if exact_match and ( id or title ):

            if id:
                group_ids.extend( id )
            elif title:
                group_ids.extend( title )

        if group_ids:
            group_filter = None

        else:   # Searching
            group_ids = self.listGroupIds()
            group_filter = _ZODBGroupFilter( id, title, **kw )

        for group_id in group_ids:

            if self._groups.get( group_id, None ):
                e_url = '%s/manage_groups' % self.getId()
                p_qs = 'group_id=%s' % group_id
                m_qs = 'group_id=%s&assign=1' % group_id

                info = {}
                info.update( self._groups[ group_id ] )

                info[ 'pluginid' ] = plugin_id
                info[ 'properties_url' ] = '%s?%s' % ( e_url, p_qs )
                info[ 'members_url' ] = '%s?%s' % ( e_url, m_qs )

                info[ 'id' ] = '%s%s' % (self.prefix, info['id'])

                if not group_filter or group_filter( info ):
                    group_info.append( info )

        return tuple( group_info )

    #
    #   IGroupsPlugin implementation
    #
    security.declarePrivate( 'getGroupsForPrincipal' )
    def getGroupsForPrincipal( self, principal, request=None ):

        """ See IGroupsPlugin.
        """
        unadorned = self._principal_groups.get( principal.getId(), () )
        return tuple(['%s%s' % (self.prefix, x) for x in unadorned])

    #
    #   (notional)IZODBGroupManager interface
    #
    security.declareProtected( ManageGroups, 'listGroupIds' )
    def listGroupIds( self ):

        """ -> ( group_id_1, ... group_id_n )
        """
        return self._groups.keys()

    security.declareProtected( ManageGroups, 'listGroupInfo' )
    def listGroupInfo( self ):

        """ -> ( {}, ...{} )

        o Return one mapping per group, with the following keys:

          - 'id' 
        """
        return self._groups.values()

    security.declareProtected( ManageGroups, 'getGroupInfo' )
    def getGroupInfo( self, group_id ):

        """ group_id -> {}
        """
        return self._groups[ group_id ]

    security.declarePrivate( 'addGroup' )
    def addGroup( self, group_id, title=None, description=None ):

        """ Add 'group_id' to the list of groups managed by this object.

        o Raise KeyError on duplicate.
        """
        if self._groups.get( group_id ) is not None:
            raise KeyError, 'Duplicate group ID: %s' % group_id

        self._groups[ group_id ] = { 'id' : group_id
                                   , 'title' : title
                                   , 'description' : description
                                   }

    security.declarePrivate( 'updateGroup' )
    def updateGroup( self, group_id, title, description ):

        """ Update properties for 'group_id'

        o Raise KeyError if group_id doesn't already exist.
        """
        self._groups[ group_id ].update({ 'title' : title
                                        , 'description' : description
                                        })
        self._groups[ group_id ] = self._groups[ group_id ]

    security.declarePrivate( 'removeGroup' )
    def removeGroup( self, group_id ):

        """ Remove 'role_id' from the list of roles managed by this
            object, removing assigned members from it before doing so.

        o Raise KeyError if 'group_id' doesn't already exist.
        """
        for principal_id in self._principal_groups.keys():
            self.removePrincipalFromGroup( principal_id, group_id )
        del self._groups[ group_id ]

    #
    #   Group assignment API
    #
    security.declareProtected( ManageGroups, 'listAvailablePrincipals' )
    def listAvailablePrincipals( self, group_id, search_id ):

        """ Return a list of principal IDs to that can belong to the group.

        o If supplied, 'search_id' constrains the principal IDs;  if not,
          return empty list.

        o Omit principals with existing assignments.
        """
        result = []

        if search_id:  # don't bother searching if no criteria

            parent = aq_parent( self )

            for info in parent.searchPrincipals( max_results=20
                                               , sort_by='id'
                                               , id=search_id
                                               , exact_match=False
                                               ):
                id = info[ 'id' ]
                title = info.get( 'title', id )
                if ( group_id not in self._principal_groups.get( id, () )
                 and group_id != id ):
                    result.append( ( id, title ) )

        return result

    security.declareProtected( ManageGroups, 'listAssignedPrincipals' )
    def listAssignedPrincipals( self, group_id ):

        """ Return a list of principal IDs belonging to a group.
        """
        result = []

        for k, v in self._principal_groups.items():
            if group_id in v:
                # should be one and only one mapping to 'k'

                parent = aq_parent( self )
                info = parent.searchPrincipals( id=k, exact_match=True )
                assert( len( info ) in ( 0, 1 ) )
                if len( info ) == 0:
                    title = '<%s: not found>' % k
                else:
                    title = info[0].get( 'title', k )
                result.append( ( k, title ) )

        return result

    security.declareProtected( ManageGroups, 'addPrincipalToGroup' )
    def addPrincipalToGroup( self, principal_id, group_id ):

        """ Add a principal to a group.

        o Return a boolean indicating whether a new assignment was created.

        o Raise KeyError if 'group_id' is unknown.
        """
        group_info = self._groups[ group_id ] # raise KeyError if unknown!

        current = self._principal_groups.get( principal_id, () )
        already = group_id in current

        if not already:
            new = current + ( group_id, )
            self._principal_groups[ principal_id ] = new

        return not already

    security.declareProtected( ManageGroups, 'removePrincipalFromGroup' )
    def removePrincipalFromGroup( self, principal_id, group_id ):

        """ Remove a prinicpal from from a group.

        o Return a boolean indicating whether the principal was already 
          a member of the group.

        o Raise KeyError if 'group_id' is unknown.

        o Ignore requests to remove a principal if not already a member
          of the group.
        """
        group_info = self._groups[ group_id ] # raise KeyError if unknown!

        current = self._principal_groups.get( principal_id, () )
        new = tuple( [ x for x in current if x != group_id ] )
        already = current != new

        if already:
            self._principal_groups[ principal_id ] = new

        return already

    #
    #   ZMI
    #
    manage_options = ( ( { 'label': 'Groups', 
                           'action': 'manage_groups', }
                         ,
                       )
                     + BasePlugin.manage_options
                     )

    security.declarePublic( 'manage_widgets' )
    manage_widgets = PageTemplateFile( 'www/zuWidgets'
                                     , globals()
                                     , __name__='manage_widgets'
                                     )

    security.declareProtected( ManageGroups, 'manage_groups' )
    manage_groups = PageTemplateFile( 'www/zgGroups'
                                    , globals()
                                    , __name__='manage_groups'
                                    )

    security.declareProtected( ManageGroups, 'manage_twoLists' )
    manage_twoLists = PageTemplateFile( '../www/two_lists'
                                      , globals()
                                      , __name__='manage_twoLists'
                                      )

    security.declareProtected( ManageGroups, 'manage_addGroup' )
    def manage_addGroup( self
                       , group_id
                       , title=None
                       , description=None
                       , RESPONSE=None
                       ):
        """ Add a group via the ZMI.
        """
        self.addGroup( group_id, title, description )

        message = 'Group+added'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageGroups, 'manage_updateGroup' )
    def manage_updateGroup( self
                          , group_id
                          , title
                          , description
                          , RESPONSE=None
                          ):
        """ Update a group via the ZMI.
        """
        self.updateGroup( group_id, title, description )

        message = 'Group+updated'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageGroups, 'manage_removeGroups' )
    def manage_removeGroups( self
                           , group_ids
                           , RESPONSE=None
                           ):
        """ Remove one or more groups via the ZMI.
        """
        group_ids = filter( None, group_ids )

        if not group_ids:
            message = 'no+groups+selected'

        else:

            for group_id in group_ids:
                self.removeGroup( group_id )

            message = 'Groups+removed'

        if RESPONSE is not None:
            RESPONSE.redirect( '%s/manage_groups?manage_tabs_message=%s'
                             % ( self.absolute_url(), message )
                             )

    security.declareProtected( ManageGroups, 'manage_addPrincipalsToGroup' )
    def manage_addPrincipalsToGroup( self
                                   , group_id
                                   , principal_ids
                                   , RESPONSE=None
                                   ):
        """ Add one or more principals to a group via the ZMI.
        """
        assigned = []

        for principal_id in principal_ids:
            if self.addPrincipalToGroup( principal_id, group_id ):
                assigned.append( principal_id )

        if not assigned:
            message = 'Principals+already+members+of+%s' % group_id
        else:
            message = '%s+added+to+%s' % ( '+'.join( assigned )
                                         , group_id
                                         )

        if RESPONSE is not None:
            RESPONSE.redirect( ( '%s/manage_groups?group_id=%s&assign=1'
                               + '&manage_tabs_message=%s'
                               ) % ( self.absolute_url(), group_id, message )
                             )

    security.declareProtected( ManageGroups
                             , 'manage_removePrincipalsFromGroup' 
                             )
    def manage_removePrincipalsFromGroup( self
                                        , group_id
                                        , principal_ids
                                        , RESPONSE=None
                                        ):
        """ Remove one or more principals from a group via the ZMI.
        """
        removed = []

        for principal_id in principal_ids:
            if self.removePrincipalFromGroup( principal_id, group_id ):
                removed.append( principal_id )

        if not removed:
            message = 'Principals+not+in+group+%s' % group_id
        else:
            message = 'Principals+%s+removed+from+%s' % ( '+'.join( removed )
                                                        , group_id
                                                        )

        if RESPONSE is not None:
            RESPONSE.redirect( ( '%s/manage_groups?group_id=%s&assign=1'
                               + '&manage_tabs_message=%s'
                               ) % ( self.absolute_url(), group_id, message )
                             )
Example #57
0
class Folder(Persistent, Contained):
    """The standard Zope Folder implementation."""

    implements(IContentContainer)

    def __init__(self):
        self.data = OOBTree()

    def keys(self):
        """Return a sequence-like object containing the names
           associated with the objects that appear in the folder
        """
        return self.data.keys()

    def __iter__(self):
        return iter(self.data.keys())

    def values(self):
        """Return a sequence-like object containing the objects that
           appear in the folder.
        """
        return self.data.values()

    def items(self):
        """Return a sequence-like object containing tuples of the form
           (name, object) for the objects that appear in the folder.
        """
        return self.data.items()

    def __getitem__(self, name):
        """Return the named object, or raise ``KeyError`` if the object
           is not found.
        """
        return self.data[name]

    def get(self, name, default=None):
        """Return the named object, or the value of the `default`
           argument if the object is not found.
        """
        return self.data.get(name, default)

    def __contains__(self, name):
        """Return true if the named object appears in the folder."""
        return self.data.has_key(name)

    def __len__(self):
        """Return the number of objects in the folder."""
        return len(self.data)

    def __setitem__(self, name, object):
        """Add the given object to the folder under the given name."""

        if not (isinstance(name, str) or isinstance(name, unicode)):
            raise TypeError("Name must be a string rather than a %s" %
                            name.__class__.__name__)
        try:
            unicode(name)
        except UnicodeError:
            raise TypeError("Non-unicode names must be 7-bit-ascii only")
        if not name:
            raise TypeError("Name must not be empty")

        if name in self.data:
            raise KeyError("name, %s, is already in use" % name)

        setitem(self, self.data.__setitem__, name, object)

    def __delitem__(self, name):
        """Delete the named object from the folder. Raises a KeyError
           if the object is not found."""
        uncontained(self.data[name], self, name)
        del self.data[name]
Example #58
0
class Evaluations(persistent.Persistent, container.contained.Contained):
    """Evaluations mapping.

    This particular implementation uses the ``zope.app.keyreference`` package
    to generate the keys of the requirements. Any key that is passed in could
    be the requirement or the ``IKeyReference`` of the requirement. This
    implementation will always convert the key to provide ``IKeyReference``
    before treating it as a true key.

    Another feature of this implementation is that if you set an evaluation
    for a requirement that has already an evaluation, then the old evaluation
    is simply overridden. The ``IContainer`` interface would raise a duplicate
    name error.
    """
    zope.interface.implements(interfaces.IEvaluations)

    def __init__(self, items=None):
        super(Evaluations, self).__init__()
        self._btree = OOBTree()
        for name, value in items or []:
            self[name] = value

    def __getitem__(self, key):
        """See zope.interface.common.mapping.IItemMapping"""
        return self._btree[IKeyReference(key)]

    def __delitem__(self, key):
        """See zope.interface.common.mapping.IWriteMapping"""
        value = self[key]
        del self._btree[IKeyReference(key)]
        event = container.contained.ObjectRemovedEvent(value, self)
        zope.event.notify(event)

    def __setitem__(self, key, value):
        """See zope.interface.common.mapping.IWriteMapping"""
        self._btree[IKeyReference(key)] = value
        value, event = container.contained.containedEvent(value, self)
        zope.event.notify(event)

    def get(self, key, default=None):
        """See zope.interface.common.mapping.IReadMapping"""
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key):
        """See zope.interface.common.mapping.IReadMapping"""
        return IKeyReference(key) in self._btree

    def keys(self):
        """See zope.interface.common.mapping.IEnumerableMapping"""
        # For now I decided to return the activities (as I think it is more
        # natural), though they are not the true keys as we know
        return [key() for key in self._btree.keys()]

    def __iter__(self):
        """See zope.interface.common.mapping.IEnumerableMapping"""
        return iter(self.keys())

    def values(self):
        """See zope.interface.common.mapping.IEnumerableMapping"""
        return self._btree.values()

    def items(self):
        """See zope.interface.common.mapping.IEnumerableMapping"""
        return [(key(), value) for key, value in self._btree.items()]

    def __len__(self):
        """See zope.interface.common.mapping.IEnumerableMapping"""
        return len(self._btree)

    def addEvaluation(self, evaluation):
        """See interfaces.IEvaluations"""
        self[evaluation.requirement] = evaluation

    def getEvaluationsForRequirement(self, req, recurse=True):
        """See interfaces.IEvaluations"""
        requirements = getRequirementList(req, recurse)
        result = [(name, ev) for name, ev in self.items()
                  if ev.requirement in requirements]
        result = Evaluations(result)
        location.locate(result, getParent(self), getName(self))
        return result

    def getEvaluationsOfEvaluator(self, evaluator):
        """See interfaces.IEvaluations"""
        result = [(name, ev) for name, ev in self.items()
                  if ev.evaluator == evaluator]
        result = Evaluations(result)
        location.locate(result, getParent(self), getName(self))
        return result

    def __repr__(self):
        try:
            parent = getParent(self)
        except TypeError:
            parent = None
        return '<%s for %r>' % (self.__class__.__name__, parent)
Example #59
0
class UserRatingStorage(Contained, Persistent):
    """BTree-based storage for user ratings, keeps a running
    statistics tally for efficiency."""
    implements(IUserRating, IRatingStorage)

    _average = 0.0
    _anon_average = 0.0
    _most_recent = None
    _penultimate = None
    _length = None
    _anon_length = None
    # BBB
    scale = 5

    def __init__(self):
        """Setup our data structures"""
        self._anon_ratings = IOBTree()
        self._ratings = OOBTree()
        self._sessions = OOBTree()
        self._length = Length()
        self._anon_length = Length()

    def rate(self, rating, username=None, session_key=None):
        """Set a rating for a particular user"""
        # We keep a running average for efficiency, we need to
        # have the current statistics to do so
        orig_total = self._average * self.numberOfRatings
        orig_rating = self._ratings.get(username, 0.0)
        rating = Rating(rating, username)
        if username:
            self._ratings[username] = rating
            if not orig_rating:
                # If the user hadn't set a rating yet, the number of
                # ratings grew
                self._length.change(1)
        else:
            # For anonymous users, we use a sequential key, which
            # may lead to conflicts.  There's probably a better way
            anon_total = self._anon_average * self._anon_count
            # Update the corresponding BTree length to get the key
            self._anon_length.change(1)
            self._anon_ratings[self._anon_count] = rating
            self._anon_average = (anon_total + rating)/self._anon_count
            # If a session key was passed in for an anonymous user
            # store it with a datestamp
            if session_key:
                self._sessions[session_key] = datetime.utcnow()
        # Calculate the updated average
        self._average = (orig_total + rating - orig_rating)/self.numberOfRatings

        # If this isn't just a change in the last rating update the
        # most recent rating
        if self._most_recent and username != self._most_recent.userid:
            self._penultimate = self._most_recent

        # Mark this new rating as the most recent
        self._most_recent = rating

        return rating

    def userRating(self, username=None):
        """Retreive the rating for the specified user, or the average
        anonymous rating if no user is specified"""
        if username is not None:
            return self._ratings.get(username, None)
        else:
            if self._anon_count:
                return Rating(self._anon_average)
            else:
                return None

    def remove_rating(self, username):
        """Remove the rating for a given user"""
        orig_total = self._average * self.numberOfRatings
        rating = self._ratings[username]
        del self._ratings[username]
        self._length.change(-1)
        # Since we want to keep track of the most recent rating, we
        # need to replace it with the second most recent if the most
        # recent was deleted
        if rating is self.most_recent:
            self._most_recent = self._penultimate
            self._penultimate = None
            if self._most_recent is None:
                ordered = sorted(self.all_user_ratings(True),
                                 key=lambda x: x.timestamp)
                if ordered:
                    self._most_recent = ordered[-1]
                    if len(ordered > 1):
                        self._penultimate = ordered[-2]
                    else:
                        self._penultimate = None
                else:
                    self._most_recent = None
                    self._penultimate = None
        # Update the average
        self._average = float(self.numberOfRatings and
                                 (orig_total - rating)/self.numberOfRatings)

    @property
    def _anon_count(self):
        # Dynamic Migration
        if self._anon_length is None:
            self._anon_length = Length(len(self._anon_ratings))
        return self._anon_length()

    @property
    def most_recent(self):
        return self._most_recent

    def all_user_ratings(self, include_anon=False):
        ratings = self._ratings.values()
        if include_anon:
            ratings = chain(ratings, self._anon_ratings.values())
        return ratings

    def all_raters(self):
        return self._ratings.keys()

    @property
    def numberOfRatings(self):
        # Dynamic Migration
        if self._length is None:
            self._length = Length(len(self._ratings))
        return self._length() + self._anon_count

    @property
    def averageRating(self):
        return self._average

    def last_anon_rating(self, session_key):
        """Returns a timestamp indicating the last time the anonymous user
        with the given session_key rated the object."""
        return self._sessions.get(session_key, None)
class ListLookup(SimpleItem):
    """ An implementation of IListLookup which uses to To address in a message,
        to lookup which list to send a message to.

    Some framework setup:
        >>> import Products.Five
        >>> from Products.Five import zcml
        >>> zcml.load_config('meta.zcml', Products.Five)
        >>> zcml.load_config('permissions.zcml', Products.Five)
        >>> zcml.load_config("configure.zcml", Products.Five.site)
        >>> from Products.listen.utilities import tests
        >>> zcml.load_config('configure.zcml', tests)

    Now let's make a fake mailing list in our site
        >>> ml = tests.install_fake_ml(self.folder, suppress_events=True)
        >>> from zope.app.component.hooks import setSite
        >>> setSite(ml)

    Create our utility:
        >>> from Products.listen.utilities.list_lookup import ListLookup, ListDoesNotExist
        >>> ll = ListLookup('list_lookup').__of__(self.folder)

    Register the list:
        >>> ll.registerList(ml)
        >>> ll.getListForAddress(ml.mailto) == ml
        True

    Attempt to register it under another address:
        >>> from zope.app.exception.interfaces import UserError
        >>> ml.mailto = '*****@*****.**'
        >>> try:
        ...     ll.registerList(ml)
        ... except UserError:
        ...     print "Raised expected error"
        ...
        Raised expected error
        >>> ll.getListForAddress(ml.mailto) == ml
        False

    Update the list address to the new address:
        >>> ll.updateList(ml)
        >>> ll.getListForAddress(ml.mailto) == ml
        True

    Add another list with the same address:
        >>> from Products.listen.utilities.tests import FakeMailingList
        >>> ml2 = FakeMailingList('ml2')
        >>> ml_id = self.folder._setObject('ml2', ml2)
        >>> ml2 = getattr(self.folder, ml_id)
        >>> ml2.mailto = ml.mailto
        >>> try:
        ...     ll.registerList(ml2)
        ... except UserError:
        ...     print "Raised expected error"
        ...
        Raised expected error

    Try to update an unregistered list:
        >>> try:
        ...     ll.updateList(ml2)
        ... except UserError:
        ...     print "Raised expected error"
        ...
        Raised expected error

    Let's try unregistering:
        >>> ll.unregisterList(ml)
        >>> ll.getListForAddress(ml.mailto)

    Unregistering a list that isn't registered shouldn't cause any problems:
        >>> ll.unregisterList(ml2)

    Let's send a mail:
        >>> ll.registerList(ml)
        >>> ll.deliverMessage({'Mail':'x-original-to: [email protected]\\r\\nTo: [email protected]\\r\\nFrom: [email protected]\\r\\nSubject: Bogus\\r\\n\\r\\nTest'})
        'Success [email protected]'

    And with an SMTP that doesn't set x-original-to:
        >>> ll.deliverMessage({'Mail':'To: [email protected]\\r\\nFrom: [email protected]\\r\\nSubject: Bogus\\r\\n\\r\\nTest'})
        'Success [email protected]'

    And another to a bad address:
        >>> from zExceptions import NotFound
        >>> try:
        ...     ll.deliverMessage({'Mail':'x-original-to: [email protected]\\r\\nTo: [email protected]\\r\\nFrom: [email protected]\\r\\nSubject: Bogus\\r\\n\\r\\nTest'})
        ... except ListDoesNotExist:
        ...     print "Raised expected error"
        ...
        Raised expected error
    """

    implements(IListLookup)

    def __init__(self, id='listen_list_lookup'):
        self.id = id
        self._mapping = OOBTree()
        self._reverse = OOBTree()
        self.__name__ = 'listen_lookup'

    # We need to provide a __parent__ property to be registerable
    def _getParent(self):
        return aq_parent(self)

    #__parent__ = property(_getParent)

    def registerList(self, ml):
        """See IListLookup interface documentation"""
        address = ml.mailto
        # normalize case
        if not address:
            # Our list does not have an address yet, this only happens when
            # the add form wasn't used.
            return
        address = address.lower()
        path = '/'.join(ml.getPhysicalPath())
        current_addr = self._reverse.get(path, None)
        current_path = self._mapping.get(address, None)
        if current_addr is not None:
            raise UserError, _("This list is already registered, use "\
                             "updateList to change the address.")
        if current_path is not None:
            raise UserError, _("A list is already registered for this address,"\
                             " you must unregister it first.")
        self._mapping[address] = path
        self._reverse[path] = address

    def updateList(self, ml):
        """See IListLookup interface documentation"""
        address = ml.mailto or ''
        # normalize case
        address = address.lower()
        path = '/'.join(ml.getPhysicalPath())
        current_addr = self._reverse.get(path, None)
        current_path = self._mapping.get(address, None)
        if (current_path is None and current_addr is not None
                and current_addr != address):
            # The mailing list address has changed to one which is unknown
            del self._mapping[current_addr]
            self._reverse[path] = address
            self._mapping[address] = path
        elif current_addr == address and current_path == path:
            # Nothing has changed, do nothing
            pass
        elif current_addr is None and current_path is None:
            # The list is not registered at all, this happens when the addform
            # was not used, stupid CMF
            self.registerList(ml)
        else:
            # The new address is already registered
            raise UserError, _("A list is already registered for this address")

    def unregisterList(self, ml):
        """See IListLookup interface documentation"""
        address = ml.mailto
        # normalize case
        current_ml = self._mapping.get(address, None)
        if not address:
            # We are deleting a list without an address
            if current_ml is not None:
                del self._reverse[current_ml]
            return
        address = address.lower()
        if current_ml == '/'.join(ml.getPhysicalPath()):
            del self._mapping[address]
            del self._reverse[current_ml]

    def getListForAddress(self, address):
        """See IListLookup interface documentation"""
        list_path = self._mapping.get(address, None)
        if list_path is not None:
            site = getSite()
            ml = site.unrestrictedTraverse(list_path)
            return aq_inner(ml)
        return None

    def deliverMessage(self, request):
        """See IListLookup interface documentation"""
        # XXX raising NotFound annoyingly hides the real problem so
        # I've added a bunch of logging.  I propose in the future we
        # change NotFound to something that actually gets logged.  But
        # I'm afraid to do that now because I don't know if we somehow
        # depend on getting a 404 here.
        message = str(request.get(MAIL_PARAMETER_NAME, None))
        if message is not None:
            message = message_from_string(message)
        else:
            logger.error("request.get(%s) returned None" % MAIL_PARAMETER_NAME)
            raise NotFound, _("The message destination cannot be deterimined.")
        # preferentially use the x-original-to header (is this postfix only?),
        # so that mails to multiple lists are handled properly
        address = message.get('x-original-to', None)
        if not address:
            address = message['to']
            cc = message['cc']
            if address and cc:
                address = address + ', ' + cc
            elif cc:
                address = cc
        # normalize case
        if not address:
            import pprint
            logger.warn("No destination found in headers:\n%s" %
                        pprint.pformat(message))
            raise NotFound, _("The message destination cannot be deterimined.")
        address = address.lower()
        if '-manager@' in address:
            address = address.replace('-manager@', '@')
        address_list = AddressList(address)
        for ml_address in address_list:
            ml = self.getListForAddress(ml_address[1])
            if ml is not None:
                break
        else:
            # raise an error on bad requests, so that the SMTP server can
            # send a proper failure message.
            logger.warn("no list found for any of %r" % str(address_list))
            raise ListDoesNotExist, _("The message address does not correspond to a "\
                             "known mailing list.")
        setSite(ml)
        return ml.manage_mailboxer(request)

    def showAddressMapping(self):
        return [{'address': k, 'path': v} for k, v in self._mapping.items()]

    def purgeInvalidEntries(self):
        counter = 0
        for path in self._reverse.keys():
            list_obj = self.unrestrictedTraverse(path, None)
            if list_obj is None:
                address = self._reverse[path]
                del self._mapping[address]
                del self._reverse[path]
                counter += 1
        return counter