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
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]
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)
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
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
class ConsumerManager(Persistent, Contained): """\ A very basic consumer manager for the default layer. This manager only capture the very basics, and does not really allow users to add their own consumers and have them approved in a way that is more integrated into the Plone (or other) CMS. """ zope.component.adapts(IAttributeAnnotatable, zope.interface.Interface) zope.interface.implements(IConsumerManager) __dummy_key = fieldproperty.FieldProperty(IConsumer['key']) __dummy_secret = fieldproperty.FieldProperty(IConsumer['secret']) @property def DUMMY_KEY(self): return self.__dummy_key @property def DUMMY_SECRET(self): return self.__dummy_secret def __init__(self): self.__dummy_key = random_string(24) self.__dummy_secret = random_string(24) self._consumers = OOBTree() def add(self, consumer): assert IConsumer.providedBy(consumer) if self.get(consumer.key): raise ValueError('consumer %s already exists', consumer.key) self._consumers[consumer.key] = consumer def get(self, consumer_key, default=None): return self._consumers.get(consumer_key, default) def getValidated(self, consumer_key, default=None): # Provision for further checks by alternative implementations. return self.get(consumer_key, default) def getAllKeys(self): return self._consumers.keys() def makeDummy(self): return Consumer(str(self.DUMMY_KEY), str(self.DUMMY_SECRET)) def remove(self, consumer): if IConsumer.providedBy(consumer): consumer = consumer.key self._consumers.pop(consumer)
class 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
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
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())
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()
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]
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)
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
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
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 ) )
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))
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()
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 + '&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)
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))
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
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)
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 + "&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 )
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
class Tokenable(Entity): """Question class""" tokens_opposition = CompositeMultipleProperty('tokens_opposition') tokens_support = CompositeMultipleProperty('tokens_support') def __init__(self, **kwargs): super(Tokenable, self).__init__(**kwargs) self.set_data(kwargs) self.allocated_tokens = OOBTree() self.len_allocated_tokens = PersistentDict({}) def add_token(self, user, evaluation_type): user_oid = get_oid(user) if user_oid in self.allocated_tokens: self.remove_token(user) self.allocated_tokens[user_oid] = evaluation_type self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] += 1 def remove_token(self, user): user_oid = get_oid(user) if user_oid in self.allocated_tokens: evaluation_type = self.allocated_tokens.pop(user_oid) self.len_allocated_tokens.setdefault(evaluation_type, 0) self.len_allocated_tokens[evaluation_type] -= 1 def evaluators(self, evaluation_type=None): if evaluation_type: return [ get_obj(key) for value, key in self.allocated_tokens.byValue( evaluation_type) ] return [get_obj(key) for key in self.allocated_tokens.keys()] def evaluation(self, user): user_oid = get_oid(user, None) return self.allocated_tokens.get(user_oid, None) def remove_tokens(self, force=False): evaluators = self.evaluators() for user in evaluators: user.remove_token(self) if force: self.remove_token(user) def user_has_token(self, user, root=None): if hasattr(user, 'has_token'): return user.has_token(self, root) return False def init_support_history(self): # [(user_oid, date, support_type), ...], support_type = {1:support, 0:oppose, -1:withdraw} if not hasattr(self, '_support_history'): setattr(self, '_support_history', PersistentList()) @property def len_support(self): return self.len_allocated_tokens.get(Evaluations.support, 0) @property def len_opposition(self): return self.len_allocated_tokens.get(Evaluations.oppose, 0)
class 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
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 {}
class ClassificationCategory(DynamicType, Traversable, Implicit, Persistent, BaseContainer): __parent__ = None __allow_access_to_unprotected_subobjects__ = True meta_type = portal_type = "ClassificationCategory" # This needs to be kept in sync with types/ClassificationCategory.xml title fti_title = "ClassificationCategory" identifier = FieldProperty(IClassificationCategory["identifier"]) title = FieldProperty(IClassificationCategory["title"]) informations = FieldProperty(IClassificationCategory["informations"]) enabled = FieldProperty(IClassificationCategory["enabled"]) def __init__(self, *args, **kwargs): self._tree = OOBTree() super(ClassificationCategory, self).__init__(*args, **kwargs) def getId(self): return self.UID() def Title(self): if self.identifier == self.title: return self.title return u"{0} - {1}".format(self.identifier, self.title) def UID(self): return IMutableUUID(self).get() def __len__(self): return len(self._tree) def __contains__(self, key): return key in self._tree def __getitem__(self, key): return self._tree[key].__of__(self) def __delitem__(self, key, suppress_container_modified=False): element = self[key].__of__(self) notify(ObjectWillBeRemovedEvent(element, self, key)) # Remove the element from _tree self._tree.pop(key) notify(ObjectRemovedEvent(element, self, key)) if not suppress_container_modified: notify(ContainerModifiedEvent(self)) def __iter__(self): return iter(self._tree) def get(self, key, default=None): element = self._tree.get(key, default) if element is default: return default return element.__of__(self) def keys(self): return self._tree.keys() def items(self): return [(i[0], i[1].__of__(self)) for i in self._tree.items()] def values(self): return [v.__of__(self) for v in self._tree.values()] def iterkeys(self): return six.iterkeys(self._tree) def itervalues(self): for v in six.itervalues(self._tree): yield v.__of__(self) def iteritems(self): for k, v in six.iteritems(self._tree): yield (k, v.__of__(self)) def allowedContentTypes(self): return [] def getTypeInfo(self): fti = DexterityFTI("ClassificationCategory") return fti def manage_delObjects(self, ids=None, REQUEST=None): """Delete the contained objects with the specified ids""" if ids is None: ids = [] if isinstance(ids, basestring): ids = [ids] for id in ids: del self[id]
class 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)
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
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
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')
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 ) )
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
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())
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)
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
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)
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)
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
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]
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)
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 ) )
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
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 ) )
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)
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