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')
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)
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
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()
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 ])
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)
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,
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')