class IntegerTestSchema(StrictSchema): field = validators.Int(not_empty=True)
class RootController(BaseController, DispatchIndex, FeedController): def __init__(self): 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.""" 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), search_comments=validators.StringBool(if_empty=False), project=validators.StringBool(if_empty=False))) def search(self, q=None, history=None, search_comments=None, project=None, limit=None, page=0, **kw): 'local wiki search' c.search_results = W.search_results c.help_modal = W.help_modal search_params = kw search_params.update({ 'q': q or '', 'history': history, 'search_comments': search_comments, 'project': project, 'limit': limit, 'page': page, 'allowed_types': ['WikiPage', 'WikiPage Snapshot'], }) return search_app(**search_params) @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, if_invalid=0), limit=validators.Int(if_empty=None, if_invalid=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, if_invalid=0), limit=validators.Int(if_empty=None, if_invalid=None))) def browse_tags(self, sort='alpha', page=0, limit=None, **kw): '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, labels={'$ne': []})) 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) count = len(page_tags) name_labels = list(page_tags) name_labels.sort() return dict(labels=page_tags, limit=limit, count=count, page=pagenum, name_labels=name_labels[start:start + limit]) @with_trailing_slash @expose('jinja:forgewiki:templates/wiki/create_page.html') def create_wiki_page(self): return {} @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) @expose() @require_post() @validate(W.subscribe_form) def subscribe(self, subscribe=None, unsubscribe=None): if subscribe: M.Mailbox.subscribe(type='direct') elif unsubscribe: M.Mailbox.unsubscribe() redirect(request.referer)
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, since, until, offset, limit) response.headers['Content-Type'] = ''
class BasicTGController(TGController): @expose() @validate(ColonLessGenericValidator()) def validator_without_columns(self, **kw): return tg.request.validation.errors['_the_form'] @expose('json:') @validate(validators={"some_int": validators.Int()}) def validated_int(self, some_int): assert isinstance(some_int, int) return dict(response=some_int) @expose('json:') @validate(validators={"a": validators.Int()}) def validated_and_unvalidated(self, a, b): assert isinstance(a, int) assert isinstance(b, unicode_text) return dict(int=a, str=b) @expose() @controller_based_validate() def validate_controller_based_validator(self, *args, **kw): return 'ok' @expose('json:') @validate(validators={"a": validators.Int(), "someemail": validators.Email()}) def two_validators(self, a=None, someemail=None, *args): errors = tg.request.validation.errors values = tg.request.validation.values return dict(a=a, someemail=someemail, errors=str(errors), values=str(values)) @expose('json:') @validate(validators={"a": validators.Int()}) def with_default_shadow(self, a, b=None ): """A default value should not cause the validated value to disappear""" assert isinstance( a, int ), type(a) return { 'int': a, } @expose('json:') @validate(validators={"e": ColonValidator()}) def error_with_colon(self, e): errors = tg.request.validation.errors return dict(errors=str(errors)) @expose('json:') @validate(validators={ "a": validators.Int(),"b":validators.Int(),"c":validators.Int(),"d":validators.Int() }) def with_default_shadow_long(self, a, b=None,c=None,d=None ): """A default value should not cause the validated value to disappear""" assert isinstance( a, int ), type(a) assert isinstance( b, int ), type(b) assert isinstance( c, int ), type(c) assert isinstance( d, int ), type(d) return { 'int': [a,b,c,d], } @expose() def display_form(self, **kwargs): return str(myform.render(values=kwargs)) @expose('json:') @validate(form=myform) def process_form(self, **kwargs): kwargs['errors'] = tg.request.validation.errors return dict(kwargs) @expose('json:') @validate(form=myform, error_handler=process_form) def send_to_error_handler(self, **kwargs): kwargs['errors'] = tg.request.validation.errors return dict(kwargs) @expose('json') def tw2form_error_handler(self, **kwargs): return dict(errors=tg.request.validation.errors) @expose('json:') @validate(form=movie_form, error_handler=tw2form_error_handler) def send_tw2_to_error_handler(self, **kwargs): return 'passed validation' @expose() @validate({'param': tw2c.IntValidator()}, error_handler=validation_errors_response) def tw2_dict_validation(self, **kwargs): return 'NO_ERROR' @expose() @validate({'param': validators.Int()}, error_handler=validation_errors_response) def formencode_dict_validation(self, **kwargs): return 'NO_ERROR' @expose('text/plain') @validate(form=FormWithFieldSet, error_handler=tw2form_error_handler) def tw2_fieldset_submit(self, **kwargs): return 'passed validation' @expose() def set_lang(self, lang=None): tg.session['tg_lang'] = lang tg.session.save() return 'ok' @expose() @validate(validators=Pwd()) def password(self, pwd1, pwd2): if tg.request.validation.errors: return "There was an error" else: return "Password ok!" @expose('json:') @before_render(lambda rem,params,output:output.update({'GOT_ERROR':'HOOKED'})) def hooked_error_handler(self, *args, **kw): return dict(GOT_ERROR='MISSED HOOK') @expose() @validate({'v':validators.Int()}, error_handler=hooked_error_handler) def with_hooked_error_handler(self, *args, **kw): return dict(GOT_ERROR='NO ERROR') @expose('json') @validate({'v': validators.Int()}) def check_tmpl_context_compatibility(self, *args, **kw): return dict(tmpl_errors=str(tg.tmpl_context.form_errors), errors=str(tg.request.validation.errors)) @expose() def error_handler(self, *args, **kw): return 'ERROR HANDLER!' @expose('json:') @validate(validators={"some_int": validators.Int()}, error_handler=error_handler) def validate_other_error_handler(self, some_int): return dict(response=some_int) def unexposed_error_handler(self, uid, **kw): return 'UID: %s' % uid @expose() @validate({'uid': validators.Int(), 'num': validators.Int()}, error_handler=unexposed_error_handler) def validate_unexposed(self, uid, num): return 'HUH' @expose() @validate({'num': validators.Int()}, error_handler=partial(unexposed_error_handler, uid=5)) def validate_partial(self, num): return 'HUH' @expose() @validate({'uid': tw2c.IntValidator(), 'num': tw2c.IntValidator()}, error_handler=error_handler_function) def validate_function(self, uid, num): return 'HUH' @expose() @validate({'uid': validators.Int(), 'num': validators.Int()}, error_handler=ErrorHandlerCallable()) def validate_callable(self, uid, num): return 'HUH' @expose() @validate({'uid': validators.Int()}, error_handler=ErrorHandlerCallable()) @validate({'num': validators.Int()}, error_handler=abort(412, error_handler=True)) def validate_multi(self, uid, num): return str(uid+num) @expose() @validate({'uid': validators.Int()}, error_handler=abort(412, error_handler=True)) def abort_error_handler(self): return 'HUH' @expose() @validate({'uid': validators.Int()}, error_handler=validation_errors_response) def validate_json_errors(self): return 'HUH' @expose() def validate_json_errors_complex_types(self, date): tg.request.validation.values = {'date': datetime.datetime.utcnow()} return validation_errors_response() @expose() @before_call(lambda remainder, params: params.setdefault('num', 5)) def hooked_error_handler(self, uid, num): return 'UID: %s, NUM: %s' % (uid, num) @expose() @validate(ThrowAwayValidationIntentValidator(), error_handler=abort(412, error_handler=True)) def throw_away_intent(self, uid): if tg.request.validation.exception: return 'ERROR' return 'UHU?' @expose() @validate(error_handler=hooked_error_handler) def passthrough_validation(self, uid): return str(uid) @expose() @validate({'uid': validators.Int()}, error_handler=hooked_error_handler) def validate_hooked(self, uid): return 'HUH' # Decorate validate_hooked with a controller wrapper Decoration.get_decoration(hooked_error_handler)\ ._register_controller_wrapper(ControllerWrapperForErrorHandler) @expose() def manually_handle_validation(self): # This is done to check that we don't break compatibility # with external modules that perform custom validation like tgext.socketio controller = self.__class__.validate_function args = (2, 'NaN') try: output = '' validate_params = get_params_with_argspec(controller, {}, args) params = DecoratedController._perform_validate(controller, validate_params) except validation_errors as inv: handler, output = DecoratedController._handle_validation_errors(controller, args, {}, inv, None) return output @expose(content_type='text/plain') @validate({ 'num': Convert(int, l_('This must be a number')) }, error_handler=validation_errors_response) def post_pow2(self, num=-1): return str(num*num) @expose(content_type='text/plain') @validate({ 'num': Convert(int, l_('This must be a number'), default=0) }, error_handler=validation_errors_response) def post_pow2_opt(self, num=-1): return str(num*num) @expose(content_type='text/plain') @validate({ 'num': Convert(int, u_('àèìòù')) }, error_handler=validation_errors_response) def unicode_error_pow(self, num=-1): return str(num*num) @expose(content_type='text/plain') @validate({ 'num': Convert(int, l_(u_('àèìòù'))) }, error_handler=validation_errors_response) def lazy_unicode_error_pow(self, num=-1): return str(num * num) @expose(content_type='text/plain') @validate({ 'val': Convert(lambda v: int(v) > 0 or int('ERROR')) }, error_handler=validation_errors_response) def chain_validation_0(self, val): return '>0' @expose(content_type='text/plain') @validate({ 'val': Convert(lambda v: int(v) > 1 or int('ERROR')) }, error_handler=chain_validation_0, chain_validation=True) def chain_validation_1(self, val): return '>1' @expose(content_type='text/plain') @validate({ 'val': Convert(lambda v: int(v) > 2 or int('ERROR')) }, error_handler=chain_validation_1, chain_validation=True) def chain_validation_2(self, val): return '>2' @expose(content_type='text/plain') @validate({ 'val': Convert(lambda v: int(v) > 3 or int('ERROR')) }, error_handler=chain_validation_2, chain_validation=True) def chain_validation_begin(self, val): return '>3'
def save(self): request = self.request user = request.user chk_type = self.request.context.chk_type all_values = not self.request.dboptions.OtherMembersActive shared_values = not not ( all_values or not self.request.matched_route.name.endswith("_local")) SuperUserGlobal = self._check_security( chk_type, chk_type.Shared == "full" or shared_values) if shared_values and not SuperUserGlobal: self._security_failure() if chk_type.ShowAdd: extra_validators = { chk_type.ID: Any(ciocvalidators.IDValidator(), validators.OneOf(["NEW"])) } else: extra_validators = { chk_type.ID: ciocvalidators.IDValidator(not_empty=chk_type.CanDelete) } if chk_type.CodeTitle: if not chk_type.CodeValidator: extra_validators[chk_type.CodeField] = ciocvalidators.String( max=chk_type.CodeMaxLength) else: extra_validators[chk_type.CodeField] = chk_type.CodeValidator if chk_type.DisplayOrder: extra_validators["DisplayOrder"] = validators.Int(min=0, max=256, if_empty=0) if chk_type.ShowOnForm: extra_validators["ShowOnForm"] = validators.Bool() for field in chk_type.ExtraFields or []: extra_validators[field["field"]] = field["validator"] extra_name_validators = {} for field in chk_type.ExtraNameFields or []: extra_name_validators[field["field"]] = field["validator"] base_schema = make_checklist_base_schema(extra_name_validators, **extra_validators) schema_params = {"chkitem": foreach.ForEach(base_schema)} schema = PostSchema(**schema_params) model_state = request.model_state model_state.form.state.chk_type = chk_type model_state.schema = schema model_state.form.variable_decode = True domain, shown_cultures = viewbase.get_domain_and_show_cultures( request.params) if model_state.validate(): # valid. Save changes and redirect root = ET.Element("CHECKLIST") for i, chkitem in enumerate(model_state.form.data["chkitem"]): if should_skip_item(chk_type, chkitem): continue chk_el = ET.SubElement(root, "CHK") ET.SubElement(chk_el, "CNT").text = str(i) for key, value in chkitem.items(): if key == chk_type.ID and value == "NEW": value = -1 elif isinstance(value, bool): value = int(value) if key != "Descriptions": if value is not None: ET.SubElement(chk_el, key).text = str(value) continue descs = ET.SubElement(chk_el, "DESCS") for culture, data in value.items(): culture = culture.replace("_", "-") if culture not in shown_cultures: continue desc = ET.SubElement(descs, "DESC") ET.SubElement(desc, "Culture").text = culture for key, value in data.items(): if value: ET.SubElement(desc, key).text = value args = [ request.dboptions.MemberID, user.Mod, ET.tostring(root, encoding="unicode"), ] with request.connmgr.get_connection("admin") as conn: sql = chk_type.UpdateSQL(shared_values) log.debug("sql: %s", sql) log.debug("args: %s", args) cursor = conn.execute(sql, *args) result = cursor.fetchone() cursor.close() if not result.Return: self._go_to_route( request.matched_route.name, _query=( ( "InfoMsg", _("The record(s) were successfully updated.", request), ), ("ShowCultures", shown_cultures), ("chk", chk_type.FieldCode), ), ) ErrMsg = _("Unable to save: ") + result.ErrMsg else: ErrMsg = _("There were validation errors.") chkitems, chkusage = self._get_edit_info( chk_type, not all_values and not shared_values, not all_values and shared_values, not SuperUserGlobal, ) record_cultures = syslanguage.active_record_cultures() chkitems = variabledecode.variable_decode(request.POST)["chkitem"] model_state.form.data["chkitem"] = chkitems type_name = "" if chk_type.Shared == "partial" and not all_values: if shared_values: type_name = _("Shared", self.request) else: type_name = _("Local", self.request) title_template = _(chk_type.PageTitleTemplate, self.request) % { "type": type_name } title = (chk_type.CheckListName if request.viewdata.PrintMode else title_template.format(chk_type.CheckListName)) return self._create_response_namespace( title, title, dict( chkitems=chkitems, record_cultures=record_cultures, shown_cultures=shown_cultures, SuperUserGlobal=SuperUserGlobal, chkusage=chkusage, chk_type=chk_type, ErrMsg=ErrMsg, ), no_index=True, )
class InvoiceItemValidator(BaseSchema): product = ProductValidator() qty = validators.Int() cost = validators.Int(min=-2000000, max=2000000) description = validators.String(not_empty=False) chained_validators = [InvoiceItemProductDescriptionValidator()]
class PayInvoiceSchema(BaseSchema): payment_id = validators.Int(min=1)
class HostsController(BaseTableController): allow_only = permissions.host_ro @expose('rnms.templates.host.index') @validate(validators={'z': validators.Int(min=1)}) def index(self, z=None, *args, **kw): if tmpl_context.form_errors: self.process_form_errors() return {} if z is not None: table_filter = {'z': z} else: table_filter = {} class HostTile(PanelTile): title = 'Host List' fullwidth = True class MyTable(BootstrapTable): data_url = url('/hosts/tabledata.json') columns = [('id', 'ID'), ('display_name', 'Name'), ('mgmt_address', 'Management Address')] filter_params = table_filter detail_url = url('/hosts/') return dict(page='host', hosttable=HostTile()) @expose('json') @validate( validators={ 'z': validators.Int(min=1), 'offset': validators.Int(min=0), 'limit': validators.Int(min=1) }) def tabledata(self, **kw): """ Provides the JSON data for the standard bootstrap table """ if tmpl_context.form_errors: self.process_form_errors() return {} conditions = [] if kw['z'] is not None: conditions.append(Host.zone_id == kw['z']) table_data = self._get_tabledata(Host, conditions=conditions, **kw) if table_data is None: return {} rows = [{ 'id': row.id, 'display_name': row.display_name, 'mgmt_address': row.mgmt_address } for row in table_data[1]] return dict(total=table_data[0], rows=rows) @expose('rnms.templates.host.detail') @validate(validators={'h': validators.Int(min=1)}) def _default(self, h): if tmpl_context.form_errors: self.process_form_errors() return dict(page='host') host = Host.by_id(h) if host is None: flash('Host ID#{} not found'.format(h), 'error') return dict(page='host') vendor, devmodel = host.snmp_type() highest_alarm = Event.host_alarm(host.id) if highest_alarm is None: host_state = 'Up' else: host_state = highest_alarm.event_state.display_name.capitalize() thehost = host class HostDetailPanel(PanelTile): title = 'Host Details' class MyHostDetails(HostDetails): host = thehost extra = { 'host_state': host_state, 'vendor': vendor, 'devmodel': devmodel, } class AttributeStatusPanel(PanelTile): title = 'Attribute Status' class AttributeStatus(AttributeStateDoughnut): host_id = h class HostEventPanel(PanelTile): title = 'Events for ' + host.display_name fullwidth = True class HostEventTable(EventTable): filter_params = {'h': h} class AttributesPanel(PanelTile): title = 'Host Attributes' class AttributesTable(BootstrapTable): data_url = url('/attributes/namelist.json', {'h': h}) detail_url = url('/attributes/') columns = [ ('display_name', 'Name'), ] sort_name = 'display_name' fit_panel = True return dict(page='host', details_panel=HostDetailPanel(), status_panel=AttributeStatusPanel(), attributes_panel=AttributesPanel(), host=host, events_panel=HostEventPanel) @expose('rnms.templates.host.map') @validate( validators={ 'z': validators.Int(min=1), 'events': validators.Bool(), 'alarmed': validators.Bool() }) def map(self, z=None, events=False, alarmed=False, **kw): """ Display a map of the Hosts, optionally filtered by Zone id and optionally showing events for those hosts """ if tmpl_context.form_errors: self.process_form_errors() return dict(page='host') class HostMapTile(PanelTile): title = 'Host Map' fullwidth = True class HostMap2(HostMap): zone_id = z alarmed_only = (alarmed == 1) if events: class HostEventTile(PanelTile): title = 'Host Events' fullwidth = True class HostEventTable(EventTable): filter_params = {'z': z} events_panel = HostEventTile() else: events_panel = None return dict(page='hosts', host_map=HostMapTile(), events_panel=events_panel) @expose('rnms.templates.widgets.select') def option(self): """ Return a list of hosts. If user has required permission it shows all, else just their ones """ if permissions.host_ro: hosts = DBSession.query(Host.id, Host.display_name) else: hosts = DBSession.query(Host.id, Host.display_name).filter( Host.id.in_( DBSession.query(Attribute.host_id).filter( Attribute.user_id == request.identity['user'].user_id))) items = hosts.all() items.insert(0, ('', '-- Choose Host --')) return dict(items=items)
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 + '/') @expose('jinja:allura:templates/project_sitemap.html') @without_trailing_slash def sitemap(self): # pragma no cover raise NotImplementedError, 'sitemap' @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): icon = c.project.icon if not icon: raise exc.HTTPNotFound return icon.serve() @expose() def user_icon(self): 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 RootController(BaseController): def __init__(self): c.short_url_lightbox = W.short_url_lightbox def _check_security(self): require_access(c.app, 'read') @expose('jinja:forgeshorturl:templates/index.html') @validate( dict(page=validators.Int(if_empty=0, if_invalid=0), limit=validators.Int(if_empty=None, if_invalid=None))) def index(self, page=0, limit=None, **kw): c.page_list = W.page_list c.page_size = W.page_size limit, pagenum, start = g.handle_paging(limit, page, default=100) p = {'app_config_id': c.app.config._id} if not has_access(c.app, 'view_private'): p['private'] = False short_urls = (ShortUrl.query.find(p)) count = short_urls.count() short_urls = short_urls.skip(start).limit(limit) return { 'short_urls': short_urls, 'limit': limit, 'pagenum': pagenum, 'count': count, 'url_len': len(ShortUrl.build_short_url(c.app, short_name='')), } @expose('jinja:forgeshorturl:templates/search.html') @validate( dict(q=validators.UnicodeString(if_empty=None), project=validators.StringBool(if_empty=False))) def search(self, q=None, project=None, limit=None, page=0, **kw): c.search_results = W.search_results c.help_modal = W.search_help search_params = kw search_params.update({ 'q': q or '', 'project': project, 'limit': limit, 'page': page, 'allowed_types': ['ShortUrl'], }) if not has_access(c.app, 'view_private'): search_params['fq'] = ['private_b:False'] d = search_app(**search_params) d['search_comments_disable'] = True d['search_history_disable'] = True d['url_len'] = len(ShortUrl.build_short_url(c.app, short_name='')) return d @expose() def _lookup(self, pname, *remainder): query = {'app_config_id': c.app.config._id, 'short_name': pname} if not has_access(c.app, 'view_private'): query['private'] = False short_url = ShortUrl.query.find(query).first() if short_url: redirect(short_url.full_url) raise exc.HTTPNotFound()
from datetime import datetime, timedelta from formencode import validators as fev from formencode import schema as fes from formencode import foreach as fef class DateTime(fev.FancyValidator): def _to_python(self, value, state=None): if '.' in value: iso, frac_s = value.split('.') else: iso, frac_s = value, '0' us = float('0.' + frac_s) * 1e6 ts = datetime.strptime(iso, '%Y-%m-%d %H:%M:%S') return ts + timedelta(microseconds=us) message_schema = fes.Schema(priority=fev.Int(if_empty=10, if_missing=10), delay=fev.Int(if_missing=0), timeout=fev.Int(if_missing=300), tags=fef.ForEach(fev.UnicodeString, convert_to_list=True, if_missing=[])) get_schema = fes.Schema(client=fev.UnicodeString(required=True), count=fev.Int(if_missing=1), timeout=fev.Int(if_missing=0)) retry_schema = fes.Schema(delay=fev.Int(if_empty=5, if_missing=5))
class SitemapsController(BaseController): """ Sitemap generation """ @validate(validators={ 'page': validators.Int(if_empty=None, if_missing=None, if_invalid=None), 'limit': validators.Int(if_empty=10000, if_missing=10000, if_invalid=10000) }) @beaker_cache(expire=60 * 60 * 4) @expose('sitemaps/google.xml') @observable(events.SitemapsController.google) def google(self, page=None, limit=10000, **kwargs): """Generate a sitemap which contains googles Video Sitemap information. This action may return a <sitemapindex> or a <urlset>, depending on how many media items are in the database, and the values of the page and limit params. :param page: Page number, defaults to 1. :type page: int :param page: max records to display on page, defaults to 10000. :type page: int """ if request.settings['sitemaps_display'] != 'True': abort(404) response.content_type = \ content_type_for_response(['application/xml', 'text/xml']) media = viewable_media(Media.query.published()) if page is None: if media.count() > limit: return dict(pages=math.ceil(media.count() / float(limit))) else: page = int(page) media = media.offset(page * limit).limit(limit) if page: links = [] else: links = [ url_for(controller='/', qualified=True), url_for(controller='/media', show='popular', qualified=True), url_for(controller='/media', show='latest', qualified=True), url_for(controller='/categories', qualified=True), ] return dict( media = media, page = page, links = links, ) @beaker_cache(expire=60 * 60, query_args=True) @expose('sitemaps/mrss.xml') @observable(events.SitemapsController.mrss) def mrss(self, **kwargs): """Generate a media rss (mRSS) feed of all the sites media.""" if request.settings['sitemaps_display'] != 'True': abort(404) response.content_type = content_type_for_response( ['application/rss+xml', 'application/xml', 'text/xml']) media = viewable_media(Media.query.published()) return dict( media = media, title = 'MediaRSS Sitemap', ) @validate(validators={ 'limit': LimitFeedItemsValidator(), 'skip': validators.Int(if_empty=0, if_missing=0, if_invalid=0) }) @beaker_cache(expire=60 * 3) @expose('sitemaps/mrss.xml') @observable(events.SitemapsController.latest) def latest(self, limit=None, skip=0, **kwargs): """Generate a media rss (mRSS) feed of all the sites media.""" if request.settings['rss_display'] != 'True': abort(404) response.content_type = content_type_for_response( ['application/rss+xml', 'application/xml', 'text/xml']) media_query = Media.query.published().order_by(Media.publish_on.desc()) media = viewable_media(media_query) if limit is not None: media = media.limit(limit) if skip > 0: media = media.offset(skip) return dict( media = media, title = 'Latest Media', ) @validate(validators={ 'limit': LimitFeedItemsValidator(), 'skip': validators.Int(if_empty=0, if_missing=0, if_invalid=0) }) @beaker_cache(expire=60 * 3) @expose('sitemaps/mrss.xml') @observable(events.SitemapsController.featured) def featured(self, limit=None, skip=0, **kwargs): """Generate a media rss (mRSS) feed of the sites featured media.""" if request.settings['rss_display'] != 'True': abort(404) response.content_type = content_type_for_response( ['application/rss+xml', 'application/xml', 'text/xml']) media_query = Media.query.in_category(get_featured_category())\ .published()\ .order_by(Media.publish_on.desc()) media = viewable_media(media_query) if limit is not None: media = media.limit(limit) if skip > 0: media = media.offset(skip) return dict( media = media, title = 'Featured Media', ) @expose() def crossdomain_xml(self, **kwargs): """Serve the crossdomain XML file manually if static_files is disabled. If someone forgets to add this Alias we might as well serve this file for them and save everyone the trouble. This only works when MediaDrop is served out of the root of a domain and if Cooliris is enabled. """ global crossdomain_app if not request.settings['appearance_enable_cooliris']: # Ensure the cache is cleared if cooliris is suddenly disabled if crossdomain_app: crossdomain_app = None raise HTTPNotFound() if not crossdomain_app: relpath = 'mediadrop/public/crossdomain.xml' abspath = os.path.join(config['here'], relpath) crossdomain_app = FileApp(abspath) return forward(crossdomain_app)
class CommitBrowser(BaseController): TreeBrowserClass=None revision_widget = SCMRevisionWidget() log_widget=SCMLogWidget() page_list=ffw.PageList() DEFAULT_PAGE_LIMIT = 25 def __init__(self, revision): self._revision = revision self._commit = c.app.repo.commit(revision) c.revision = revision if self._commit is None: raise exc.HTTPNotFound self.tree = self.TreeBrowserClass(self._commit, tree=self._commit.tree) @expose('jinja:allura:templates/repo/commit.html') @validate(dict(page=validators.Int(if_empty=0), limit=validators.Int(if_empty=DEFAULT_PAGE_LIMIT))) def index(self, page=0, limit=DEFAULT_PAGE_LIMIT, **kw): c.revision_widget = self.revision_widget c.page_list = self.page_list result = dict(commit=self._commit) if self._commit: result.update(self._commit.context()) tree = self._commit.tree limit, page, start = g.handle_paging(limit, page, default=self.DEFAULT_PAGE_LIMIT) diffs = self._commit.paged_diffs(start=start, end=start+limit) result['artifacts'] = [ (t,f) for t in ('added', 'removed', 'changed', 'copied') for f in diffs[t] if t == 'removed' or tree.get_blob_by_path(f)] count = diffs['total'] result.update(dict(page=page, limit=limit, count=count)) return result @expose('jinja:allura:templates/repo/commit_basic.html') def basic(self, **kw): c.revision_widget = self.revision_widget result = dict(commit=self._commit) if self._commit: result.update(self._commit.context()) return result @expose('jinja:allura:templates/repo/tarball.html') def tarball(self, **kw): path = request.params.get('path') if not asbool(tg.config.get('scm.repos.tarball.enable', False)): raise exc.HTTPNotFound() rev = self._commit.url().split('/')[-2] status = c.app.repo.get_tarball_status(rev, path) if status is None and request.method == 'POST': allura.tasks.repo_tasks.tarball.post(revision=rev, path=path) redirect('tarball' + '?path={0}'.format(path) if path else '') return dict(commit=self._commit, revision=rev, status=status or 'na') @expose('json:') def tarball_status(self, path=None, **kw): if not asbool(tg.config.get('scm.repos.tarball.enable', False)): raise exc.HTTPNotFound() rev = self._commit.url().split('/')[-2] return dict(status=c.app.repo.get_tarball_status(rev, path) or 'na') @expose('jinja:allura:templates/repo/log.html') @with_trailing_slash @validate(dict(page=validators.Int(if_empty=0), limit=validators.Int(if_empty=25))) def log(self, limit=25, path=None, **kw): is_file = False if path: is_file = c.app.repo.is_file(path, self._commit._id) commits = list(islice(c.app.repo.log( revs=self._commit._id, path=path, id_only=False, page_size=limit+1), limit+1)) next_commit = None if len(commits) > limit: next_commit = commits.pop() c.log_widget = self.log_widget return dict( username=c.user._id and c.user.username, branch=None, log=commits, next_commit=next_commit, limit=limit, path=path, is_file=is_file, **kw)
class LocationSchema(BaseSchema): display_name = validators.String(not_empty=True) display_order = validators.Int() capacity = validators.Int()
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, fields={ 'author_user_name_t': 'Username', 'text': '"Post text"', 'timestamp_dt': 'Date posted. Example: timestamp_dt:[2018-01-01T00:00:00Z TO *]', 'name_s': 'Subject' }) 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.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, subscribed=M.Mailbox.subscribed(artifact=current_forum), subscribed_to_tool=M.Mailbox.subscribed(), ) @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, subscribe=False, **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, subscribe=subscribe) if 'attachment' in kw: p.add_multiple_attachments(kw['attachment']) 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)), )
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, if_invalid=None), limit=V.Int(if_empty=None, if_invalid=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 SubSchema(Schema): age = validators.Int() name = validators.String(not_empty=True)
class FormSchema(Schema): name = validators.String(not_empty=True) age = validators.Int(min=13, max=99) color = validators.OneOf(['red', 'blue', 'black', 'green']) filter_extra_fields = True allow_extra_fields = True
class InvoiceSchema(BaseSchema): person = ExistingPersonValidator(not_empty=True) due_date = validators.DateConverter(month_style='dd/mm/yyyy') items = ForEach(InvoiceItemValidator()) item_count = validators.Int(min=0) # no max, doesn't hit database
class RootController(BaseController): """ The root controller for the rubrica application. All the other controllers and WSGI applications should be mounted on this controller. For example:: panel = ControlPanelController() another_app = AnotherWSGIApplication() Keep in mind that WSGI applications shouldn't be mounted directly: They must be wrapped around with :class:`tg.controllers.WSGIAppController`. """ secc = SecureController() admin = AdminController(model, DBSession, config_type=TGAdminConfig) error = ErrorController() check = not_anonymous(msg='Solo gli utenti loggati possono accedere') def _before(self, *args, **kw): tmpl_context.project_name = "rubrica" @paginate("data", items_per_page=10) @expose('rubrica.templates.standard_index') def index(self, **kw): """handle index page""" if not request.identity: redirect('/login') data = DBSession.query(Contatto).filter_by(owner=request.identity['user'].user_id) ordering = kw.get('ordercol') if ordering and ordering[0] == '+': data = data.order_by(asc(ordering[1:])) elif ordering and ordering[0] == '-': data = data.order_by(desc(ordering[1:])) return dict(page='index', grid=tabella, data=data) @expose('json') @require(check) def esponi(self): """Espone la rubrica in formato JSON""" data = DBSession.query(Contatto).filter_by(owner=request.identity['user'].user_id).all() return dict(data=data) @expose('json') @require(check) def download(self): """Download della rubrica""" data = DBSession.query(Contatto).filter_by(owner=request.identity['user'].user_id).all() response.content_type = 'applications/json' response.headerlist.append(('Content-Disposition', 'attachment;filename=rubrica.json')) return dict(data=data) @expose('rubrica.templates.submitForm') @require(check) def add(self, **kw): """Aggiunge contatto""" return dict(page='add', form=SubmitForm) @expose() @require(check) @validate(SubmitForm, error_handler=add) def save(self, **kw): """Salva il contatto aggiunto con add""" contatto = Contatto(name=kw['nome'], phone=kw['telefono']) request.identity['user'].contacts.append(contatto) DBSession.add(contatto) redirect('/index') @expose() @require(check) @validate({"item_id": validators.Int(not_empty=True)}) def delete(self, item_id): """Elimina contatto""" if not DBSession.query(exists().where(Contatto.id == item_id)).scalar(): flash(_("Il contatto non esiste")) redirect('/index') if DBSession.query(Contatto).get(item_id) in request.identity['user'].contacts: contatto = DBSession.query(Contatto).get(item_id) DBSession.delete(contatto) redirect('/index') else: flash(_("Non puoi eliminare questo contatto")) redirect('/index') @expose('rubrica.templates.about') def about(self): """Handle the 'about' page.""" return dict(page='about') @expose('rubrica.templates.environ') def environ(self): """This method showcases TG's access to the wsgi environment.""" return dict(page='environ', environment=request.environ) @expose('json') @expose('rubrica.templates.data') def data(self, **kw): """ This method showcases how you can use the same controller for a data page and a display page. """ return dict(page='data', params=kw) @expose('rubrica.templates.index') @require(predicates.has_permission('manage', msg=l_('Only for managers'))) def manage_permission_only(self, **kw): """Illustrate how a page for managers only works.""" return dict(page='managers stuff') @expose('rubrica.templates.index') @require(predicates.is_user('editor', msg=l_('Only for the editor'))) def editor_user_only(self, **kw): """Illustrate how a page exclusive for the editor works.""" return dict(page='editor stuff') @expose('rubrica.templates.login') def login(self, came_from=lurl('/'), failure=None, login=''): """Start the user login.""" if failure is not None: if failure == 'user-not-found': flash(_('User not found'), 'error') elif failure == 'invalid-password': flash(_('Invalid Password'), 'error') login_counter = request.environ.get('repoze.who.logins', 0) if failure is None and login_counter > 0: flash(_('Wrong credentials'), 'warning') return dict(page='login', login_counter=str(login_counter), came_from=came_from, login=login) @expose() def post_login(self, came_from=lurl('/')): """ Redirect the user to the initially requested page on successful authentication or redirect her back to the login page if login failed. """ if not request.identity: login_counter = request.environ.get('repoze.who.logins', 0) + 1 redirect('/login', params=dict(came_from=came_from, __logins=login_counter)) userid = request.identity['repoze.who.userid'] flash(_('Welcome back, %s!') % userid) # Do not use tg.redirect with tg.url as it will add the mountpoint # of the application twice. return HTTPFound(location=came_from) @expose() def post_logout(self, came_from=lurl('/')): """ Redirect the user to the initially requested page on logout and say goodbye as well. """ flash(_('We hope to see you soon!')) return HTTPFound(location=came_from)
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')
def __init__(self, request, lecture): self.request = request formfields = [ FormField('type', label='Typ', type='select', options=[[type, data['name']] for type, data in list( request.config['lecture_types'].items())], value=lecture.type, required=True), FormField('name', label='Name', type='text', size=100, value=lecture.name, required=True, placeholder="Lineare Algebra II"), FormField('term', label='Semester', type='select', options=utils.getTerms(), value=lecture.term), FormField('lsf_id', label='Veranstaltungsnummer', type='text', size=20, value=lecture.lsf_id, placeholder="LSF#123456"), FormField('lecturer', label='Dozent', type='text', size=40, value=lecture.lecturer), FormField('url', label='Homepage', size=100, value=lecture.url, placeholder="https://example.com"), FormField('mode', label='Anmeldemodus', type='select', options=utils.modes, value=lecture.mode), FormField('minimum_preferences', label='Minimum möglicher Termine', size=5, comment='''Bei Präferenzenanmeldung: Studenten müssen mindestens an soviel Terminen können. (Leer: Defaultformel)''', value=lecture.minimum_preferences, validator=validators.Int()), FormField('tutor_rights', label='Tutorenrechte', type='select', options=utils.tutorRights, value=lecture.tutor_rights), FormField( 'password', label='Passwort für Übungsleiter', size=40, comment= 'Bei leerem Passwort keine Anmeldung als Übungsleiter möglich', value=lecture.password), FormField('is_visible', label='Sichtbar', type='radio', options=[[1, 'Ja'], [0, 'Nein']], value=boolToValue(lecture.is_visible)) ] #if request.permissionInfo.has_permission('change_assistant'): #assistants = request.db.query(models.User).filter(models.User.is_assistant==1).order_by(models.User.last_name).all() #formfields.append( #FormField('assistant', #label='Assistent', #type='select', #options=[[a.id, unicode(a)] for a in assistants], #value=lecture.assistant.id, #required=True, #)) ObjectForm.__init__(self, lecture, formfields, request, send='Ändern')
class fields(WidgetsList): """This WidgetsList is just a container.""" title=TextField(validator = validators.NotEmpty()) year = TextField(size=4, validator=validators.Int())
class RootController(BaseController, DispatchIndex, FeedController): 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() 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) @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=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): '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) 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() @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) 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)
class ForumController(DiscussionController): M = ModelConfig W = WidgetConfig def _check_security(self): require_access(self.discussion, 'read') def __init__(self, forum_id): self.ThreadController = ForumThreadController self.PostController = ForumPostController self.moderate = ForumModerationController(self) self.discussion = DM.Forum.query.get(app_config_id=c.app.config._id, shortname=forum_id) if not self.discussion: raise exc.HTTPNotFound() super(ForumController, self).__init__() @expose() def _lookup(self, id=None, *remainder): if id and self.discussion: return ForumController(self.discussion.shortname + '/' + id), remainder else: raise exc.HTTPNotFound() @expose('jinja:forgediscussion:templates/index.html') @validate( dict(page=validators.Int(if_empty=0, if_invalid=0), limit=validators.Int(if_empty=None, if_invalid=None))) def index(self, threads=None, limit=None, page=0, count=0, **kw): if self.discussion.deleted: redirect(self.discussion.url() + 'deleted') limit, page, start = g.handle_paging(limit, page) c.subscribed = M.Mailbox.subscribed(artifact=self.discussion) threads = DM.ForumThread.query.find(dict(discussion_id=self.discussion._id, num_replies={'$gt': 0})) \ .sort([('flags', pymongo.DESCENDING), ('last_post_date', pymongo.DESCENDING)]) c.discussion = self.W.discussion c.discussion_header = self.W.discussion_header c.whole_forum_subscription_form = self.W.subscribe_form return dict(discussion=self.discussion, count=threads.count(), threads=threads.skip(start).limit(int(limit)).all(), limit=limit, page=page) @expose('jinja:forgediscussion:templates/discussionforums/deleted.html') def deleted(self): return dict() @expose('json:') @require_post() @validate(W.subscribe_form) def subscribe_to_forum(self, subscribe=None, unsubscribe=None, shortname=None, **kw): if subscribe: self.discussion.subscribe(type='direct') elif unsubscribe: self.discussion.unsubscribe() return { 'status': 'ok', 'subscribed': M.Mailbox.subscribed(artifact=self.discussion), 'subscribed_to_tool': M.Mailbox.subscribed(), }
class ForumThreadController(ThreadController): W = WidgetConfig @expose('jinja:forgediscussion:templates/discussionforums/thread.html') @validate( dict(page=validators.Int(if_empty=0, if_invalid=0), limit=validators.Int(if_empty=25, if_invalid=25))) def index(self, limit=25, page=0, count=0, **kw): if self.thread.discussion.deleted and not has_access( c.app, 'configure')(): redirect(self.thread.discussion.url() + 'deleted') c.thread_subscription_form = self.W.subscribe_form return super(ForumThreadController, self).index(limit=limit, page=page, count=count, show_moderate=True, **kw) @h.vardec @expose() @require_post() @validate(pass_validator, index) def moderate(self, **kw): require_access(self.thread, 'moderate') if self.thread.discussion.deleted and not has_access( c.app, 'configure')(): redirect(self.thread.discussion.url() + 'deleted') args = self.W.moderate_thread.validate(kw, None) tasks.calc_forum_stats.post(self.thread.discussion.shortname) if args.pop('delete', None): url = self.thread.discussion.url() self.thread.delete() redirect(url) forum = args.pop('discussion') if forum != self.thread.discussion: tasks.calc_forum_stats.post(forum.shortname) self.thread.set_forum(forum) self.thread.flags = args.pop('flags', []) self.thread.subject = args.pop('subject', self.thread.subject) redirect(self.thread.url()) @expose('json:') @require_post() @validate(W.subscribe_form) def subscribe(self, subscribe=None, unsubscribe=None, **kw): if subscribe: self.thread.subscribe() elif unsubscribe: self.thread.unsubscribe() sub_tool = M.Mailbox.subscribed() sub_forum = M.Mailbox.subscribed(artifact=self.discussion) return { 'status': 'ok', 'subscribed': M.Mailbox.subscribed(artifact=self.thread), 'subscribed_to_tool': sub_tool or sub_forum, 'subscribed_to_entire_name': 'forum' if sub_forum else 'discussion tool', }
class PageController(BaseController, FeedController): 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) 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') self.rate_limit() def rate_limit(self): if WM.Page.is_limit_exceeded(c.app.config): msg = 'Page creation rate limit exceeded. ' log.warn(msg + c.app.config.url()) flash(msg + 'Please try again later.', 'error') redirect('..') 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, IndexError): 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, if_invalid=None), page=validators.Int(if_empty=0, if_invalid=0), limit=validators.Int(if_empty=None, if_invalid=None))) def index(self, version=None, page=0, limit=None, **kw): if not self.page: redirect(c.app.url + h.urlquote(self.title) + '/edit') c.confirmation = W.confirmation 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, _ = g.handle_paging(limit, page) limit, pagenum = h.paging_sanitizer(limit, pagenum, 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) subscribed_to_page = M.Mailbox.subscribed(artifact=self.page) c.subscribe_form.tool_subscribed = M.Mailbox.subscribed() return dict( page=page, cur=cur, prev=prev, next=next, page_subscribed=subscribed_to_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.confirmation = W.confirmation c.markdown_editor = W.markdown_editor 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, **kw): require_access(self.page, 'delete') self.page.delete() return dict(location='../' + self.page.title + '/?deleted=True') @without_trailing_slash @expose('json:') @require_post() def undelete(self, **kw): 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, if_invalid=0), limit=validators.Int(if_empty=None, if_invalid=None))) def history(self, page=0, limit=None, **kw): if not self.page: raise exc.HTTPNotFound c.page_list = W.page_list c.page_size = W.page_size c.confirmation = W.confirmation 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) 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`. """ if not self.page: return None return FeedArgs( {'ref_id': self.page.index_id()}, 'Recent changes to %s' % self.page.title, self.page.url()) @without_trailing_slash @expose('json:') @require_post() @validate(dict(version=validators.Int(if_empty=1, if_invalid=1))) def revert(self, version, **kw): 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, 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.spam_checker.check(text, artifact=self.page, user=c.user, content_type='wiki') g.director.create_activity(c.user, activity_verb, self.page, related_nodes=[c.project], tags=['wiki']) 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, **kw): if not self.page: raise exc.HTTPNotFound require_access(self.page, 'edit') self.page.add_multiple_attachments(file_info) if is_ajax(request): return redirect(request.referer) @expose('json:') @require_post() @validate(W.subscribe_form) def subscribe(self, subscribe=None, unsubscribe=None, **kw): if not self.page: raise exc.HTTPNotFound if subscribe: self.page.subscribe(type='direct') elif unsubscribe: self.page.unsubscribe() return { 'status': 'ok', 'subscribed': M.Mailbox.subscribed(artifact=self.page), 'subscribed_to_tool': M.Mailbox.subscribed(), }
class ForumController(DiscussionController): M = ModelConfig W = WidgetConfig def _check_security(self): require_access(self.discussion, 'read') def __init__(self, forum_id): self.ThreadController = ForumThreadController self.PostController = ForumPostController self.moderate = ForumModerationController(self) self.discussion = DM.Forum.query.get(app_config_id=c.app.config._id, shortname=forum_id) if not self.discussion: raise exc.HTTPNotFound() super(ForumController, self).__init__() @expose() def _lookup(self, id=None, *remainder): if id and self.discussion: return ForumController(self.discussion.shortname + '/' + id), remainder else: raise exc.HTTPNotFound() @expose('jinja:forgediscussion:templates/index.html') @validate( dict(page=validators.Int(if_empty=0, if_invalid=0), limit=validators.Int(if_empty=None, if_invalid=None))) def index(self, threads=None, limit=None, page=0, count=0, **kw): if self.discussion.deleted: redirect(self.discussion.url() + 'deleted') limit, page, start = g.handle_paging(limit, page) if not c.user.is_anonymous(): c.subscribed = M.Mailbox.subscribed(artifact=self.discussion) c.tool_subscribed = M.Mailbox.subscribed() threads = DM.ForumThread.query.find(dict(discussion_id=self.discussion._id, num_replies={'$gt': 0})) \ .sort([('flags', pymongo.DESCENDING), ('last_post_date', pymongo.DESCENDING)]) c.discussion = self.W.discussion c.discussion_header = self.W.discussion_header c.whole_forum_subscription_form = self.W.subscribe_form return dict(discussion=self.discussion, count=threads.count(), threads=threads.skip(start).limit(int(limit)).all(), limit=limit, page=page) @expose('jinja:forgediscussion:templates/discussionforums/deleted.html') def deleted(self): return dict() @expose('json:') @require_post() @validate(W.subscribe_form) def subscribe_to_forum(self, subscribe=None, unsubscribe=None, shortname=None, **kw): if subscribe: self.discussion.subscribe(type='direct') # unsubscribe from all individual threads that are part of this forum, so you don't have overlapping subscriptions forumthread_index_prefix = (DM.ForumThread.__module__ + '.' + DM.ForumThread.__name__).replace( '.', '/') + '#' thread_mboxes = M.Mailbox.query.find( dict( user_id=c.user._id, project_id=c.project._id, app_config_id=c.app.config._id, artifact_index_id=re.compile( '^' + re.escape(forumthread_index_prefix)), )).all() # get the ForumThread objects from the subscriptions thread_index_ids = [ mbox.artifact_index_id for mbox in thread_mboxes ] threads_by_id = mapped_artifacts_from_index_ids(thread_index_ids, DM.ForumThread, objectid_id=False) for mbox in thread_mboxes: thread_id = mbox.artifact_index_id.split('#')[1] thread = threads_by_id[thread_id] # only delete if the ForumThread is part of this forum if thread.discussion_id == self.discussion._id: mbox.delete() elif unsubscribe: self.discussion.unsubscribe() return { 'status': 'ok', 'subscribed': M.Mailbox.subscribed(artifact=self.discussion), 'subscribed_to_tool': M.Mailbox.subscribed(), }
class MediaController(BaseController): allow_only = has_permission('edit') @expose_xhr('admin/media/index.html', 'admin/media/index-table.html') @paginate('media', items_per_page=15) @observable(events.Admin.MediaController.index) def index(self, page=1, search=None, filter=None, podcast=None, category=None, tag=None, **kwargs): """List media with pagination and filtering. :param page: Page number, defaults to 1. :type page: int :param search: Optional search term to filter by :type search: unicode or None :param podcast_filter: Optional podcast to filter by :type podcast_filter: int or None :rtype: dict :returns: media The list of :class:`~mediacore.model.media.Media` instances for this page. search The given search term, if any search_form The :class:`~mediacore.forms.admin.SearchForm` instance podcast The podcast object for rendering if filtering by podcast. """ media = Media.query.options(orm.undefer('comment_count_published')) if search: media = media.admin_search(search) else: media = media.order_by_status()\ .order_by(Media.publish_on.desc(), Media.modified_on.desc()) if not filter: pass elif filter == 'unreviewed': media = media.reviewed(False) elif filter == 'unencoded': media = media.reviewed().encoded(False) elif filter == 'drafts': media = media.drafts() elif filter == 'published': media = media.published() if category: category = fetch_row(Category, slug=category) media = media.filter(Media.categories.contains(category)) if tag: tag = fetch_row(Tag, slug=tag) media = media.filter(Media.tags.contains(tag)) if podcast: podcast = fetch_row(Podcast, slug=podcast) media = media.filter(Media.podcast == podcast) return dict( media = media, search = search, search_form = search_form, media_filter = filter, category = category, tag = tag, podcast = podcast, ) def json_error(self, *args, **kwargs): validation_exception = tmpl_context._current_obj().validation_exception return dict(success=False, message=validation_exception.msg) @expose('admin/media/edit.html') @validate(validators={'podcast': validators.Int()}) @autocommit @observable(events.Admin.MediaController.edit) def edit(self, id, **kwargs): """Display the media forms for editing or adding. This page serves as the error_handler for every kind of edit action, if anything goes wrong with them they'll be redirected here. :param id: Media ID :type id: ``int`` or ``"new"`` :param \*\*kwargs: Extra args populate the form for ``"new"`` media :returns: media :class:`~mediacore.model.media.Media` instance media_form The :class:`~mediacore.forms.admin.media.MediaForm` instance media_action ``str`` form submit url media_values ``dict`` form values file_add_form The :class:`~mediacore.forms.admin.media.AddFileForm` instance file_add_action ``str`` form submit url file_edit_form The :class:`~mediacore.forms.admin.media.EditFileForm` instance file_edit_action ``str`` form submit url thumb_form The :class:`~mediacore.forms.admin.ThumbForm` instance thumb_action ``str`` form submit url update_status_form The :class:`~mediacore.forms.admin.media.UpdateStatusForm` instance update_status_action ``str`` form submit url """ media = fetch_row(Media, id) if tmpl_context.action == 'save' or id == 'new': # Use the values from error_handler or GET for new podcast media media_values = kwargs user = request.perm.user media_values.setdefault('author_name', user.display_name) media_values.setdefault('author_email', user.email_address) else: # Pull the defaults from the media item media_values = dict( podcast = media.podcast_id, slug = media.slug, title = media.title, author_name = media.author.name, author_email = media.author.email, description = media.description, tags = ', '.join((tag.name for tag in media.tags)), categories = [category.id for category in media.categories], notes = media.notes, ) # Re-verify the state of our Media object in case the data is nonsensical if id != 'new': media.update_status() return dict( media = media, media_form = media_form, media_action = url_for(action='save'), media_values = media_values, category_tree = Category.query.order_by(Category.name).populated_tree(), file_add_form = add_file_form, file_add_action = url_for(action='add_file'), file_edit_form = edit_file_form, file_edit_action = url_for(action='edit_file'), thumb_form = thumb_form, thumb_action = url_for(action='save_thumb'), update_status_form = update_status_form, update_status_action = url_for(action='update_status'), ) @expose_xhr(request_method='POST') @validate_xhr(media_form, error_handler=edit) @autocommit @observable(events.Admin.MediaController.save) def save(self, id, slug, title, author_name, author_email, description, notes, podcast, tags, categories, delete=None, **kwargs): """Save changes or create a new :class:`~mediacore.model.media.Media` instance. Form handler the :meth:`edit` action and the :class:`~mediacore.forms.admin.media.MediaForm`. Redirects back to :meth:`edit` after successful editing and :meth:`index` after successful deletion. """ media = fetch_row(Media, id) if delete: self._delete_media(media) DBSession.commit() redirect(action='index', id=None) if not slug: slug = title elif slug.startswith('_stub_'): slug = slug[len('_stub_'):] if slug != media.slug: media.slug = get_available_slug(Media, slug, media) media.title = title media.author = Author(author_name, author_email) media.description = description media.notes = notes media.podcast_id = podcast media.set_tags(tags) media.set_categories(categories) media.update_status() DBSession.add(media) DBSession.flush() if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) if request.is_xhr: status_form_xhtml = unicode(update_status_form.display( action=url_for(action='update_status', id=media.id), media=media)) return dict( media_id = media.id, values = {'slug': slug}, link = url_for(action='edit', id=media.id), status_form = status_form_xhtml, ) else: redirect(action='edit', id=media.id) @expose('json', request_method='POST') @validate(add_file_form, error_handler=json_error) @autocommit @observable(events.Admin.MediaController.add_file) def add_file(self, id, file=None, url=None, **kwargs): """Save action for the :class:`~mediacore.forms.admin.media.AddFileForm`. Creates a new :class:`~mediacore.model.media.MediaFile` from the uploaded file or the local or remote URL. :param id: Media ID. If ``"new"`` a new Media stub is created. :type id: :class:`int` or ``"new"`` :param file: The uploaded file :type file: :class:`cgi.FieldStorage` or ``None`` :param url: A URL to a recognizable audio or video file :type url: :class:`unicode` or ``None`` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful media_id The :attr:`~mediacore.model.media.Media.id` which is important if new media has just been created. file_id The :attr:`~mediacore.model.media.MediaFile.id` for the newly created file. edit_form The rendered XHTML :class:`~mediacore.forms.admin.media.EditFileForm` for this file. status_form The rendered XHTML :class:`~mediacore.forms.admin.media.UpdateStatusForm` """ if id == 'new': media = Media() user = request.perm.user media.author = Author(user.display_name, user.email_address) # Create a temp stub until we can set it to something meaningful timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') media.title = u'Temporary stub %s' % timestamp media.slug = get_available_slug(Media, '_stub_' + timestamp) media.reviewed = True DBSession.add(media) DBSession.flush() else: media = fetch_row(Media, id) media_file = add_new_media_file(media, file, url) if media.slug.startswith('_stub_'): media.title = media_file.display_name media.slug = get_available_slug(Media, '_stub_' + media.title) # The thumbs may have been created already by add_new_media_file if id == 'new' and not has_thumbs(media): create_default_thumbs_for(media) media.update_status() # Render some widgets so the XHTML can be injected into the page edit_form_xhtml = unicode(edit_file_form.display( action=url_for(action='edit_file', id=media.id), file=media_file)) status_form_xhtml = unicode(update_status_form.display( action=url_for(action='update_status', id=media.id), media=media)) data = dict( success = True, media_id = media.id, file_id = media_file.id, file_type = media_file.type, edit_form = edit_form_xhtml, status_form = status_form_xhtml, title = media.title, slug = media.slug, description = media.description, link = url_for(action='edit', id=media.id), duration = helpers.duration_from_seconds(media.duration), ) return data @expose('json', request_method='POST') @autocommit @observable(events.Admin.MediaController.edit_file) def edit_file(self, id, file_id, file_type=None, duration=None, delete=None, bitrate=None, width_height=None, **kwargs): """Save action for the :class:`~mediacore.forms.admin.media.EditFileForm`. Changes or deletes a :class:`~mediacore.model.media.MediaFile`. XXX: We do NOT use the @validate decorator due to complications with partial validation. The JS sends only the value it wishes to change, so we only want to validate that one value. FancyValidator.if_missing seems to eat empty values and assign them None, but there's an important difference to us between None (no value from the user) and an empty value (the user is clearing the value of a field). :param id: Media ID :type id: :class:`int` :rtype: JSON dict :returns: success bool message Error message, if unsuccessful status_form Rendered XHTML for the status form, updated to reflect the changes made. """ media = fetch_row(Media, id) data = dict(success=False) file_id = int(file_id) # Just in case validation failed somewhere. for file in media.files: if file.id == file_id: break else: file = None fields = edit_file_form.c try: if file is None: data['message'] = _('File "%s" does not exist.') % file_id elif file_type: file.type = fields.file_type.validate(file_type) data['success'] = True elif duration is not None: media.duration = fields.duration.validate(duration) data['success'] = True data['duration'] = helpers.duration_from_seconds(media.duration) elif width_height is not None: width_height = fields.width_height.validate(width_height) file.width, file.height = width_height or (0, 0) data['success'] = True elif bitrate is not None: file.bitrate = fields.bitrate.validate(bitrate) data['success'] = True elif delete: file.storage.delete(file.unique_id) DBSession.delete(file) DBSession.flush() # media.files must be updated to reflect the file deletion above DBSession.refresh(media) data['success'] = True else: data['message'] = _('No action to perform.') except Invalid, e: data['success'] = False data['message'] = unicode(e) if data['success']: data['file_type'] = file.type media.update_status() DBSession.flush() # Return the rendered widget for injection status_form_xhtml = unicode(update_status_form.display( action=url_for(action='update_status'), media=media)) data['status_form'] = status_form_xhtml return data
class SiteAdminController(object): def __init__(self): self.task_manager = TaskManagerController() c.site_admin_sidebar_menu = self.sidebar_menu() self.user = AdminUserDetailsController() self.delete_projects = DeleteProjectsController() def _check_security(self): with h.push_context(config.get('site_admin_project', 'allura'), neighborhood=config.get('site_admin_project_nbhd', 'Projects')): require_access(c.project, 'admin') @expose() def _lookup(self, name, *remainder): for ep_name in sorted(g.entry_points['site_admin'].keys()): admin_extension = g.entry_points['site_admin'][ep_name] controller = admin_extension().controllers.get(name) if controller: return controller(), remainder raise HTTPNotFound, name def sidebar_menu(self): base_url = '/nf/admin/' links = [ SitemapEntry('Home', base_url, ui_icon=g.icons['admin']), SitemapEntry('Add Subscribers', base_url + 'add_subscribers', ui_icon=g.icons['admin']), SitemapEntry('New Projects', base_url + 'new_projects', ui_icon=g.icons['admin']), SitemapEntry('Reclone Repo', base_url + 'reclone_repo', ui_icon=g.icons['admin']), SitemapEntry('Task Manager', base_url + 'task_manager?state=busy', ui_icon=g.icons['stats']), SitemapEntry('Search Projects', base_url + 'search_projects', ui_icon=g.icons['search']), SitemapEntry('Delete Projects', base_url + 'delete_projects', ui_icon=g.icons['delete']), SitemapEntry('Search Users', base_url + 'search_users', ui_icon=g.icons['search']), ] for ep_name in sorted(g.entry_points['site_admin']): g.entry_points['site_admin'][ep_name]().update_sidebar_menu(links) return links @expose('jinja:allura:templates/site_admin_index.html') @with_trailing_slash def index(self): return {} def subscribe_artifact(self, url, user): artifact_url = urlparse(url).path[1:-1].split("/") neighborhood = M.Neighborhood.query.find({ "url_prefix": "/" + artifact_url[0] + "/" }).first() if artifact_url[0] == "u": project = M.Project.query.find({ "shortname": artifact_url[0] + "/" + artifact_url[1], "neighborhood_id": neighborhood._id }).first() else: project = M.Project.query.find({ "shortname": artifact_url[1], "neighborhood_id": neighborhood._id }).first() appconf = M.AppConfig.query.find({ "options.mount_point": artifact_url[2], "project_id": project._id }).first() if appconf.url() == urlparse(url).path: M.Mailbox.subscribe(user_id=user._id, app_config_id=appconf._id, project_id=project._id) return True tool_packages = h.get_tool_packages(appconf.tool_name) classes = set() for depth, cls in dfs(M.Artifact, build_model_inheritance_graph()): for pkg in tool_packages: if cls.__module__.startswith(pkg + '.'): classes.add(cls) for cls in classes: for artifact in cls.query.find({"app_config_id": appconf._id}): if artifact.url() == urlparse(url).path: M.Mailbox.subscribe(user_id=user._id, app_config_id=appconf._id, project_id=project._id, artifact=artifact) return True return False @expose('jinja:allura:templates/site_admin_add_subscribers.html') @without_trailing_slash def add_subscribers(self, **data): if request.method == 'POST': url = data['artifact_url'] user = M.User.by_username(data['for_user']) if not user or user == M.User.anonymous(): flash('Invalid login', 'error') return data try: ok = self.subscribe_artifact(url, user) except: log.warn("Can't subscribe to artifact", exc_info=True) ok = False if ok: flash('User successfully subscribed to the artifact') return {} else: flash('Artifact not found', 'error') return data @expose('jinja:allura:templates/site_admin_new_projects.html') @without_trailing_slash def new_projects(self, **kwargs): start_dt = kwargs.pop('start-dt', '') end_dt = kwargs.pop('end-dt', '') try: start_dt = datetime.strptime(start_dt, '%Y/%m/%d %H:%M:%S') except ValueError: start_dt = datetime.utcnow() + timedelta(days=1) try: end_dt = datetime.strptime(end_dt, '%Y/%m/%d %H:%M:%S') except ValueError: end_dt = start_dt - timedelta(days=3) if not end_dt else end_dt start = bson.ObjectId.from_datetime(start_dt) end = bson.ObjectId.from_datetime(end_dt) nb = M.Neighborhood.query.get(name='Users') projects = (M.Project.query.find({ 'neighborhood_id': { '$ne': nb._id }, 'deleted': False, '_id': { '$lt': start, '$gt': end }, }).sort('_id', -1)).all() # pre-populate roles cache, so we won't query mongo for roles for every project # when getting admins with p.admins() in a template Credentials.get().load_project_roles(*[p._id for p in projects]) step = start_dt - end_dt params = request.params.copy() params['start-dt'] = (start_dt + step).strftime('%Y/%m/%d %H:%M:%S') params['end-dt'] = (end_dt + step).strftime('%Y/%m/%d %H:%M:%S') newer_url = tg.url(params=params).lstrip('/') params['start-dt'] = (start_dt - step).strftime('%Y/%m/%d %H:%M:%S') params['end-dt'] = (end_dt - step).strftime('%Y/%m/%d %H:%M:%S') older_url = tg.url(params=params).lstrip('/') return { 'projects': projects, 'newer_url': newer_url, 'older_url': older_url, 'window_start': start_dt, 'window_end': end_dt, } @expose('jinja:allura:templates/site_admin_reclone_repo.html') @without_trailing_slash @validate( dict(prefix=validators.NotEmpty(), shortname=validators.NotEmpty(), mount_point=validators.NotEmpty())) def reclone_repo(self, prefix=None, shortname=None, mount_point=None, **data): if request.method == 'POST': if c.form_errors: error_msg = 'Error: ' for msg in list(c.form_errors): names = { 'prefix': 'Neighborhood prefix', 'shortname': 'Project shortname', 'mount_point': 'Repository mount point' } error_msg += '%s: %s ' % (names[msg], c.form_errors[msg]) flash(error_msg, 'error') return dict(prefix=prefix, shortname=shortname, mount_point=mount_point) nbhd = M.Neighborhood.query.get(url_prefix='/%s/' % prefix) if not nbhd: flash('Neighborhood with prefix %s not found' % prefix, 'error') return dict(prefix=prefix, shortname=shortname, mount_point=mount_point) c.project = M.Project.query.get(shortname=shortname, neighborhood_id=nbhd._id) if not c.project: flash( 'Project with shortname %s not found in neighborhood %s' % (shortname, nbhd.name), 'error') return dict(prefix=prefix, shortname=shortname, mount_point=mount_point) c.app = c.project.app_instance(mount_point) if not c.app: flash( 'Mount point %s not found on project %s' % (mount_point, c.project.shortname), 'error') return dict(prefix=prefix, shortname=shortname, mount_point=mount_point) source_url = c.app.config.options.get('init_from_url') source_path = c.app.config.options.get('init_from_path') if not (source_url or source_path): flash('%s does not appear to be a cloned repo' % c.app, 'error') return dict(prefix=prefix, shortname=shortname, mount_point=mount_point) allura.tasks.repo_tasks.reclone_repo.post(prefix=prefix, shortname=shortname, mount_point=mount_point) flash('Repository is being recloned') else: prefix = 'p' shortname = '' mount_point = '' return dict(prefix=prefix, shortname=shortname, mount_point=mount_point) def _search(self, model, fields, add_fields, q=None, f=None, page=0, limit=None, **kw): all_fields = fields + [(fld, fld) for fld in add_fields] c.search_form = W.admin_search_form(all_fields) c.page_list = W.page_list c.page_size = W.page_size count = 0 objects = [] limit, page, start = g.handle_paging(limit, page, default=25) if q: match = search.site_admin_search(model, q, f, rows=limit, start=start) if match: count = match.hits objects = match.docs ids = [obj['id'].split('#')[1] for obj in objects] ids = [bson.ObjectId(_id) for _id in ids if _id != 'None'] mongo_objects = {} for obj in model.query.find({'_id': {'$in': ids}}): mongo_objects[str(obj._id)] = obj for i in range(len(objects)): obj = objects[i] _id = obj['id'].split('#')[1] obj['object'] = mongo_objects.get(_id) # Some objects can be deleted, but still have index in solr, should skip those objects = [o for o in objects if o.get('object')] def convert_fields(obj): # throw the type away (e.g. '_s' from 'url_s') result = {} for k, val in obj.iteritems(): name = k.rsplit('_', 1) if len(name) == 2: name = name[0] else: name = k result[name] = val return result return { 'q': q, 'f': f, 'objects': map(convert_fields, objects), 'count': count, 'page': page, 'limit': limit, 'fields': fields, 'additional_fields': add_fields, 'type_s': model.type_s, } @without_trailing_slash @expose('jinja:allura:templates/site_admin_search.html') @validate(validators=dict(q=validators.UnicodeString(if_empty=None), limit=validators.Int(if_invalid=None), page=validators.Int(if_empty=0, if_invalid=0))) def search_projects(self, q=None, f=None, page=0, limit=None, **kw): fields = [('shortname', 'shortname'), ('name', 'full name')] add_fields = aslist( tg.config.get('search.project.additional_search_fields'), ',') r = self._search(M.Project, fields, add_fields, q, f, page, limit, **kw) r['search_results_template'] = 'allura:templates/site_admin_search_projects_results.html' r['additional_display_fields'] = \ aslist(tg.config.get('search.project.additional_display_fields'), ',') r['provider'] = ProjectRegistrationProvider.get() return r @without_trailing_slash @expose('jinja:allura:templates/site_admin_search.html') @validate(validators=dict(q=validators.UnicodeString(if_empty=None), limit=validators.Int(if_invalid=None), page=validators.Int(if_empty=0, if_invalid=0))) def search_users(self, q=None, f=None, page=0, limit=None, **kw): fields = [('username', 'username'), ('display_name', 'display name')] add_fields = aslist( tg.config.get('search.user.additional_search_fields'), ',') r = self._search(M.User, fields, add_fields, q, f, page, limit, **kw) r['objects'] = [ dict(u, status=h.get_user_status(u['object'])) for u in r['objects'] ] r['search_results_template'] = 'allura:templates/site_admin_search_users_results.html' r['additional_display_fields'] = \ aslist(tg.config.get('search.user.additional_display_fields'), ',') r['provider'] = AuthenticationProvider.get(request) return r