def location_breadcrumbs(self, fqname): """ Assemble the location using breadcrumbs (was: title) :rtype: list :returns: location breadcrumbs items in tuple (segment_name, fq_name, exists) """ breadcrumbs = [] current_item = '' if not isinstance(fqname, CompositeName): fqname = split_fqname(fqname) if fqname.field != NAME_EXACT: return [(fqname, fqname, bool(self.storage.get_item(**fqname.query)))] namespace = fqname.namespace fq_current = CompositeName(u'', NAME_EXACT, namespace) fq_segment = CompositeName(u'', NAME_EXACT, namespace or '~') breadcrumbs.append((fq_segment, fq_current, False)) item_name = fqname.value if not item_name: return breadcrumbs for segment in item_name.split('/'): current_item += segment fq_current = CompositeName(namespace, NAME_EXACT, current_item) fq_segment = CompositeName(namespace, NAME_EXACT, segment) breadcrumbs.append( (fq_segment, fq_current, bool(self.storage.get_item(**fq_current.query)))) current_item += '/' return breadcrumbs
def location_breadcrumbs(self, fqname): """ Split the incoming fqname into segments. Reassemble into a list of tuples. If the fqname has a namespace, the first tuple's segment_name will have the namespace as a prefix. :rtype: list :returns: location breadcrumbs items in tuple (segment_name, fq_name, exists) """ breadcrumbs = [] current_item = '' if not isinstance(fqname, CompositeName): fqname = split_fqname(fqname) if fqname.field != NAME_EXACT: return [ (fqname, fqname, bool(self.storage.get_item(**fqname.query))) ] # flaskg.unprotected_storage.get_item(**fqname.query) namespace = segment1_namespace = fqname.namespace item_name = fqname.value if not item_name: return breadcrumbs for segment in item_name.split('/'): current_item += segment fq_current = CompositeName(namespace, NAME_EXACT, current_item) fq_segment = CompositeName(segment1_namespace, NAME_EXACT, segment) breadcrumbs.append( (fq_segment, fq_current, bool(self.storage.get_item(**fq_current.query)))) current_item += '/' segment1_namespace = u'' return breadcrumbs
def make_flat_index(self, subitems, isglobalindex=False): """ Create two IndexEntry lists - ``dirs`` and ``files`` - from a list of subitems. Direct subitems are added to the ``files`` list. For indirect subitems, its ancestor which is a direct subitem is added to the ``dirs`` list. Supposing current index root is 'foo' and when 'foo/bar/la' is encountered, 'foo/bar' is added to ``dirs``. The direct subitem need not exist. When both a subitem itself and some of its subitems are in the subitems list, it appears in both ``files`` and ``dirs``. :param isglobalindex: True if the query is for global indexes. """ prefixes = [u''] if isglobalindex else self.subitem_prefixes # IndexEntry instances of "file" subitems files = [] # IndexEntry instances of "directory" subitems dirs = [] added_dir_relnames = set() for rev in subitems: fullnames = rev.meta[NAME] for fullname in fullnames: prefix = self.get_prefix_match(fullname, prefixes) fullname_fqname = CompositeName(rev.meta[NAMESPACE], NAME_EXACT, fullname) if prefix is not None: relname = fullname[len(prefix):] if '/' in relname: # Find the *direct* subitem that is the ancestor of current # (indirect) subitem. e.g. suppose when the index root is # 'foo', and current item (`rev`) is 'foo/bar/lorem/ipsum', # 'foo/bar' will be found. direct_relname = relname.partition('/')[0] direct_relname_fqname = CompositeName( rev.meta[NAMESPACE], NAME_EXACT, direct_relname) if direct_relname_fqname not in added_dir_relnames: added_dir_relnames.add(direct_relname_fqname) direct_fullname = prefix + direct_relname direct_fullname_fqname = CompositeName( rev.meta[NAMESPACE], NAME_EXACT, direct_fullname) fqname = CompositeName(rev.meta[NAMESPACE], NAME_EXACT, direct_fullname) direct_rev = get_storage_revision(fqname) dirs.append( IndexEntry(direct_relname, direct_fullname_fqname, direct_rev.meta)) else: files.append( IndexEntry(relname, fullname_fqname, rev.meta)) return dirs, files
def get_namespaces(self, ns): """ Return the list of tuples (composite name, namespace) referring to namespaces other than the current namespace. """ ns = u'' if ns.value == '~' else ns.value namespace_root_mapping = [] for namespace, _ in app.cfg.namespace_mapping: namespace = namespace.rstrip('/') if namespace != ns: fq_namespace = CompositeName(namespace, NAME_EXACT, u'') namespace_root_mapping.append((namespace or '~', fq_namespace.get_root_fqname())) return namespace_root_mapping
def get_namespaces(self, ns=None): """ Return the list of tuples (composite name, namespace) referring to namespaces other than the current namespace. """ if ns is not None and ns.value == '~': ns = u'' namespace_root_mapping = [] for namespace, _ in app.cfg.namespace_mapping: namespace = namespace.rstrip('/') if ns is None or namespace != ns: fq_namespace = CompositeName(namespace, NAME_EXACT, u'') namespace_root_mapping.append((namespace or '~', fq_namespace.get_root_fqname())) return namespace_root_mapping
def get_namespaces(self, ns=None): """ Return the list of tuples (composite name, namespace) referring to namespaces other than the current namespace. """ if ns is not None and ns.value == "~": ns = u"" namespace_root_mapping = [] for namespace, _ in app.cfg.namespace_mapping: namespace = namespace.rstrip("/") if ns is None or namespace != ns: fq_namespace = CompositeName(namespace, NAME_EXACT, u"") namespace_root_mapping.append((namespace or "~", fq_namespace.get_root_fqname())) return namespace_root_mapping
def item_acl_report(): """ Return a list of all items in the wiki along with the ACL Meta-data """ all_items = flaskg.storage.documents(wikiname=app.cfg.interwikiname) items_acls = [] for item in all_items: item_namespace = item.meta.get(NAMESPACE) item_id = item.meta.get(ITEMID) item_name = item.meta.get(NAME) item_acl = item.meta.get(ACL) acl_default = item_acl is None fqname = CompositeName(item_namespace, u'itemid', item_id) if acl_default: for namespace, acl_config in app.cfg.acl_mapping: if item_namespace == namespace[:-1]: item_acl = acl_config['default'] items_acls.append({ 'name': item_name, 'itemid': item_id, 'fqname': fqname, 'acl': item_acl, 'acl_default': acl_default }) return render_template('admin/item_acl_report.html', title_name=_('Item ACL Report'), items_acls=items_acls)
def userbrowser(): """ User Account Browser """ groups = flaskg.groups revs = user.search_users() # all users user_accounts = [] for rev in revs: user_groups = [] user_names = rev.meta[NAME] for groupname in groups: group = groups[groupname] for name in user_names: if name in group: user_groups.append(groupname) user_accounts.append( dict(uid=rev.meta[ITEMID], name=user_names, fqname=CompositeName(NAMESPACE_USERPROFILES, NAME_EXACT, rev.name), email=rev.meta[EMAIL] if EMAIL in rev.meta else rev.meta[EMAIL_UNVALIDATED], disabled=rev.meta[DISABLED], groups=user_groups)) return render_template('admin/userbrowser.html', user_accounts=user_accounts, title_name=_(u"Users"))
def _rename(self, name, comment, action, delete=False): self._save(self.meta, self.content.data, name=name, action=action, comment=comment, delete=delete) old_prefix = self.subitem_prefixes[0] old_prefixlen = len(old_prefix) if not delete: new_prefix = name + '/' for child in self.get_subitem_revs(): for child_oldname in child.meta[NAME]: if child_oldname.startswith(old_prefix): if delete: child_newname = None else: # rename child_newname = new_prefix + child_oldname[ old_prefixlen:] old_fqname = CompositeName(self.fqname.namespace, self.fqname.field, child_oldname) item = Item.create(old_fqname.fullname) item._save(item.meta, item.content.data, name=child_newname, action=action, comment=comment, delete=delete)
def userbrowser(): """ User Account Browser """ groups = flaskg.groups member_groups = {} # {username: [list of groups], ...} for groupname in groups: group = groups[groupname] for member in group.members: member_groups[member] = member_groups.get(member, []) + [group.name] revs = user.search_users() # all users user_accounts = [] for rev in revs: user_names = rev.meta[NAME] user_groups = member_groups.get(user_names[0], []) for name in user_names[1:]: user_groups = user_groups + member_groups.get(name, []) user_accounts.append(dict(uid=rev.meta[ITEMID], name=user_names, fqname=CompositeName(NAMESPACE_USERS, NAME_EXACT, rev.name), email=rev.meta[EMAIL] if EMAIL in rev.meta else rev.meta[EMAIL_UNVALIDATED], disabled=rev.meta[DISABLED], groups=user_groups)) return render_template('admin/userbrowser.html', user_accounts=user_accounts, title_name=_(u"Users"))
def group_acl_report(group_name): """ Display a 2-column table of items and ACLs, where the ACL rule specifies any WikiGroup or ConfigGroup name. """ group = search_group(group_name) all_items = flaskg.storage.documents(wikiname=app.cfg.interwikiname) group_items = [] for item in all_items: acl_iterator = ACLStringIterator(ACL_RIGHTS_CONTENTS, item.meta.get(ACL, '')) for modifier, entries, rights in acl_iterator: if group_name in entries: item_id = item.meta.get(ITEMID) fqname = CompositeName(item.meta.get(NAMESPACE), u'itemid', item_id) group_items.append( dict(name=item.meta.get(NAME), itemid=item_id, fqname=fqname, rights=rights)) return render_template('admin/group_acl_report.html', title_name=_(u'Group ACL Report'), group_items=group_items, group_name=group_name)
def test_user(self): meta = { keys.ITEMID: make_uuid(), keys.REVID: make_uuid(), keys.NAME: [ u"user name", ], keys.NAMESPACE: u"userprofiles", keys.EMAIL: u"*****@*****.**", keys.SUBSCRIPTIONS: [ u"{0}:{1}".format(keys.ITEMID, make_uuid()), u"{0}::foo".format(keys.NAME), u"{0}::bar".format(keys.TAGS), u"{0}::".format(keys.NAMERE), u"{0}:userprofiles:a".format(keys.NAMEPREFIX), ] } invalid_meta = { keys.SUBSCRIPTIONS: [ u"", u"unknown_tag:123", u"{0}:123".format(keys.ITEMID), u"{0}:foo".format(keys.NAME), ] } state = { 'trusted': False, # True for loading a serialized representation or other trusted sources keys.NAME: u'somename', # name we decoded from URL path keys.ACTION: keys.ACTION_SAVE, keys.HOSTNAME: u'localhost', keys.ADDRESS: u'127.0.0.1', keys.WIKINAME: u'ThisWiki', keys.NAMESPACE: u'', keys.FQNAME: CompositeName(u'', u'', u'somename') } m = UserMetaSchema(meta) valid = m.validate(state) assert m[keys.CONTENTTYPE].value == CONTENTTYPE_USER if not valid: for e in m.children: print e.valid, e print m.valid, m assert valid m = UserMetaSchema(invalid_meta) valid = m.validate(state) assert not valid for e in m.children: if e.name in (keys.SUBSCRIPTIONS, ): for value in e: assert not value.valid
def rename(self, name, comment=u''): """ rename this item to item <name> (replace current name by another name in the NAME list) """ fqname = CompositeName(self.fqname.namespace, self.fqname.field, name) if flaskg.storage.get_item(**fqname.query): raise NameNotUniqueError(L_("An item named %s already exists in the namespace %s." % (name, fqname.namespace))) return self._rename(name, comment, action=ACTION_RENAME)
def build_index(basename, relnames): """ Build a list of IndexEntry by hand, useful as a test helper. """ return [(IndexEntry( relname, CompositeName(NAMESPACE_DEFAULT, NAME_EXACT, '/'.join( (basename, relname))), Item.create('/'.join((basename, relname))).meta)) for relname in relnames]
def split_navilink(self, text): """ Split navibar links into pagename, link to page Admin or user might want to use shorter navibar items by using the [[page|title]] or [[url|title]] syntax. Supported syntax: * PageName * WikiName:PageName * wiki:WikiName:PageName * url * all targets as seen above with title: [[target|title]] :param text: the text used in config or user preferences :rtype: tuple :returns: pagename or url, link to page or url """ title = None wiki_local = '' # means local wiki # Handle [[pagename|title]] or [[url|title]] formats if text.startswith('[[') and text.endswith(']]'): text = text[2:-2] try: target, title = text.split('|', 1) target = target.strip() title = title.strip() except (ValueError, TypeError): # Just use the text as is. target = text.strip() else: target = text if wikiutil.is_URL(target): if not title: title = target return target, title, wiki_local # remove wiki: url prefix if target.startswith("wiki:"): target = target[5:] wiki_name, namespace, field, item_name = split_interwiki(target) if wiki_name == 'Self': wiki_name = '' href = url_for_item(item_name, namespace=namespace, wiki_name=wiki_name, field=field) if not title: title = shorten_fqname(CompositeName(namespace, field, item_name)) return href, title, wiki_name
def build_mixed_index(basename, spec): """ Build a list of MixedIndexEntry by hand, useful as a test helper. :spec is a list of (relname, hassubitem) tuples. """ return [(MixedIndexEntry( relname, CompositeName(NAMESPACE_DEFAULT, NAME_EXACT, '/'.join( (basename, relname))), Item.create('/'.join((basename, relname))).meta, hassubitem)) for relname, hassubitem in spec]
def do_modify(self): is_new = isinstance(self.content, NonExistentContent) closed = self.meta.get('closed') Form = TicketSubmitForm if is_new else TicketUpdateForm if request.method in ['GET', 'HEAD']: form = Form.from_item(self) elif request.method == 'POST': form = Form.from_request(request) if form.validate(): meta, data, message, data_file = form._dump(self) try: if not is_new and message: create_comment(self.meta, message) self.modify(meta, data) if data_file: file_upload(self, data_file) except AccessDenied: abort(403) else: try: fqname = CompositeName(self.meta[NAMESPACE], ITEMID, self.meta[ITEMID]) except KeyError: fqname = self.fqname return redirect(url_for('.show_item', item_name=fqname)) # XXX When creating new item, suppress the "foo doesn't exist. Create it?" dummy content data_rendered = None if is_new else Markup(self.content._render_data()) files = get_files(self) comments, roots = get_comments(self) suggested_tags = get_itemtype_specific_tags(ITEMTYPE_TICKET) return render_template( self.submit_template if is_new else self.modify_template, is_new=is_new, closed=closed, item_name=self.name, data_rendered=data_rendered, form=form, suggested_tags=suggested_tags, item=self, files=files, comments=comments, Markup=Markup, datetime=datetime, roots=roots, build_tree=build_tree, )
def _verify_parents(self, new_name, namespace, old_name=''): """ If this is a subitem, verify all parent items exist. Return None if OK, raise error if not OK. """ name_segments = new_name.split('/') for idx in range(len(name_segments) - 1): root_name = '/'.join(name_segments[:idx + 1]) fqname = CompositeName(namespace, NAME_EXACT, root_name) parent_item = flaskg.unprotected_storage.get_item(**fqname.query) if parent_item.itemid is None: raise MissingParentError( _("Cannot create or rename item '%(new_name)s' because parent '%(parent_name)s' is missing.", new_name=new_name, parent_name=name_segments[idx]))
def test_content(self): class REV(dict): """ fake rev """ rev = REV() rev[keys.ITEMID] = make_uuid() rev[keys.REVID] = make_uuid() rev[keys.ACL] = u"All:read" meta = { keys.REVID: make_uuid(), keys.PARENTID: make_uuid(), keys.NAME: [ u"a", ], keys.NAMESPACE: u"", keys.ACL: u"All:read", keys.TAGS: [u"foo", u"bar"], } state = { 'trusted': False, # True for loading a serialized representation or other trusted sources keys.NAME: u'somename', # name we decoded from URL path keys.ACTION: keys.ACTION_SAVE, keys.HOSTNAME: u'localhost', keys.ADDRESS: u'127.0.0.1', keys.USERID: make_uuid(), keys.HASH_ALGORITHM: u'b9064b9a5efd8c6cef2d38a8169a0e1cbfdb41ba', keys.SIZE: 0, keys.WIKINAME: u'ThisWiki', keys.NAMESPACE: u'', 'rev_parent': rev, 'acl_parent': u"All:read", 'contenttype_current': u'text/x.moin.wiki;charset=utf-8', 'contenttype_guessed': u'text/plain;charset=utf-8', keys.FQNAME: CompositeName(u'', u'', u'somename'), } m = ContentMetaSchema(meta) valid = m.validate(state) assert m[keys.CONTENTTYPE].value == u'text/x.moin.wiki;charset=utf-8' if not valid: for e in m.children: print e.valid, e print m.valid, m assert valid
def test_validjson(): """ Tests for changes to metadata when modifying an item. Does not apply to usersettings form. """ app.cfg.namespace_mapping = [(u'', 'default_backend'), (u'ns1/', 'default_backend'), (u'ns1/ns2/', 'other_backend')] item = Item.create(u'ns1/ns2/existingname') meta = {NAMESPACE: u'ns1/ns2', CONTENTTYPE: u'text/plain;charset=utf-8'} become_trusted() item._save(meta, data='This is a valid Item.') valid_itemid = 'a1924e3d0a34497eab18563299d32178' # ('names', 'namespace', 'field', 'value', 'result') tests = [ ([u'somename', u'@revid'], '', '', 'somename', False), # item names cannot begin with @ # TODO for above? - create item @x, get error message, change name in meta to xx, get an item with names @40x and alias of xx ([u'bar', u'ns1'], '', '', 'bar', False ), # item names cannot match namespace names ([u'foo', u'foo', u'bar'], '', '', 'foo', False), # names in the name list must be unique. ( [u'ns1ns2ns3', u'ns1/subitem'], '', '', 'valid', False ), # Item names must not match with existing namespaces; items cannot be in 2 namespaces ( [u'foobar', u'validname'], '', ITEMID, valid_itemid + '8080', False ), # attempts to change itemid in meta result in "Item(s) named foobar, validname already exist." ([u'barfoo', u'validname'], '', ITEMID, valid_itemid.replace('a', 'y'), False), # similar to above ( [], '', 'itemid', valid_itemid, True ), # deleting all names from the metadata of an existing item will make it nameless, succeeds ([u'existingname'], 'ns1/ns2', '', 'existingname', False), # item already exists ] for name, namespace, field, value, result in tests: meta = {NAME: name, NAMESPACE: namespace} x = JSON(json.dumps(meta)) y = Names(name) state = dict(fqname=CompositeName(namespace, field, value), itemid=None, meta=meta) value = x.validate(state) and y.validate(state) assert value == result
def get_subscribers(**meta): """ Get all users that are subscribed to the item :param meta: key/value pairs from item metadata - itemid, name, namespace, tags keys :return: a set of Subscriber objects """ itemid = meta.get(ITEMID) name = meta.get(NAME) namespace = meta.get(NAMESPACE) fqname = CompositeName(namespace, ITEMID, itemid) tags = meta.get(TAGS) terms = [] if itemid is not None: terms.extend( [Term(SUBSCRIPTION_IDS, u"{0}:{1}".format(ITEMID, itemid))]) if namespace is not None: if name is not None: terms.extend( Term(SUBSCRIPTION_IDS, u"{0}:{1}:{2}".format( NAME, namespace, name_)) for name_ in name) if tags is not None: terms.extend( Term(SUBSCRIPTION_IDS, u"{0}:{1}:{2}".format( TAGS, namespace, tag)) for tag in tags) query = Or(terms) with flaskg.storage.indexer.ix[LATEST_REVS].searcher() as searcher: result_iterators = [ searcher.search(query, limit=None), ] subscription_patterns = searcher.lexicon(SUBSCRIPTION_PATTERNS) patterns = get_matched_subscription_patterns(subscription_patterns, **meta) result_iterators.extend( searcher.documents(subscription_patterns=pattern) for pattern in patterns) subscribers = set() for user in chain.from_iterable(result_iterators): email = user.get(EMAIL) if email: from MoinMoin.user import User u = User(uid=user.get(ITEMID)) if u.may.read(fqname): locale = user.get(LOCALE, DEFAULT_LOCALE) subscribers.add( Subscriber(user[ITEMID], user[NAME][0], email, locale)) return subscribers
def user_acl_report(uid): all_items = flaskg.storage.documents(wikiname=app.cfg.interwikiname) groups = flaskg.groups theuser = user.User(uid=uid) itemwise_acl = [] for item in all_items: fqname = CompositeName(item.meta.get(NAMESPACE), u'itemid', item.meta.get(ITEMID)) itemwise_acl.append({'name': item.meta.get(NAME), 'itemid': item.meta.get(ITEMID), 'fqname': fqname, 'read': theuser.may.read(fqname), 'write': theuser.may.write(fqname), 'create': theuser.may.create(fqname), 'admin': theuser.may.admin(fqname), 'destroy': theuser.may.destroy(fqname)}) return render_template('admin/user_acl_report.html', title_name=_(u'User ACL Report'), user_names=theuser.name, itemwise_acl=itemwise_acl)
def path_breadcrumbs(self): """ Assemble the path breadcrumbs (a.k.a.: trail) :rtype: list :returns: path breadcrumbs items in tuple (wiki_name, item_name, url, exists, err) """ user = self.user breadcrumbs = [] trail = user.get_trail() for interwiki_item_name in trail: wiki_name, namespace, field, item_name = split_interwiki(interwiki_item_name) fqname = CompositeName(namespace, field, item_name) err = not is_known_wiki(wiki_name) href = url_for_item(wiki_name=wiki_name, **fqname.split) if is_local_wiki(wiki_name): exists = bool(self.storage.get_item(**fqname.query)) wiki_name = '' # means "this wiki" for the theme code else: exists = True # we can't detect existance of remote items if item_name: breadcrumbs.append((wiki_name, fqname, href, exists, err)) return breadcrumbs
def get_files(self): check_itemid(self) if self.meta.get(ITEMID) and self.meta.get(NAME): refers_to = self.meta[ITEMID] prefix = self.meta[ITEMID] + '/' else: refers_to = self.fqname.value prefix = self.fqname.value + '/' query = And([ Term(WIKINAME, app.cfg.interwikiname), Term(REFERS_TO, refers_to), Term(ELEMENT, u'file') ]) revs = flaskg.storage.search(query, limit=None) files = [] for rev in revs: names = rev.meta[NAME] for name in names: relname = name[len(prefix):] file_fqname = CompositeName(rev.meta[NAMESPACE], ITEMID, rev.meta[ITEMID]) files.append(IndexEntry(relname, file_fqname, rev.meta)) return files
def do_modify(self): """ Process new ticket, changes to ticket meta data, and/or a new comment against original ticket description. User has clicked "Submit ticket" or "Update ticket" button to get here. If user clicks Save button to add a comment to a prior comment it is not processed here - see /+comment in views.py. """ is_new = isinstance(self.content, NonExistentContent) closed = self.meta.get('closed') Form = TicketSubmitForm if is_new else TicketUpdateForm if request.method in ['GET', 'HEAD']: form = Form.from_item(self) elif request.method == 'POST': form = Form.from_request(request) if form.validate(): meta, data, message, data_file = form._dump( self ) # saves new ticket revision if ticket meta has changed try: if not is_new and message: # user created a new comment create_comment(self.meta, message) if is_new: self.modify(meta, data) if data_file: file_upload(self, data_file) except AccessDenied: abort(403) else: try: fqname = CompositeName(self.meta[NAMESPACE], ITEMID, self.meta[ITEMID]) except KeyError: fqname = self.fqname return redirect(url_for('.show_item', item_name=fqname)) if is_new: data_rendered = None files = {} roots = [] comments = {} else: data_rendered = Markup(self.content._render_data()) files = get_files(self) comments, roots = get_comments(self) suggested_tags = get_itemtype_specific_tags(ITEMTYPE_TICKET) ordered_comments = [] # list of tuples [(comment, indent),,, ] for root in roots: ordered_comments += [ (root, 0), ] + build_tree(comments, root, [], 0) return render_template( self.submit_template if is_new else self.modify_template, is_new=is_new, closed=closed, item_name=self.name, data_rendered=data_rendered, form=form, suggested_tags=suggested_tags, item=self, files=files, datetime=datetime.datetime, ordered_comments=ordered_comments, render_comment_data=render_comment_data, )