Exemplo n.º 1
0
 def test_IncludeHandlesCircularRecursion(self):
     # detect circular recursion and create error message
     update_item('page1', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 '{{page2}}')
     update_item('page2', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 '{{page3}}')
     update_item('page3', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 '{{page4}}')
     update_item('page4', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 '{{page2}}')
     page1 = Item.create('page1')
     rendered = page1.content._render_data()
     # an error message will follow strong tag
     assert '<strong class="moin-error">' in rendered
Exemplo n.º 2
0
def build_index(basename, relnames):
    """
    Build a list of IndexEntry by hand, useful as a test helper for index testing.
    Files have no subitems, meta content is reduced to required keys.
    """
    files = [(IndexEntry(
        relname,
        CompositeName(NAMESPACE_DEFAULT, NAME_EXACT, '/'.join(
            (basename, relname))),
        Item.create('/'.join((basename, relname))).meta))
             for relname in relnames]
    return [(IndexEntry(f.relname, f.fullname,
                        {key: f.meta[key]
                         for key in (CONTENTTYPE, ITEMTYPE)})) for f in files]
Exemplo n.º 3
0
    def do_show(self, revid):
        """
        Show a blog item and a list of its blog entries below it.

        If tag GET-parameter is defined, the list of blog entries consists only
        of those entries that contain the tag value in their lists of tags.
        """
        # for now it is just one tag=value, later it could be tag=value1&tag=value2&...
        tag = request.values.get('tag')
        prefix = self.name + '/'
        current_timestamp = int(time.time())
        terms = [
            Term(WIKINAME, app.cfg.interwikiname),
            # Only blog entry itemtypes
            Term(ITEMTYPE, ITEMTYPE_BLOG_ENTRY),
            # Only sub items of this item
            Prefix(NAME_EXACT, prefix),
        ]
        if tag:
            terms.append(Term(TAGS, tag))
        query = And(terms)

        def ptime_sort_key(searcher, docnum):
            """
            Compute the publication time key for blog entries sorting.

            If PTIME is not defined, we use MTIME.
            """
            fields = searcher.stored_fields(docnum)
            ptime = fields.get(PTIME, fields[MTIME])
            return ptime

        ptime_sort_facet = FunctionFacet(ptime_sort_key)

        revs = flaskg.storage.search(query,
                                     sortedby=ptime_sort_facet,
                                     reverse=True,
                                     limit=None)
        blog_entry_items = [
            Item.create(rev.name, rev_id=rev.revid) for rev in revs
        ]
        return render_template(
            'blog/main.html',
            item_name=self.name,
            fqname=split_fqname(self.name),
            blog_item=self,
            blog_entry_items=blog_entry_items,
            tag=tag,
            item=self,
        )
Exemplo n.º 4
0
    def test_item_can_have_several_names(self):
        content = u"This is page content"

        update_item(
            u'Page', {
                NAME: [
                    u'Page',
                    u'Another name',
                ],
                CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'
            }, content)

        item1 = Item.create(u'Page')
        assert item1.name == u'Page'
        assert item1.meta[CONTENTTYPE] == 'text/x.moin.wiki;charset=utf-8'
        assert item1.content.data == content

        item2 = Item.create(u'Another name')
        assert item2.name == u'Another name'
        assert item2.meta[CONTENTTYPE] == 'text/x.moin.wiki;charset=utf-8'
        assert item2.content.data == content

        assert item1.rev.revid == item2.rev.revid
Exemplo n.º 5
0
 def test_meta_filter(self):
     name = u'Test_item'
     contenttype = u'text/plain;charset=utf-8'
     meta = {
         'test_key': 'test_val',
         CONTENTTYPE: contenttype,
         NAME: [u'test_name'],
         ADDRESS: u'1.2.3.4'
     }
     item = Item.create(name)
     result = Item.meta_filter(item, meta)
     # keys like NAME, ITEMID, REVID, DATAID are filtered
     expected = {'test_key': 'test_val', CONTENTTYPE: contenttype}
     assert result == expected
Exemplo n.º 6
0
 def test_InlineIncludeImage(self):
     # the 3rd parameter, u'',  should be a binary string defining a png image, but it is not needed for this simple test
     update_item('logo.png', {CONTENTTYPE: 'image/png'}, '')
     # simple transclusion
     update_item('page1', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 '{{logo.png}}')
     rendered = Item.create('page1').content._render_data()
     assert '<p><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
     assert '/logo.png" /></span></p>' in rendered
     # simple transclusion with alt text and width
     update_item('page1', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 '{{logo.png|my alt text|width="100"}}')
     rendered = Item.create('page1').content._render_data()
     assert '<p><span class="moin-transclusion" data-href="/logo.png"><img alt="my alt text" src=' in rendered
     assert 'logo.png" width="100" /></span></p>' in rendered
     # within paragraph
     update_item('page1', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 'text {{logo.png}} text')
     rendered = Item.create('page1').content._render_data()
     assert '<p>text <span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
     assert '/logo.png" /></span> text</p>' in rendered
     # within markup
     update_item(
         'page1', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
         "Normal ''italic '''bold {{logo.png}} bold''' italic'' normal")
     rendered = Item.create('page1').content._render_data()
     assert '<p>Normal <em>italic <strong>bold <span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
     assert '/logo.png" /></span> bold</strong> italic</em> normal</p>' in rendered
     # multiple transclusions
     update_item('page1', {CONTENTTYPE: 'text/x.moin.wiki;charset=utf-8'},
                 '{{logo.png}}{{logo.png}}')
     rendered = Item.create('page1').content._render_data()
     assert '<p><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
     assert '/logo.png" /></span><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
     # check for old bug
     assert '<p />' not in rendered
     assert '<p></p>' not in rendered
Exemplo n.º 7
0
    def testIndex(self):
        # create a toplevel and some sub-items
        basename = u'Foo'
        for name in ['', '/ab', '/cd/ef', '/gh', '/ij', '/ij/kl', ]:
            item = Item.create(basename + name)
            item._save({CONTENTTYPE: u'text/plain;charset=utf-8'}, "foo")
        item = Item.create(basename + '/mn')
        item._save({CONTENTTYPE: u'image/jpeg'}, "JPG")

        baseitem = Item.create(basename)

        # test Item.make_flat_index
        # TODO: test Item.get_subitem_revs
        dirs, files = baseitem.get_index()
        assert dirs == build_index(basename, [u'cd', u'ij'])
        assert files == build_index(basename, [u'ab', u'gh', u'ij', u'mn'])

        # test Item.get_mixed_index
        mixed_index = baseitem.get_mixed_index()
        assert mixed_index == build_mixed_index(basename, [
            (u'ab', False),
            (u'cd', True),
            (u'gh', False),
            (u'ij', True),
            (u'mn', False),
        ])

        # check filtered index when startswith param is passed
        dirs, files = baseitem.get_index(startswith=u'a')
        assert dirs == []
        assert files == build_index(basename, [u'ab'])

        # check filtered index when contenttype_groups is passed
        ctgroups = ["Other Text Items"]
        dirs, files = baseitem.get_index(selected_groups=ctgroups)
        assert dirs == build_index(basename, [u'cd', u'ij'])
        assert files == build_index(basename, [u'ab', u'gh', u'ij'])
Exemplo n.º 8
0
def create_comment(meta, message):
    """
    Create a new item comment against original description, refers_to links to original.
    """
    current_timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%H_%M_%S")
    item_name = meta[ITEMID] + '/' + 'comment_' + str(current_timestamp)
    item = Item.create(item_name)
    item.modify({},
                data=message,
                element='comment',
                contenttype_guessed='text/x.moin.wiki;charset=utf-8',
                refers_to=meta[ITEMID],
                reply_to='',
                author=flaskg.user.name[0],
                timestamp=time.ctime())
Exemplo n.º 9
0
def file_upload(self, data_file):
    contenttype = data_file.content_type  # guess by browser, based on file name
    data = data_file.stream
    check_itemid(self)
    if self.meta.get(ITEMID) and self.meta.get(NAME):
        item_name = self.meta[ITEMID] + '/' + data_file.filename
        refers_to = self.meta[ITEMID]
    else:
        item_name = self.fqname.value + '/' + data_file.filename
        refers_to = self.fqname.value
    try:
        item = Item.create(item_name)
        item.modify({}, data, contenttype_guessed=contenttype, refers_to=refers_to, element=u'file')
    except AccessDenied:
        abort(403)
Exemplo n.º 10
0
    def test_rename_acts_only_in_active_name_in_case_there_are_several_names(
            self):
        content = u"This is page content"

        update_item(
            u'Page', {
                NAME: [
                    u'First',
                    u'Second',
                    u'Third',
                ],
                CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'
            }, content)

        item = Item.create(u'Second')
        item.rename(u'New name', comment=u'renamed')

        item1 = Item.create(u'First')
        assert item1.name == u'First'
        assert item1.meta[CONTENTTYPE] == 'text/x.moin.wiki;charset=utf-8'
        assert item1.content.data == content

        item2 = Item.create(u'New name')
        assert item2.name == u'New name'
        assert item2.meta[CONTENTTYPE] == 'text/x.moin.wiki;charset=utf-8'
        assert item2.content.data == content

        item3 = Item.create(u'Third')
        assert item3.name == u'Third'
        assert item3.meta[CONTENTTYPE] == 'text/x.moin.wiki;charset=utf-8'
        assert item3.content.data == content

        assert item1.rev.revid == item2.rev.revid == item3.rev.revid

        item4 = Item.create(u'Second')
        assert item4.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
Exemplo n.º 11
0
    def testRevisionUpdate(self):
        """
        creates two revisions of a container item
        """
        item_name = u'ContainerItem2'
        item = Item.create(item_name,
                           itemtype=ITEMTYPE_DEFAULT,
                           contenttype=u'application/x-tar')
        filecontent = 'abcdefghij'
        content_length = len(filecontent)
        members = set(['example1.txt'])
        item.content.put_member('example1.txt',
                                filecontent,
                                content_length,
                                expected_members=members)
        filecontent = 'AAAABBBB'
        content_length = len(filecontent)
        item.content.put_member('example1.txt',
                                filecontent,
                                content_length,
                                expected_members=members)

        item = Item.create(item_name, contenttype=u'application/x-tar')
        assert item.content.get_member('example1.txt').read() == filecontent
Exemplo n.º 12
0
    def test_InlineInclude(self):

        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'before {{page2}} after')
        # transclude single paragraph as inline
        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'Single line')
        rendered = Item.create(u'page1').content._render_data()
        assert '<p>before <span class="moin-transclusion" data-href="/page2">Single line</span> after</p>' in rendered
        # transclude multiple paragraphs as block
        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'Two\n\nParagraphs')
        rendered = Item.create(u'page1').content._render_data()
        assert '<p>before </p><div class="moin-transclusion" data-href="/page2"><p>Two</p><p>Paragraphs</p></div><p> after</p></div>' in rendered
        # transclude single paragraph with internal markup as inline
        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"this text contains ''italic'' string")
        rendered = Item.create(u'page1').content._render_data()
        assert 'before <span class="moin-transclusion" data-href="/page2">this text contains <em>italic</em>' in rendered
        # transclude single paragraph as only content within a paragraph
        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'Content of page2 is\n\n{{page2}}')
        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"Single Line")
        rendered = Item.create(u'page1').content._render_data()
        assert '<p>Content of page2 is</p><p><span class="moin-transclusion" data-href="/page2">Single Line</span></p>' in rendered
        # transclude single row table within a paragraph, block element forces paragraph to be split into 2 parts
        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'before {{page2}} after')
        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"|| table || cell ||")
        rendered = Item.create(u'page1').content._render_data()
        assert '<p>before </p><div class="moin-transclusion" data-href="/page2"><table' in rendered
        assert '</table></div><p> after</p>' in rendered
        assert rendered.count('<table') == 1
        # transclude two row table within a paragraph, block element forces paragraph to be split into 2 parts
        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'before {{page2}} after')
        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"|| this || has ||\n|| two || rows ||")
        rendered = Item.create(u'page1').content._render_data()
        # inclusion of block item within a paragraph results in a before and after p
        assert '<p>before </p><div class="moin-transclusion" data-href="/page2"><table' in rendered
        assert '</table></div><p> after</p>' in rendered
        assert rendered.count('<table') == 1
        # transclude nonexistent item
        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'before {{nonexistent}} after')
        rendered = Item.create(u'page1').content._render_data()
        assert '<p>before <span class="moin-transclusion" data-href="/nonexistent"><a href="/+modify/nonexistent">' in rendered
        assert '</a></span> after</p>' in rendered
        # transclude empty item
        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'text {{page2}} text')
        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"")
        rendered = Item.create(u'page1').content._render_data()
        assert '<p>text <span class="moin-transclusion" data-href="/page2"></span> text</p>' in rendered
Exemplo n.º 13
0
 def do_show(self, revid):
     blog_item_name = self.name.rsplit('/', 1)[0]
     try:
         blog_item = Item.create(blog_item_name)
     except AccessDenied:
         abort(403)
     if not isinstance(blog_item, Blog):
         # The parent item of this blog entry item is not a Blog item.
         abort(403)
     return render_template('blog/entry.html',
                            item_name=self.name,
                            fqname=blog_item.fqname,
                            blog_item=blog_item,
                            blog_entry_item=self,
                            item=self,
                           )
Exemplo n.º 14
0
def test_validjson():
    """
    Tests for changes to metadata when modifying an item.

    Does not apply to usersettings form.
    """
    app.cfg.namespace_mapping = [('', 'default_backend'),
                                 ('ns1/', 'default_backend'),
                                 ('users/', 'other_backend')]
    item = Item.create('users/existingname')
    meta = {NAMESPACE: 'users', CONTENTTYPE: '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 = [
        (['somename', '@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
        (['bar', 'ns1'], '', '', 'bar', False
         ),  # item names cannot match namespace names
        (['foo', 'foo', 'bar'], '', '', 'foo',
         False),  # names in the name list must be unique.
        (
            ['ns1ns2ns3', 'ns1/subitem'], '', '', 'valid', False
        ),  # Item names must not match with existing namespaces; items cannot be in 2 namespaces
        (
            ['foobar', 'validname'], '', ITEMID, valid_itemid + '8080', False
        ),  # attempts to change itemid in meta result in "Item(s) named foobar, validname already exist."
        (['barfoo', '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
        (['existingname'], 'users', '', '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
Exemplo n.º 15
0
    def test_get_subscribers(self, item, item_name, namespace, tag_name):
        users = get_subscribers(**item.meta)
        assert users == set()

        name1 = u'baz'
        password = u'password'
        email1 = u'*****@*****.**'
        name2 = u"bar"
        email2 = u"*****@*****.**"
        name3 = u"barbaz"
        email3 = u"*****@*****.**"
        user.create_user(username=name1, password=password, email=email1, validate=False, locale=u'en')
        user1 = user.User(name=name1, password=password)
        user.create_user(username=name2, password=password, email=email2, validate=False)
        user2 = user.User(name=name2, password=password)
        user.create_user(username=name3, password=password, email=email3, verify_email=True, locale=u"en")
        user3 = user.User(name=name3, password=password, email=email3)
        subscribers = get_subscribers(**item.meta)
        assert subscribers == set()

        namere = r'.*'
        nameprefix = u"fo"
        subscription_lists = [
            ["{0}:{1}".format(ITEMID, item.meta[ITEMID])],
            ["{0}:{1}:{2}".format(TAGS, namespace, tag_name)],
            ["{0}:{1}:{2}".format(NAME, namespace, item_name)],
            ["{0}:{1}:{2}".format(NAMERE, namespace, namere)],
            ["{0}:{1}:{2}".format(NAMEPREFIX, namespace, nameprefix)],
        ]
        users = [user1, user2, user3]
        expected_names = {user1.name0, user2.name0}
        for subscriptions in subscription_lists:
            for user_ in users:
                user_.profile._meta[SUBSCRIPTIONS] = subscriptions
                user_.save(force=True)
            subscribers = get_subscribers(**item.meta)
            subscribers_names = {subscriber.name for subscriber in subscribers}
            assert subscribers_names == expected_names

        meta = {CONTENTTYPE: u'text/plain;charset=utf-8',
                ACL: u"{0}: All:read,write".format(user1.name0)}
        item._save(meta, comment=u"")
        item = Item.create(item_name)
        subscribers = get_subscribers(**item.meta)
        assert {subscriber.name for subscriber in subscribers} == {user2.name0}
Exemplo n.º 16
0
def modify_acl(item_name):
    fqname = split_fqname(item_name)
    item = Item.create(item_name)
    meta = dict(item.meta)
    new_acl = request.form.get(fqname.fullname)
    is_valid = acl_validate(new_acl)
    if is_valid:
        if new_acl in ('Empty', ''):
            meta[ACL] = ''
        elif new_acl == 'None' and ACL in meta:
            del(meta[ACL])
        else:
            meta[ACL] = new_acl
        item._save(meta=meta)
        flash(L_("Success! ACL saved.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "info")
    else:
        flash(L_("Nothing changed, invalid ACL.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "error")
    return redirect(url_for('.item_acl_report'))
Exemplo n.º 17
0
    def get_item_names(self, name='', startswith='', kind='files', skiptag=''):
        """
        For the specified item, return the fullname of matching descndents.

        Input:

           name: the name of the item to get.  If '' is passed, then the
                 top-level item is used.

           startwith: a substring the matching pages must begin with.  If no
                      value is specified, then all pages are returned.

           kind: the kind of page to return.  Valid values include:

                 files: decendents that do not contain decendents. (default)
                 dirs:  decendents that contain decendents.
                 both:  both 'files' and 'dirs', with duplicates removed.

            skiptag: skip items having this tag

        Output:

           A List of descendent items using their "fullname" value
        """
        try:
            item = Item.create(name)
        except AccessDenied:
            abort(403)
        dirs, files = item.get_index(startswith)
        item_names = []
        if not kind or kind == "files" or kind == "both":
            for item in files:
                if skiptag and TAGS in item.meta and skiptag in item.meta[TAGS]:
                    continue
                item_names.append(item.fullname.value)
        if kind == "dirs" or kind == "both":
            for item in dirs:
                if skiptag and skiptag in item.meta[TAGS]:
                    continue
                item_names.append(item.fullname.value)
        if kind == "both":
            item_names = list(set(item_names))  # remove duplicates
        return item_names
Exemplo n.º 18
0
def build_mixed_index(basename, spec):
    """
    Build a list of MixedIndexEntry by hand, useful as a test helper for index testing.
    The mixed index is a combo of dirs and files with empty meta (dirs) or reduced
    meta (files).

    :spec is a list of (relname, hassubitem) tuples.
    """
    files = [(MixedIndexEntry(
        relname,
        CompositeName(NAMESPACE_DEFAULT, NAME_EXACT, '/'.join(
            (basename, relname))),
        Item.create('/'.join((basename, relname))).meta, hassubitem))
             for relname, hassubitem in spec]
    return [
        (MixedIndexEntry(f.relname, f.fullname, {} if f.hassubitems else
                         {key: f.meta[key]
                          for key in (CONTENTTYPE, ITEMTYPE)}, f.hassubitems))
        for f in files
    ]
Exemplo n.º 19
0
    def _render_data_diff_html(self, oldrev, newrev, template, rev_links={}):
        """ Render HTML formatted meta and content diff of 2 revisions

        :param oldrev: old revision object
        :param newrev: new revision object
        :param template: name of the template to be rendered
        :return: HTML data with meta and content diff
        """
        from moin.items import Item  # XXX causes import error if placed near top
        diffs = self._get_data_diff_html(oldrev.data, newrev.data)
        item = Item.create(newrev.meta['name'][0], rev_id=newrev.meta['revid'])
        rendered = Markup(item.content._render_data())
        return render_template(template,
                               item_name=self.name,
                               oldrev=oldrev,
                               newrev=newrev,
                               diffs=diffs,
                               rendered=rendered,
                               rev_links=rev_links,
                               )
Exemplo n.º 20
0
 def test_IncludeAsLinkAlternate(self):
     # the 3rd parameter, u'',  should be a binary string defining a png image, but it is not needed for this simple test
     update_item(u'logo.png', {CONTENTTYPE: u'image/png'}, u'')
     update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"Single Line")
     # image as link alternate
     update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"text [[page2|{{logo.png}}]] text")
     rendered = Item.create(u'page1').content._render_data()
     assert '<p>text <a href="/page2"><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src="' in rendered
     assert '/logo.png" /></span></a> text</p>' in rendered
     # link alternate with image embedded in markup
     update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"text [[page2|plain '''bold {{logo.png}} bold''' plain]] text")
     rendered = Item.create(u'page1').content._render_data()
     assert '<p>text <a href="/page2">plain <strong>bold <span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src="' in rendered
     assert '/logo.png" /></span> bold</strong> plain</a> text</p>' in rendered
     # nonexistent image used in link alternate
     # XXX html validation errora: A inside A - the image alternate turns into an A-tag to create the non-existant image.  Error is easily seen.
     # IE9, Firefox, Chrome, Safari, and Opera display this OK;  the only usable hyperlink is to create the missing image.
     update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"text [[page2|{{logoxxx.png}}]] text")
     rendered = Item.create(u'page1').content._render_data()
     assert '<p>text <a href="/page2"><span class="moin-transclusion" data-href="/logoxxx.png"><a href="/+modify/logoxxx.png">' in rendered
     assert '</a></span></a> text</p>' in rendered
     # image used as alternate to nonexistent page
     update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"text [[page2xxx|{{logo.png}}]] text")
     rendered = Item.create(u'page1').content._render_data()
     assert '<p>text <a class="moin-nonexistent" href="/page2xxx"><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src="' in rendered
     assert '/logo.png" /></span></a> text</p>' in rendered
     # transclude block elem as link alternate to nonexistent page
     # XXX html validation errors, block element inside A.
     # IE9, Firefox, Chrome, Safari, and Opera display this OK;  the hyperlink is the entire div enclosing the block elem
     update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'text [[MyPage|{{page2}}]] text')
     update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"Double\n\nLine")
     rendered = Item.create(u'page1').content._render_data()
     assert '<p>text <a class="moin-nonexistent" href="/MyPage"><div class="moin-transclusion" data-href="/page2"><p>Double</p><p>Line</p></div></a> text</p>' in rendered
     # transclude empty item as link alternate to nonexistent page
     # hyperlink will be empty span and invisible
     update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'text [[MyPage|{{page2}}]] text')
     update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u"")
     rendered = Item.create(u'page1').content._render_data()
     assert '<p>text <a class="moin-nonexistent" href="/MyPage"><span class="moin-transclusion" data-href="/page2"></span></a> text</p>' in rendered
     # transclude external page as link alternate to nonexistent page
     update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'text [[MyPage|{{http://moinmo.in}}]] text')
     rendered = Item.create(u'page1').content._render_data()
     assert '<p>text <a class="moin-nonexistent" href="/MyPage"><object class="moin-transclusion" data="http://moinmo.in" data-href="http://moinmo.in">http://moinmo.in</object></a> text</p>' in rendered
Exemplo n.º 21
0
    def macro(self, content, arguments, page_url, alternative):
        if arguments:
            item_count = int(arguments[0])
        else:
            item_count = 1

        all_item_names = self.get_item_names()

        # Now select random item from the full list, and if it exists and
        # we can read it, save.
        random_item_names = []
        found = 0
        while found < item_count and all_item_names:
            # Take one random item from the list
            item_name = random.choice(all_item_names)
            all_item_names.remove(item_name)

            # Filter out items the user may not read.
            try:
                item = Item.create(item_name)
                random_item_names.append(item_name)
                found += 1
            except AccessDenied:
                pass

        if not random_item_names:
            return

        random_item_names.sort()

        result = moin_page.span()
        for name in random_item_names:
            link = unicode(Iri(scheme=u'wiki', authority=u'',
                               path=u'/' + name))
            result.append(
                moin_page.a(attrib={xlink.href: link}, children=[name]))
            result.append(", ")

        del result[-1]  # kill last comma
        return result
Exemplo n.º 22
0
    def test_rename_recursion(self):
        update_item(u'Page', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'},
                    u'Page 1')
        update_item(u'Page/Child',
                    {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'},
                    u'this is child')
        update_item(u'Page/Child/Another',
                    {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'},
                    u'another child')

        item = Item.create(u'Page')
        item.rename(u'Renamed_Page', comment=u'renamed')

        # items at original name and its contents after renaming
        item = Item.create(u'Page')
        assert item.name == u'Page'
        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
        item = Item.create(u'Page/Child')
        assert item.name == u'Page/Child'
        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT
        item = Item.create(u'Page/Child/Another')
        assert item.name == u'Page/Child/Another'
        assert item.meta[CONTENTTYPE] == CONTENTTYPE_NONEXISTENT

        # item at new name and its contents after renaming
        item = Item.create(u'Renamed_Page')
        assert item.name == u'Renamed_Page'
        assert item.meta[NAME_OLD] == [u'Page']
        assert item.meta[COMMENT] == u'renamed'
        assert item.content.data == u'Page 1'

        item = Item.create(u'Renamed_Page/Child')
        assert item.name == u'Renamed_Page/Child'
        assert item.meta[NAME_OLD] == [u'Page/Child']
        assert item.meta[COMMENT] == u'renamed'
        assert item.content.data == u'this is child'

        item = Item.create(u'Renamed_Page/Child/Another')
        assert item.name == u'Renamed_Page/Child/Another'
        assert item.meta[NAME_OLD] == [u'Page/Child/Another']
        assert item.meta[COMMENT] == u'renamed'
        assert item.content.data == u'another child'
Exemplo n.º 23
0
 def test_data_conversion(self):
     item_name = 'Text_Item'
     item = Item.create(item_name, ITEMTYPE_DEFAULT, 'text/plain;charset=utf-8')
     test_text = 'This \n is \n a \n Test'
     # test for data_internal_to_form
     result = Text.data_internal_to_form(item.content, test_text)
     expected = 'This \r\n is \r\n a \r\n Test'
     assert result == expected
     # test for data_form_to_internal
     test_form = 'This \r\n is \r\n a \r\n Test'
     result = Text.data_form_to_internal(item.content, test_text)
     expected = test_text
     assert result == expected
     # test for data_internal_to_storage
     result = Text.data_internal_to_storage(item.content, test_text)
     expected = b'This \r\n is \r\n a \r\n Test'
     assert result == expected
     # test for data_storage_to_internal
     data_storage = b'This \r\n is \r\n a \r\n Test'
     result = Text.data_storage_to_internal(item.content, data_storage)
     expected = test_text
     assert result == expected
Exemplo n.º 24
0
def modify_acl(item_name):
    fqname = split_fqname(item_name)
    item = Item.create(item_name)
    meta = dict(item.meta)
    old_acl = meta.get(ACL, '')
    new_acl = request.form.get(fqname.fullname)
    is_valid = acl_validate(new_acl)
    if is_valid:
        if new_acl in ('Empty', ''):
            meta[ACL] = ''
        elif new_acl == 'None' and ACL in meta:
            del(meta[ACL])
        else:
            meta[ACL] = new_acl
        try:
            item._save(meta=meta)
        except AccessDenied:
            # superuser viewed item acl report and tried to change acl but lacked admin permission
            flash(L_("Failed! Not authorized.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=old_acl), "error")
            return redirect(url_for('.item_acl_report'))
        flash(L_("Success! ACL saved.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "info")
    else:
        flash(L_("Nothing changed, invalid ACL.<br>Item: %(item_name)s<br>ACL: %(acl_rule)s", item_name=fqname.fullname, acl_rule=new_acl), "error")
    return redirect(url_for('.item_acl_report'))
Exemplo n.º 25
0
def nuke_item(name):
    """ complete destroys an item """
    item = Item.create(name)
    item.destroy()
Exemplo n.º 26
0
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]
Exemplo n.º 27
0
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]
Exemplo n.º 28
0
 def item(self, item_name, meta):
     item = Item.create(item_name)
     item._save(meta)
     return Item.create(item_name)
Exemplo n.º 29
0
    def recurse(self, elem, page_href):
        # on first call, elem.tag.name=='page'.
        # Descendants (body, div, p, include, page, etc.) are processed by recursing through DOM

        # stack is used to detect transclusion loops
        page_href_new = elem.get(moin_page.page_href)
        if page_href_new:
            page_href_new = Iri(page_href_new)
            if page_href_new != page_href:
                page_href = page_href_new
                self.stack.append(page_href)
            else:
                self.stack.append(None)
        else:
            self.stack.append(None)

        try:
            if elem.tag == xinclude.include:
                # we have already recursed several levels and found a transclusion: "{{SomePage}}" or <<Include(...)>>
                # process the transclusion and add it to the DOM.  Subsequent recursions will traverse through
                # the transclusion's elements.
                href = elem.get(xinclude.href)
                xpointer = elem.get(xinclude.xpointer)

                xp_include_pages = None
                xp_include_sort = None
                xp_include_items = None
                xp_include_skipitems = None
                xp_include_heading = None
                xp_include_level = None

                if xpointer:
                    # we are working on an <<Include(abc)>> macro, not a {{transclusion}}
                    xp = XPointer(xpointer)
                    xp_include = None
                    xp_namespaces = {}
                    for entry in xp:
                        uri = None
                        name = entry.name.split(':', 1)
                        if len(name) > 1:
                            prefix, name = name
                            uri = xp_namespaces.get(prefix, False)
                        else:
                            name = name[0]

                        if uri is None and name == 'xmlns':
                            d_prefix, d_uri = entry.data.split('=', 1)
                            xp_namespaces[d_prefix] = d_uri
                        elif uri == moin_page.namespace and name == 'include':
                            xp_include = XPointer(entry.data)

                    if xp_include:
                        for entry in xp_include:
                            name, data = entry.name, entry.data_unescape
                            # TODO: These do not include all parameters in moin 1.9 Include macro docs:
                            # <<Include(pagename, heading, level, from="regex", to="regex", sort=ascending|descending, items=n, skipitems=n, titlesonly, editlink)>>
                            # these are currently unsupported in moin 2.0: from, to, titlesonly, editlink
                            if name == 'pages':  # pages == pagename in moin 1.9
                                xp_include_pages = data
                            elif name == 'sort':
                                xp_include_sort = data
                            elif name == 'items':
                                xp_include_items = int(data)
                            elif name == 'skipitems':
                                xp_include_skipitems = int(data)
                            elif name == 'heading':
                                xp_include_heading = data
                            elif name == 'level':
                                xp_include_level = data

                included_elements = []
                if href:
                    # We have a single page to transclude or include
                    href = Iri(href)
                    link = Iri(scheme='wiki', authority='')
                    if href.scheme == 'wiki':
                        if href.authority:
                            raise ValueError(
                                "can't handle xinclude for non-local authority"
                            )
                        else:
                            path = href.path[1:]
                    elif href.scheme == 'wiki.local':
                        page = page_href
                        path = href.path
                        if path[0] == '':
                            # /subitem
                            tmp = page.path[1:]
                            tmp.extend(path[1:])
                            path = tmp
                        elif path[0] == '..':
                            # ../sisteritem
                            path = page.path[1:] + path[1:]
                    else:
                        raise ValueError(
                            "can't handle xinclude for schemes other than wiki or wiki.local"
                        )

                    link.path = path

                    if flaskg.user.may.read(unicode(path)):
                        page = Item.create(unicode(path))
                        pages = ((page, link), )
                    else:
                        # ACLs prevent user from viewing a transclusion - show message
                        message = moin_page.p(children=(_(
                            'Access Denied, transcluded content suppressed.')))
                        attrib = {html.class_: 'warning'}
                        div = ET.Element(moin_page.div,
                                         attrib,
                                         children=(message, ))
                        container = ET.Element(moin_page.body,
                                               children=(div, ))
                        return [
                            container, 0
                        ]  # replace transclusion with container's child

                elif xp_include_pages:
                    # we have regex of pages to include:  <<Include(^qqq)>>
                    query = And([
                        Term(WIKINAME, app.cfg.interwikiname),
                        Regex(NAME_EXACT, xp_include_pages)
                    ])
                    reverse = xp_include_sort == 'descending'
                    results = flaskg.storage.search(query,
                                                    sortedby=NAME_EXACT,
                                                    reverse=reverse,
                                                    limit=None)
                    pagelist = [result.name for result in results]
                    if xp_include_skipitems is not None:
                        pagelist = pagelist[xp_include_skipitems:]
                    if xp_include_items is not None:
                        pagelist = pagelist[xp_include_items + 1:]
                    pages = ((Item.create(p),
                              Iri(scheme='wiki', authority='', path='/' + p))
                             for p in pagelist)
                    if not pagelist:
                        msg = _(
                            'Error: no items found matching "<<Include({0})>>"'
                        ).format(xp_include_pages)
                        attrib = {html.class_: 'moin-error'}
                        strong = ET.Element(moin_page.strong, attrib, (msg, ))
                        included_elements.append(strong)

                for page, p_href in pages:
                    if p_href.path[0] != '/':
                        p_href.path = IriPath('/' + '/'.join(p_href.path))
                    if p_href in self.stack:
                        # we have a transclusion loop, create an error message showing list of pages forming loop
                        loop = self.stack[self.stack.index(p_href):]
                        loop = [
                            u'{0}'.format(ref.path[1:])
                            for ref in loop if ref is not None
                        ] + [page.name]
                        msg = u'Error: Transclusion loop via: ' + u', '.join(
                            loop)
                        attrib = {html.class_: 'moin-error'}
                        strong = ET.Element(moin_page.strong, attrib, (msg, ))
                        included_elements.append(strong)
                        continue

                    if xp_include_heading is not None:
                        attrib = {xlink.href: p_href}
                        children = (xp_include_heading or page.name, )
                        elem_a = ET.Element(moin_page.a,
                                            attrib,
                                            children=children)
                        attrib = {
                            moin_page.outline_level: xp_include_level or '1'
                        }
                        elem_h = ET.Element(moin_page.h,
                                            attrib,
                                            children=(elem_a, ))
                        included_elements.append(elem_h)

                    page_doc = page.content.internal_representation(
                        attributes=Arguments(keyword=elem.attrib))
                    if isinstance(page.rev.data, file):
                        page.rev.data.close()

                    self.recurse(page_doc, page_href)

                    # The href needs to be an absolute URI, without the prefix "wiki://"
                    page_doc = mark_item_as_transclusion(page_doc, p_href.path)
                    included_elements.append(page_doc)

                if len(included_elements) > 1:
                    # use a div as container
                    result = ET.Element(moin_page.div)
                    result.extend(included_elements)
                elif included_elements:
                    result = included_elements[0]
                else:
                    result = None
                #  end of processing for transclusion; the "result" will get inserted into the DOM below
                return result

            # Traverse the DOM by calling self.recurse with each child of the current elem.
            # Starting elem.tag.name=='page'.
            container = []
            i = 0
            while i < len(elem):
                child = elem[i]
                if isinstance(child, ET.Node):

                    ret = self.recurse(child, page_href)

                    if ret:
                        # Either child or a descendant of child is a transclusion.
                        # See top of this script for notes on why these DOM adjustments are required.
                        if isinstance(ret, ET.Node
                                      ) and elem.tag.name in NO_BLOCK_CHILDREN:
                            body = ret[0]
                            if len(body) == 0:
                                # the transcluded item is empty, insert an empty span into DOM
                                attrib = Attributes(ret).convert()
                                elem[i] = ET.Element(moin_page.span,
                                                     attrib=attrib)
                            elif (isinstance(body[0], ET.Node)
                                  and (len(body) > 1 or body[0].tag.name
                                       not in ('p', 'object', 'a'))):
                                # Complex case: "some text {{BlockItem}} more text" or "\n{{BlockItem}}\n" where
                                # the BlockItem body contains multiple p's, a table, preformatted text, etc.
                                # These block elements cannot be made a child of the current elem, so we create
                                # a container to replace elem.
                                # Create nodes to hold any siblings before and after current child (elem[i])
                                before = copy.deepcopy(elem)
                                after = copy.deepcopy(elem)
                                before[:] = elem[0:i]
                                after[:] = elem[i + 1:]
                                if len(before):
                                    # there are siblings before transclude, save them in container
                                    container.append(before)
                                new_trans_ptr = len(container)
                                # get attributes from page node;
                                # we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"}
                                attrib = Attributes(ret).convert()
                                # current elem will likely be replaced by container so we need to copy data-lineno attr
                                if html.data_lineno in elem.attrib:
                                    attrib[html.data_lineno] = elem.attrib[
                                        html.data_lineno]
                                # make new div node to hold transclusion, copy children, and save in container
                                div = ET.Element(moin_page.div,
                                                 attrib=attrib,
                                                 children=body[:])
                                container.append(
                                    div)  # new_trans_ptr is index to this
                                if len(after):
                                    container.append(after)
                                if elem.tag.name == 'a':
                                    # invalid input [[MyPage|{{BlockItem}}]],
                                    # best option is to retain A-tag and fail html validation
                                    # TODO: error may not be obvious to user - add error message
                                    elem[i] = div
                                else:
                                    # move up 1 level in recursion where elem becomes the child and
                                    # is usually replaced by container
                                    return [container, new_trans_ptr]
                            else:
                                # default action for inline transclusions or odd things like circular transclusion error messages
                                classes = child.attrib.get(html.class_,
                                                           '').split()
                                classes += ret.attrib.get(html.class_,
                                                          '').split()
                                ret.attrib[html.class_] = ' '.join(classes)
                                elem[i] = ret
                        elif isinstance(ret, types.ListType):
                            # a container has been returned.
                            # Note: there are multiple places where a container may be constructed
                            ret_container, trans_ptr = ret
                            # trans_ptr points to the transclusion within ret_container.
                            # Here the transclusion will always contain a block level element
                            if elem.tag.name in NO_BLOCK_CHILDREN:
                                # Complex case, transclusion effects grand-parent, great-grand-parent, e.g.:
                                # "/* comment {{BlockItem}} */" or  "text ''italic {{BlockItem}} italic'' text"
                                # elem is an inline element, build a bigger container to replace elem's parent,
                                before = copy.deepcopy(elem)
                                after = copy.deepcopy(elem)
                                before[:] = elem[0:i] + ret_container[
                                    0:trans_ptr]
                                after[:] = ret_container[trans_ptr +
                                                         1:] + elem[i + 1:]
                                if len(before):
                                    container.append(before)
                                new_trans_ptr = len(container)
                                # child may have classes like "comment" that must be added to transcluded element
                                classes = child.attrib.get(
                                    moin_page.class_, '').split()
                                # must use moin_page.class_ above, but use html.class below per html_out.py code
                                classes += ret_container[trans_ptr].attrib.get(
                                    html.class_, '').split()
                                ret_container[trans_ptr].attrib[
                                    html.class_] = ' '.join(classes)
                                container.append(ret_container[trans_ptr]
                                                 )  # the transclusion
                                if len(after):
                                    container.append(after)
                                return [container, new_trans_ptr]
                            else:
                                # elem is a block element
                                for grandchild in child:
                                    if isinstance(
                                            grandchild, ET.Node
                                    ) and grandchild.tag.name == u'include':
                                        # the include may have classes that must be added to transcluded element
                                        classes = grandchild.attrib.get(
                                            html.class_, '').split()
                                        classes += ret_container[
                                            trans_ptr].attrib.get(
                                                html.class_, '').split()
                                        ret_container[trans_ptr].attrib[
                                            html.class_] = ' '.join(classes)
                                # replace child element with the container generated in lower recursion
                                elem[i:i +
                                     1] = ret_container  # elem[i] is the child
                        else:
                            # default action for any ret not fitting special cases above,
                            # e.g. tranclusion is within a table cell
                            elem[i] = ret
                # we are finished with this child, advance to next sibling
                i += 1

        finally:
            self.stack.pop()
Exemplo n.º 30
0
def atom(item_name):
    # Currently atom feeds behave in the fol. way
    # - Text diffs are shown in a side-by-side fashion
    # - The current binary item is fully rendered in the feed
    # - Image(binary)'s diff is shown using PIL
    # - First item is always rendered fully
    # - Revision meta(id, size and comment) is shown for parent and current revision
    query = Term(WIKINAME, app.cfg.interwikiname)
    if item_name:
        query = And([
            query,
            Term(NAME_EXACT, item_name),
        ])
    revs = list(
        flaskg.storage.search(query,
                              idx_name=LATEST_REVS,
                              sortedby=[MTIME],
                              reverse=True,
                              limit=1))
    if revs:
        rev = revs[0]
        cid = cache_key(usage="atom", revid=rev.revid, item_name=item_name)
        content = app.cache.get(cid)
    else:
        content = None
        cid = None
    if content is None:
        if not item_name:
            title = u"{0}".format(app.cfg.sitename)
        else:
            title = u"{0} - {1}".format(app.cfg.sitename, item_name)
        feed = AtomFeed(title=title,
                        feed_url=request.url,
                        url=request.host_url)
        query = Term(WIKINAME, app.cfg.interwikiname)
        if item_name:
            query = And([
                query,
                Term(NAME_EXACT, item_name),
            ])
        history = flaskg.storage.search(query,
                                        idx_name=ALL_REVS,
                                        sortedby=[MTIME],
                                        reverse=True,
                                        limit=100)
        for rev in history:
            name = rev.name
            item = rev.item
            this_revid = rev.meta[REVID]
            previous_revid = rev.meta.get(PARENTID)
            this_rev = rev
            try:
                hl_item = Item.create(name, rev_id=this_revid)
                if previous_revid is not None:
                    # HTML diff for subsequent revisions
                    previous_rev = item[previous_revid]
                    content = hl_item.content._render_data_diff_atom(
                        previous_rev, this_rev)
                else:
                    # full html rendering for new items
                    content = render_template(
                        'atom.html',
                        get='first_revision',
                        rev=this_rev,
                        content=Markup(hl_item.content._render_data()),
                        revision=this_revid)
                content_type = 'html'
            except Exception as e:
                logging.exception("content rendering crashed")
                content = _(u'MoinMoin feels unhappy.')
                content_type = 'text'
            author = get_editor_info(rev.meta, external=True)
            rev_comment = rev.meta.get(COMMENT, '')
            if rev_comment:
                # Trim down extremely long revision comment
                if len(rev_comment) > 80:
                    content = render_template('atom.html',
                                              get='comment_cont_merge',
                                              comment=rev_comment[79:],
                                              content=Markup(content))
                    rev_comment = u"{0}...".format(rev_comment[:79])
                feed_title = u"{0} - {1}".format(author.get(NAME, ''),
                                                 rev_comment)
            else:
                feed_title = u"{0}".format(author.get(NAME, ''))
            if not item_name:
                feed_title = u"{0} - {1}".format(name, feed_title)
            feed.add(
                title=feed_title,
                title_type='text',
                summary=content,
                summary_type=content_type,
                author=author,
                url=url_for_item(name, rev=this_revid, _external=True),
                updated=datetime.fromtimestamp(rev.meta[MTIME]),
            )
        content = feed.to_string()
        # Hack to add XSLT stylesheet declaration since AtomFeed doesn't allow this
        content = content.split("\n")
        content.insert(1, render_template('atom.html', get='xml'))
        content = "\n".join(content)
        if cid is not None:
            app.cache.set(cid, content)
    return Response(content, content_type='application/atom+xml')