Browse Source

added search for orders, users and consumables

php2python
Holger Frey 7 years ago
parent
commit
d9d630a2af
  1. 14
      ordr2/models/orders.py
  2. 31
      ordr2/resources/admin.py
  3. 16
      ordr2/resources/orders.py
  4. 11
      ordr2/templates/admin/consumable_list.jinja2
  5. 4
      ordr2/templates/admin/user_list.jinja2
  6. 8
      ordr2/templates/orders/list.jinja2
  7. 30
      ordr2/views/admin.py
  8. 83
      ordr2/views/orders.py
  9. 1
      setup.py

14
ordr2/models/orders.py

@ -6,7 +6,7 @@ from collections import namedtuple
from datetime import datetime from datetime import datetime
from sqlalchemy import ( from sqlalchemy import (
Column, Column,
Date, DateTime,
Enum, Enum,
Float, Float,
Integer, Integer,
@ -47,9 +47,9 @@ class Consumable(Base):
unit_price = Column(Float, nullable=False) unit_price = Column(Float, nullable=False)
currency = Column(Text, nullable=False, default='EUR') currency = Column(Text, nullable=False, default='EUR')
comment = Column(Text, nullable=False, default='') comment = Column(Text, nullable=False, default='')
date_created = Column(Date, nullable=False, default=datetime.utcnow) date_created = Column(DateTime, nullable=False, default=datetime.utcnow)
date_modified = Column( date_modified = Column(
Date, DateTime,
nullable=False, nullable=False,
default=datetime.utcnow, default=datetime.utcnow,
onupdate=datetime.utcnow onupdate=datetime.utcnow
@ -83,13 +83,13 @@ class Order(Base):
account = Column(Text, nullable=False, default='') account = Column(Text, nullable=False, default='')
comment = Column(Text, nullable=False, default='') comment = Column(Text, nullable=False, default='')
created_date = Column(Date, nullable=False, default=datetime.utcnow) created_date = Column(DateTime, nullable=False, default=datetime.utcnow)
created_by = Column(Text, nullable=False) created_by = Column(Text, nullable=False)
approval_date = Column(Date, nullable=True) approval_date = Column(DateTime, nullable=True)
approval_by = Column(Text, nullable=False, default='') approval_by = Column(Text, nullable=False, default='')
ordered_date = Column(Date, nullable=True) ordered_date = Column(DateTime, nullable=True)
ordered_by = Column(Text, nullable=False, default='') ordered_by = Column(Text, nullable=False, default='')
completed_date = Column(Date, nullable=True) completed_date = Column(DateTime, nullable=True)
completed_by = Column(Text, nullable=False, default='') completed_by = Column(Text, nullable=False, default='')

31
ordr2/resources/admin.py

@ -1,3 +1,5 @@
from sqlalchemy import or_
from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone
from .base import BaseResource, PaginationResourceMixin from .base import BaseResource, PaginationResourceMixin
@ -34,6 +36,7 @@ class UserList(BaseResource, PaginationResourceMixin):
def prepare_filtered_query(self, dbsession, filter_params): def prepare_filtered_query(self, dbsession, filter_params):
''' setup the base filtered query ''' ''' setup the base filtered query '''
query = dbsession.query(self.sql_model_class) query = dbsession.query(self.sql_model_class)
role_name = filter_params.get('role', None) role_name = filter_params.get('role', None)
try: try:
role_name = role_name.lower() role_name = role_name.lower()
@ -42,6 +45,20 @@ class UserList(BaseResource, PaginationResourceMixin):
except (AttributeError, ValueError): except (AttributeError, ValueError):
role_name = None role_name = None
self.filters['role'] = role_name self.filters['role'] = role_name
search = filter_params.get('search', None)
if search:
term = '%{}%'.format(search)
query = query.filter(
or_(
self.sql_model_class.user_name.ilike(term),
self.sql_model_class.first_name.ilike(term),
self.sql_model_class.last_name.ilike(term),
self.sql_model_class.email.ilike(term)
)
)
self.filters['search'] = search
return query return query
@ -98,6 +115,7 @@ class ConsumableList(BaseResource, PaginationResourceMixin):
def prepare_filtered_query(self, dbsession, filter_params): def prepare_filtered_query(self, dbsession, filter_params):
''' setup the base filtered query ''' ''' setup the base filtered query '''
query = dbsession.query(self.sql_model_class) query = dbsession.query(self.sql_model_class)
category_name = filter_params.get('category', None) category_name = filter_params.get('category', None)
try: try:
category_name = category_name.lower() category_name = category_name.lower()
@ -106,6 +124,19 @@ class ConsumableList(BaseResource, PaginationResourceMixin):
except (AttributeError, ValueError): except (AttributeError, ValueError):
category_name = None category_name = None
self.filters['category'] = category_name self.filters['category'] = category_name
search = filter_params.get('search', None)
if search:
term = '%{}%'.format(search)
query = query.filter(
or_(
self.sql_model_class.cas_description.ilike(term),
self.sql_model_class.vendor.ilike(term),
self.sql_model_class.catalog_nr.ilike(term)
)
)
self.filters['search'] = search
return query return query

16
ordr2/resources/orders.py

@ -1,3 +1,5 @@
from sqlalchemy import or_
from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone
from .base import BaseResource, PaginationResourceMixin from .base import BaseResource, PaginationResourceMixin
@ -54,6 +56,20 @@ class OrderList(BaseResource, PaginationResourceMixin):
query = query.filter_by(created_by=user_name) query = query.filter_by(created_by=user_name)
self.filters['user'] = user_name self.filters['user'] = user_name
search = filter_params.get('search', None)
if search:
term = '%{}%'.format(search)
query = query.filter(
or_(
self.sql_model_class.cas_description.ilike(term),
self.sql_model_class.vendor.ilike(term),
self.sql_model_class.catalog_nr.ilike(term),
self.sql_model_class.account.ilike(term),
self.sql_model_class.created_by.ilike(term)
)
)
self.filters['search'] = search
return query return query

11
ordr2/templates/admin/consumable_list.jinja2

@ -14,11 +14,20 @@
Consumables Consumables
</h1> </h1>
</div> </div>
{{ macros.filter_box('Category', 'category', categories) }} {{ macros.filter_box('Category', 'category', categories, {'search':None}) }}
</div> </div>
<div class="span10"> <div class="span10">
<div class="page-controls"> <div class="page-controls">
<form action="{{ request.resource_url(context, 'actions') }}" method="POST">
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}">
<div class="input-append search">
<input type="search" name="search" size="30" placeholder="Search" value="{{ context.filters.get('search', None) or '' }}">
<label class="add-on">
<button type="submit" class="search" name="action" value="search">Search</button>
</label>
</div>
</form>
<div class="actions"> <div class="actions">
<a href="{{ request.resource_url(context, 'new') }}" rel="tooltip" data-original-title="New" class="btn-flat single"><i class="add"></i></a> <a href="{{ request.resource_url(context, 'new') }}" rel="tooltip" data-original-title="New" class="btn-flat single"><i class="add"></i></a>
</div> </div>

4
ordr2/templates/admin/user_list.jinja2

@ -14,7 +14,7 @@
Users Users
</h1> </h1>
</div> </div>
{{ macros.filter_box('Role', 'role', roles) }} {{ macros.filter_box('Role', 'role', roles, {'search':None}) }}
</div> </div>
<div class="span10"> <div class="span10">
@ -22,7 +22,7 @@
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> <input type="hidden" name="csrf_token" value="{{get_csrf_token()}}">
<div class="page-controls"> <div class="page-controls">
<div class="input-append search"> <div class="input-append search">
<input type="search" name="search" size="30" placeholder="Search"> <input type="search" name="search" size="30" placeholder="Search" value="{{ context.filters.get('search', None) or '' }}">
<label class="add-on"> <label class="add-on">
<button type="submit" class="search" name="action" value="search">Search</button> <button type="submit" class="search" name="action" value="search">Search</button>
</label> </label>

8
ordr2/templates/orders/list.jinja2

@ -11,11 +11,11 @@
<div class="span2"> <div class="span2">
<div class="page-controls"> <div class="page-controls">
<h1> <h1>
Users Orders
</h1> </h1>
</div> </div>
{{ macros.filter_box('All Orders', 'status', stati, {'user': None} ) }} {{ macros.filter_box('All Orders', 'status', stati, {'user': None, 'search':None} ) }}
{{ macros.filter_box('My Orders', 'status', stati, {'user': request.user.user_name} ) }} {{ macros.filter_box('My Orders', 'status', stati, {'user': request.user.user_name, 'search':None} ) }}
</div> </div>
<div class="span10"> <div class="span10">
@ -23,7 +23,7 @@
<input type="hidden" name="csrf_token" value="{{get_csrf_token()}}"> <input type="hidden" name="csrf_token" value="{{get_csrf_token()}}">
<div class="page-controls"> <div class="page-controls">
<div class="input-append search"> <div class="input-append search">
<input type="search" name="search" size="30" placeholder="Search"> <input type="search" name="search" size="30" placeholder="Search" value="{{ context.filters.get('search', None) or '' }}">
<label class="add-on"> <label class="add-on">
<button type="submit" class="search" name="action" value="search">Search</button> <button type="submit" class="search" name="action" value="search">Search</button>
</label> </label>

30
ordr2/views/admin.py

@ -63,6 +63,21 @@ def change_column_view(context, request):
return HTTPFound(context.url()) return HTTPFound(context.url())
@view_config(
context='ordr2:resources.UserList',
name='actions',
request_param='action=search',
permission='view',
request_method='POST'
)
def user_search(context, request):
term = request.POST.get('search', '')
term = term.strip()
if term:
return HTTPFound(context.url(search=term, role=None, p=1))
return HTTPFound(context.url())
@view_config( @view_config(
context='ordr2:resources.UserList', context='ordr2:resources.UserList',
name='actions', name='actions',
@ -271,6 +286,21 @@ def consumable_list(context, request):
return {'consumables': consumables, 'categories': categories} return {'consumables': consumables, 'categories': categories}
@view_config(
context='ordr2:resources.ConsumableList',
name='actions',
request_param='action=search',
permission='view',
request_method='POST'
)
def consumable_search(context, request):
term = request.POST.get('search', '')
term = term.strip()
if term:
return HTTPFound(context.url(search=term, category=None, p=1))
return HTTPFound(context.url())
@view_config( @view_config(
context='ordr2:resources.ConsumableList', context='ordr2:resources.ConsumableList',
name='new', name='new',

83
ordr2/views/orders.py

@ -1,10 +1,13 @@
import deform import deform
import io
import xlsxwriter
from datetime import datetime from datetime import datetime
from collections import OrderedDict from collections import OrderedDict
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.renderers import render from pyramid.renderers import render
from pyramid.response import FileIter
from pyramid.view import view_config from pyramid.view import view_config
from ordr2.events import OrderStatusChange from ordr2.events import OrderStatusChange
@ -58,6 +61,86 @@ def change_column_view(context, request):
return HTTPFound(context.url()) return HTTPFound(context.url())
@view_config(
context='ordr2:resources.OrderList',
name='actions',
request_param='action=export',
permission='view',
request_method='POST',
renderer='ordr2:templates/orders/edit_multiple_stati.jinja2'
)
def download_view(context, request):
''' see https://xlsxwriter.readthedocs.io/example_http_server3.html '''
# Create an in-memory output file for the new workbook.
output = io.BytesIO()
# Even though the final file will be in memory the module uses temp
# files during assembly for efficiency. To avoid this on servers that
# don't allow temp files, for example the Google APP Engine, set the
# 'in_memory' constructor option to True:
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
worksheet = workbook.add_worksheet()
# formatting
bold = workbook.add_format({'bold': 1})
# Add a number format for cells with money.
money_format = workbook.add_format({'num_format': '0.00'})
# Add an Excel date format.
date_format = workbook.add_format({'num_format': 'yy-mm-dd hh:mm'})
# Write the column headers
headers = [
'Placed On', 'CAS / Description', 'Vendor', 'Catalog Nr',
'Package Size', 'Unit Price', 'Quantity', 'Total Price', 'Currency',
'Account', 'Status', 'Category', 'Placed By'
]
for col, header in enumerate(headers):
worksheet.write(0, col, header, bold)
for row, resource in enumerate(context.items()):
order = resource.model
worksheet.write(row + 1, 0, order.created_date, date_format)
worksheet.write_string(row + 1, 1, order.cas_description)
worksheet.write_string(row + 1, 2, order.vendor)
worksheet.write_string(row + 1, 3, order.catalog_nr)
worksheet.write_string(row + 1, 4, order.package_size)
worksheet.write(row + 1, 5, order.unit_price, money_format)
worksheet.write(row + 1, 6, order.amount)
worksheet.write(row + 1, 7, order.total_price, money_format)
worksheet.write_string(row + 1, 8, order.currency)
worksheet.write_string(row + 1, 9, order.account)
worksheet.write_string(row + 1, 10, order.status.value.capitalize())
worksheet.write_string(row + 1, 11, order.category.value.capitalize())
worksheet.write_string(row + 1, 12, order.created_by)
# Close the workbook before streaming the data.
workbook.close()
# Rewind the buffer.
output.seek(0)
response = request.response
response.app_iter = FileIter(output)
headers = response.headers
headers['Content-Type'] = 'application/download'
headers['Accept-Ranges'] = 'bite'
headers['Content-Disposition'] = 'attachment;filename=order-list.xlsx'
return response
@view_config(
context='ordr2:resources.OrderList',
name='actions',
request_param='action=search',
permission='view',
request_method='POST'
)
def search(context, request):
term = request.POST.get('search', '')
term = term.strip()
if term:
return HTTPFound(context.url(search=term, user=None, status=None, p=1))
return HTTPFound(context.url())
@view_config( @view_config(
context='ordr2:resources.OrderList', context='ordr2:resources.OrderList',
name='actions', name='actions',

1
setup.py

@ -25,6 +25,7 @@ requirements = [
'deform', 'deform',
'PyYAML', 'PyYAML',
'tqdm', 'tqdm',
'XlsxWriter',
] ]
setup_requirements = [ setup_requirements = [