| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| import re |
| import logging |
| from datetime import datetime, timedelta |
| from urllib import unquote |
| |
| from bson import ObjectId |
| from tg import expose, flash, redirect, validate, request, config, session |
| from tg.decorators import with_trailing_slash, without_trailing_slash |
| from pylons import tmpl_context as c, app_globals as g |
| from paste.deploy.converters import asbool |
| from webob import exc |
| import jinja2 |
| import pymongo |
| |
| from ming.utils import LazyProperty |
| |
| from allura import model as M |
| from allura.app import SitemapEntry |
| from allura.lib.base import WsgiDispatchController |
| from allura.lib import helpers as h |
| from allura.lib.helpers import iter_entry_points |
| from allura.lib import utils |
| from allura.lib.decorators import require_post |
| from allura.controllers.error import ErrorController |
| from allura.controllers.feed import FeedArgs, FeedController |
| from allura.controllers.rest import nbhd_lookup_first_path |
| from allura.lib.security import require_access |
| from allura.lib.security import RoleCache |
| from allura.lib.widgets import forms as ff |
| from allura.lib.widgets import form_fields as ffw |
| from allura.lib.widgets import project_list as plw |
| from allura.lib import plugin, exceptions |
| from .auth import AuthController |
| from .search import SearchController, ProjectBrowseController |
| from .static import NewForgeController |
| |
| log = logging.getLogger(__name__) |
| |
| |
| class W: |
| resize_editor = ffw.AutoResizeTextarea() |
| project_summary = plw.ProjectSummary() |
| add_project = plugin.ProjectRegistrationProvider.get().add_project_widget(antispam=True) |
| page_list = ffw.PageList() |
| page_size = ffw.PageSize() |
| project_select = ffw.NeighborhoodProjectSelect |
| neighborhood_overview_form = ff.NeighborhoodOverviewForm() |
| award_grant_form = ff.AwardGrantForm |
| |
| |
| class NeighborhoodController(object): |
| |
| '''Manages a neighborhood of projects. |
| ''' |
| |
| def __init__(self, neighborhood): |
| self.neighborhood = neighborhood |
| self.neighborhood_name = self.neighborhood.name |
| self.browse = NeighborhoodProjectBrowseController(neighborhood=self.neighborhood) |
| # 'admin' without underscore will pass through to _lookup which will find the regular "admin" tool mounted |
| # on the --init-- Project record for this neighborhood. |
| self._admin = NeighborhoodAdminController(self.neighborhood) |
| self._moderate = NeighborhoodModerateController(self.neighborhood) |
| self.import_project = ProjectImporterController(self.neighborhood) |
| |
| def _check_security(self): |
| require_access(self.neighborhood, 'read') |
| |
| def _before(self, *args, **kwargs): |
| # TurboGears runs this before each request |
| c.project = self.neighborhood.neighborhood_project |
| |
| @expose() |
| def _lookup(self, pname, *remainder): |
| c.project, remainder = nbhd_lookup_first_path(self.neighborhood, pname, c.user, remainder) |
| return ProjectController(), remainder |
| |
| @expose('jinja:allura:templates/neighborhood_project_list.html') |
| @with_trailing_slash |
| def index(self, sort='alpha', limit=25, page=0, **kw): |
| if self.neighborhood.redirect: |
| redirect(self.neighborhood.redirect) |
| if not self.neighborhood.has_home_tool: |
| mount = c.project.ordered_mounts()[0] |
| if mount is not None: |
| if 'ac' in mount: |
| redirect(mount['ac'].options.mount_point + '/') |
| elif 'sub' in mount: |
| redirect(mount['sub'].url()) |
| else: |
| redirect(c.project.app_configs[0].options.mount_point + '/') |
| c.project_summary = W.project_summary |
| c.page_list = W.page_list |
| limit, page, start = g.handle_paging(limit, page) |
| pq = M.Project.query.find(dict( |
| neighborhood_id=self.neighborhood._id, |
| deleted=False, |
| is_nbhd_project=False, |
| )) |
| if sort == 'alpha': |
| pq.sort('name') |
| else: |
| pq.sort('last_updated', pymongo.DESCENDING) |
| count = pq.count() |
| nb_max_projects = self.neighborhood.get_max_projects() |
| projects = pq.skip(start).limit(int(limit)).all() |
| categories = M.ProjectCategory.query.find( |
| {'parent_id': None}).sort('name').all() |
| c.custom_sidebar_menu = [] |
| if h.has_access(self.neighborhood, 'register')() and (nb_max_projects is None or count < nb_max_projects): |
| c.custom_sidebar_menu += [ |
| SitemapEntry('Add a Project', self.neighborhood.url() |
| + 'add_project', ui_icon=g.icons['add']), |
| SitemapEntry('') |
| ] |
| c.custom_sidebar_menu = c.custom_sidebar_menu + [ |
| SitemapEntry(cat.label, self.neighborhood.url() + 'browse/' + cat.name) for cat in categories |
| ] |
| return dict(neighborhood=self.neighborhood, |
| title="Welcome to " + self.neighborhood.name, |
| text=g.markdown.cached_convert( |
| self.neighborhood, 'homepage'), |
| projects=projects, |
| sort=sort, |
| limit=limit, page=page, count=count) |
| |
| @expose('jinja:allura:templates/neighborhood_add_project.html') |
| @without_trailing_slash |
| def add_project(self, **form_data): |
| with h.login_overlay(): |
| require_access(self.neighborhood, 'register') |
| verify = c.form_errors == {'_the_form': u'phone-verification'} |
| c.show_phone_verification_overlay = verify |
| c.add_project = W.add_project |
| form_data.setdefault('tools', W.add_project.default_tools) |
| form_data['neighborhood'] = self.neighborhood.name |
| return dict(neighborhood=self.neighborhood, form_data=form_data) |
| |
| @expose('jinja:allura:templates/phone_verification_fragment.html') |
| def phone_verification_fragment(self, *args, **kw): |
| return {} |
| |
| @expose('json:') |
| def verify_phone(self, number, **kw): |
| p = plugin.ProjectRegistrationProvider.get() |
| result = p.verify_phone(c.user, number) |
| request_id = result.pop('request_id', None) |
| if request_id: |
| session['phone_verification.request_id'] = request_id |
| number_hash = utils.phone_number_hash(number) |
| session['phone_verification.number_hash'] = number_hash |
| session.save() |
| if 'error' in result: |
| result['error'] = jinja2.Markup.escape(result['error']) |
| result['error'] = h.really_unicode(result['error']) |
| return result |
| |
| @expose('json:') |
| def check_phone_verification(self, pin, **kw): |
| p = plugin.ProjectRegistrationProvider.get() |
| request_id = session.get('phone_verification.request_id') |
| number_hash = session.get('phone_verification.number_hash') |
| res = p.check_phone_verification(c.user, request_id, pin, number_hash) |
| if 'error' in res: |
| res['error'] = jinja2.Markup.escape(res['error']) |
| res['error'] = h.really_unicode(res['error']) |
| return res |
| |
| @expose('json:') |
| @validate(W.add_project) |
| def check_names(self, **raw_data): |
| return c.form_errors |
| |
| @h.vardec |
| @expose() |
| @validate(W.add_project, error_handler=add_project) |
| @utils.AntiSpam.validate('Spambot protection engaged') |
| @require_post() |
| def register( |
| self, project_unixname=None, project_description=None, project_name=None, neighborhood=None, |
| private_project=None, tools=None, **kw): |
| require_access(self.neighborhood, 'register') |
| if private_project: |
| require_access(self.neighborhood, 'admin') |
| neighborhood = M.Neighborhood.query.get(name=neighborhood) |
| |
| project_description = h.really_unicode( |
| project_description or '').encode('utf-8') |
| project_name = h.really_unicode(project_name or '').encode('utf-8') |
| project_unixname = h.really_unicode( |
| project_unixname or '').encode('utf-8').lower() |
| try: |
| c.project = neighborhood.register_project(project_unixname, |
| project_name=project_name, private_project=private_project) |
| except exceptions.ProjectOverlimitError: |
| flash( |
| "You have exceeded the maximum number of projects you are allowed to create", 'error') |
| redirect('add_project') |
| except exceptions.ProjectRatelimitError: |
| flash( |
| "Project creation rate limit exceeded. Please try again later.", 'error') |
| redirect('add_project') |
| except exceptions.ProjectPhoneVerificationError: |
| flash('You must pass phone verification', 'error') |
| redirect('add_project') |
| except Exception as e: |
| log.error('error registering project: %s', |
| project_unixname, exc_info=True) |
| flash('Internal Error. Please try again later.', 'error') |
| redirect('add_project') |
| |
| if project_description: |
| c.project.short_description = project_description |
| offset = c.project.next_mount_point(include_hidden=True) |
| if tools and not neighborhood.project_template: |
| anchored_tools = neighborhood.get_anchored_tools() |
| install_params = [] |
| for i, tool in enumerate(tools): |
| if (tool.lower() not in anchored_tools.keys()) and (c.project.app_instance(tool) is None): |
| install_params.append(dict(ep_name=tool, ordinal=i + offset)) |
| c.project.install_apps(install_params) |
| redirect(c.project.script_name + 'admin/?first-visit') |
| |
| @expose() |
| def icon(self, **kw): |
| icon = self.neighborhood.icon |
| if not icon: |
| raise exc.HTTPNotFound |
| return icon.serve() |
| |
| @expose('json:') |
| def users(self, **kw): |
| p = self.neighborhood.neighborhood_project |
| return { |
| 'options': [{ |
| 'value': u.username, |
| 'label': '%s (%s)' % (u.display_name, u.username) |
| } for u in p.users()] |
| } |
| |
| |
| class NeighborhoodProjectBrowseController(ProjectBrowseController): |
| |
| def __init__(self, neighborhood=None, category_name=None, parent_category=None): |
| self.neighborhood = neighborhood |
| super(NeighborhoodProjectBrowseController, self).__init__( |
| category_name=category_name, parent_category=parent_category) |
| self.nav_stub = '%sbrowse/' % self.neighborhood.url() |
| self.additional_filters = {'neighborhood_id': self.neighborhood._id} |
| |
| @expose() |
| def _lookup(self, category_name, *remainder): |
| c.project = self.neighborhood.neighborhood_project |
| category_name = unquote(category_name) |
| return NeighborhoodProjectBrowseController(neighborhood=self.neighborhood, category_name=category_name, parent_category=self.category), remainder |
| |
| @expose('jinja:allura:templates/neighborhood_project_list.html') |
| @without_trailing_slash |
| def index(self, sort='alpha', limit=25, page=0, **kw): |
| c.project_summary = W.project_summary |
| c.page_list = W.page_list |
| limit, page, start = g.handle_paging(limit, page) |
| projects, count = self._find_projects( |
| sort=sort, limit=limit, start=start) |
| title = self._build_title() |
| c.custom_sidebar_menu = self._build_nav() |
| return dict(projects=projects, |
| title=title, |
| text=None, |
| neighborhood=self.neighborhood, |
| sort=sort, |
| limit=limit, page=page, count=count) |
| |
| |
| class HostNeighborhoodController(WsgiDispatchController, NeighborhoodController): |
| |
| '''Neighborhood controller with support for use as a root controller, for |
| instance, when using adobe.domain.net (if this is allowed). |
| ''' |
| |
| auth = AuthController() |
| error = ErrorController() |
| nf = NewForgeController() |
| search = SearchController() |
| |
| |
| class ToolListController(object): |
| |
| """Renders a list of all tools of a given type in the current project.""" |
| |
| @expose('jinja:allura:templates/tool_list.html') |
| def _default(self, tool_name, page=0, limit=200, **kw): |
| c.page_list = W.page_list |
| tool_name = tool_name.lower() |
| entries = c.project.sitemap(included_tools=[tool_name], |
| tools_only=True, per_tool_limit=None) |
| total_entries = len(entries) |
| limit, page = h.paging_sanitizer(limit, page, total_entries) |
| start = page * limit |
| return dict( |
| page=page, |
| limit=limit, |
| total_entries=total_entries, |
| entries=entries[start:start + limit], |
| type=g.entry_points['tool'][tool_name].tool_label if entries else None, |
| ) |
| |
| |
| class ProjectController(FeedController): |
| |
| def __init__(self): |
| self.screenshot = ScreenshotsController() |
| self._list = ToolListController() |
| |
| @expose('json:') |
| def _nav(self, admin_options=False, **kw): |
| return c.project.nav_data(admin_options=admin_options) |
| |
| @expose() |
| def _lookup(self, name, *remainder): |
| name = unquote(name) |
| if name == '_nav.json': |
| return self, ['_nav'] |
| |
| if c.project.deleted: |
| if c.user not in c.project.admins(): |
| raise exc.HTTPNotFound, name |
| app = c.project.app_instance(name) |
| |
| if app: |
| c.app = app |
| if app.root: |
| return app.root, remainder |
| 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 |
| raise exc.HTTPNotFound, name |
| |
| @expose('jinja:allura:templates/members.html') |
| @with_trailing_slash |
| def _members(self, **kw): |
| users = [] |
| admins = [] |
| developers = [] |
| for user in c.project.users(): |
| roles = M.ProjectRole.query.find( |
| {'_id': {'$in': M.ProjectRole.by_user(user).roles}}) |
| roles = set([r.name for r in roles]) |
| u = dict( |
| display_name=user.display_name, |
| username=user.username, |
| url=user.url(), |
| roles=', '.join(sorted(roles))) |
| if 'Admin' in roles: |
| admins.append(u) |
| elif 'Developer' in roles: |
| developers.append(u) |
| else: |
| users.append(u) |
| get_username = lambda user: user['username'] |
| admins = sorted(admins, key=get_username) |
| developers = sorted(developers, key=get_username) |
| users = sorted(users, key=get_username) |
| return dict(users=admins + developers + users) |
| |
| def _check_security(self): |
| require_access(c.project, 'read') |
| |
| @expose() |
| @with_trailing_slash |
| def index(self, **kw): |
| mount = c.project.first_mount_visible(c.user) |
| activity_enabled = asbool(config.get('activitystream.enabled', False)) |
| if mount is not None: |
| if 'ac' in mount: |
| redirect(mount['ac'].options.mount_point + '/') |
| elif 'sub' in mount: |
| redirect(mount['sub'].url()) |
| else: |
| redirect(c.project.app_configs[0].options.mount_point + '/') |
| |
| 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), |
| 'Recent changes to Project %s' % project.name, |
| project.url()) |
| |
| @expose() |
| def icon(self, **kw): |
| icon = c.project.icon |
| if not icon: |
| raise exc.HTTPNotFound |
| return icon.serve() |
| |
| @expose() |
| def user_icon(self, **kw): |
| try: |
| return self.icon() |
| except exc.HTTPNotFound: |
| redirect(g.forge_static('images/user.png')) |
| |
| @expose('json:') |
| def user_search(self, term='', **kw): |
| if len(term) < 3: |
| raise exc.HTTPBadRequest('"term" param must be at least length 3') |
| named_roles = RoleCache( |
| g.credentials, |
| g.credentials.project_roles(project_id=c.project.root_project._id).named) |
| users = M.User.query.find({ |
| '_id': {'$in': named_roles.userids_that_reach}, |
| 'display_name': re.compile(r'(?i)%s' % re.escape(term)), |
| 'disabled': False, |
| 'pending': False, |
| }).sort('username').limit(10).all() |
| return dict( |
| users=[ |
| dict( |
| label='%s (%s)' % (u.get_pref('display_name'), u.username), |
| value=u.username, |
| id=u.username) |
| for u in users]) |
| |
| @expose('json:') |
| def users(self, **kw): |
| users = c.project.users() |
| if c.user and c.user in users: |
| users.remove(c.user) |
| users.insert(0, c.user) |
| |
| return { |
| 'options': [{ |
| 'value': u.username, |
| 'label': '%s (%s)' % (u.display_name, u.username) |
| } for u in users] |
| } |
| |
| |
| class ScreenshotsController(object): |
| |
| @expose() |
| def _lookup(self, filename, *args): |
| if args: |
| filename = unquote(filename) |
| else: |
| filename = unquote(request.path.rsplit('/', 1)[-1]) |
| return ScreenshotController(filename), args |
| |
| |
| class ScreenshotController(object): |
| |
| def __init__(self, filename): |
| self.filename = filename |
| |
| @expose() |
| def index(self, embed=True, **kw): |
| return self._screenshot.serve(embed) |
| |
| @expose() |
| def thumb(self, embed=True): |
| return self._thumb.serve(embed) |
| |
| @LazyProperty |
| def _screenshot(self): |
| f = M.ProjectFile.query.get( |
| project_id=c.project._id, |
| category='screenshot', |
| filename=self.filename) |
| if not f: |
| raise exc.HTTPNotFound |
| return f |
| |
| @LazyProperty |
| def _thumb(self): |
| f = M.ProjectFile.query.get( |
| project_id=c.project._id, |
| category='screenshot_thumb', |
| filename=self.filename) |
| if not f: |
| raise exc.HTTPNotFound |
| return f |
| |
| |
| def set_nav(neighborhood): |
| project = neighborhood.neighborhood_project |
| if project: |
| c.project = project |
| g.set_app('admin') |
| else: |
| admin_url = neighborhood.url() + '_admin/' |
| c.custom_sidebar_menu = [ |
| SitemapEntry('Overview', admin_url + 'overview'), |
| SitemapEntry('Awards', admin_url + 'accolades')] |
| |
| |
| class NeighborhoodAdminController(object): |
| |
| def __init__(self, neighborhood): |
| self.neighborhood = neighborhood |
| self.awards = NeighborhoodAwardsController(self.neighborhood) |
| self.stats = NeighborhoodStatsController(self.neighborhood) |
| |
| def _check_security(self): |
| require_access(self.neighborhood, 'admin') |
| |
| @with_trailing_slash |
| @expose() |
| def index(self, **kw): |
| utils.permanent_redirect('overview') |
| |
| @without_trailing_slash |
| @expose('jinja:allura:templates/neighborhood_admin_overview.html') |
| def overview(self, **kw): |
| set_nav(self.neighborhood) |
| c.overview_form = W.neighborhood_overview_form |
| allow_undelete = asbool(config.get('allow_project_undelete', True)) |
| return dict( |
| neighborhood=self.neighborhood, |
| allow_project_undelete=allow_undelete) |
| |
| @without_trailing_slash |
| @expose('jinja:allura:templates/neighborhood_admin_permissions.html') |
| def permissions(self): |
| set_nav(self.neighborhood) |
| return dict(neighborhood=self.neighborhood) |
| |
| @expose('json:') |
| def project_search(self, term='', **kw): |
| if len(term) < 3: |
| raise exc.HTTPBadRequest('"term" param must be at least length 3') |
| project_regex = re.compile('(?i)%s' % re.escape(term)) |
| projects = M.Project.query.find(dict( |
| neighborhood_id=self.neighborhood._id, deleted=False, |
| shortname=project_regex)).sort('shortname') |
| return dict( |
| projects=[ |
| dict( |
| label=p.shortname, |
| value=p.shortname, |
| id=p.shortname) |
| for p in projects]) |
| |
| @without_trailing_slash |
| @expose('jinja:allura:templates/neighborhood_admin_accolades.html') |
| def accolades(self): |
| set_nav(self.neighborhood) |
| awards = M.Award.query.find( |
| dict(created_by_neighborhood_id=self.neighborhood._id)).all() |
| awards_count = len(awards) |
| grants = M.AwardGrant.query.find( |
| dict(granted_by_neighborhood_id=self.neighborhood._id)) |
| grants_count = grants.count() |
| c.award_grant_form = W.award_grant_form( |
| awards=awards, |
| project_select_url=self.neighborhood.url() + '_admin/project_search') |
| return dict( |
| awards=awards, |
| awards_count=awards_count, |
| grants=grants, |
| grants_count=grants_count, |
| neighborhood=self.neighborhood) |
| |
| @expose() |
| @require_post() |
| @validate(W.neighborhood_overview_form, error_handler=overview) |
| def update(self, name=None, css=None, homepage=None, project_template=None, icon=None, **kw): |
| # We need to get neighborhood from Mongo to populate Ming's session. If |
| # neighborhood object is coming from cache (i.e. |
| # neighborhood.cache.duration is set), then it will be absent in Ming's |
| # session for current thread, thus all changes will not be flushed to |
| # disk. See #7890 for details. |
| nbhd = M.Neighborhood.query.get(_id=self.neighborhood._id) |
| c.project = nbhd.neighborhood_project |
| h.log_if_changed(nbhd, 'name', name, |
| 'change neighborhood name to %s' % name) |
| nbhd_redirect = kw.pop('redirect', '') |
| h.log_if_changed(nbhd, 'redirect', nbhd_redirect, |
| 'change neighborhood redirect to %s' % nbhd_redirect) |
| h.log_if_changed(nbhd, 'homepage', homepage, |
| 'change neighborhood homepage to %s' % homepage) |
| h.log_if_changed(nbhd, 'css', css, |
| 'change neighborhood css to %s' % css) |
| h.log_if_changed(nbhd, 'project_template', project_template, |
| 'change neighborhood project template to %s' |
| % project_template) |
| allow_browse = kw.get('allow_browse', False) |
| h.log_if_changed(nbhd, 'allow_browse', allow_browse, |
| 'change neighborhood allow browse to %s' |
| % allow_browse) |
| show_title = kw.get('show_title', False) |
| h.log_if_changed(nbhd, 'show_title', show_title, |
| 'change neighborhood show title to %s' % show_title) |
| project_list_url = kw.get('project_list_url', '') |
| h.log_if_changed(nbhd, 'project_list_url', project_list_url, |
| 'change neighborhood project list url to %s' |
| % project_list_url) |
| tracking_id = kw.get('tracking_id', '') |
| h.log_if_changed(nbhd, 'tracking_id', tracking_id, |
| 'update neighborhood tracking_id') |
| prohibited_tools = kw.get('prohibited_tools', '') |
| |
| result = True |
| if prohibited_tools.strip() != '': |
| for prohibited_tool in prohibited_tools.split(','): |
| if prohibited_tool.strip() not in g.entry_points['tool']: |
| flash('Prohibited tools "%s" is invalid' % |
| prohibited_tool.strip(), 'error') |
| result = False |
| |
| if result: |
| h.log_if_changed(nbhd, 'prohibited_tools', prohibited_tools, |
| 'update neighborhood prohibited tools') |
| |
| anchored_tools = kw.get('anchored_tools', '') |
| validate_tools = dict() |
| result = True |
| if anchored_tools.strip() != '': |
| try: |
| validate_tools = dict( |
| (tool.split(':')[0].lower(), tool.split(':')[1]) |
| for tool in anchored_tools.replace(' ', '').split(',')) |
| except Exception: |
| flash('Anchored tools "%s" is invalid' % |
| anchored_tools, 'error') |
| result = False |
| |
| for tool in validate_tools.keys(): |
| if tool not in g.entry_points['tool']: |
| flash('Anchored tools "%s" is invalid' % |
| anchored_tools, 'error') |
| result = False |
| if result: |
| h.log_if_changed(nbhd, 'anchored_tools', anchored_tools, |
| 'update neighborhood anchored tools') |
| |
| if icon is not None and icon != '': |
| if self.neighborhood.icon: |
| self.neighborhood.icon.delete() |
| M.AuditLog.log('update neighborhood icon') |
| M.NeighborhoodFile.save_image( |
| icon.filename, icon.file, content_type=icon.type, |
| square=True, thumbnail_size=(48, 48), |
| thumbnail_meta=dict(neighborhood_id=self.neighborhood._id)) |
| redirect('overview') |
| |
| @expose('jinja:allura:templates/neighborhood_help.html') |
| @with_trailing_slash |
| def help(self, **kw): |
| require_access(self.neighborhood, 'admin') |
| set_nav(self.neighborhood) |
| return dict( |
| neighborhood=self.neighborhood, |
| ) |
| |
| |
| class NeighborhoodStatsController(object): |
| |
| def __init__(self, neighborhood): |
| self.neighborhood = neighborhood |
| |
| @with_trailing_slash |
| @expose('jinja:allura:templates/neighborhood_stats.html') |
| def index(self, **kw): |
| delete_count = M.Project.query.find( |
| dict(neighborhood_id=self.neighborhood._id, deleted=True)).count() |
| public_count = 0 |
| private_count = 0 |
| last_updated_30 = 0 |
| last_updated_60 = 0 |
| last_updated_90 = 0 |
| today_date = datetime.today() |
| # arbitrary limit for efficiency |
| if M.Project.query.find(dict(neighborhood_id=self.neighborhood._id, deleted=False)).count() < 20000: |
| for p in M.Project.query.find(dict(neighborhood_id=self.neighborhood._id, deleted=False)): |
| if p.private: |
| private_count = private_count + 1 |
| else: |
| public_count = public_count + 1 |
| if today_date - p.last_updated < timedelta(days=30): |
| last_updated_30 = last_updated_30 + 1 |
| if today_date - p.last_updated < timedelta(days=60): |
| last_updated_60 = last_updated_60 + 1 |
| if today_date - p.last_updated < timedelta(days=90): |
| last_updated_90 = last_updated_90 + 1 |
| |
| set_nav(self.neighborhood) |
| return dict( |
| delete_count=delete_count, |
| public_count=public_count, |
| private_count=private_count, |
| last_updated_30=last_updated_30, |
| last_updated_60=last_updated_60, |
| last_updated_90=last_updated_90, |
| neighborhood=self.neighborhood, |
| ) |
| |
| @without_trailing_slash |
| @expose('jinja:allura:templates/neighborhood_stats_adminlist.html') |
| def adminlist(self, sort='alpha', limit=25, page=0, **kw): |
| limit, page, start = g.handle_paging(limit, page) |
| |
| pq = M.Project.query.find( |
| dict(neighborhood_id=self.neighborhood._id, deleted=False)) |
| if sort == 'alpha': |
| pq.sort('name') |
| else: |
| pq.sort('last_updated', pymongo.DESCENDING) |
| count = pq.count() |
| projects = pq.skip(start).limit(int(limit)).all() |
| |
| entries = [] |
| for proj in projects: |
| admin_role = M.ProjectRole.query.get( |
| project_id=proj.root_project._id, name='Admin') |
| if admin_role is None: |
| continue |
| user_role_list = M.ProjectRole.query.find( |
| dict(project_id=proj.root_project._id, name=None)).all() |
| for ur in user_role_list: |
| if ur.user is not None and admin_role._id in ur.roles: |
| entries.append({'project': proj, 'user': ur.user}) |
| |
| set_nav(self.neighborhood) |
| return dict(entries=entries, |
| sort=sort, |
| limit=limit, page=page, count=count, |
| page_list=W.page_list, |
| neighborhood=self.neighborhood, |
| ) |
| |
| |
| class NeighborhoodModerateController(object): |
| |
| def __init__(self, neighborhood): |
| self.neighborhood = neighborhood |
| |
| def _check_security(self): |
| require_access(self.neighborhood, 'admin') |
| |
| @expose('jinja:allura:templates/neighborhood_moderate.html') |
| def index(self, **kw): |
| c.project = self.neighborhood.neighborhood_project |
| other_nbhds = list(M.Neighborhood.query.find( |
| dict(_id={'$ne': self.neighborhood._id})).sort('name')) |
| return dict(neighborhood=self.neighborhood, |
| neighborhoods=other_nbhds) |
| |
| @expose() |
| @require_post() |
| def invite(self, pid, neighborhood_id, invite=None, uninvite=None): |
| p = M.Project.query.get(shortname=pid, deleted=False, |
| neighborhood_id=ObjectId(neighborhood_id)) |
| if p is None: |
| flash("Can't find %s" % pid, 'error') |
| redirect('.') |
| if p.neighborhood == self.neighborhood: |
| flash("%s is already in the neighborhood" % pid, 'error') |
| redirect('.') |
| if invite: |
| if self.neighborhood._id in p.neighborhood_invitations: |
| flash("%s is already invited" % pid, 'warning') |
| redirect('.') |
| p.neighborhood_invitations.append(self.neighborhood._id) |
| flash('%s invited' % pid) |
| elif uninvite: |
| if self.neighborhood._id not in p.neighborhood_invitations: |
| flash("%s is already uninvited" % pid, 'warning') |
| redirect('.') |
| p.neighborhood_invitations.remove(self.neighborhood._id) |
| flash('%s uninvited' % pid) |
| redirect('.') |
| |
| @expose() |
| @require_post() |
| def evict(self, pid): |
| p = M.Project.query.get( |
| shortname=pid, neighborhood_id=self.neighborhood._id, deleted=False) |
| if p is None: |
| flash("Cannot evict %s; it's not in the neighborhood" |
| % pid, 'error') |
| redirect('.') |
| if not p.is_root: |
| flash("Cannot evict %s; it's a subproject" % pid, 'error') |
| redirect('.') |
| n = M.Neighborhood.query.get(name='Projects') |
| p.neighborhood_id = n._id |
| if self.neighborhood._id in p.neighborhood_invitations: |
| p.neighborhood_invitations.remove(self.neighborhood._id) |
| flash('%s evicted to Projects' % pid) |
| redirect('.') |
| |
| |
| class NeighborhoodAwardsController(object): |
| |
| def __init__(self, neighborhood=None): |
| if neighborhood is not None: |
| self.neighborhood = neighborhood |
| |
| @expose('jinja:allura:templates/awards.html') |
| def index(self, **kw): |
| require_access(self.neighborhood, 'admin') |
| awards = M.Award.query.find( |
| dict(created_by_neighborhood_id=self.neighborhood._id)).all() |
| return dict(awards=awards or [], count=len(awards)) |
| |
| @expose('jinja:allura:templates/award_not_found.html') |
| def not_found(self, **kw): |
| return dict() |
| |
| @expose('jinja:allura:templates/grants.html') |
| def grants(self, **kw): |
| require_access(self.neighborhood, 'admin') |
| grants = M.AwardGrant.query.find( |
| dict(granted_by_neighborhood_id=self.neighborhood._id)) |
| count = grants.count() |
| return dict(grants=grants or [], count=count) |
| |
| @expose() |
| def _lookup(self, award_id, *remainder): |
| return AwardController(self.neighborhood, award_id), remainder |
| |
| @expose() |
| @require_post() |
| def create(self, icon=None, short=None, full=None): |
| require_access(self.neighborhood, 'admin') |
| app_config_id = ObjectId() |
| if short: |
| award = M.Award(app_config_id=app_config_id) |
| award.short = short |
| award.full = full |
| award.created_by_neighborhood_id = self.neighborhood._id |
| if hasattr(icon, 'filename'): |
| M.AwardFile.save_image( |
| icon.filename, icon.file, content_type=icon.type, |
| square=True, thumbnail_size=(48, 48), |
| thumbnail_meta=dict(award_id=award._id)) |
| redirect(request.referer) |
| |
| @expose() |
| @require_post() |
| def grant(self, grant=None, recipient=None, url=None, comment=None): |
| require_access(self.neighborhood, 'admin') |
| grant_q = M.Award.query.find(dict(short=grant, |
| created_by_neighborhood_id=self.neighborhood._id)).first() |
| recipient_q = M.Project.query.find(dict( |
| neighborhood_id=self.neighborhood._id, shortname=recipient, |
| deleted=False)).first() |
| if grant_q and recipient_q: |
| app_config_id = ObjectId() |
| award = M.AwardGrant(app_config_id=app_config_id) |
| award.award_id = grant_q._id |
| award.granted_to_project_id = recipient_q._id |
| award.granted_by_neighborhood_id = self.neighborhood._id |
| award.award_url = url |
| award.comment = comment |
| with h.push_context(recipient_q._id): |
| g.post_event('project_updated') |
| redirect(request.referer) |
| |
| |
| class AwardController(object): |
| |
| def __init__(self, neighborhood=None, award_id=None): |
| self.neighborhood = neighborhood |
| if award_id: |
| self.award = M.Award.query.find(dict(_id=ObjectId(award_id), |
| created_by_neighborhood_id=self.neighborhood._id)).first() |
| |
| @with_trailing_slash |
| @expose('jinja:allura:templates/award.html') |
| def index(self, **kw): |
| require_access(self.neighborhood, 'admin') |
| set_nav(self.neighborhood) |
| if self.award is not None: |
| return dict(award=self.award, neighborhood=self.neighborhood) |
| else: |
| redirect('not_found') |
| |
| @expose('jinja:allura:templates/award_not_found.html') |
| def not_found(self, **kw): |
| return dict() |
| |
| @expose() |
| def _lookup(self, recipient, *remainder): |
| recipient = unquote(recipient) |
| return GrantController(self.neighborhood, self.award, recipient), remainder |
| |
| @expose() |
| def icon(self, **kw): |
| icon = self.award.icon |
| if not icon: |
| raise exc.HTTPNotFound |
| return icon.serve() |
| |
| @expose() |
| @require_post() |
| def update(self, icon=None, short=None, full=None): |
| require_access(self.neighborhood, 'admin') |
| self.award.short = short |
| self.award.full = full |
| if hasattr(icon, 'filename'): |
| if self.award.icon: |
| self.award.icon.delete() |
| M.AwardFile.save_image( |
| icon.filename, icon.file, content_type=icon.type, |
| square=True, thumbnail_size=(48, 48), |
| thumbnail_meta=dict(award_id=self.award._id)) |
| for grant in M.AwardGrant.query.find(dict(award_id=self.award._id)): |
| with h.push_context(grant.granted_to_project_id): |
| g.post_event('project_updated') |
| flash('Award updated.') |
| redirect(self.award.longurl()) |
| |
| @expose() |
| @require_post() |
| def delete(self): |
| require_access(self.neighborhood, 'admin') |
| if self.award: |
| grants = M.AwardGrant.query.find(dict(award_id=self.award._id)) |
| for grant in grants: |
| grant.delete() |
| with h.push_context(grant.granted_to_project_id): |
| g.post_event('project_updated') |
| M.AwardFile.query.remove(dict(award_id=self.award._id)) |
| self.award.delete() |
| redirect(request.referer) |
| |
| |
| class GrantController(object): |
| |
| def __init__(self, neighborhood=None, award=None, recipient=None): |
| self.neighborhood = neighborhood |
| if recipient is not None and award is not None: |
| self.recipient = recipient.replace('_', '/') |
| self.award = M.Award.query.get(_id=award._id) |
| self.project = M.Project.query.find(dict(shortname=self.recipient, |
| neighborhood_id=self.neighborhood._id)).first() |
| self.grant = M.AwardGrant.query.get(award_id=self.award._id, |
| granted_to_project_id=self.project._id) |
| |
| @with_trailing_slash |
| @expose('jinja:allura:templates/grant.html') |
| def index(self, **kw): |
| require_access(self.neighborhood, 'admin') |
| if self.grant is not None: |
| return dict(grant=self.grant) |
| else: |
| redirect('not_found') |
| |
| @expose('jinja:allura:templates/award_not_found.html') |
| def not_found(self, **kw): |
| return dict() |
| |
| @expose() |
| def icon(self, **kw): |
| icon = self.award.icon |
| if not icon: |
| raise exc.HTTPNotFound |
| return icon.serve() |
| |
| @expose() |
| @require_post() |
| def revoke(self): |
| require_access(self.neighborhood, 'admin') |
| self.grant.delete() |
| with h.push_context(self.project._id): |
| g.post_event('project_updated') |
| redirect(request.referer) |
| |
| |
| class ProjectImporterController(object): |
| |
| def __init__(self, neighborhood, *a, **kw): |
| super(ProjectImporterController, self).__init__(*a, **kw) |
| self.neighborhood = neighborhood |
| |
| @expose() |
| def _lookup(self, source, *rest): |
| # iter_entry_points is a generator with 0 or 1 items, so a loop is the easiest way to handle |
| for ep in iter_entry_points('allura.project_importers', source): |
| return ep.load()(self.neighborhood), rest |
| |
| raise exc.HTTPNotFound |