def fields(self): username = ew.TextField( name='username', label='Desired Username', validator=plugin.AuthenticationProvider.get( None).username_validator(), ) fields = [ ew.TextField(name='display_name', label='Displayed Name', validator=V.UnicodeString(not_empty=True)), username, ] if asbool(config.get('auth.require_email_addr', False)): fields.append( ew.TextField(name='email', label='Your e-mail', validator=fev.Email(not_empty=True))) fields += [ ew.PasswordField( name='pw', label='New Password', attrs=dict( minlength=asint(tg.config.get('auth.min_password_len', 6)), maxlength=asint(tg.config.get('auth.max_password_len', 30))), validator=V.UnicodeString( not_empty=True, min=asint(tg.config.get('auth.min_password_len', 6)), max=asint(tg.config.get('auth.max_password_len', 30)))), ew.PasswordField(name='pw2', label='New Password (again)', validator=V.UnicodeString(not_empty=True)), ] return fields
def fields(self): return [ ew.PasswordField( name='oldpw', label='Old Password', validator=V.UnicodeString(not_empty=True), attrs=dict( required=True, autocomplete='current-password', ), ), ew.PasswordField( name='pw', label='New Password', attrs=dict( minlength=asint(tg.config.get('auth.min_password_len', 6)), maxlength=asint(tg.config.get('auth.max_password_len', 30)), required=True, autocomplete='new-password', ), validator=V.UnicodeString( not_empty=True, min=asint(tg.config.get('auth.min_password_len', 6)), max=asint(tg.config.get('auth.max_password_len', 30)))), ew.PasswordField( name='pw2', label='New Password (again)', validator=V.UnicodeString(not_empty=True), attrs=dict( required=True, autocomplete='new-password', ), ), ew.HiddenField(name='return_to'), ]
class fields(ew_core.NameList): uppercategory_id = ew.HiddenField(attrs={'value': ''}, show_errors=False) categoryname = ew.TextField(label="Category name", attrs={}, validator=V.UnicodeString(not_empty=True)) shortname = ew.TextField( label="Short name", validator=V.UnicodeString(), attrs={'placeholder': 'optional; unique identifier'})
class fields(ew_core.NameList): startdate = ew.TextField( label='Start date', validator=formencode.All( V.DateValidator(), V.UnicodeString(not_empty=True))) enddate = ew.TextField( label='End date', validator=formencode.All( V.DateValidator(), V.UnicodeString(not_empty=True)))
def fields(self): provider = plugin.ProjectRegistrationProvider.get() tools_options = [] for ep, tool in six.iteritems(g.entry_points["tool"]): if tool.status == 'production' and tool._installable(tool_name=ep, nbhd=c.project.neighborhood, project_tools=[]): tools_options.append(ew.Option(label=tool.tool_label, html_value=ep)) return ew_core.NameList([ ew.HiddenField(name='project_description', label='Public Description'), ew.HiddenField(name='neighborhood', label='Neighborhood'), ew.Checkbox(name='private_project', label="", attrs={'class': 'unlabeled'}), ew.InputField(name='project_name', label='Project Name', field_type='text', validator=formencode.All( V.UnicodeString(not_empty=True, max=40), V.MaxBytesValidator(max=40)), ), ew.InputField(name='project_unixname', label='Short Name', field_type='text', attrs={ 'title': 'Create a URL name that matches your project name as closely as possible to improve search indexing and maximize visibility.', 'class': 'tooltip' }, validator=provider.shortname_validator), ew.CheckboxSet(name='tools', options=tools_options), ])
def fields(self): return ew_core.NameList([ ew.TextField(name='title', validator=v.UnicodeString( not_empty=True, messages={'empty': "You must provide a Title"}), attrs=dict(placeholder='Enter your title here', title='Enter your title here', style='width: 425px')), ffw.MarkdownEdit(name='text', show_label=False, attrs=dict(placeholder='Enter your content here', title='Enter your content here')), ew.SingleSelectField(name='state', options=[ ew.Option(py_value='draft', label='Draft'), ew.Option(py_value='published', label='Published') ]), ffw.LabelEdit(name='labels', placeholder='Add labels here', title='Add labels here'), ew.InputField( name='attachment', label='Attachment', field_type='file', attrs={'multiple': 'True'}, validator=fev.FieldStorageUploadConverter(if_missing=None)), ])
def fields(self): socialnetworks = aslist(tg.config.get('socialnetworks', ['Facebook', 'Linkedin', 'Twitter', 'Google+']), ',') return [ ew.SingleSelectField( name='socialnetwork', label='Social network', validator=V.UnicodeString(not_empty=True), options=[ew.Option(py_value=name, label=name) for name in socialnetworks]), ew.TextField( name='accounturl', label='Account url', validator=V.UnicodeString(not_empty=True)) ]
class fields(ew_core.NameList): subject = ew.TextField(validator=v.UnicodeString( not_empty=True, messages={'empty': "You must provide a Subject"}), attrs=dict( placeholder='Enter your subject here', title='Enter your subject here', style='width: 425px'), label='Subject') message = ew.TextArea(validator=v.UnicodeString( not_empty=True, messages={'empty': "You must provide a Message"}), attrs=dict(placeholder='Enter your message here', title='Enter your message here', style='width: 425px; height:200px'), label='Message') cc = ew.Checkbox(label='Send me a copy')
class fields(ew_core.NameList): selected_skill = ew.HiddenField( attrs={'value': ''}, show_errors=False, validator=V.UnicodeString(not_empty=True)) level = ew.SingleSelectField( label="Level of knowledge", options=[ ew.Option(py_value="low", label="Low level"), ew.Option(py_value="medium", label="Medium level"), ew.Option(py_value="high", label="Advanced level")], validator=formencode.All( V.OneOfValidator(['low', 'medium', 'high']), V.UnicodeString(not_empty=True))) comment = ew.TextArea( label="Additional comments", validator=V.UnicodeString(not_empty=False), attrs={'rows': 5, 'cols': 30})
class fields(ew_core.NameList): weekday = ew.SingleSelectField( label='Weekday', options=[ew.Option(py_value=wd, label=wd) for wd in weekdays], validator=formencode.All( V.OneOfValidator(weekdays), V.UnicodeString(not_empty=True))) starttime = ew.TextField( label='Start time', validator=formencode.All( V.TimeValidator(), V.UnicodeString(not_empty=True))) endtime = ew.TextField( label='End time', validator=formencode.All( V.TimeValidator(), V.UnicodeString(not_empty=True)))
def display(self, **kw): categories = kw.get('categories') self.fields['selected_category'].options = [ ew.Option(py_value=el.trove_cat_id, label=el.fullname) for el in categories ] self.fields['selected_category'].validator = formencode.All( V.OneOfValidator(categories), V.UnicodeString(not_empty=True)) return super(ForgeForm, self).display(**kw)
class ProjectImportForm(schema.Schema): def __init__(self, source): super(ProjectImportForm, self).__init__() provider = ProjectRegistrationProvider.get() self.add_field('tools', ToolsValidator(source)) self.add_field('project_shortname', provider.shortname_validator) self.allow_extra_fields = True neighborhood = fev.NotEmpty() project_name = v.UnicodeString(not_empty=True, max=40)
def fields(self): # Since @property is readonly we can't modify field values in display() method # Returns fields modified by display() if self._fields: return self._fields list_of_fields = [ ew.SingleSelectField( name='sex', label='Gender', options=[ew.Option(py_value=v, label=v, selected=False) for v in ['Male', 'Female', 'Unknown', 'Other']], validator=formencode.All( V.OneOfValidator(['Male', 'Female', 'Unknown', 'Other']), V.UnicodeString(not_empty=True))), ew.SingleSelectField( name='country', label='Country of residence', validator=V.MapValidator(country_names, not_empty=False), options=[ew.Option(py_value=" ", label=" -- Unknown -- ", selected=False)] + [ew.Option(py_value=c, label=n, selected=False) for c, n in sorted(list(country_names.items()), key=lambda k_v: k_v[1])], attrs={'onchange': 'selectTimezone(this.value)'}), ew.TextField( name='city', label='City of residence', attrs=dict(value=None), validator=V.UnicodeString(not_empty=False)), ew.SingleSelectField( name='timezone', label='Timezone', attrs={'id': 'tz'}, validator=V.OneOfValidator(common_timezones, not_empty=False), options=[ew.Option(py_value=" ", label=" -- Unknown -- ")] + [ew.Option(py_value=n, label=n) for n in sorted(common_timezones)]) ] if asbool(tg.config.get('auth.allow_birth_date', True)): list_of_fields[1:1] = self.birth_date_fields return list_of_fields
def fields(self): fields = [ ew.HiddenField(name='app_id', label='App'), ew.TextField(name='name', label='Name', validator=v.UnicodeString()), ew.TextField(name='shortname', label='Short Name', validator=All( fev.Regex(r"^[^\s\/\.]*$", not_empty=True, messages={ 'invalid': 'Shortname cannot contain space . or /', 'empty': 'You must create a short name for the forum.'}), UniqueForumShortnameValidator())), ew.TextField(name='parent', label='Parent Forum'), ew.TextField(name='description', label='Description', validator=v.UnicodeString()), ew.TextField(name='monitoring_email', label='Monitoring Email', validator=fev.Email()), ew.Checkbox(name="members_only", label="Developer Only"), ew.Checkbox(name="anon_posts", label="Allow Anonymous Posts") ] return fields
def fields(self): return [ ew.SingleSelectField(name='grant', label='Award', options=self.award_options()), ffw.NeighborhoodProjectSelect(self._project_select_url, name='recipient'), ew.TextField(name='url', label='Award URL', validator=fev.URL()), ew.TextArea(name='comment', label='Comment', validator=V.UnicodeString(not_empty=False), attrs={ 'rows': 5, 'cols': 30 }), ]
def fields(self): fields = ew_core.NameList() fields.append(ffw.MarkdownEdit(name='text')) fields.append(ew.HiddenField(name='forum', if_missing=None)) fields.append(ew.Checkbox(name='subscribe', label='Subscribe', if_missing=False)) if ew_core.widget_context.widget: # we are being displayed if ew_core.widget_context.render_context.get('show_subject', self.show_subject): fields.append( ew.TextField(name='subject', attrs=dict(style="width:97%"))) else: # We are being validated validator = v.UnicodeString(not_empty=True, if_missing='') fields.append(ew.TextField(name='subject', validator=validator)) fields.append(NullValidator(name=self.att_name)) return fields
class MarkdownEdit(ew.TextArea): template = 'jinja:allura:templates/widgets/markdown_edit.html' validator = v.UnicodeString() defaults = dict(ew.TextArea.defaults, name=None, value=None, show_label=True) def from_python(self, value, state=None): return value def resources(self): for r in super(MarkdownEdit, self).resources(): yield r yield ew.JSLink('js/jquery.lightbox_me.js') yield ew.CSSLink('css/easymde.min.css', compress=False) yield ew.CSSLink('css/markitup_sf.css') yield ew.CSSLink('css/show-hint.css') yield ew.JSLink('js/easymde.min.js') yield ew.JSLink('js/sf_markitup.js') yield ew.JSLink('js/show-hint.js') yield ew.JSLink('js/usermentions-helper.js') yield onready('getProjectUsers(\'%s/users\')' % c.project.url())
class RootController(BaseController): @expose() def index(self, **kw): now = datetime.utcnow() redirect(c.app.url + now.strftime('%Y/%m/%d/')) @with_trailing_slash @expose('jinja:forgechat:templates/chat/search.html') @validate( dict(q=v.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 = SearchResults() c.help_modal = SearchHelp(comments=False, history=False, fields={ 'sender_t': 'username', 'text': '"Message text"', }) search_params = kw search_params.update({ 'q': q or '', 'project': project, 'limit': limit, 'page': page, 'allowed_types': ['Chat Message'], }) d = search_app(**search_params) d['search_comments_disable'] = True d['search_history_disable'] = True return d @expose() def _lookup(self, y, m, d, *rest): y, m, d = int(y), int(m), int(d) return DayController(date(y, m, d)), rest
class GitHubRepoImportForm(ToolImportForm): gh_user_name = v.UnicodeString(not_empty=True) gh_project_name = GitHubProjectNameValidator()
class DeleteProjectsController(object): delete_form_validators = dict( projects=v.UnicodeString(if_empty=None), reason=v.UnicodeString(if_empty=None), disable_users=validators.StringBool(if_empty=False)) def remove_comments(self, lines): return [l.split('#', 1)[0] for l in lines] def parse_projects(self, projects): """Takes projects from user input and returns a list of tuples (input, project, error)""" provider = ProjectRegistrationProvider.get() projects = projects.splitlines() projects = self.remove_comments(projects) parsed_projects = [] for input in projects: if input.strip(): p, error = provider.project_from_url(input.strip()) parsed_projects.append((input, p, error)) return parsed_projects def format_parsed_projects(self, projects): template = '{} # {}' lines = [] for input, p, error in projects: comment = 'OK: {}'.format(p.url()) if p else error lines.append(template.format(input, comment)) return '\n'.join(lines) @with_trailing_slash @expose('jinja:allura:templates/site_admin_delete_projects.html') @validate(validators=delete_form_validators) def index(self, projects=None, reason=None, disable_users=False, **kw): return { 'projects': projects, 'reason': reason, 'disable_users': disable_users } @expose('jinja:allura:templates/site_admin_delete_projects_confirm.html') @require_post() @without_trailing_slash @validate(validators=delete_form_validators) def confirm(self, projects=None, reason=None, disable_users=False, **kw): if not projects: flash('No projects specified', 'warning') redirect('.') parsed_projects = self.parse_projects(projects) projects = self.format_parsed_projects(parsed_projects) edit_link = './?projects={}&reason={}&disable_users={}'.format( h.urlquoteplus(projects), h.urlquoteplus(reason or ''), h.urlquoteplus(disable_users)) return { 'projects': projects, 'parsed_projects': parsed_projects, 'edit_link': edit_link, 'reason': reason, 'disable_users': disable_users } @expose() @require_post() @without_trailing_slash @validate(validators=delete_form_validators) def really_delete(self, projects=None, reason=None, disable_users=False, **kw): if not projects: flash('No projects specified', 'warning') redirect('.') projects = self.parse_projects(projects) task_params = [p.url().strip('/') for (_, p, _) in projects if p] if not task_params: flash('Unable to parse at least one project from your input', 'warning') redirect('.') task_params = ' '.join(task_params) if reason: task_params = '-r {} {}'.format(pipes.quote(reason), task_params) if disable_users: task_params = '--disable-users {}'.format(task_params) DeleteProjects.post(task_params) flash('Delete scheduled', 'ok') redirect('.')
class SiteAdminController(object): def __init__(self): self.task_manager = TaskManagerController() self.user = AdminUserDetailsController() self.delete_projects = DeleteProjectsController() self.site_notifications = SiteNotificationController() def _check_security(self): require_site_admin(c.user) c.site_admin_sidebar_menu = self.sidebar_menu() @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']), SitemapEntry('Site Notifications', base_url + 'site_notifications', ui_icon=g.icons['admin']), ] 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, **kw): 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 Exception: 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: if f in ('username', 'shortname'): # these are always lowercase, so search by lowercase q = q.lower() match = search.site_admin_search(model, q, f, rows=limit, start=start) if match: count = match.hits objects = match.docs ids = [obj['id'] for obj in objects] mongo_objects = search.mapped_artifacts_from_index_ids( ids, model) 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 six.iteritems(obj): 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': list(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=v.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=v.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
class fields(ew_core.NameList): name = ew.InputField(field_type='text', label='Name', validator=formencode.All( V.UnicodeString(not_empty=True, max=40), V.MaxBytesValidator(max=40), ), attrs={'maxlength': 40, 'title': "This is the publicly viewable name of the project, and will appear on project listings. It should be what you want to see as the project title in search listing.", 'class': 'tooltip', }) summary = ew.InputField(field_type="text", label='Short Summary', validator=formencode.All( V.UnicodeString(max=70), V.MaxBytesValidator(max=70)), attrs={'maxlength': 70, 'title': 'Briefly state what your project is and what it does without repeating the project name. This summary appears in Google search results beneath the project name.', 'class': 'tooltip', }) short_description = ew.TextArea(label='Full Description', validator=formencode.All( V.UnicodeString(max=1000), V.MaxBytesValidator(max=1000), ), attrs={ 'title': 'Describe the full functionality of your project using related keywords. The first sentence has the most impact on search. Provide unique content that calls out keywords and describes the merits of your project.', 'class': 'tooltip' }) # Apparently, child field must be CompoundField with custom template # for SortableRepeatedField to work properly, that's why FeaturesField # is not just ew.TextField features = ffw.SortableRepeatedField( label='Features', show_msg=False, show_button=False, append_to='bottom', extra_field_on_focus_name='feature', field=FeaturesField()) icon = ew.FileField(label='Icon', attrs={'accept': 'image/*'}, validator=V.IconValidator()) external_homepage = ew.InputField(field_type="text", label='Homepage', validator=fev.URL(add_http=True)) video_url = ew.InputField(field_type="text", label="Video (YouTube)", attrs={'title': 'Paste in a Youtube URL', 'class': 'tooltip'}, validator=V.YouTubeConverter()) support_page = ew.InputField(field_type="text", label='Support Page') support_page_url = ew.InputField( field_type="text", label='Support Page URL', validator=fev.URL(add_http=True, if_empty='')) removal = ew.InputField(field_type="text", label='Removal') moved_to_url = ew.InputField( field_type="text", label='Moved Project to URL', validator=fev.URL(add_http=True, if_empty='')) delete = ew.InputField(field_type="hidden", label='Delete') delete_icon = ew.InputField(field_type="hidden", label='Delete Icon') undelete = ew.InputField(field_type="hidden", label='Undelete') tracking_id = ew.InputField( field_type="text", label="Google Analytics ID", attrs=(dict(placeholder='UA-123456-0', pattern='UA-[0-9]+-[0-9]+'))) twitter_handle = ew.InputField( field_type="text", label='Twitter Handle') facebook_page = ew.InputField(field_type="text", label='Facebook page', validator=fev.URL(add_http=True))
class GitHubWikiImportForm(ToolImportForm): gh_project_name = GitHubProjectNameValidator() gh_user_name = v.UnicodeString(not_empty=True) tool_option = v.UnicodeString(if_missing='')
class ToolImportForm(schema.Schema): def __init__(self, tool_class): super(ToolImportForm, self).__init__() self.add_field('mount_point', v.MountPointValidator(tool_class)) mount_label = v.UnicodeString()
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=v.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()
class WebhookCreateForm(schema.Schema): url = fev.URL(not_empty=True) secret = v.UnicodeString()
class fields(ew_core.NameList): newnumber = ew.TextField(label='New telephone number', attrs={'value': ''}, validator=V.UnicodeString(not_empty=True))
class fields(ew_core.NameList): skypeaccount = ew.TextField(label='Skype account', attrs={'value': ''}, validator=V.UnicodeString(not_empty=False))
class RootController(BaseController, FeedController): def __init__(self): self._discuss = AppDiscussionController() def _check_security(self): require_access(c.app, 'read') @expose('jinja:forgeblog:templates/blog/index.html') @with_trailing_slash @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): query_filter = dict(app_config_id=c.app.config._id) if not has_access(c.app, 'write')(): query_filter['state'] = 'published' q = BM.BlogPost.query.find(query_filter) post_count = q.count() limit, page, _ = g.handle_paging(limit, page) limit, page = h.paging_sanitizer(limit, page, post_count) posts = q.sort('timestamp', pymongo.DESCENDING) \ .skip(page * limit).limit(limit) c.form = W.preview_post_form c.pager = W.pager return dict(posts=posts, page=page, limit=limit, count=post_count) @with_trailing_slash @expose('jinja:forgeblog:templates/blog/search.html') @validate( dict(q=v.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): 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': ['Blog Post', 'Blog Post Snapshot'], 'fq': ['state_s:published'] }) return search_app(**search_params) @expose('jinja:forgeblog:templates/blog/edit_post.html') @without_trailing_slash def new(self, **kw): require_access(c.app, 'write') self.rate_limit(BM.BlogPost, 'Create/edit', c.app.config.url()) post = dict(state='published') c.form = W.new_post_form return dict( post=post, subscribed_to_tool=M.Mailbox.subscribed(), ) @memorable_forget() @expose() @require_post() # both new & edit submit here, but validate with new since it adds a field (subscribe) @validate(form=W.new_post_form, error_handler=new) @without_trailing_slash def save(self, **kw): require_access(c.app, 'write') self.rate_limit(BM.BlogPost, 'Create/edit', c.app.config.url()) attachment = kw.pop('attachment', None) post = BM.BlogPost.new(**kw) g.spam_checker.check(kw['title'] + '\n' + kw['text'], artifact=post, user=c.user, content_type='blog-post') if attachment is not None: post.add_multiple_attachments(attachment) notification_tasks.send_usermentions_notification.post( post.index_id(), kw['text']) redirect(h.urlquote(h.really_unicode(post.url()))) @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, year=None, month=None, name=None, *rest): if year is None or month is None or name is None: raise exc.HTTPNotFound() slug = '/'.join((year, month, six.moves.urllib.parse.unquote(name).decode('utf-8'))) post = BM.BlogPost.query.get(slug=slug, app_config_id=c.app.config._id) if post is None: raise exc.HTTPNotFound() return PostController(post), rest 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, link=BM.BlogPost.link_regex), 'Recent posts to %s' % app.config.options.mount_point, app.url)
class RootController(BaseController, DispatchIndex, FeedController): class W(object): new_topic = DW.NewTopicPost(submit_text='Post') announcements_table = FW.AnnouncementsTable() add_forum = AddForumShort() search_results = SearchResults() search_help = SearchHelp( comments=False, history=False, 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', error_url='create_topic') def save_new_topic(self, subject=None, text=None, forum=None, subscribe=False, **kw): self.rate_limit(model.ForumPost, 'Topic creation', six.ensure_text(request.referer or '/')) 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(six.ensure_text(request.referer or '/')) 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=v.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, } }, ], cursor={}) 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)), )