Add CostFieldRenderer and tweak product view template
				
					
				
			latter being for easier customization
This commit is contained in:
		
							parent
							
								
									95b2ce25c1
								
							
						
					
					
						commit
						3dfe3dfa28
					
				
					 4 changed files with 135 additions and 103 deletions
				
			
		|  | @ -44,7 +44,7 @@ from .vendors import VendorFieldRenderer, PurchaseFieldRenderer | ||||||
| from .products import (GPCFieldRenderer, ScancodeFieldRenderer, | from .products import (GPCFieldRenderer, ScancodeFieldRenderer, | ||||||
|                        DepartmentFieldRenderer, SubdepartmentFieldRenderer, CategoryFieldRenderer, |                        DepartmentFieldRenderer, SubdepartmentFieldRenderer, CategoryFieldRenderer, | ||||||
|                        BrandFieldRenderer, ProductFieldRenderer, |                        BrandFieldRenderer, ProductFieldRenderer, | ||||||
|                        PriceFieldRenderer, PriceWithExpirationFieldRenderer) |                        CostFieldRenderer, PriceFieldRenderer, PriceWithExpirationFieldRenderer) | ||||||
| 
 | 
 | ||||||
| from .custorders import CustomerOrderFieldRenderer | from .custorders import CustomerOrderFieldRenderer | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8; -*- | ||||||
| ################################################################################ | ################################################################################ | ||||||
| # | # | ||||||
| #  Rattail -- Retail Software Framework | #  Rattail -- Retail Software Framework | ||||||
| #  Copyright © 2010-2016 Lance Edgar | #  Copyright © 2010-2017 Lance Edgar | ||||||
| # | # | ||||||
| #  This file is part of Rattail. | #  This file is part of Rattail. | ||||||
| # | # | ||||||
|  | @ -30,6 +30,7 @@ from rattail.gpc import GPC | ||||||
| from rattail.db import model | from rattail.db import model | ||||||
| from rattail.db.util import maxlen | from rattail.db.util import maxlen | ||||||
| 
 | 
 | ||||||
|  | import formalchemy as fa | ||||||
| from formalchemy import TextFieldRenderer | from formalchemy import TextFieldRenderer | ||||||
| from formalchemy.fields import SelectFieldRenderer | from formalchemy.fields import SelectFieldRenderer | ||||||
| from webhelpers.html import tags, literal | from webhelpers.html import tags, literal | ||||||
|  | @ -158,6 +159,18 @@ class BrandFieldRenderer(AutocompleteFieldRenderer): | ||||||
|     service_route = 'brands.autocomplete' |     service_route = 'brands.autocomplete' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class CostFieldRenderer(fa.FieldRenderer): | ||||||
|  |     """ | ||||||
|  |     Renders fields which reference a ProductCost object | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def render_readonly(self, **kwargs): | ||||||
|  |         cost = self.raw_value | ||||||
|  |         if not cost: | ||||||
|  |             return '' | ||||||
|  |         return '${:0.2f}'.format(cost.unit_cost) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class PriceFieldRenderer(TextFieldRenderer): | class PriceFieldRenderer(TextFieldRenderer): | ||||||
|     """ |     """ | ||||||
|     Renderer for fields which reference a :class:`ProductPrice` instance. |     Renderer for fields which reference a :class:`ProductPrice` instance. | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| ## -*- coding: utf-8 -*- | ## -*- coding: utf-8; -*- | ||||||
| <%inherit file="/master/view.mako" /> | <%inherit file="/master/view.mako" /> | ||||||
| <%namespace file="/forms/lib.mako" import="render_field_readonly" /> | <%namespace file="/forms/lib.mako" import="render_field_readonly" /> | ||||||
| 
 | 
 | ||||||
| <%def name="head_tags()"> | <%def name="extra_styles()"> | ||||||
|   ${parent.head_tags()} |   ${parent.extra_styles()} | ||||||
|   <style type="text/css"> |   <style type="text/css"> | ||||||
|     #product-main { |     #product-main { | ||||||
|         margin-top: 1em; |         margin-top: 1em; | ||||||
|  | @ -20,6 +20,52 @@ | ||||||
|   </style> |   </style> | ||||||
| </%def> | </%def> | ||||||
| 
 | 
 | ||||||
|  | ############################## | ||||||
|  | ## page body | ||||||
|  | ############################## | ||||||
|  | 
 | ||||||
|  | <div class="form-wrapper"> | ||||||
|  |   <ul class="context-menu"> | ||||||
|  |     ${self.context_menu_items()} | ||||||
|  |   </ul> | ||||||
|  | 
 | ||||||
|  |   <div class="panel" id="product-main"> | ||||||
|  |     <h2>Product</h2> | ||||||
|  |     <div class="panel-body"> | ||||||
|  |       <div style="clear: none; float: left;"> | ||||||
|  |         ${self.render_main_fields(form)} | ||||||
|  |       </div> | ||||||
|  |       % if image_url: | ||||||
|  |           % if image_path is not Undefined: | ||||||
|  |               ${h.image(image_url, "Product Image", id='product-image', path=image_path, use_pil=False)} | ||||||
|  |           % else: | ||||||
|  |               ${h.image(image_url, "Product Image", id='product-image', width=150, height=150)} | ||||||
|  |           % endif | ||||||
|  |       % endif | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  |   <div class="panel-wrapper"> <!-- left column --> | ||||||
|  | 
 | ||||||
|  |     ${self.left_column()} | ||||||
|  | 
 | ||||||
|  |   </div> <!-- left column --> | ||||||
|  | 
 | ||||||
|  |   <div class="panel-wrapper"> <!-- right column --> | ||||||
|  | 
 | ||||||
|  |     ${self.right_column()} | ||||||
|  | 
 | ||||||
|  |   </div> <!-- right column --> | ||||||
|  | 
 | ||||||
|  |   % if buttons: | ||||||
|  |       ${buttons|n} | ||||||
|  |   % endif | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | ############################## | ||||||
|  | ## rendering methods | ||||||
|  | ############################## | ||||||
|  | 
 | ||||||
| <%def name="context_menu_items()"> | <%def name="context_menu_items()"> | ||||||
|   ${parent.context_menu_items()} |   ${parent.context_menu_items()} | ||||||
|   % if version_count is not Undefined and request.has_perm('instance.versions.view'): |   % if version_count is not Undefined and request.has_perm('instance.versions.view'): | ||||||
|  | @ -38,6 +84,37 @@ | ||||||
|   ${self.extra_main_fields(form)} |   ${self.extra_main_fields(form)} | ||||||
| </%def> | </%def> | ||||||
| 
 | 
 | ||||||
|  | <%def name="left_column()"> | ||||||
|  |   <div class="panel"> | ||||||
|  |     <h2>Pricing</h2> | ||||||
|  |     <div class="panel-body"> | ||||||
|  |       ${self.render_price_fields(form)} | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <div class="panel"> | ||||||
|  |     <h2>Flags</h2> | ||||||
|  |     <div class="panel-body"> | ||||||
|  |       ${self.render_flag_fields(form)} | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   ${self.extra_left_panels()} | ||||||
|  | </%def> | ||||||
|  | 
 | ||||||
|  | <%def name="right_column()"> | ||||||
|  |   <div class="panel"> | ||||||
|  |     <h2>Organization</h2> | ||||||
|  |     <div class="panel-body"> | ||||||
|  |       ${self.render_organization_fields(form)} | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   ${self.movement_panel()} | ||||||
|  |   ${self.sources_panel()} | ||||||
|  |   ${self.notes_panel()} | ||||||
|  |   ${self.ingredients_panel()} | ||||||
|  |   ${self.lookup_codes_panel()} | ||||||
|  |   ${self.extra_right_panels()} | ||||||
|  | </%def> | ||||||
|  | 
 | ||||||
| <%def name="extra_main_fields(form)"></%def> | <%def name="extra_main_fields(form)"></%def> | ||||||
| 
 | 
 | ||||||
| <%def name="render_organization_fields(form)"> | <%def name="render_organization_fields(form)"> | ||||||
|  | @ -101,6 +178,36 @@ | ||||||
|   </div> |   </div> | ||||||
| </%def> | </%def> | ||||||
| 
 | 
 | ||||||
|  | <%def name="sources_panel()"> | ||||||
|  |   <div class="panel-grid" id="product-costs"> | ||||||
|  |     <h2>Vendor Sources</h2> | ||||||
|  |     <div class="newgrid full no-border"> | ||||||
|  |       <table> | ||||||
|  |         <thead> | ||||||
|  |           <th>Pref.</th> | ||||||
|  |           <th>Vendor</th> | ||||||
|  |           <th>Code</th> | ||||||
|  |           <th>Case Size</th> | ||||||
|  |           <th>Case Cost</th> | ||||||
|  |           <th>Unit Cost</th> | ||||||
|  |         </thead> | ||||||
|  |         <tbody> | ||||||
|  |           % for cost in instance.costs: | ||||||
|  |               <tr> | ||||||
|  |                 <td class="center">${'X' if cost.preference == 1 else ''}</td> | ||||||
|  |                 <td>${cost.vendor}</td> | ||||||
|  |                 <td class="center">${cost.code or ''}</td> | ||||||
|  |                 <td class="center">${h.pretty_quantity(cost.case_size)}</td> | ||||||
|  |                 <td class="right">${'$ %0.2f' % cost.case_cost if cost.case_cost is not None else ''}</td> | ||||||
|  |                 <td class="right">${'$ %0.4f' % cost.unit_cost if cost.unit_cost is not None else ''}</td> | ||||||
|  |               </tr> | ||||||
|  |           % endfor | ||||||
|  |         </tbody> | ||||||
|  |       </table> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </%def> | ||||||
|  | 
 | ||||||
| <%def name="notes_panel()"> | <%def name="notes_panel()"> | ||||||
|   <div class="panel"> |   <div class="panel"> | ||||||
|     <h2>Notes</h2> |     <h2>Notes</h2> | ||||||
|  | @ -119,102 +226,6 @@ | ||||||
|   </div> |   </div> | ||||||
| </%def> | </%def> | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| <div class="form-wrapper"> |  | ||||||
|   <ul class="context-menu"> |  | ||||||
|     ${self.context_menu_items()} |  | ||||||
|   </ul> |  | ||||||
| 
 |  | ||||||
|   <div class="panel" id="product-main"> |  | ||||||
|     <h2>Product</h2> |  | ||||||
|     <div class="panel-body"> |  | ||||||
|       <div style="clear: none; float: left;"> |  | ||||||
|         ${self.render_main_fields(form)} |  | ||||||
|       </div> |  | ||||||
|       % if image_url: |  | ||||||
|           % if image_path is not Undefined: |  | ||||||
|               ${h.image(image_url, "Product Image", id='product-image', path=image_path, use_pil=False)} |  | ||||||
|           % else: |  | ||||||
|               ${h.image(image_url, "Product Image", id='product-image', width=150, height=150)} |  | ||||||
|           % endif |  | ||||||
|       % endif |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| 
 |  | ||||||
|   <div class="panel-wrapper"> <!-- left column --> |  | ||||||
| 
 |  | ||||||
|     <div class="panel"> |  | ||||||
|       <h2>Pricing</h2> |  | ||||||
|       <div class="panel-body"> |  | ||||||
|         ${self.render_price_fields(form)} |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     <div class="panel"> |  | ||||||
|       <h2>Flags</h2> |  | ||||||
|       <div class="panel-body"> |  | ||||||
|         ${self.render_flag_fields(form)} |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     ${self.extra_left_panels()} |  | ||||||
| 
 |  | ||||||
|   </div> <!-- left column --> |  | ||||||
| 
 |  | ||||||
|   <div class="panel-wrapper"> <!-- right column --> |  | ||||||
| 
 |  | ||||||
|     <div class="panel"> |  | ||||||
|       <h2>Organization</h2> |  | ||||||
|       <div class="panel-body"> |  | ||||||
|         ${self.render_organization_fields(form)} |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     ${self.movement_panel()} |  | ||||||
| 
 |  | ||||||
|     <div class="panel-grid" id="product-costs"> |  | ||||||
|       <h2>Vendor Sources</h2> |  | ||||||
|       <div class="newgrid full no-border"> |  | ||||||
|         <table> |  | ||||||
|           <thead> |  | ||||||
|             <th>Pref.</th> |  | ||||||
|             <th>Vendor</th> |  | ||||||
|             <th>Code</th> |  | ||||||
|             <th>Case Size</th> |  | ||||||
|             <th>Case Cost</th> |  | ||||||
|             <th>Unit Cost</th> |  | ||||||
|           </thead> |  | ||||||
|           <tbody> |  | ||||||
|             % for cost in instance.costs: |  | ||||||
|                 <tr> |  | ||||||
|                   <td class="center">${'X' if cost.preference == 1 else ''}</td> |  | ||||||
|                   <td>${cost.vendor}</td> |  | ||||||
|                   <td class="center">${cost.code or ''}</td> |  | ||||||
|                   <td class="center">${h.pretty_quantity(cost.case_size)}</td> |  | ||||||
|                   <td class="right">${'$ %0.2f' % cost.case_cost if cost.case_cost is not None else ''}</td> |  | ||||||
|                   <td class="right">${'$ %0.4f' % cost.unit_cost if cost.unit_cost is not None else ''}</td> |  | ||||||
|                 </tr> |  | ||||||
|             % endfor |  | ||||||
|           </tbody> |  | ||||||
|         </table> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     ${self.notes_panel()} |  | ||||||
| 
 |  | ||||||
|     ${self.ingredients_panel()} |  | ||||||
| 
 |  | ||||||
|     ${self.lookup_codes_panel()} |  | ||||||
| 
 |  | ||||||
|     ${self.extra_right_panels()} |  | ||||||
| 
 |  | ||||||
|   </div> <!-- right column --> |  | ||||||
| 
 |  | ||||||
|   % if buttons: |  | ||||||
|       ${buttons|n} |  | ||||||
|   % endif |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
| <%def name="extra_left_panels()"></%def> | <%def name="extra_left_panels()"></%def> | ||||||
| 
 | 
 | ||||||
| <%def name="extra_right_panels()"></%def> | <%def name="extra_right_panels()"></%def> | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8; -*- | ||||||
| ################################################################################ | ################################################################################ | ||||||
| # | # | ||||||
| #  Rattail -- Retail Software Framework | #  Rattail -- Retail Software Framework | ||||||
|  | @ -216,6 +216,14 @@ class ProductsView(MasterView): | ||||||
|          |          | ||||||
|         g.vendor.set(label="Pref. Vendor") |         g.vendor.set(label="Pref. Vendor") | ||||||
| 
 | 
 | ||||||
|  |         g.joiners['cost'] = lambda q: q.outerjoin(model.ProductCost, | ||||||
|  |                                                   sa.and_( | ||||||
|  |                                                       model.ProductCost.product_uuid == model.Product.uuid, | ||||||
|  |                                                       model.ProductCost.preference == 1)) | ||||||
|  |         g.sorters['cost'] = g.make_sorter(model.ProductCost.unit_cost) | ||||||
|  |         g.filters['cost'] = g.make_filter('cost', model.ProductCost.unit_cost) | ||||||
|  |         g.cost.set(renderer=forms.renderers.CostFieldRenderer) | ||||||
|  | 
 | ||||||
|         g.default_sortkey = 'upc' |         g.default_sortkey = 'upc' | ||||||
| 
 | 
 | ||||||
|         if self.print_labels and self.request.has_perm('products.print_labels'): |         if self.print_labels and self.request.has_perm('products.print_labels'): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar