blob: 5f293a65d0a0b3ac205b992b5e714e7235957a8b [file] [log] [blame]
# 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 itertools import chain, islice
from pkg_resources import iter_entry_points
from bson import ObjectId
from tg import expose, flash, redirect, validate, request, response, config
from tg.decorators import with_trailing_slash, without_trailing_slash, override_template
from pylons import tmpl_context as c, app_globals as g
from paste.deploy.converters import asbool
from webob import exc
import pymongo
from formencode import validators
from formencode.api import Invalid
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 import utils
from allura.lib.decorators import require_post
from allura.controllers.error import ErrorController
from allura.controllers.feed import FeedArgs, FeedController
from allura.lib.security import require_access, has_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()
class NeighborhoodController(object):
'''Manages a neighborhood of projects.
'''
def __init__(self, neighborhood):
self.neighborhood = neighborhood
self.neighborhood_name = self.neighborhood.name
self.prefix = self.neighborhood.shortname_prefix
self.browse = NeighborhoodProjectBrowseController(neighborhood=self.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')
@expose()
def _lookup(self, pname, *remainder):
pname = unquote(pname)
provider = plugin.ProjectRegistrationProvider.get()
try:
provider.shortname_validator.to_python(pname, check_allowed=False, neighborhood=self.neighborhood)
except Invalid as e:
raise exc.HTTPNotFound, pname
project = M.Project.query.get(shortname=self.prefix + pname, neighborhood_id=self.neighborhood._id)
if project is None and self.prefix == 'u/':
# create user-project if it is missing
user = M.User.query.get(username=pname, disabled=False)
if user:
project = self.neighborhood.register_project(
plugin.AuthenticationProvider.get(request).user_project_shortname(user),
user=user, user_project=True)
if project is None:
# look for neighborhood tools matching the URL
project = self.neighborhood.neighborhood_project
c.project = project
return ProjectController()._lookup(pname, *remainder)
if project and self.prefix == 'u/':
# make sure user-projects are associated with an enabled user
user = project.user_project_of
if not user or user.disabled:
raise exc.HTTPNotFound
if project.database_configured == False:
if remainder == ('user_icon',):
redirect(g.forge_static('images/user.png'))
elif c.user.username == pname:
log.info('Configuring %s database for access to %r',
pname, remainder)
project.configure_project(is_user_project=True)
else:
raise exc.HTTPNotFound, pname
c.project = project
if project is None or (project.deleted and not has_access(c.project, 'update')()):
raise exc.HTTPNotFound, pname
if project.neighborhood.name != self.neighborhood_name:
redirect(project.url())
return ProjectController(), remainder
@expose('jinja:allura:templates/neighborhood_project_list.html')
@with_trailing_slash
def index(self, sort='alpha', limit=25, page=0, **kw):
c.project = self.neighborhood.neighborhood_project
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['plus']),
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.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):
c.project = self.neighborhood.neighborhood_project
require_access(self.neighborhood, 'register')
c.add_project = W.add_project
form_data.setdefault('tools', [u'Wiki',u'Git',u'Tickets',u'Discussion'])
form_data['neighborhood'] = self.neighborhood.name
return dict(neighborhood=self.neighborhood, form_data=form_data)
@expose('json:')
def suggest_name(self, project_name=''):
provider = plugin.ProjectRegistrationProvider.get()
return dict(suggested_name=provider.suggest_name(project_name,
self.neighborhood))
@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 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()
for i, tool in enumerate(tools):
if (tool.lower() not in anchored_tools.keys()) and (c.project.app_instance(tool) is None):
c.project.install_app(tool, ordinal=i + offset)
flash('Welcome to the %s Project System! '
'To get started, fill out some information about your project.' % config['site_name'])
redirect(c.project.script_name + 'admin/overview')
@expose()
def icon(self, **kw):
icon = self.neighborhood.icon
if not icon:
raise exc.HTTPNotFound
return icon.serve()
@expose('json:')
def users(self):
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.sourceforge.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, *args, **kw):
tool_name = tool_name.lower()
entries = [e for e in c.project.sitemap()
if e.tool_name and e.tool_name.lower() == tool_name]
return dict(entries=entries, type=g.entry_points['tool'][tool_name].tool_label if entries else None)
class ProjectController(FeedController):
def __init__(self):
setattr(self, '_nav.json', self._nav)
self.screenshot = ScreenshotsController()
self._list = ToolListController()
@expose('json:')
def _nav(self):
menu = []
for s in c.project.grouped_navbar_entries():
entry = dict(name=s.label, url=s.url, icon=s.ui_icon, tool_name=s.tool_name)
if s.children:
entry['children'] = [dict(name=child.label, url=child.url, icon=child.ui_icon, tool_name=child.tool_name)
for child in s.children]
menu.append(entry)
return dict(menu=menu)
@expose()
def _lookup(self, name, *remainder):
name = unquote(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
@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': user.project_role().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 = 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())
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=''):
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,
}).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):
return {
'options': [{
'value': u.username,
'label': '%s (%s)' % (u.display_name, u.username)
} for u in c.project.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
return dict(neighborhood=self.neighborhood)
@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=''):
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 = len(grants)
c.project_select = W.project_select(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):
nbhd = self.neighborhood
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')
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()
if M.Project.query.find(dict(neighborhood_id=self.neighborhood._id, deleted=False)).count() < 20000: # arbitrary limit for efficiency
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))
count = len(awards)
return dict(awards=awards or [], count=count)
@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 = len(grants)
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):
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
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):
for ep in iter_entry_points('allura.project_importers', source):
return ep.load()(self.neighborhood), rest