Commits

g...@13f79535-47bb-0310-9956-ffa450edef68  committed 9a993f3 Draft

products: basic list, view, create and edit pages for products #37

  • Participants
  • Parent commits 94038f4

Comments (0)

Files changed (5)

File multiproduct/templates/product_delete.html

+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:i18n="http://genshi.edgewall.org/i18n"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="layout.html" />
+  <head>
+    <title>Delete Product ${product.name}</title>
+  </head>
+
+  <body>
+    <div id="content" class="product">
+      <h1>Delete Product ${product.name}</h1>
+
+    <form id="edit" action="" method="post">
+      <div>
+        <input type="hidden" name="action" value="delete" />
+        <p><strong>Are you sure you want to delete this product?</strong></p>
+      </div>
+      <div class="buttons">
+        <input type="submit" name="cancel" value="${_('Cancel')}" />
+        <input type="submit" value="${_('Delete product')}" />
+      </div>
+    </form>
+
+    </div>
+  </body>
+</html>

File multiproduct/templates/product_edit.html

+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:i18n="http://genshi.edgewall.org/i18n"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="layout.html" />
+  <head>
+    <py:choose test="product._exists">
+      <title py:when="True">Edit Product ${product.name}</title>
+      <title py:otherwise="">New Product</title>
+    </py:choose>
+  </head>
+
+  <body>
+    <div id="content" class="product">
+      <py:choose test="product._exists">
+        <h1 py:when="True">Edit Product ${product.name}</h1>
+        <h1 py:otherwise="">New Product</h1>
+      </py:choose>
+
+      <form id="edit" action="" method="post">
+        <div class="field">
+          <input type="hidden" name="action" value="edit" />
+          <label>Short name for the product (prefix):<br />
+            <input type="text" id="prefix" name="prefix" size="32" value="${product.prefix or req.args.get('prefix')}" readonly="${product._exists and 'readonly'}"/>
+          </label>
+          <label>Name of the product:<br />
+            <input type="text" id="name" name="name" size="32" value="${product.name or req.args.get('name')}" />
+          </label>
+        </div>
+        <div class="field">
+          <fieldset class="iefix">
+            <label for="description" i18n:msg="">Description (you may use <a tabindex="42"
+                   href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here):</label>
+            <p><textarea id="description" name="description" class="wikitext trac-resizable" rows="10" cols="78">
+${product.description}</textarea></p>
+          </fieldset>
+        </div>
+        <div class="buttons" py:choose="product._exists">
+          <input py:when="True" type="submit" value="${_('Submit changes')}" />
+          <input py:otherwise="" type="submit" value="${_('Add Product')}" />
+          <input type="submit" name="cancel" value="${_('Cancel')}" />
+        </div>
+      </form>
+    </div>
+  </body>
+</html>

File multiproduct/templates/product_list.html

+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:i18n="http://genshi.edgewall.org/i18n"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="layout.html" />
+  <head>
+    <title>Products</title>
+  </head>
+
+  <body>
+    <div id="content" class="products">
+      <h1>Products</h1>
+
+      <div class="products">
+        <div py:for="product in products" class="product">
+
+          <div class="info trac-progress">
+            <h2><a href="${href.products(product.prefix)}">
+              Product: <em>${product.name}</em>
+            </a></h2>
+          </div>
+
+          <div class="description" xml:space="preserve">
+            ${wiki_to_html(context.child(product.resource), product.description)}
+          </div>
+
+        </div>
+      </div>
+
+      <div py:if="'PRODUCT_CREATE' in perm" class="buttons">
+       <form method="get" action="${href.products()}"><div>
+        <input type="hidden" name="action" value="new" />
+        <input type="submit" value="${_('Add new product')}" />
+       </div></form>
+      </div>
+
+    </div>
+  </body>
+</html>

File multiproduct/templates/product_view.html

+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:i18n="http://genshi.edgewall.org/i18n"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="layout.html" />
+  <head>
+    <title>Product ${product.name}</title>
+    <link py:if="'PRODUCT_MODIFY' in perm(product.resource)" rel="alternate" type="application/x-wiki"
+          title="Edit this product" href="${href.products(product.prefix, action='edit')}" />
+  </head>
+
+  <body>
+    <div id="content" class="product">
+      <h1>Product ${product.name}</h1>
+      <div class="description" xml:space="preserve">
+        ${wiki_to_html(context, product.description)}
+      </div>
+
+      <div py:if="'PRODUCT_MODIFY' in perm(product.resource) or
+                  'PRODUCT_DELETE' in perm(product.resource)"
+           class="buttons">
+        <form py:if="'PRODUCT_MODIFY' in perm(product.resource)" method="get" action="" id="editproduct">
+          <div>
+            <input type="hidden" name="action" value="edit" />
+            <input type="submit" value="${_('Edit product')}" />
+          </div>
+        </form>
+        <form py:if="'PRODUCT_DELETE' in perm(product.resource)" method="get" action="" id="deleteproduct">
+          <div>
+            <input type="hidden" name="action" value="delete" />
+            <input type="submit" value="${_('Delete product')}" />
+          </div>
+        </form>
+      </div>
+
+    </div>
+  </body>
+</html>

File multiproduct/web_ui.py

 """
 import re
 
+from genshi.builder import tag
+
 from trac.core import Component, implements, TracError
+from trac.web.chrome import (add_link, add_notice, add_warning, prevnext_nav,
+                             Chrome, INavigationContributor)
 from trac.resource import ResourceNotFound
 from trac.util.translation import _
 from trac.web.api import IRequestFilter, IRequestHandler, Request, HTTPNotFound
 class ProductModule(Component):
     """Base Product behaviour"""
     
-    implements(IRequestFilter, IRequestHandler)
+    implements(IRequestFilter, IRequestHandler, INavigationContributor)
+    
+    def get_active_navigation_item(self, req):
+        return 'products'
+    
+    def get_navigation_items(self, req):
+        if 'PRODUCT_VIEW' in req.perm:
+            yield ('mainnav', 'products',
+                   tag.a(_('Products'), href=req.href.products(), accesskey=3))
     
     # IRequestFilter methods
     def pre_process_request(self, req, handler):
     
     def process_request(self, req):
         """process request handler"""
-        if req.args.get('productid', None):
-            return 'product.html', None, None
-        return 'product_list.html', None, None
+        
+        req.perm.require('PRODUCT_VIEW')
+        pid = req.args.get('productid', None)
+        action = req.args.get('action', 'view')
+        
+        products = [p for p in Product.select(self.env)
+                    if 'PRODUCT_VIEW' in req.perm(p.resource)]
+        
+        if pid is not None:
+            add_link(req, 'up', req.href.products(), _('Products'))
+        
+        try:
+            product = Product(self.env, {'prefix': pid})
+        except ResourceNotFound:
+            product = Product(self.env)
+        
+        data = {'product': product}
+        
+        if req.method == 'POST':
+            if req.args.has_key('cancel'):
+                req.redirect(req.href.products(product.prefix))
+            elif action == 'edit':
+                return self._do_save(req, product)
+            elif action == 'delete':
+                req.perm(product.resource).require('PRODUCT_DELETE')
+                retarget_to = req.args.get('retarget', None)
+                name = product.name
+                product.delete(resources_to=retarget_to)
+                add_notice(req, _('The product "%(n)s" has been deleted.',
+                                  n = name))
+                req.redirect(req.href.products())
+        elif action in ('new', 'edit'):
+            return self._render_editor(req, product)
+        elif action == 'delete':
+            req.perm(product.resource).require('PRODUCT_DELETE')
+            return 'product_delete.html', data, None
+        
+        if pid is None:
+            data = {'products': products}
+            return 'product_list.html', data, None
+        
+        def add_product_link(rel, product):
+            href = req.href.products(product.prefix)
+            add_link(req, rel, href, _('Product "%(name)s"',
+                                       name=product.name))
+        
+        idx = [i for i, p in enumerate(products) if p.name == product.name]
+        if idx:
+            idx = idx[0]
+            if idx > 0:
+                add_product_link('first', products[0])
+                add_product_link('prev', products[idx - 1])
+            if idx < len(products) - 1:
+                add_product_link('next', products[idx + 1])
+                add_product_link('last', products[-1])        
+        prevnext_nav(req, _('Previous Product'), _('Next Product'),
+                     _('Back to Product List'))
+        return 'product_view.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}
+        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')
+        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()
+                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