blob: 66bf8500d65a29501ff7173026c0c2d18940c2a1 [file] [log] [blame]
# -*- coding: UTF-8 -*-
#
# 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
from trac.core import Component, TracError, implements
from trac.resource import Neighborhood, Resource, ResourceNotFound
from trac.web.api import HTTPNotFound, IRequestHandler, IRequestFilter
from trac.web.chrome import (
Chrome, INavigationContributor, add_link, add_notice, add_warning,
prevnext_nav, web_context
)
from multiproduct.env import resolve_product_href, lookup_product_env
from multiproduct.hooks import PRODUCT_RE
from multiproduct.model import Product
from multiproduct.env import ProductEnvironment
from multiproduct.util.translation import _
# requests to the following URLs will be skipped in the global scope
# (no more redirection to default product)
IGNORED_REQUESTS_RE = \
re.compile(r'^/(?P<section>milestone|roadmap|search|'
r'(raw-|zip-)?attachment/(ticket|milestone))(?P<pathinfo>.*)')
class ProductModule(Component):
"""Base Product behaviour"""
implements(IRequestFilter, IRequestHandler)
# IRequestFilter methods
def pre_process_request(self, req, handler):
if not isinstance(self.env, ProductEnvironment) and \
IGNORED_REQUESTS_RE.match(req.path_info):
return None
return handler
def post_process_request(req, template, data, content_type):
return template, data, content_type
# IRequestHandler methods
def match_request(self, req):
m = PRODUCT_RE.match(req.path_info)
if m:
req.args['productid'] = m.group('pid')
req.args['pathinfo'] = m.group('pathinfo')
return not m is None
def process_request(self, req):
"""process request handler"""
req.perm.require('PRODUCT_VIEW')
pid = req.args.get('productid', None)
if pid:
req.perm('product', pid).require('PRODUCT_VIEW')
try:
product = Product(self.env, {'prefix': pid})
except ResourceNotFound:
product = Product(self.env)
path_info = req.args.get('pathinfo')
if path_info and path_info != '/':
if not product._exists:
# bh:ticket:561 - Display product list and warning message
if pid:
add_warning(req, _("Product %(pid)s not found", pid=pid))
return self._render_list(req)
else:
raise HTTPNotFound(
_('Unable to render product page. Wrong setup?'))
if pid:
add_link(req, 'up', req.href.products(), _('Products'))
action = req.args.get('action', 'view')
if req.method == 'POST':
if 'cancel' in req.args:
req.redirect(req.href.products(product.prefix))
elif action == 'edit':
return self._do_save(req, product)
elif action == 'delete':
raise TracError(_('Product removal is not allowed!'))
elif action in ('new', 'edit'):
return self._render_editor(req, product)
elif action == 'delete':
raise TracError(_('Product removal is not allowed!'))
if not product._exists:
if pid:
# bh:ticket:561 - Display product list and warning message
add_warning(req, _("Product %(pid)s not found", pid=pid))
return self._render_list(req)
data = {'product': product,
'context': web_context(req, product.resource)}
return 'product_view.html', data, None
def _render_list(self, req):
"""products list"""
products = [p for p in Product.select(self.env)
if 'PRODUCT_VIEW' in req.perm(Neighborhood('product',
p.prefix))]
map(lambda p: setattr(p, 'href', resolve_product_href(
lookup_product_env(self.env, p.prefix), self.env)), products)
data = {'products': products,
'context': web_context(req, Resource('product', None))}
return 'product_list.html', data, None
def _render_editor(self, req, product):
"""common processing for creating rendering the edit page"""
if product._exists:
req.perm(product.resource).require('PRODUCT_MODIFY')
else:
req.perm(product.resource).require('PRODUCT_CREATE')
chrome = Chrome(self.env)
chrome.add_jquery_ui(req)
chrome.add_wiki_toolbars(req)
data = {'product': product,
'context': web_context(req, product.resource)}
return 'product_edit.html', data, None
def _do_save(self, req, product):
"""common processing for product save events"""
req.perm.require('PRODUCT_VIEW')
name = req.args.get('name')
prefix = req.args.get('prefix')
description = req.args.get('description', '')
owner = req.args.get('owner') or req.authname
keys = {'prefix': prefix}
field_data = {'name': name,
'description': description,
'owner': owner,
}
warnings = []
def warn(msg):
add_warning(req, msg)
warnings.append(msg)
if product._exists:
if name != product.name and Product.select(self.env,
where={'name': name}):
warn(_('A product with name "%(name)s" already exists, please '
'choose a different name.', name=name))
elif not name:
warn(_('You must provide a name for the product.'))
else:
req.perm.require('PRODUCT_MODIFY')
product.update_field_dict(field_data)
product.update(req.authname)
add_notice(req, _('Your changes have been saved.'))
else:
req.perm.require('PRODUCT_CREATE')
if not prefix:
warn(_('You must provide a prefix for the product.'))
elif Product.select(self.env, where={'prefix': prefix}):
warn(_('Product "%(id)s" already exists, please choose another '
'prefix.', id=prefix))
if not name:
warn(_('You must provide a name for the product.'))
elif Product.select(self.env, where={'name': name}):
warn(_('A product with name "%(name)s" already exists, please '
'choose a different name.', name=name))
if not warnings:
prod = Product(self.env)
prod.update_field_dict(keys)
prod.update_field_dict(field_data)
prod.insert()
add_notice(req, _('The product "%(id)s" has been added.',
id=prefix))
if warnings:
product.update_field_dict(keys)
product.update_field_dict(field_data)
return self._render_editor(req, product)
req.redirect(req.href.products(prefix))
# helper methods for INavigationContributor implementations
@classmethod
def get_product_path(cls, env, req, itempath):
"""Provide a navigation item path"""
product = req.args.get('productid', '')
if product and env.is_component_enabled(ProductModule):
return req.href('products', product, itempath)
return req.href(itempath)
@classmethod
def get_product_list(cls, env, req, href_fcn=None):
"""Returns a list of products as (prefix, name, url) tuples
"""
if href_fcn is None:
href_fcn = req.href.products
product_list = []
for product in Product.select(env):
if 'PRODUCT_VIEW' in req.perm(Neighborhood('product',
product.prefix).
child(product.resource)):
product_list.append((product.prefix, product.name,
href_fcn(product.prefix)))
return product_list