示例#1
0
class RootController(BaseController, DispatchIndex):

    def __init__(self):
        setattr(self, 'feed.atom', self.feed)
        setattr(self, 'feed.rss', self.feed)
        c.create_page_lightbox = W.create_page_lightbox
        self._discuss = AppDiscussionController()

    def _check_security(self):
        require_access(c.app, 'read')

    @with_trailing_slash
    @expose()
    def index(self, **kw):
        redirect(h.really_unicode(c.app.root_page_name).encode('utf-8')+'/')

    @expose()
    def _lookup(self, pname, *remainder):
        """Instantiate a Page object, and continue dispatch there."""
        # HACK: The TG request extension machinery will strip off the end of
        # a dotted wiki page name if it matches a known file extension. Here,
        # we reassemble the original page name.
        if request.response_ext and not remainder:
            pname += request.response_ext
        return PageController(pname), remainder

    @expose()
    def new_page(self, title):
        redirect(h.really_unicode(title).encode('utf-8') + '/')

    @with_trailing_slash
    @expose('jinja:forgewiki:templates/wiki/search.html')
    @validate(dict(q=validators.UnicodeString(if_empty=None),
                   history=validators.StringBool(if_empty=False),
                   project=validators.StringBool(if_empty=False)))
    def search(self, q=None, history=None, project=None, limit=None, page=0, **kw):
        'local wiki search'
        if project:
            redirect(c.project.url() + 'search?' + urlencode(dict(q=q, history=history)))
        results = []
        count=0
        limit, page, start = g.handle_paging(limit, page, default=25)
        if not q:
            q = ''
        else:
            results = search(
                q, rows=limit, start=start,
                fq=[
                    'is_history_b:%s' % history,
                    'project_id_s:%s' % c.project._id,
                    'mount_point_s:%s'% c.app.config.options.mount_point,
                    '-deleted_b:true'])
            if results: count=results.hits
        c.search_results = W.search_results
        return dict(q=q, history=history, results=results or [],
                    count=count, limit=limit, page=page)

    @with_trailing_slash
    @expose('jinja:forgewiki:templates/wiki/browse.html')
    @validate(dict(sort=validators.UnicodeString(if_empty='alpha'),
                   show_deleted=validators.StringBool(if_empty=False),
                   page=validators.Int(if_empty=0),
                   limit=validators.Int(if_empty=None)))
    def browse_pages(self, sort='alpha', show_deleted=False, page=0, limit=None, **kw):
        'list of all pages in the wiki'
        c.page_list = W.page_list
        c.page_size = W.page_size
        limit, pagenum, start = g.handle_paging(limit, page, default=25)
        count = 0
        pages = []
        uv_pages = []
        criteria = dict(app_config_id=c.app.config._id)
        can_delete = has_access(c.app, 'delete')()
        show_deleted = show_deleted and can_delete
        if not can_delete:
            criteria['deleted'] = False
        q = WM.Page.query.find(criteria)
        if sort == 'alpha':
            q = q.sort('title')
        count = q.count()
        q = q.skip(start).limit(int(limit))
        for page in q:
            recent_edit = page.history().first()
            p = dict(title=page.title, url=page.url(), deleted=page.deleted)
            if recent_edit:
                p['updated'] = recent_edit.timestamp
                p['user_label'] = recent_edit.author.display_name
                p['user_name'] = recent_edit.author.username
                pages.append(p)
            else:
                if sort == 'recent':
                    uv_pages.append(p)
                else:
                    pages.append(p)
        if sort == 'recent':
            pages.sort(reverse=True, key=lambda x:(x['updated']))
            pages = pages + uv_pages
        return dict(pages=pages, can_delete=can_delete, show_deleted=show_deleted,
                    limit=limit, count=count, page=pagenum)

    @with_trailing_slash
    @expose('jinja:forgewiki:templates/wiki/browse_tags.html')
    @validate(dict(sort=validators.UnicodeString(if_empty='alpha'),
                   page=validators.Int(if_empty=0),
                   limit=validators.Int(if_empty=None)))
    def browse_tags(self, sort='alpha', page=0, limit=None):
        'list of all labels in the wiki'
        c.page_list = W.page_list
        c.page_size = W.page_size
        limit, pagenum, start = g.handle_paging(limit, page, default=25)
        count = 0
        page_tags = {}
        q = WM.Page.query.find(dict(app_config_id=c.app.config._id, deleted=False))
        count = q.count()
        q = q.skip(start).limit(int(limit))
        for page in q:
            if page.labels:
                for label in page.labels:
                    if label not in page_tags:
                        page_tags[label] = []
                    page_tags[label].append(page)
        return dict(labels=page_tags, limit=limit, count=count, page=pagenum)

    @with_trailing_slash
    @expose('jinja:allura:templates/markdown_syntax.html')
    def markdown_syntax(self):
        'Display a page about how to use markdown.'
        return dict(example=MARKDOWN_EXAMPLE)

    @with_trailing_slash
    @expose('jinja:allura:templates/markdown_syntax_dialog.html')
    def markdown_syntax_dialog(self):
        'Display a page about how to use markdown.'
        return dict(example=MARKDOWN_EXAMPLE)

    @without_trailing_slash
    @expose()
    @validate(dict(
            since=h.DateTimeConverter(if_empty=None, if_invalid=None),
            until=h.DateTimeConverter(if_empty=None, if_invalid=None),
            offset=validators.Int(if_empty=None),
            limit=validators.Int(if_empty=None)))
    def feed(self, since=None, until=None, offset=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        title = 'Recent changes to %s' % c.app.config.options.mount_point
        feed = M.Feed.feed(
            dict(project_id=c.project._id,app_config_id=c.app.config._id),
            feed_type,
            title,
            c.app.url,
            title,
            since, until, offset, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')
示例#2
0
class PageController(BaseController):

    def __init__(self, title):
        self.title = h.really_unicode(unquote(title))
        self.page = WM.Page.query.get(
            app_config_id=c.app.config._id, title=self.title)
        if self.page is not None:
            self.attachment = WikiAttachmentsController(self.page)
        c.create_page_lightbox = W.create_page_lightbox
        setattr(self, 'feed.atom', self.feed)
        setattr(self, 'feed.rss', self.feed)

    def _check_security(self):
        if self.page:
            require_access(self.page, 'read')
            if self.page.deleted:
                require_access(self.page, 'delete')
        else:
            require_access(c.app, 'create')

    def fake_page(self):
        return dict(
            title=self.title,
            text='',
            labels=[],
            viewable_by=['all'],
            attachments=[])

    def get_version(self, version):
        if not version: return self.page
        try:
            return self.page.get_version(version)
        except ValueError:
            return None

    @expose()
    def _lookup(self, pname, *remainder):
        page = WM.Page.query.get(
            app_config_id=c.app.config._id, title=pname)
        if page:
            redirect(page.url())
        else:
            raise exc.HTTPNotFound

    @with_trailing_slash
    @expose('jinja:forgewiki:templates/wiki/page_view.html')
    @validate(dict(version=validators.Int(if_empty=None),
                   page=validators.Int(if_empty=0),
                   limit=validators.Int(if_empty=25)))
    def index(self, version=None, page=0, limit=25, **kw):
        if not self.page:
            redirect(c.app.url+h.urlquote(self.title)+'/edit')
        c.thread = W.thread
        c.attachment_list = W.attachment_list
        c.subscribe_form = W.page_subscribe_form
        post_count = self.page.discussion_thread.post_count
        limit, pagenum = h.paging_sanitizer(limit, page, post_count)
        page = self.get_version(version)
        if page is None:
            if version: redirect('.?version=%d' % (version-1))
            else: redirect('.')
        elif 'all' not in page.viewable_by and c.user.username not in page.viewable_by:
            raise exc.HTTPForbidden(detail="You may not view this page.")
        cur = page.version
        if cur > 1: prev = cur-1
        else: prev = None
        next = cur+1
        hide_left_bar = not (c.app.show_left_bar)
        return dict(
            page=page,
            cur=cur, prev=prev, next=next,
            subscribed=M.Mailbox.subscribed(artifact=self.page),
            hide_left_bar=hide_left_bar, show_meta=c.app.show_right_bar,
            pagenum=pagenum, limit=limit, count=post_count)

    @without_trailing_slash
    @expose('jinja:forgewiki:templates/wiki/page_edit.html')
    def edit(self):
        page_exists = self.page
        if self.page:
            require_access(self.page, 'edit')
            page = self.page
        else:
            page = self.fake_page()
        c.markdown_editor = W.markdown_editor
        c.user_select = ffw.ProjectUserSelect()
        c.attachment_add = W.attachment_add
        c.attachment_list = W.attachment_list
        c.label_edit = W.label_edit
        hide_left_bar = not c.app.show_left_bar
        return dict(page=page, page_exists=page_exists,
            hide_left_bar=hide_left_bar)

    @without_trailing_slash
    @expose('json')
    @require_post()
    def delete(self):
        require_access(self.page, 'delete')
        M.Shortlink.query.remove(dict(ref_id=self.page.index_id()))
        self.page.deleted = True
        suffix = " {dt.hour}:{dt.minute}:{dt.second} {dt.day}-{dt.month}-{dt.year}".format(dt=datetime.utcnow())
        self.page.title += suffix
        return dict(location='../'+self.page.title+'/?deleted=True')

    @without_trailing_slash
    @expose('json')
    @require_post()
    def undelete(self):
        require_access(self.page, 'delete')
        self.page.deleted = False
        M.Shortlink.from_artifact(self.page)
        return dict(location='./edit')

    @without_trailing_slash
    @expose('jinja:forgewiki:templates/wiki/page_history.html')
    @validate(dict(page=validators.Int(if_empty=0),
                   limit=validators.Int(if_empty=None)))
    def history(self, page=0, limit=None):
        if not self.page:
            raise exc.HTTPNotFound
        c.page_list = W.page_list
        c.page_size = W.page_size
        limit, pagenum, start = g.handle_paging(limit, page, default=25)
        count = 0
        pages = self.page.history()
        count = pages.count()
        pages = pages.skip(start).limit(int(limit))
        return dict(title=self.title, pages=pages,
                    limit=limit, count=count, page=pagenum)

    @without_trailing_slash
    @expose('jinja:forgewiki:templates/wiki/page_diff.html')
    @validate(dict(
            v1=validators.Int(),
            v2=validators.Int()))
    def diff(self, v1, v2, **kw):
        if not self.page:
            raise exc.HTTPNotFound
        p1 = self.get_version(v1)
        p2 = self.get_version(v2)
        result = h.diff_text(p1.text, p2.text)
        return dict(p1=p1, p2=p2, edits=result)

    @without_trailing_slash
    @expose(content_type='text/plain')
    def raw(self):
        if not self.page:
            raise exc.HTTPNotFound
        return pformat(self.page)

    @without_trailing_slash
    @expose()
    @validate(dict(
            since=h.DateTimeConverter(if_empty=None, if_invalid=None),
            until=h.DateTimeConverter(if_empty=None, if_invalid=None),
            offset=validators.Int(if_empty=None),
            limit=validators.Int(if_empty=None)))
    def feed(self, since=None, until=None, offset=None, limit=None):
        if not self.page:
            raise exc.HTTPNotFound
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        feed = M.Feed.feed(
            {'ref_id':self.page.index_id()},
            feed_type,
            'Recent changes to %s' % self.page.title,
            self.page.url(),
            'Recent changes to %s' % self.page.title,
            since, until, offset, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')

    @without_trailing_slash
    @expose('json')
    @require_post()
    @validate(dict(version=validators.Int(if_empty=1)))
    def revert(self, version):
        if not self.page:
            raise exc.HTTPNotFound
        require_access(self.page, 'edit')
        orig = self.get_version(version)
        if orig:
            self.page.text = orig.text
        self.page.commit()
        return dict(location='.')

    @without_trailing_slash
    @h.vardec
    @expose()
    @require_post()
    def update(self, title=None, text=None,
               labels=None, labels_old=None,
               viewable_by=None,
               new_viewable_by=None,**kw):
        activity_verb = 'created'
        if not title:
            flash('You must provide a title for the page.','error')
            redirect('edit')
        title = title.replace('/', '-')
        if not self.page:
            # the page doesn't exist yet, so create it
            self.page = WM.Page.upsert(self.title)
            self.page.viewable_by = ['all']
        else:
            require_access(self.page, 'edit')
            activity_verb = 'modified'
        name_conflict = None
        if self.page.title != title:
            name_conflict = WM.Page.query.find(dict(app_config_id=c.app.config._id, title=title, deleted=False)).first()
            if name_conflict:
                flash('There is already a page named "%s".' % title, 'error')
            else:
                if self.page.title == c.app.root_page_name:
                    WM.Globals.query.get(app_config_id=c.app.config._id).root = title
                self.page.title = title
                activity_verb = 'renamed'
        self.page.text = text
        if labels:
            self.page.labels = labels.split(',')
        else:
            self.page.labels = []
        self.page.commit()
        g.director.create_activity(c.user, activity_verb, self.page,
                target=c.project)
        if new_viewable_by:
            if new_viewable_by == 'all':
                self.page.viewable_by.append('all')
            else:
                user = c.project.user_in_project(str(new_viewable_by))
                if user:
                    self.page.viewable_by.append(user.username)
        if viewable_by:
            for u in viewable_by:
                if u.get('delete'):
                    if u['id'] == 'all':
                        self.page.viewable_by.remove('all')
                    else:
                        user = M.User.by_username(str(u['id']))
                        if user:
                            self.page.viewable_by.remove(user.username)
        redirect('../' + h.really_unicode(self.page.title).encode('utf-8') + ('/' if not name_conflict else '/edit'))

    @without_trailing_slash
    @expose()
    @require_post()
    def attach(self, file_info=None):
        if not self.page:
            raise exc.HTTPNotFound
        require_access(self.page, 'edit')
        if hasattr(file_info, 'file'):
            self.page.attach(file_info.filename, file_info.file, content_type=file_info.type)
        redirect(request.referer)

    @expose()
    @validate(W.subscribe_form)
    def subscribe(self, subscribe=None, unsubscribe=None):
        if not self.page:
            raise exc.HTTPNotFound
        if subscribe:
            self.page.subscribe(type='direct')
        elif unsubscribe:
            self.page.unsubscribe()
        redirect(request.referer)
示例#3
0
class RootController(BaseController):
    def __init__(self):
        setattr(self, 'feed.atom', self.feed)
        setattr(self, 'feed.rss', self.feed)
        self._discuss = AppDiscussionController()

    def _check_security(self):
        require_access(c.app, 'read')

    @expose('jinja:forgeblog:templates/blog/index.html')
    @with_trailing_slash
    def index(self, page=0, limit=10, **kw):
        query_filter = dict(app_config_id=c.app.config._id)
        if not has_access(c.app, 'write')():
            query_filter['state'] = 'published'
        q = BM.BlogPost.query.find(query_filter)
        post_count = q.count()
        limit, page = h.paging_sanitizer(limit, page, post_count)
        posts = q.sort('timestamp', pymongo.DESCENDING) \
                 .skip(page * limit).limit(limit)
        c.form = W.preview_post_form
        c.pager = W.pager
        return dict(posts=posts, page=page, limit=limit, count=post_count)

    @expose('jinja:forgeblog:templates/blog/search.html')
    @validate(
        dict(q=validators.UnicodeString(if_empty=None),
             history=validators.StringBool(if_empty=False)))
    def search(self, q=None, history=None, **kw):
        'local tool search'
        results = []
        count = 0
        if not q:
            q = ''
        else:
            results = search(q,
                             fq=[
                                 'state_s:published',
                                 'is_history_b:%s' % history,
                                 'project_id_s:%s' % c.project._id,
                                 'mount_point_s:%s' %
                                 c.app.config.options.mount_point
                             ])
            if results: count = results.hits
        return dict(q=q, history=history, results=results or [], count=count)

    @expose('jinja:forgeblog:templates/blog/edit_post.html')
    @without_trailing_slash
    def new(self, **kw):
        require_access(c.app, 'write')
        now = datetime.utcnow()
        post = dict(state='published')
        c.form = W.new_post_form
        return dict(post=post)

    @expose()
    @require_post()
    @validate(form=W.edit_post_form, error_handler=new)
    @without_trailing_slash
    def save(self, **kw):
        require_access(c.app, 'write')
        post = BM.BlogPost()
        for k, v in kw.iteritems():
            setattr(post, k, v)
        post.neighborhood_id = c.project.neighborhood_id
        post.make_slug()
        post.commit()
        M.Thread(discussion_id=post.app_config.discussion_id,
                 ref_id=post.index_id(),
                 subject='%s discussion' % post.title)
        redirect(h.really_unicode(post.url()).encode('utf-8'))

    @without_trailing_slash
    @expose()
    @validate(
        dict(since=h.DateTimeConverter(if_empty=None, if_invalid=None),
             until=h.DateTimeConverter(if_empty=None, if_invalid=None),
             offset=validators.Int(if_empty=None),
             limit=validators.Int(if_empty=None)))
    def feed(self, since=None, until=None, offset=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        title = '%s - %s' % (c.project.name, c.app.config.options.mount_label)
        feed = M.Feed.feed(
            dict(project_id=c.project._id, app_config_id=c.app.config._id),
            feed_type, title, c.app.url, title, since, until, offset, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')

    @with_trailing_slash
    @expose('jinja:allura:templates/markdown_syntax_dialog.html')
    def markdown_syntax_dialog(self):
        'Static dialog page about how to use markdown.'
        return dict()

    @expose()
    def _lookup(self, year, month, name, *rest):
        slug = '/'.join((year, month, urllib2.unquote(name).decode('utf-8')))
        post = BM.BlogPost.query.get(slug=slug, app_config_id=c.app.config._id)
        if post is None:
            raise exc.HTTPNotFound()
        return PostController(post), rest
示例#4
0
class PostController(BaseController):
    def __init__(self, post):
        self.post = post
        setattr(self, 'feed.atom', self.feed)
        setattr(self, 'feed.rss', self.feed)

    def _check_security(self):
        require_access(self.post, 'read')

    @expose('jinja:forgeblog:templates/blog/post.html')
    @with_trailing_slash
    @validate(
        dict(page=validators.Int(if_empty=0),
             limit=validators.Int(if_empty=25)))
    def index(self, page=0, limit=25, **kw):
        if self.post.state == 'draft':
            require_access(self.post, 'write')
        c.form = W.view_post_form
        c.subscribe_form = W.subscribe_form
        c.thread = W.thread
        post_count = self.post.discussion_thread.post_count
        limit, page = h.paging_sanitizer(limit, page, post_count)
        version = kw.pop('version', None)
        post = self._get_version(version)
        base_post = self.post
        return dict(post=post,
                    base_post=base_post,
                    page=page,
                    limit=limit,
                    count=post_count)

    @expose('jinja:forgeblog:templates/blog/edit_post.html')
    @without_trailing_slash
    def edit(self, **kw):
        require_access(self.post, 'write')
        c.form = W.edit_post_form
        c.attachment_add = W.attachment_add
        c.attachment_list = W.attachment_list
        c.label_edit = W.label_edit
        return dict(post=self.post)

    @without_trailing_slash
    @expose('jinja:forgeblog:templates/blog/post_history.html')
    def history(self):
        posts = self.post.history()
        return dict(title=self.post.title, posts=posts)

    @without_trailing_slash
    @expose('jinja:forgeblog:templates/blog/post_diff.html')
    def diff(self, v1, v2):
        p1 = self._get_version(int(v1))
        p2 = self._get_version(int(v2))
        result = h.diff_text(p1.text, p2.text)
        return dict(p1=p1, p2=p2, edits=result)

    @expose()
    @require_post()
    @validate(form=W.edit_post_form, error_handler=edit)
    @without_trailing_slash
    def save(self, delete=None, **kw):
        require_access(self.post, 'write')
        if delete:
            self.post.delete()
            flash('Post deleted', 'info')
            redirect(h.really_unicode(c.app.url).encode('utf-8'))
        for k, v in kw.iteritems():
            setattr(self.post, k, v)
        self.post.commit()
        redirect('.')

    @without_trailing_slash
    @require_post()
    @expose()
    def revert(self, version):
        require_access(self.post, 'write')
        orig = self._get_version(version)
        if orig:
            self.post.text = orig.text
        self.post.commit()
        redirect('.')

    @expose()
    @validate(W.subscribe_form)
    def subscribe(self, subscribe=None, unsubscribe=None):
        if subscribe:
            self.post.subscribe(type='direct')
        elif unsubscribe:
            self.post.unsubscribe()
        redirect(h.really_unicode(request.referer).encode('utf-8'))

    @without_trailing_slash
    @expose()
    @validate(
        dict(since=h.DateTimeConverter(if_empty=None, if_invalid=None),
             until=h.DateTimeConverter(if_empty=None, if_invalid=None),
             offset=validators.Int(if_empty=None),
             limit=validators.Int(if_empty=None)))
    def feed(self, since=None, until=None, offset=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        feed = M.Feed.feed(dict(ref_id=self.post.index_id()), feed_type,
                           'Recent changes to %s' % self.post.title,
                           self.post.url(),
                           'Recent changes to %s' % self.post.title, since,
                           until, offset, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')

    def _get_version(self, version):
        if not version: return self.post
        try:
            return self.post.get_version(version)
        except ValueError:
            raise exc.HTTPNotFound()
示例#5
0
class ProjectController(object):
    def __init__(self):
        setattr(self, 'feed.rss', self.feed)
        setattr(self, 'feed.atom', self.feed)
        setattr(self, '_nav.json', self._nav)
        self.screenshot = ScreenshotsController()

    @expose('json:')
    def _nav(self):
        return dict(menu=[
            dict(name=s.label, url=s.url, icon=s.ui_icon)
            for s in c.project.sitemap()
        ])

    @expose()
    def _lookup(self, name, *remainder):
        name = unquote(name)
        if not h.re_path_portion.match(name):
            raise exc.HTTPNotFound, name
        subproject = M.Project.query.get(
            shortname=c.project.shortname + '/' + name,
            neighborhood_id=c.project.neighborhood_id)
        if subproject:
            c.project = subproject
            c.app = None
            return ProjectController(), remainder
        app = c.project.app_instance(name)
        if app is None:
            raise exc.HTTPNotFound, name
        c.app = app
        if not app.root:
            raise exc.HTTPNotFound, name

        return app.root, remainder

    def _check_security(self):
        require_access(c.project, 'read')

    @expose()
    @with_trailing_slash
    def index(self, **kw):
        mount = c.project.first_mount('read')
        activity_enabled = config.get('activitystream.enabled', False)
        activity_enabled = request.cookies.get('activitystream.enabled',
                                               activity_enabled)
        activity_enabled = asbool(activity_enabled)
        if activity_enabled and c.project.app_instance('activity'):
            redirect('activity/')
        elif mount is not None:
            if 'ac' in mount:
                redirect(mount['ac'].options.mount_point + '/')
            elif 'sub' in mount:
                redirect(mount['sub'].url())
        elif c.project.app_instance('profile'):
            redirect('profile/')
        else:
            redirect(c.project.app_configs[0].options.mount_point + '/')

    @without_trailing_slash
    @expose()
    @validate(
        dict(since=h.DateTimeConverter(if_empty=None, if_invalid=None),
             until=h.DateTimeConverter(if_empty=None, if_invalid=None),
             page=validators.Int(if_empty=None),
             limit=validators.Int(if_empty=None)))
    def feed(self, since=None, until=None, page=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        title = 'Recent changes to Project %s' % c.project.name
        feed = M.Feed.feed(dict(project_id=c.project._id), feed_type, title,
                           c.project.url(), title, since, until, page, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')

    @expose()
    def icon(self, **kw):
        icon = c.project.icon
        if not icon:
            raise exc.HTTPNotFound
        return icon.serve()

    @expose()
    def user_icon(self, **kw):
        try:
            return self.icon()
        except exc.HTTPNotFound:
            redirect(g.forge_static('images/user.png'))

    @expose('json:')
    def user_search(self, term=''):
        if len(term) < 3:
            raise exc.HTTPBadRequest('"term" param must be at least length 3')
        users = M.User.by_display_name(term)
        named_roles = RoleCache(
            g.credentials,
            g.credentials.project_roles(
                project_id=c.project.root_project._id).named)
        result = [[], []]
        for u in users:
            if u._id in named_roles.userids_that_reach:
                result[0].append(u)
            else:
                pass
                # comment this back in if you want non-project-member users
                # in the search results
                #result[1].append(u)
        result = list(islice(chain(*result), 10))
        return dict(users=[
            dict(label='%s (%s)' % (u.get_pref('display_name'), u.username),
                 value=u.username,
                 id=u.username) for u in result
        ])
示例#6
0
class FeedController(object):
    """Mixin class which adds RSS and Atom feed endpoints to an existing
    controller.

    Feeds will be accessible at the following URLs:

        http://host/path/to/controller/feed -> RSS
        http://host/path/to/controller/feed.rss -> RSS
        http://host/path/to/controller/feed.atom -> Atom

    A default feed is provided by :meth:`get_feed`. Subclasses that need
    a customized feed should override :meth:`get_feed`.

    """
    FEED_TYPES = ['.atom', '.rss']
    FEED_NAMES = ['feed{0}'.format(typ) for typ in FEED_TYPES]

    def __getattr__(self, name):
        if name in self.FEED_NAMES:
            return self.feed
        raise AttributeError(name)

    def _get_feed_type(self, request):
        for typ in self.FEED_TYPES:
            if request.environ['PATH_INFO'].endswith(typ):
                return typ.lstrip('.')
        return 'rss'

    @without_trailing_slash
    @expose()
    @validate(
        dict(since=h.DateTimeConverter(if_empty=None, if_invalid=None),
             until=h.DateTimeConverter(if_empty=None, if_invalid=None),
             page=V.Int(if_empty=None),
             limit=V.Int(if_empty=None)))
    def feed(self, since=None, until=None, page=None, limit=None, **kw):
        """Return a utf8-encoded XML feed (RSS or Atom) to the browser.
        """
        feed_def = self.get_feed(c.project, c.app, c.user)
        if not feed_def:
            raise exc.HTTPNotFound
        feed = M.Feed.feed(feed_def.query, self._get_feed_type(request),
                           feed_def.title, feed_def.url, feed_def.description,
                           since, until, page, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')

    def get_feed(self, project, app, user):
        """Return a default :class:`FeedArgs` for this controller.

        Subclasses should override to customize the feed.

        :param project: :class:`allura.model.project.Project`
        :param app: :class:`allura.app.Application`
        :param user: :class:`allura.model.auth.User`
        :rtype: :class:`FeedArgs`

        """
        return FeedArgs(
            dict(project_id=project._id, app_config_id=app.config._id),
            'Recent changes to %s' % app.config.options.mount_point, app.url)
示例#7
0
class RootController(BaseController, DispatchIndex, FeedController):
    class W(object):
        new_topic = DW.NewTopicPost(submit_text='Post')

        announcements_table = FW.AnnouncementsTable()
        add_forum = AddForumShort()
        search_results = SearchResults()
        search_help = SearchHelp(comments=False, history=False)

    def _check_security(self):
        require_access(c.app, 'read')

    @with_trailing_slash
    @expose('jinja:forgediscussion:templates/discussionforums/index.html')
    def index(self, new_forum=False, **kw):
        c.new_topic = self.W.new_topic
        c.new_topic = self.W.new_topic
        c.add_forum = self.W.add_forum
        c.announcements_table = self.W.announcements_table
        announcements = model.ForumThread.query.find(
            dict(
                app_config_id=c.app.config._id,
                flags='Announcement',
            )).all()
        forums = model.Forum.query.find(
            dict(app_config_id=c.app.config._id, parent_id=None,
                 deleted=False)).all()
        forums = [f for f in forums if h.has_access(f, 'read')()]
        return dict(forums=forums,
                    announcements=announcements,
                    hide_forum=(not new_forum))

    @expose('jinja:forgediscussion:templates/discussionforums/index.html')
    def new_forum(self, **kw):
        require_access(c.app, 'configure')
        return self.index(new_forum=True, **kw)

    @h.vardec
    @expose()
    @require_post()
    @validate(form=W.add_forum, error_handler=index)
    def add_forum_short(self, add_forum=None, **kw):
        require_access(c.app, 'configure')
        f = utils.create_forum(c.app, add_forum)
        redirect(f.url())

    @with_trailing_slash
    @expose(
        'jinja:forgediscussion:templates/discussionforums/create_topic.html')
    def create_topic(self, forum_name=None, new_forum=False, **kw):
        forums = model.Forum.query.find(
            dict(app_config_id=c.app.config._id, parent_id=None,
                 deleted=False))
        c.new_topic = self.W.new_topic
        my_forums = []
        forum_name = h.really_unicode(
            unquote(forum_name)) if forum_name else None
        current_forum = None
        for f in forums:
            if forum_name == f.shortname:
                current_forum = f
            if has_access(f, 'post')():
                my_forums.append(f)
        return dict(forums=my_forums, current_forum=current_forum)

    @memorable_forget()
    @h.vardec
    @expose()
    @require_post()
    @validate(W.new_topic, error_handler=create_topic)
    @AntiSpam.validate('Spambot protection engaged')
    def save_new_topic(self, subject=None, text=None, forum=None, **kw):
        self.rate_limit(model.ForumPost, 'Topic creation', request.referer)
        discussion = model.Forum.query.get(app_config_id=c.app.config._id,
                                           shortname=forum)
        if discussion.deleted and not has_access(c.app, 'configure')():
            flash('This forum has been removed.')
            redirect(request.referrer)
        require_access(discussion, 'post')
        thd = discussion.get_discussion_thread(
            dict(headers=dict(Subject=subject)))[0]
        p = thd.post(subject, text)
        thd.post_to_feed(p)
        flash('Message posted')
        redirect(thd.url())

    @with_trailing_slash
    @expose('jinja:forgediscussion:templates/discussionforums/search.html')
    @validate(
        dict(q=validators.UnicodeString(if_empty=None),
             history=validators.StringBool(if_empty=False),
             project=validators.StringBool(if_empty=False),
             limit=validators.Int(if_empty=None, if_invalid=None),
             page=validators.Int(if_empty=0, if_invalid=0)))
    def search(self,
               q=None,
               history=None,
               project=None,
               limit=None,
               page=0,
               **kw):
        c.search_results = self.W.search_results
        c.help_modal = self.W.search_help
        search_params = kw
        search_params.update({
            'q':
            q or '',
            'history':
            history,
            'project':
            project,
            'limit':
            limit,
            'page':
            page,
            'allowed_types': ['Post', 'Post Snapshot', 'Discussion', 'Thread'],
        })
        d = search_app(**search_params)
        d['search_comments_disable'] = True
        return d

    @expose('jinja:allura:templates/markdown_syntax.html')
    def markdown_syntax(self, **kw):
        'Static page explaining markdown.'
        return dict()

    @with_trailing_slash
    @expose('jinja:allura:templates/markdown_syntax_dialog.html')
    def markdown_syntax_dialog(self, **kw):
        'Static dialog page about how to use markdown.'
        return dict()

    @expose()
    def _lookup(self, id=None, *remainder):
        if id:
            id = unquote(id)
            forum = model.Forum.query.get(app_config_id=c.app.config._id,
                                          shortname=id)
            if forum is None:
                raise exc.HTTPNotFound()
            c.forum = forum
            return ForumController(id), remainder
        else:
            raise exc.HTTPNotFound()

    def get_feed(self, project, app, user):
        """Return a :class:`allura.controllers.feed.FeedArgs` object describing
        the xml feed for this controller.

        Overrides :meth:`allura.controllers.feed.FeedController.get_feed`.

        """
        return FeedArgs(
            dict(project_id=project._id, app_config_id=app.config._id),
            'Recent posts to %s' % app.config.options.mount_label, app.url)

    @without_trailing_slash
    @expose('jinja:forgediscussion:templates/discussionforums/stats_graph.html'
            )
    def stats(self, dates=None, forum=None, **kw):
        if not dates:
            dates = "{} to {}".format(
                (date.today() - timedelta(days=60)).strftime('%Y-%m-%d'),
                date.today().strftime('%Y-%m-%d'))
        return dict(
            dates=dates,
            selected_forum=forum,
        )

    @expose('json:')
    @validate(
        dict(
            begin=h.DateTimeConverter(if_empty=None, if_invalid=None),
            end=h.DateTimeConverter(if_empty=None, if_invalid=None),
        ))
    def stats_data(self, begin=None, end=None, forum=None, **kw):
        end = end or date.today()
        begin = begin or end - timedelta(days=60)

        discussion_id_q = {
            '$in':
            [d._id for d in c.app.forums if d.shortname == forum or not forum]
        }
        # must be ordered dict, so that sorting by this works properly
        grouping = OrderedDict()
        grouping['year'] = {'$year': '$timestamp'}
        grouping['month'] = {'$month': '$timestamp'}
        grouping['day'] = {'$dayOfMonth': '$timestamp'}
        mongo_data = model.ForumPost.query.aggregate([
            {
                '$match': {
                    'discussion_id': discussion_id_q,
                    'status': 'ok',
                    'timestamp': {
                        # convert date to datetime to make pymongo happy
                        '$gte': datetime.combine(begin, time.min),
                        '$lte': datetime.combine(end, time.max),
                    },
                    'deleted': False,
                }
            },
            {
                '$group': {
                    '_id': grouping,
                    'posts': {
                        '$sum': 1
                    },
                }
            },
            {
                '$sort': {
                    '_id': pymongo.ASCENDING,
                }
            },
        ])['result']

        def reformat_data(mongo_data):
            def item(day, val):
                return [calendar.timegm(day.timetuple()) * 1000, val]

            next_expected_date = begin
            for d in mongo_data:
                this_date = datetime(d['_id']['year'], d['_id']['month'],
                                     d['_id']['day'])
                for day in h.daterange(next_expected_date, this_date):
                    yield item(day, 0)
                yield item(this_date, d['posts'])
                next_expected_date = this_date + timedelta(days=1)
            for day in h.daterange(next_expected_date,
                                   end + timedelta(days=1)):
                yield item(day, 0)

        return dict(
            begin=begin,
            end=end,
            data=list(reformat_data(mongo_data)),
        )
                description=kw['description'])
            M.Notification.post(
                mr, 'merge_request',
                subject='Merge request: ' + mr.summary)
            t = M.Thread(
                discussion_id=c.app.config.discussion_id,
                artifact_reference=mr.index_id(),
                subject='Discussion for Merge Request #:%s: %s' % (
                    mr.request_number, mr.summary))
            session(t).flush()
            redirect(mr.url())

    @without_trailing_slash
    @expose()
    @validate(dict(
            since=h.DateTimeConverter(if_empty=None, if_invalid=None),
            until=h.DateTimeConverter(if_empty=None, if_invalid=None),
            offset=validators.Int(if_empty=None),
            limit=validators.Int(if_empty=None)))
    def feed(self, since=None, until=None, offset=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        title = 'Recent changes to %s' % c.app.config.options.mount_point
        feed = M.Feed.feed(
            dict(project_id=c.project._id,app_config_id=c.app.config._id),
            feed_type,
            title,
            c.app.url,
            title,
示例#9
0
class RootController(BaseController):
    class W(object):
        forum_subscription_form = FW.ForumSubscriptionForm()
        new_topic = DW.NewTopicPost(submit_text='Post')
        announcements_table = FW.AnnouncementsTable()
        add_forum = AddForumShort()
        search_results = SearchResults()

    def _check_security(self):
        require_access(c.app, 'read')

    @with_trailing_slash
    @expose('jinja:forgediscussion:templates/discussionforums/index.html')
    def index(self, new_forum=False, **kw):
        c.new_topic = self.W.new_topic
        c.new_topic = self.W.new_topic
        c.add_forum = self.W.add_forum
        c.announcements_table = self.W.announcements_table
        announcements = model.ForumThread.query.find(
            dict(
                app_config_id=c.app.config._id,
                flags='Announcement',
            )).all()
        forums = model.Forum.query.find(
            dict(app_config_id=c.app.config._id, parent_id=None,
                 deleted=False)).all()
        forums = [f for f in forums if h.has_access(f, 'read')()]
        threads = dict()
        for forum in forums:
            threads[forum._id] = model.ForumThread.query.find(
                dict(discussion_id=forum._id,
                     num_replies={'$gt':
                                  0})).sort('mod_date',
                                            pymongo.DESCENDING).limit(6).all()
        return dict(forums=forums,
                    threads=threads,
                    announcements=announcements,
                    hide_forum=(not new_forum))

    @expose('jinja:forgediscussion:templates/discussionforums/index.html')
    def new_forum(self, **kw):
        require_access(c.app, 'configure')
        return self.index(new_forum=True, **kw)

    @h.vardec
    @expose()
    @require_post()
    @validate(form=W.add_forum, error_handler=index)
    def add_forum_short(self, add_forum=None, **kw):
        require_access(c.app, 'configure')
        f = utils.create_forum(c.app, add_forum)
        redirect(f.url())

    @with_trailing_slash
    @expose(
        'jinja:forgediscussion:templates/discussionforums/create_topic.html')
    def create_topic(self, new_forum=False, **kw):
        c.new_topic = self.W.new_topic
        forums = [
            f for f in model.Forum.query.find(
                dict(app_config_id=c.app.config._id, parent_id=None)).all()
            if has_access(f, 'post')() and not f.deleted
        ]
        return dict(forums=forums)

    @h.vardec
    @expose()
    @require_post()
    @validate(W.new_topic, error_handler=create_topic)
    @AntiSpam.validate('Spambot protection engaged')
    def save_new_topic(self, subject=None, text=None, forum=None, **kw):
        discussion = model.Forum.query.get(app_config_id=c.app.config._id,
                                           shortname=forum)
        if discussion.deleted and not has_access(c.app, 'configure')():
            flash('This forum has been removed.')
            redirect(request.referrer)
        require_access(discussion, 'post')
        thd = discussion.get_discussion_thread(
            dict(headers=dict(Subject=subject)))[0]
        post = thd.post(subject, text)
        flash('Message posted')
        redirect(thd.url())

    @with_trailing_slash
    @expose('jinja:forgediscussion:templates/discussionforums/search.html')
    @validate(
        dict(q=validators.UnicodeString(if_empty=None),
             history=validators.StringBool(if_empty=False),
             project=validators.StringBool(if_empty=False),
             limit=validators.Int(if_empty=None),
             page=validators.Int(if_empty=0)))
    def search(self,
               q=None,
               history=False,
               project=False,
               limit=None,
               page=0,
               **kw):
        'local tool search'
        if project:
            redirect(c.project.url() + 'search?' +
                     urlencode(dict(q=q, history=history)))
        results = []
        count = 0
        limit, page, start = g.handle_paging(limit, page, default=25)
        if not q:
            q = ''
        else:
            results = search(
                q,
                rows=limit,
                start=start,
                fq=[
                    'is_history_b:%s' % history,
                    'project_id_s:%s' % c.project._id,
                    'mount_point_s:%s' % c.app.config.options.mount_point,
                    '-deleted_b:true'
                ])
            if results: count = results.hits
        c.search_results = self.W.search_results
        return dict(q=q,
                    history=history,
                    results=results or [],
                    count=count,
                    limit=limit,
                    page=page)

    @expose('jinja:allura:templates/markdown_syntax.html')
    def markdown_syntax(self):
        'Static page explaining markdown.'
        return dict()

    @with_trailing_slash
    @expose('jinja:allura:templates/markdown_syntax_dialog.html')
    def markdown_syntax_dialog(self):
        'Static dialog page about how to use markdown.'
        return dict()

    @expose()
    def _lookup(self, id=None, *remainder):
        if id:
            id = unquote(id)
            return ForumController(id), remainder
        else:
            raise exc.HTTPNotFound()

    @h.vardec
    @expose()
    @validate(W.forum_subscription_form)
    def subscribe(self, **kw):
        require_authenticated()
        forum = kw.pop('forum', [])
        thread = kw.pop('thread', [])
        objs = []
        for data in forum:
            objs.append(
                dict(obj=model.Forum.query.get(shortname=data['shortname'],
                                               app_config_id=c.app.config._id),
                     subscribed=bool(data.get('subscribed'))))
        for data in thread:
            objs.append(
                dict(obj=model.Thread.query.get(_id=data['id']),
                     subscribed=bool(data.get('subscribed'))))
        for obj in objs:
            if obj['subscribed']:
                obj['obj'].subscriptions[str(c.user._id)] = True
            else:
                obj['obj'].subscriptions.pop(str(c.user._id), None)
        redirect(request.referer)

    @expose()
    @validate(
        dict(since=h.DateTimeConverter(if_empty=None),
             until=h.DateTimeConverter(if_empty=None),
             page=validators.Int(if_empty=None),
             limit=validators.Int(if_empty=None)))
    def feed(self, since=None, until=None, page=None, limit=None):
        if request.environ['PATH_INFO'].endswith('.atom'):
            feed_type = 'atom'
        else:
            feed_type = 'rss'
        title = 'Recent posts to %s' % c.app.config.options.mount_label

        feed = Feed.feed(
            dict(project_id=c.project._id, app_config_id=c.app.config._id),
            feed_type, title, c.app.url, title, since, until, page, limit)
        response.headers['Content-Type'] = ''
        response.content_type = 'application/xml'
        return feed.writeString('utf-8')