Remove legacy 'forms' package and templates
yay!
This commit is contained in:
parent
66769ab34b
commit
d0b78babd2
|
@ -1,39 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Forms
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from formencode import Schema
|
|
||||||
|
|
||||||
from .core import Form, Field, FieldSet, GenericFieldSet
|
|
||||||
from .simpleform import SimpleForm, FormRenderer
|
|
||||||
from .alchemy import AlchemyForm
|
|
||||||
from .fields import AssociationProxyField
|
|
||||||
from .renderers import *
|
|
||||||
|
|
||||||
from . import fields
|
|
||||||
from . import renderers
|
|
||||||
from . import validators
|
|
|
@ -1,117 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
FormAlchemy Forms
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from rattail.core import Object
|
|
||||||
|
|
||||||
import formalchemy as fa
|
|
||||||
from pyramid.renderers import render
|
|
||||||
from webhelpers2.html import HTML, tags
|
|
||||||
|
|
||||||
from tailbone.db import Session
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateEngine(fa.templates.TemplateEngine):
|
|
||||||
"""
|
|
||||||
Mako template engine for FormAlchemy.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render(self, template, prefix='/forms/', suffix='.mako', **kwargs):
|
|
||||||
template = ''.join((prefix, template, suffix))
|
|
||||||
return render(template, kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AlchemyForm(Object):
|
|
||||||
"""
|
|
||||||
Form to contain a :class:`formalchemy.FieldSet` instance.
|
|
||||||
"""
|
|
||||||
id = None
|
|
||||||
create_label = "Create"
|
|
||||||
update_label = "Save"
|
|
||||||
|
|
||||||
allow_successive_creates = False
|
|
||||||
|
|
||||||
def __init__(self, request, fieldset, session=None, csrf_field='_csrf', **kwargs):
|
|
||||||
super(AlchemyForm, self).__init__(**kwargs)
|
|
||||||
self.request = request
|
|
||||||
self.fieldset = fieldset
|
|
||||||
self.session = session
|
|
||||||
self.csrf_field = csrf_field
|
|
||||||
|
|
||||||
def _get_readonly(self):
|
|
||||||
return self.fieldset.readonly
|
|
||||||
def _set_readonly(self, val):
|
|
||||||
self.fieldset.readonly = val
|
|
||||||
readonly = property(_get_readonly, _set_readonly)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def successive_create_label(self):
|
|
||||||
return "%s and continue" % self.create_label
|
|
||||||
|
|
||||||
def csrf(self, name=None):
|
|
||||||
"""
|
|
||||||
NOTE: this method was copied from `pyramid_simpleform.FormRenderer`
|
|
||||||
|
|
||||||
Returns the CSRF hidden input. Creates new CSRF token
|
|
||||||
if none has been assigned yet.
|
|
||||||
|
|
||||||
The name of the hidden field is **_csrf** by default.
|
|
||||||
"""
|
|
||||||
name = name or self.csrf_field
|
|
||||||
|
|
||||||
token = self.request.session.get_csrf_token()
|
|
||||||
if token is None:
|
|
||||||
token = self.request.session.new_csrf_token()
|
|
||||||
|
|
||||||
return tags.hidden(name, value=token)
|
|
||||||
|
|
||||||
def csrf_token(self, name=None):
|
|
||||||
"""
|
|
||||||
NOTE: this method was copied from `pyramid_simpleform.FormRenderer`
|
|
||||||
|
|
||||||
Convenience function. Returns CSRF hidden tag inside hidden DIV.
|
|
||||||
"""
|
|
||||||
return HTML.tag("div", self.csrf(name), style="display:none;")
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs['form'] = self
|
|
||||||
if self.readonly:
|
|
||||||
template = '/forms/form_readonly.mako'
|
|
||||||
else:
|
|
||||||
template = '/forms/form.mako'
|
|
||||||
return render(template, kwargs)
|
|
||||||
|
|
||||||
def render_fields(self):
|
|
||||||
return self.fieldset.render()
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
self.fieldset.sync()
|
|
||||||
self.session.flush()
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
self.fieldset.rebind(data=self.request.params)
|
|
||||||
return self.fieldset.validate()
|
|
|
@ -1,119 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Forms Core
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from rattail.util import OrderedDict, prettify
|
|
||||||
|
|
||||||
import formalchemy
|
|
||||||
from formalchemy.helpers import content_tag
|
|
||||||
from pyramid.renderers import render
|
|
||||||
|
|
||||||
|
|
||||||
class Form(object):
|
|
||||||
"""
|
|
||||||
Base class for all forms.
|
|
||||||
"""
|
|
||||||
create_label = "Create"
|
|
||||||
update_label = "Save"
|
|
||||||
|
|
||||||
def __init__(self, request, readonly=False, action_url=None):
|
|
||||||
self.request = request
|
|
||||||
self.readonly = readonly
|
|
||||||
self.action_url = action_url
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs.setdefault('form', self)
|
|
||||||
if self.readonly:
|
|
||||||
template = '/forms/form_readonly.mako'
|
|
||||||
else:
|
|
||||||
template = '/forms/form.mako'
|
|
||||||
return render(template, kwargs)
|
|
||||||
|
|
||||||
def render_fields(self, **kwargs):
|
|
||||||
kwargs.setdefault('fieldset', self.fieldset)
|
|
||||||
if self.readonly:
|
|
||||||
template = '/forms/fieldset_readonly.mako'
|
|
||||||
else:
|
|
||||||
template = '/forms/fieldset.mako'
|
|
||||||
return render(template, kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class Field(object):
|
|
||||||
"""
|
|
||||||
Manually create instances of this class to populate a simple form.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, value=None, label=None, requires_label=True):
|
|
||||||
self.name = name
|
|
||||||
self.value = value
|
|
||||||
self._label = label or prettify(self.name)
|
|
||||||
self.requires_label = requires_label
|
|
||||||
|
|
||||||
def is_required(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def label(self):
|
|
||||||
return self._label
|
|
||||||
|
|
||||||
def label_tag(self, **html_options):
|
|
||||||
"""
|
|
||||||
Logic stolen from FormAlchemy so all fields can render their own label.
|
|
||||||
Original docstring follows.
|
|
||||||
|
|
||||||
return the <label /> tag for the field.
|
|
||||||
"""
|
|
||||||
html_options.update(for_=self.name)
|
|
||||||
if 'class_' in html_options:
|
|
||||||
html_options['class_'] += self.is_required() and ' field_req' or ' field_opt'
|
|
||||||
else:
|
|
||||||
html_options['class_'] = self.is_required() and 'field_req' or 'field_opt'
|
|
||||||
return content_tag('label', self.label(), **html_options)
|
|
||||||
|
|
||||||
def render_readonly(self):
|
|
||||||
if self.value is None:
|
|
||||||
return ''
|
|
||||||
return unicode(self.value)
|
|
||||||
|
|
||||||
|
|
||||||
class FieldSet(object):
|
|
||||||
"""
|
|
||||||
Generic fieldset for use with manually-created simple forms.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.fields = OrderedDict()
|
|
||||||
self.render_fields = self.fields
|
|
||||||
|
|
||||||
|
|
||||||
class GenericFieldSet(formalchemy.FieldSet):
|
|
||||||
"""
|
|
||||||
FieldSet class based on FormAlchemy, but without the SQLAlchemy magic.
|
|
||||||
"""
|
|
||||||
__sa__ = False
|
|
||||||
_bound_pk = None
|
|
||||||
data = None
|
|
||||||
prettify = staticmethod(prettify)
|
|
|
@ -1,106 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
FormAlchemy Fields
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import formalchemy as fa
|
|
||||||
|
|
||||||
|
|
||||||
def AssociationProxyField(name, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns a FormAlchemy ``Field`` class which is aware of association
|
|
||||||
proxies.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class ProxyField(fa.Field):
|
|
||||||
|
|
||||||
def sync(self):
|
|
||||||
if not self.is_readonly():
|
|
||||||
setattr(self.parent.model, self.name,
|
|
||||||
self.renderer.deserialize())
|
|
||||||
|
|
||||||
def value(obj):
|
|
||||||
return getattr(obj, name, None)
|
|
||||||
|
|
||||||
kwargs.setdefault('value', value)
|
|
||||||
return ProxyField(name, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultEmailField(fa.Field):
|
|
||||||
"""
|
|
||||||
Generic field for view/edit of default email address for a contact
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name=None, **kwargs):
|
|
||||||
kwargs.setdefault('value', self.value)
|
|
||||||
if 'renderer' not in kwargs:
|
|
||||||
from tailbone.forms.renderers import EmailFieldRenderer
|
|
||||||
kwargs['renderer'] = EmailFieldRenderer
|
|
||||||
super(DefaultEmailField, self).__init__(name, **kwargs)
|
|
||||||
|
|
||||||
def value(self, contact):
|
|
||||||
if contact.emails:
|
|
||||||
return contact.emails[0].address
|
|
||||||
|
|
||||||
def sync(self):
|
|
||||||
if not self.is_readonly():
|
|
||||||
address = self._deserialize()
|
|
||||||
contact = self.parent.model
|
|
||||||
if contact.emails:
|
|
||||||
if address:
|
|
||||||
email = contact.emails[0]
|
|
||||||
email.address = address
|
|
||||||
else:
|
|
||||||
contact.emails.pop(0)
|
|
||||||
elif address:
|
|
||||||
email = contact.add_email_address(address)
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultPhoneField(fa.Field):
|
|
||||||
"""
|
|
||||||
Generic field for view/edit of default phone number for a contact
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name=None, **kwargs):
|
|
||||||
kwargs.setdefault('value', self.value)
|
|
||||||
super(DefaultPhoneField, self).__init__(name, **kwargs)
|
|
||||||
|
|
||||||
def value(self, contact):
|
|
||||||
if contact.phones:
|
|
||||||
return contact.phones[0].number
|
|
||||||
|
|
||||||
def sync(self):
|
|
||||||
if not self.is_readonly():
|
|
||||||
number = self._deserialize()
|
|
||||||
contact = self.parent.model
|
|
||||||
if contact.phones:
|
|
||||||
if number:
|
|
||||||
phone = contact.phones[0]
|
|
||||||
phone.number = number
|
|
||||||
else:
|
|
||||||
contact.phones.pop(0)
|
|
||||||
elif number:
|
|
||||||
phone = contact.add_phone_number(number)
|
|
|
@ -1,51 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
FormAlchemy Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from .core import CustomFieldRenderer, DateFieldRenderer
|
|
||||||
|
|
||||||
from .common import (StrippedTextFieldRenderer, CodeTextAreaFieldRenderer, AutocompleteFieldRenderer,
|
|
||||||
DecimalFieldRenderer, CurrencyFieldRenderer, QuantityFieldRenderer,
|
|
||||||
DateTimeFieldRenderer, DateTimePrettyFieldRenderer, LocalDateTimeFieldRenderer, TimeFieldRenderer,
|
|
||||||
EmailFieldRenderer, EnumFieldRenderer, YesNoFieldRenderer)
|
|
||||||
|
|
||||||
from .files import FileFieldRenderer
|
|
||||||
|
|
||||||
from .people import PersonFieldRenderer, PeopleFieldRenderer, CustomerFieldRenderer
|
|
||||||
from .users import UserFieldRenderer, PermissionsFieldRenderer
|
|
||||||
from .employees import EmployeeFieldRenderer
|
|
||||||
|
|
||||||
from .stores import StoreFieldRenderer
|
|
||||||
from .vendors import VendorFieldRenderer, PurchaseFieldRenderer
|
|
||||||
from .products import (GPCFieldRenderer, ScancodeFieldRenderer,
|
|
||||||
DepartmentFieldRenderer, SubdepartmentFieldRenderer, CategoryFieldRenderer,
|
|
||||||
BrandFieldRenderer, ProductFieldRenderer,
|
|
||||||
CostFieldRenderer, PriceFieldRenderer, PriceWithExpirationFieldRenderer)
|
|
||||||
|
|
||||||
from .custorders import CustomerOrderFieldRenderer
|
|
||||||
|
|
||||||
from .batch import BatchIDFieldRenderer, HandheldBatchFieldRenderer, HandheldBatchesFieldRenderer
|
|
|
@ -1,103 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Batch Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import random
|
|
||||||
|
|
||||||
import formalchemy as fa
|
|
||||||
from webhelpers2.html import tags, HTML
|
|
||||||
|
|
||||||
from tailbone.forms.renderers import FileFieldRenderer as BaseFileFieldRenderer
|
|
||||||
|
|
||||||
|
|
||||||
class BatchIDFieldRenderer(fa.FieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for batch ID fields.
|
|
||||||
"""
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
try:
|
|
||||||
batch_id = self.raw_value
|
|
||||||
except AttributeError:
|
|
||||||
# this can happen when creating a new batch, b/c the default value
|
|
||||||
# comes from a sequence
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if batch_id:
|
|
||||||
return '{:08d}'.format(batch_id)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class FileFieldRenderer(BaseFileFieldRenderer):
|
|
||||||
"""
|
|
||||||
Custom file field renderer for batches based on a single source data file.
|
|
||||||
In edit mode, shows a file upload field. In readonly mode, shows the
|
|
||||||
filename and its size.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_size(self):
|
|
||||||
size = super(FileFieldRenderer, self).get_size()
|
|
||||||
if size:
|
|
||||||
return size
|
|
||||||
batch = self.field.parent.model
|
|
||||||
path = batch.filepath(self.request.rattail_config, filename=self.field.value)
|
|
||||||
if os.path.isfile(path):
|
|
||||||
return os.stat(path)[stat.ST_SIZE]
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
return BaseFileFieldRenderer.render(self, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class HandheldBatchFieldRenderer(fa.FieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for inventory batch's "handheld batch" field.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
batch = self.raw_value
|
|
||||||
if batch:
|
|
||||||
return tags.link_to(
|
|
||||||
batch.id_str,
|
|
||||||
self.request.route_url('batch.handheld.view', uuid=batch.uuid))
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class HandheldBatchesFieldRenderer(fa.FieldRenderer):
|
|
||||||
"""
|
|
||||||
Renders a list of associated handheld batches, for a given (presumably
|
|
||||||
inventory or labels) batch.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
items = ''
|
|
||||||
for handheld in self.raw_value:
|
|
||||||
text = tags.link_to(handheld.handheld.id_str,
|
|
||||||
self.request.route_url('batch.handheld.view', uuid=handheld.handheld_uuid))
|
|
||||||
items += HTML.tag('li', c=text)
|
|
||||||
return HTML.tag('ul', c=items)
|
|
|
@ -1,63 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Batch Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import random
|
|
||||||
|
|
||||||
from formalchemy.ext import fsblob
|
|
||||||
|
|
||||||
|
|
||||||
class BounceMessageFieldRenderer(fsblob.FileFieldRenderer):
|
|
||||||
"""
|
|
||||||
Custom file field renderer for email bounce messages. In readonly mode,
|
|
||||||
shows the filename and size.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def new(cls, request, handler):
|
|
||||||
name = 'Configured%s_%s' % (cls.__name__, unicode(random.random())[2:])
|
|
||||||
return type(str(name), (cls,), dict(request=request, handler=handler))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def storage_path(self):
|
|
||||||
return self.handler.root_msgdir
|
|
||||||
|
|
||||||
def get_size(self):
|
|
||||||
size = super(BounceMessageFieldRenderer, self).get_size()
|
|
||||||
if size:
|
|
||||||
return size
|
|
||||||
bounce = self.field.parent.model
|
|
||||||
path = os.path.join(self.handler.msgpath(bounce))
|
|
||||||
if os.path.isfile(path):
|
|
||||||
return os.stat(path)[stat.ST_SIZE]
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_url(self, filename):
|
|
||||||
bounce = self.field.parent.model
|
|
||||||
return self.request.route_url('emailbounces.download', uuid=bounce.uuid)
|
|
|
@ -1,315 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Common Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from rattail.time import localtime, make_utc
|
|
||||||
from rattail.util import pretty_quantity
|
|
||||||
|
|
||||||
import formalchemy as fa
|
|
||||||
from formalchemy import fields as fa_fields, helpers as fa_helpers
|
|
||||||
from pyramid.renderers import render
|
|
||||||
from webhelpers2.html import HTML, tags
|
|
||||||
|
|
||||||
from tailbone.util import pretty_datetime, raw_datetime
|
|
||||||
|
|
||||||
|
|
||||||
class StrippedTextFieldRenderer(fa.TextFieldRenderer):
|
|
||||||
"""
|
|
||||||
Standard text field renderer, which strips whitespace from either end of
|
|
||||||
the input value on deserialization.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def deserialize(self):
|
|
||||||
value = super(StrippedTextFieldRenderer, self).deserialize()
|
|
||||||
if value is not None:
|
|
||||||
return value.strip()
|
|
||||||
|
|
||||||
|
|
||||||
class CodeTextAreaFieldRenderer(fa.TextAreaFieldRenderer):
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if not value:
|
|
||||||
return ''
|
|
||||||
return HTML.tag('pre', c=value)
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs.setdefault('size', (80, 8))
|
|
||||||
return super(CodeTextAreaFieldRenderer, self).render(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AutocompleteFieldRenderer(fa.FieldRenderer):
|
|
||||||
"""
|
|
||||||
Custom renderer for an autocomplete field.
|
|
||||||
"""
|
|
||||||
|
|
||||||
service_route = None
|
|
||||||
width = '300px'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def focus_name(self):
|
|
||||||
return self.name + '-textbox'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def needs_focus(self):
|
|
||||||
return not bool(self.value or self.field_value)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def field_display(self):
|
|
||||||
return self.raw_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def field_value(self):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def service_url(self):
|
|
||||||
return self.request.route_url(self.service_route)
|
|
||||||
|
|
||||||
def render(self, options=None, **kwargs):
|
|
||||||
if kwargs.pop('autocomplete', True):
|
|
||||||
return self.render_autocomplete(**kwargs)
|
|
||||||
# 'selected' is a kwarg for autocomplete template *and* select tag
|
|
||||||
kwargs.pop('selected', None)
|
|
||||||
return self.render_dropdown(options, **kwargs)
|
|
||||||
|
|
||||||
def render_autocomplete(self, **kwargs):
|
|
||||||
kwargs.setdefault('field_name', self.name)
|
|
||||||
kwargs.setdefault('field_value', self.field_value)
|
|
||||||
kwargs.setdefault('field_display', self.field_display)
|
|
||||||
kwargs.setdefault('service_url', self.service_url)
|
|
||||||
kwargs.setdefault('width', self.width)
|
|
||||||
return render('/forms/field_autocomplete.mako', kwargs)
|
|
||||||
|
|
||||||
def render_dropdown(self, options, **kwargs):
|
|
||||||
# NOTE: this logic copied from formalchemy.fields.SelectFieldRenderer.render()
|
|
||||||
kwargs.setdefault('auto-enhance', 'true')
|
|
||||||
if callable(options):
|
|
||||||
L = fa_fields._normalized_options(options(self.field.parent))
|
|
||||||
if not self.field.is_required() and not self.field.is_collection:
|
|
||||||
L.insert(0, self.field._null_option)
|
|
||||||
else:
|
|
||||||
L = list(options)
|
|
||||||
if len(L) > 0:
|
|
||||||
if len(L[0]) == 2:
|
|
||||||
L = [(k, self.stringify_value(v)) for k, v in L]
|
|
||||||
else:
|
|
||||||
L = [fa_fields._stringify(k) for k in L]
|
|
||||||
return fa_fields.h.select(self.name, self.value, L, **kwargs)
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.field_display
|
|
||||||
if value is None:
|
|
||||||
return u''
|
|
||||||
return unicode(value)
|
|
||||||
|
|
||||||
|
|
||||||
class DateTimeFieldRenderer(fa.DateTimeFieldRenderer):
|
|
||||||
"""
|
|
||||||
This renderer assumes the datetime field value is in UTC, and will convert
|
|
||||||
it to the local time zone before rendering it in the standard "raw" format.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if not value:
|
|
||||||
return ''
|
|
||||||
return raw_datetime(self.request.rattail_config, value)
|
|
||||||
|
|
||||||
|
|
||||||
class DateTimePrettyFieldRenderer(fa.DateTimeFieldRenderer):
|
|
||||||
"""
|
|
||||||
Custom date/time field renderer, which displays a "pretty" value in
|
|
||||||
read-only mode, leveraging config to show the correct timezone.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if not value:
|
|
||||||
return ''
|
|
||||||
return pretty_datetime(self.request.rattail_config, value)
|
|
||||||
|
|
||||||
|
|
||||||
class LocalDateTimeFieldRenderer(fa.DateTimeFieldRenderer):
|
|
||||||
"""
|
|
||||||
This renderer assumes the datetime field value is "naive" in local time zone.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if not value:
|
|
||||||
return ''
|
|
||||||
value = localtime(self.request.rattail_config, value)
|
|
||||||
return raw_datetime(self.request.rattail_config, value)
|
|
||||||
|
|
||||||
|
|
||||||
class TimeFieldRenderer(fa.TimeFieldRenderer):
|
|
||||||
"""
|
|
||||||
Custom renderer for time fields. In edit mode, renders a simple text
|
|
||||||
input, which is expected to become a 'timepicker' widget in the UI.
|
|
||||||
However the particular magic required for that lives in 'tailbone.js'.
|
|
||||||
"""
|
|
||||||
format = '%I:%M %p'
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs.setdefault('class_', 'timepicker')
|
|
||||||
return fa_helpers.text_field(self.name, value=self.value, **kwargs)
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
return self.render_value(self.raw_value)
|
|
||||||
|
|
||||||
def render_value(self, value):
|
|
||||||
value = self.convert_value(value)
|
|
||||||
if isinstance(value, datetime.time):
|
|
||||||
return value.strftime(self.format)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def convert_value(self, value):
|
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
if not value.tzinfo:
|
|
||||||
value = make_utc(value, tzinfo=True)
|
|
||||||
return localtime(self.request.rattail_config, value).time()
|
|
||||||
return value
|
|
||||||
|
|
||||||
def stringify_value(self, value, as_html=False):
|
|
||||||
if not as_html:
|
|
||||||
return self.render_value(value)
|
|
||||||
return super(TimeFieldRenderer, self).stringify_value(value, as_html=as_html)
|
|
||||||
|
|
||||||
def _serialized_value(self):
|
|
||||||
return self.params.getone(self.name)
|
|
||||||
|
|
||||||
def deserialize(self):
|
|
||||||
value = self._serialized_value()
|
|
||||||
if value:
|
|
||||||
try:
|
|
||||||
return datetime.datetime.strptime(value, self.format).time()
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EmailFieldRenderer(fa.TextFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for email address fields
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
address = self.raw_value
|
|
||||||
if not address:
|
|
||||||
return ''
|
|
||||||
return tags.link_to(address, 'mailto:{}'.format(address))
|
|
||||||
|
|
||||||
|
|
||||||
class EnumFieldRenderer(fa_fields.SelectFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for simple enumeration fields.
|
|
||||||
"""
|
|
||||||
enumeration = {}
|
|
||||||
render_key = False
|
|
||||||
|
|
||||||
def __init__(self, arg, render_key=False):
|
|
||||||
if isinstance(arg, dict):
|
|
||||||
self.enumeration = arg
|
|
||||||
self.render_key = render_key
|
|
||||||
else:
|
|
||||||
self(arg)
|
|
||||||
|
|
||||||
def __call__(self, field):
|
|
||||||
super(EnumFieldRenderer, self).__init__(field)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if value is None:
|
|
||||||
return ''
|
|
||||||
rendered = self.enumeration.get(value, unicode(value))
|
|
||||||
if self.render_key:
|
|
||||||
rendered = '{} - {}'.format(value, rendered)
|
|
||||||
return rendered
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
opts = [(self.enumeration[x], x) for x in self.enumeration]
|
|
||||||
if not self.field.is_required():
|
|
||||||
opts.insert(0, self.field._null_option)
|
|
||||||
return fa_fields.SelectFieldRenderer.render(self, opts, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class DecimalFieldRenderer(fa.FieldRenderer):
|
|
||||||
"""
|
|
||||||
Sort of generic field renderer for decimal values. You must provide the
|
|
||||||
number of places after the decimal (scale). Note that this in turn relies
|
|
||||||
on simple string formatting; the renderer does not attempt any mathematics
|
|
||||||
of its own.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, scale):
|
|
||||||
self.scale = scale
|
|
||||||
|
|
||||||
def __call__(self, field):
|
|
||||||
super(DecimalFieldRenderer, self).__init__(field)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if value is None:
|
|
||||||
return ''
|
|
||||||
fmt = '{{0:0.{0}f}}'.format(self.scale)
|
|
||||||
return fmt.format(value)
|
|
||||||
|
|
||||||
|
|
||||||
class CurrencyFieldRenderer(fa_fields.FloatFieldRenderer):
|
|
||||||
"""
|
|
||||||
Sort of generic field renderer for currency values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if value is None:
|
|
||||||
return ''
|
|
||||||
if value < 0:
|
|
||||||
return "(${:0,.2f})".format(0 - value)
|
|
||||||
return "${:0,.2f}".format(value)
|
|
||||||
|
|
||||||
|
|
||||||
class QuantityFieldRenderer(fa_fields.FloatFieldRenderer):
|
|
||||||
"""
|
|
||||||
Sort of generic field renderer for quantity values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
return pretty_quantity(self.raw_value)
|
|
||||||
|
|
||||||
|
|
||||||
class YesNoFieldRenderer(fa.CheckBoxFieldRenderer):
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if value is None:
|
|
||||||
return u''
|
|
||||||
return u'Yes' if value else u'No'
|
|
|
@ -1,100 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Core Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
import formalchemy as fa
|
|
||||||
from formalchemy.fields import AbstractField
|
|
||||||
from pyramid.renderers import render
|
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldRenderer(fa.FieldRenderer):
|
|
||||||
"""
|
|
||||||
Base class for renderers which accept customization args, and "fake out"
|
|
||||||
FormAlchemy by pretending to still be a renderer factory when in fact it's
|
|
||||||
already dealing with a renderer instance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if len(args) == 1 and isinstance(args[0], AbstractField):
|
|
||||||
super(CustomFieldRenderer, self).__init__(args[0])
|
|
||||||
self.init(**kwargs)
|
|
||||||
else:
|
|
||||||
assert len(args) == 0
|
|
||||||
self.init(**kwargs)
|
|
||||||
|
|
||||||
def __call__(self, field):
|
|
||||||
super(CustomFieldRenderer, self).__init__(field)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def init(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def rattail_config(self):
|
|
||||||
return self.request.rattail_config
|
|
||||||
|
|
||||||
|
|
||||||
class DateFieldRenderer(CustomFieldRenderer, fa.DateFieldRenderer):
|
|
||||||
"""
|
|
||||||
Date field renderer which uses jQuery UI datepicker widget when rendering
|
|
||||||
in edit mode.
|
|
||||||
"""
|
|
||||||
date_format = '%Y-%m-%d'
|
|
||||||
change_year = False
|
|
||||||
|
|
||||||
def init(self, date_format=None, change_year=False):
|
|
||||||
if date_format:
|
|
||||||
self.date_format = date_format
|
|
||||||
self.change_year = change_year
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if value is None:
|
|
||||||
return ''
|
|
||||||
return value.strftime(self.date_format)
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs['name'] = self.name
|
|
||||||
kwargs['value'] = self.value
|
|
||||||
kwargs['change_year'] = self.change_year
|
|
||||||
return render('/forms/fields/date.mako', kwargs)
|
|
||||||
|
|
||||||
def deserialize(self):
|
|
||||||
value = self._serialized_value()
|
|
||||||
if not value:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return datetime.datetime.strptime(value, '%Y-%m-%d')
|
|
||||||
except ValueError:
|
|
||||||
raise fa.ValidationError("Date value must be in YYYY-MM-DD format")
|
|
||||||
except Exception as error:
|
|
||||||
raise fa.ValidationError(unicode(error))
|
|
||||||
|
|
||||||
def _serialized_value(self):
|
|
||||||
return self.params.getone(self.name)
|
|
|
@ -1,42 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Customer order field renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import formalchemy as fa
|
|
||||||
from webhelpers2.html import tags
|
|
||||||
|
|
||||||
|
|
||||||
class CustomerOrderFieldRenderer(fa.fields.SelectFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renders a link to the customer order
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
order = self.raw_value
|
|
||||||
if not order:
|
|
||||||
return ''
|
|
||||||
return tags.link_to(order, self.request.route_url('custorders.view', uuid=order.uuid))
|
|
|
@ -1,49 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Employee Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import six
|
|
||||||
from webhelpers2.html import tags
|
|
||||||
|
|
||||||
from tailbone.forms.renderers import AutocompleteFieldRenderer
|
|
||||||
|
|
||||||
|
|
||||||
class EmployeeFieldRenderer(AutocompleteFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Employee` instance fields.
|
|
||||||
"""
|
|
||||||
service_route = 'employees.autocomplete'
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
employee = self.raw_value
|
|
||||||
if not employee:
|
|
||||||
return ''
|
|
||||||
render_name = kwargs.get('render_name', six.text_type)
|
|
||||||
title = render_name(employee)
|
|
||||||
if kwargs.get('hyperlink') and self.request.has_perm('employees.view'):
|
|
||||||
return tags.link_to(title, self.request.route_url('employees.view', uuid=employee.uuid))
|
|
||||||
return title
|
|
|
@ -1,102 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Batch Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import random
|
|
||||||
|
|
||||||
from formalchemy.ext import fsblob
|
|
||||||
from formalchemy.fields import FileFieldRenderer as Base
|
|
||||||
|
|
||||||
|
|
||||||
class FileFieldRenderer(fsblob.FileFieldRenderer):
|
|
||||||
"""
|
|
||||||
Custom file field renderer. In readonly mode, shows a filename and its
|
|
||||||
size; in edit mode, supports a single file upload.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def new(cls, view, **kwargs):
|
|
||||||
name = b'Configured{}_{}'.format(cls.__name__, str(random.random())[2:])
|
|
||||||
return type(name, (cls,), dict(view=view, **kwargs))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def request(self):
|
|
||||||
return self.view.request
|
|
||||||
|
|
||||||
@property
|
|
||||||
def storage_path(self):
|
|
||||||
return self.view.upload_dir
|
|
||||||
|
|
||||||
def get_file_path(self):
|
|
||||||
"""
|
|
||||||
Returns the absolute path to the data file.
|
|
||||||
"""
|
|
||||||
if hasattr(self, 'file_path'):
|
|
||||||
return self.file_path
|
|
||||||
return self.field.value
|
|
||||||
|
|
||||||
def get_size(self):
|
|
||||||
"""
|
|
||||||
Returns the size of the data file, in bytes.
|
|
||||||
"""
|
|
||||||
path = self.get_file_path()
|
|
||||||
if path and os.path.isfile(path):
|
|
||||||
return os.stat(path)[stat.ST_SIZE]
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_url(self, filename):
|
|
||||||
"""
|
|
||||||
Must return a URL suitable for downloading the file
|
|
||||||
"""
|
|
||||||
url = self.get_download_url()
|
|
||||||
if url:
|
|
||||||
if callable(url):
|
|
||||||
return url(filename)
|
|
||||||
return url
|
|
||||||
return self.view.get_action_url('download', self.field.parent.model)
|
|
||||||
|
|
||||||
def get_download_url(self):
|
|
||||||
if hasattr(self, 'download_url'):
|
|
||||||
return self.download_url
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
return Base.render(self, **kwargs)
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
"""
|
|
||||||
Render the filename and the binary size in a human readable with a link
|
|
||||||
to the file itself.
|
|
||||||
"""
|
|
||||||
value = self.get_file_path()
|
|
||||||
if value:
|
|
||||||
content = '{} ({})'.format(fsblob.normalized_basename(value),
|
|
||||||
self.readable_size())
|
|
||||||
return fsblob.h.content_tag('a', content,
|
|
||||||
href=self.get_url(value), **kwargs)
|
|
||||||
return ''
|
|
|
@ -1,81 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
People Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import six
|
|
||||||
import formalchemy as fa
|
|
||||||
from webhelpers2.html import tags, HTML
|
|
||||||
|
|
||||||
from tailbone.forms.renderers.common import AutocompleteFieldRenderer
|
|
||||||
|
|
||||||
|
|
||||||
class PersonFieldRenderer(AutocompleteFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Person` instance fields.
|
|
||||||
"""
|
|
||||||
service_route = 'people.autocomplete'
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
person = self.raw_value
|
|
||||||
if not person:
|
|
||||||
return ''
|
|
||||||
return tags.link_to(person, self.request.route_url('people.view', uuid=person.uuid))
|
|
||||||
|
|
||||||
|
|
||||||
class PeopleFieldRenderer(fa.FieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for "people" list relationship
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
html = ''
|
|
||||||
people = self.raw_value
|
|
||||||
if not people:
|
|
||||||
return html
|
|
||||||
for person in people:
|
|
||||||
link = tags.link_to(person, self.request.route_url('people.view', uuid=person.uuid))
|
|
||||||
html += HTML.tag('li', c=link)
|
|
||||||
html = HTML.tag('ul', c=html)
|
|
||||||
return html
|
|
||||||
|
|
||||||
|
|
||||||
class CustomerFieldRenderer(AutocompleteFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Customer` instance fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
service_route = 'customers.autocomplete'
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
customer = self.raw_value
|
|
||||||
if not customer:
|
|
||||||
return ''
|
|
||||||
text = self.render_value(customer)
|
|
||||||
return tags.link_to(text, self.request.route_url('customers.view', uuid=customer.uuid))
|
|
||||||
|
|
||||||
def render_value(self, customer):
|
|
||||||
return six.text_type(customer)
|
|
|
@ -1,224 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Product Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from rattail.gpc import GPC
|
|
||||||
from rattail.db import model
|
|
||||||
from rattail.db.util import maxlen
|
|
||||||
|
|
||||||
import formalchemy as fa
|
|
||||||
from formalchemy import TextFieldRenderer
|
|
||||||
from formalchemy.fields import SelectFieldRenderer
|
|
||||||
from webhelpers2.html import tags, literal
|
|
||||||
|
|
||||||
from tailbone.forms.renderers.common import AutocompleteFieldRenderer
|
|
||||||
from tailbone.util import pretty_datetime
|
|
||||||
|
|
||||||
|
|
||||||
class ProductFieldRenderer(AutocompleteFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Product` instance fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
service_route = 'products.autocomplete'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def field_display(self):
|
|
||||||
product = self.raw_value
|
|
||||||
if product:
|
|
||||||
return product.full_description
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
product = self.raw_value
|
|
||||||
if not product:
|
|
||||||
return ""
|
|
||||||
render = kwargs.get('render_product', self.render_product)
|
|
||||||
text = render(product)
|
|
||||||
if kwargs.get('hyperlink', True):
|
|
||||||
return tags.link_to(text, self.request.route_url('products.view', uuid=product.uuid))
|
|
||||||
return text
|
|
||||||
|
|
||||||
def render_product(self, product):
|
|
||||||
return six.text_type(product)
|
|
||||||
|
|
||||||
|
|
||||||
class ProductKeyFieldRenderer(TextFieldRenderer):
|
|
||||||
"""
|
|
||||||
Base class for product key field renderers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
value = self.raw_value
|
|
||||||
if value is None:
|
|
||||||
return ''
|
|
||||||
value = self.render_value(value)
|
|
||||||
if kwargs.get('link'):
|
|
||||||
product = self.field.parent.model
|
|
||||||
value = tags.link_to(value, kwargs['link'](product))
|
|
||||||
return value
|
|
||||||
|
|
||||||
def render_value(self, value):
|
|
||||||
return unicode(value)
|
|
||||||
|
|
||||||
|
|
||||||
class GPCFieldRenderer(ProductKeyFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.gpc.GPC` fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def length(self):
|
|
||||||
# Hm, should maybe consider hard-coding this...?
|
|
||||||
return len(unicode(GPC(0)))
|
|
||||||
|
|
||||||
def render_value(self, gpc):
|
|
||||||
return gpc.pretty()
|
|
||||||
|
|
||||||
|
|
||||||
class ScancodeFieldRenderer(ProductKeyFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Product.scancode` field
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def length(self):
|
|
||||||
return maxlen(model.Product.scancode)
|
|
||||||
|
|
||||||
|
|
||||||
class DepartmentFieldRenderer(SelectFieldRenderer):
|
|
||||||
"""
|
|
||||||
Shows the department number as well as the name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs.setdefault('auto-enhance', 'true')
|
|
||||||
return super(DepartmentFieldRenderer, self).render(**kwargs)
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
department = self.raw_value
|
|
||||||
if not department:
|
|
||||||
return ''
|
|
||||||
if department.number:
|
|
||||||
text = '({}) {}'.format(department.number, department.name)
|
|
||||||
else:
|
|
||||||
text = department.name
|
|
||||||
return tags.link_to(text, self.request.route_url('departments.view', uuid=department.uuid))
|
|
||||||
|
|
||||||
|
|
||||||
class SubdepartmentFieldRenderer(SelectFieldRenderer):
|
|
||||||
"""
|
|
||||||
Shows a link to the subdepartment.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
subdept = self.raw_value
|
|
||||||
if not subdept:
|
|
||||||
return ""
|
|
||||||
if subdept.number:
|
|
||||||
text = "({}) {}".format(subdept.number, subdept.name)
|
|
||||||
else:
|
|
||||||
text = subdept.name
|
|
||||||
return tags.link_to(text, self.request.route_url('subdepartments.view', uuid=subdept.uuid))
|
|
||||||
|
|
||||||
|
|
||||||
class CategoryFieldRenderer(SelectFieldRenderer):
|
|
||||||
"""
|
|
||||||
Shows a link to the category.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
category = self.raw_value
|
|
||||||
if not category:
|
|
||||||
return ""
|
|
||||||
if category.code:
|
|
||||||
text = "({}) {}".format(category.code, category.name)
|
|
||||||
else:
|
|
||||||
text = category.name
|
|
||||||
return tags.link_to(text, self.request.route_url('categories.view', uuid=category.uuid))
|
|
||||||
|
|
||||||
|
|
||||||
class BrandFieldRenderer(AutocompleteFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Brand` instance fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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):
|
|
||||||
"""
|
|
||||||
Renderer for fields which reference a :class:`ProductPrice` instance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
price = self.field.raw_value
|
|
||||||
if price:
|
|
||||||
if not price.product.not_for_sale:
|
|
||||||
if price.price is not None and price.pack_price is not None:
|
|
||||||
if price.multiple > 1:
|
|
||||||
return literal('$ %0.2f / %u ($ %0.2f / %u)' % (
|
|
||||||
price.price, price.multiple,
|
|
||||||
price.pack_price, price.pack_multiple))
|
|
||||||
return literal('$ %0.2f ($ %0.2f / %u)' % (
|
|
||||||
price.price, price.pack_price, price.pack_multiple))
|
|
||||||
if price.price is not None:
|
|
||||||
if price.multiple > 1:
|
|
||||||
return '$ %0.2f / %u' % (price.price, price.multiple)
|
|
||||||
return '$ %0.2f' % price.price
|
|
||||||
if price.pack_price is not None:
|
|
||||||
return '$ %0.2f / %u' % (price.pack_price, price.pack_multiple)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class PriceWithExpirationFieldRenderer(PriceFieldRenderer):
|
|
||||||
"""
|
|
||||||
Price field renderer which also displays the expiration date, if present.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
result = super(PriceWithExpirationFieldRenderer, self).render_readonly(**kwargs)
|
|
||||||
if result:
|
|
||||||
price = self.field.raw_value
|
|
||||||
if price.ends:
|
|
||||||
result = '{0} ({1})'.format(
|
|
||||||
result, pretty_datetime(self.request.rattail_config, price.ends))
|
|
||||||
return result
|
|
|
@ -1,49 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Store Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from formalchemy.fields import SelectFieldRenderer
|
|
||||||
from webhelpers2.html import tags
|
|
||||||
|
|
||||||
|
|
||||||
class StoreFieldRenderer(SelectFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Store` instance fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs.setdefault('auto-enhance', 'true')
|
|
||||||
return super(StoreFieldRenderer, self).render(**kwargs)
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
store = self.raw_value
|
|
||||||
if not store:
|
|
||||||
return ""
|
|
||||||
text = "({}) {}".format(store.id, store.name)
|
|
||||||
if kwargs.get('hyperlink', True):
|
|
||||||
return tags.link_to(text, self.request.route_url('stores.view', uuid=store.uuid))
|
|
||||||
return text
|
|
|
@ -1,97 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
User Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from rattail.db import model
|
|
||||||
from rattail.db.auth import has_permission, administrator_role
|
|
||||||
|
|
||||||
import formalchemy
|
|
||||||
from webhelpers2.html import HTML, tags
|
|
||||||
|
|
||||||
from tailbone.db import Session
|
|
||||||
|
|
||||||
|
|
||||||
class UserFieldRenderer(formalchemy.TextFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail:rattail.db.model.User` instance fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
user = self.raw_value
|
|
||||||
if not user:
|
|
||||||
return ''
|
|
||||||
title = six.text_type(user)
|
|
||||||
if kwargs.get('hyperlink') and self.request.has_perm('users.view'):
|
|
||||||
return tags.link_to(title, self.request.route_url('users.view', uuid=user.uuid))
|
|
||||||
return title
|
|
||||||
|
|
||||||
|
|
||||||
def PermissionsFieldRenderer(permissions, include_guest=False, include_authenticated=False):
|
|
||||||
|
|
||||||
class PermissionsFieldRenderer(formalchemy.FieldRenderer):
|
|
||||||
|
|
||||||
def deserialize(self):
|
|
||||||
perms = []
|
|
||||||
i = len(self.name) + 1
|
|
||||||
for key in self.params:
|
|
||||||
if key.startswith(self.name):
|
|
||||||
perms.append(key[i:])
|
|
||||||
return perms
|
|
||||||
|
|
||||||
def _render(self, readonly=False, **kwargs):
|
|
||||||
principal = self.field.model
|
|
||||||
html = ''
|
|
||||||
for groupkey in sorted(permissions, key=lambda k: permissions[k]['label'].lower()):
|
|
||||||
inner = HTML.tag('p', c=permissions[groupkey]['label'])
|
|
||||||
perms = permissions[groupkey]['perms']
|
|
||||||
rendered = False
|
|
||||||
for key in sorted(perms, key=lambda p: perms[p]['label'].lower()):
|
|
||||||
checked = has_permission(Session(), principal, key,
|
|
||||||
include_guest=include_guest,
|
|
||||||
include_authenticated=include_authenticated)
|
|
||||||
if checked or not readonly:
|
|
||||||
label = perms[key]['label']
|
|
||||||
if readonly:
|
|
||||||
span = HTML.tag('span', c="[X]" if checked else "[ ]")
|
|
||||||
inner += HTML.tag('p', class_='perm', c=span + ' ' + label)
|
|
||||||
else:
|
|
||||||
inner += tags.checkbox(self.name + '-' + key,
|
|
||||||
checked=checked, label=label)
|
|
||||||
rendered = True
|
|
||||||
if rendered:
|
|
||||||
html += HTML.tag('div', class_='group', c=inner)
|
|
||||||
return html or "(none granted)"
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
return self._render(**kwargs)
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
return self._render(readonly=True, **kwargs)
|
|
||||||
|
|
||||||
return PermissionsFieldRenderer
|
|
|
@ -1,60 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Vendor Field Renderers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from formalchemy.fields import SelectFieldRenderer
|
|
||||||
from webhelpers2.html import tags
|
|
||||||
|
|
||||||
from tailbone.forms.renderers.common import AutocompleteFieldRenderer
|
|
||||||
|
|
||||||
|
|
||||||
class VendorFieldRenderer(AutocompleteFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Vendor` instance fields.
|
|
||||||
"""
|
|
||||||
service_route = 'vendors.autocomplete'
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
vendor = self.raw_value
|
|
||||||
if not vendor:
|
|
||||||
return ""
|
|
||||||
text = "({}) {}".format(vendor.id, vendor.name)
|
|
||||||
if kwargs.get('hyperlink', True):
|
|
||||||
return tags.link_to(text, self.request.route_url('vendors.view', uuid=vendor.uuid))
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
class PurchaseFieldRenderer(SelectFieldRenderer):
|
|
||||||
"""
|
|
||||||
Renderer for :class:`rattail.db.model.Purchase` relation fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def render_readonly(self, **kwargs):
|
|
||||||
purchase = self.raw_value
|
|
||||||
if not purchase:
|
|
||||||
return ''
|
|
||||||
return tags.link_to(purchase, self.request.route_url('purchases.view', uuid=purchase.uuid))
|
|
|
@ -1,82 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Simple Forms
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from rattail.util import prettify
|
|
||||||
|
|
||||||
import pyramid_simpleform
|
|
||||||
from pyramid_simpleform import renderers
|
|
||||||
from webhelpers2.html import tags, HTML
|
|
||||||
|
|
||||||
from tailbone.forms import Form
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleForm(Form):
|
|
||||||
"""
|
|
||||||
Customized simple form.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request, schema, obj=None, **kwargs):
|
|
||||||
super(SimpleForm, self).__init__(request, **kwargs)
|
|
||||||
self._form = pyramid_simpleform.Form(request, schema=schema, obj=obj)
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self._form, attr)
|
|
||||||
|
|
||||||
def render(self, **kwargs):
|
|
||||||
kwargs['form'] = FormRenderer(self)
|
|
||||||
return super(SimpleForm, self).render(**kwargs)
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
return self._form.validate()
|
|
||||||
|
|
||||||
|
|
||||||
class FormRenderer(renderers.FormRenderer):
|
|
||||||
"""
|
|
||||||
Customized form renderer. Provides some extra methods for convenience.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.form, attr)
|
|
||||||
|
|
||||||
def field_div(self, name, field, label=None):
|
|
||||||
errors = self.errors_for(name)
|
|
||||||
if errors:
|
|
||||||
errors = [HTML.tag('div', class_='field-error', c=x) for x in errors]
|
|
||||||
errors = tags.literal('').join(errors)
|
|
||||||
|
|
||||||
label = HTML.tag('label', for_=name, c=label or prettify(name))
|
|
||||||
inner = HTML.tag('div', class_='field', c=field)
|
|
||||||
|
|
||||||
outer_class = 'field-wrapper {}'.format(name)
|
|
||||||
if errors:
|
|
||||||
outer_class += ' error'
|
|
||||||
outer = HTML.tag('div', class_=outer_class, c=(errors or '') + label + inner)
|
|
||||||
return outer
|
|
||||||
|
|
||||||
def referrer_field(self):
|
|
||||||
return self.hidden('referrer', value=self.form.request.get_referrer())
|
|
|
@ -1,153 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Custom Form Validators
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from rattail.db import model
|
|
||||||
from rattail.db.util import validate_email_address, validate_phone_number
|
|
||||||
from rattail.gpc import GPC
|
|
||||||
|
|
||||||
import formencode as fe
|
|
||||||
import formalchemy as fa
|
|
||||||
|
|
||||||
from tailbone.db import Session
|
|
||||||
|
|
||||||
|
|
||||||
class ValidGPC(fe.validators.FancyValidator):
|
|
||||||
"""
|
|
||||||
Validator for fields which should contain GPC value.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _to_python(self, value, state):
|
|
||||||
if value is not None:
|
|
||||||
digits = re.sub(r'\D', '', value)
|
|
||||||
if digits:
|
|
||||||
try:
|
|
||||||
return GPC(digits)
|
|
||||||
except ValueError as error:
|
|
||||||
raise fe.Invalid("Invalid UPC: {}".format(error), value, state)
|
|
||||||
|
|
||||||
def _from_python(self, upc, state):
|
|
||||||
if upc is None:
|
|
||||||
return ''
|
|
||||||
return upc.pretty()
|
|
||||||
|
|
||||||
|
|
||||||
class ModelValidator(fe.validators.FancyValidator):
|
|
||||||
"""
|
|
||||||
Generic validator for data model reference fields.
|
|
||||||
"""
|
|
||||||
model_class = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def model_name(self):
|
|
||||||
self.model_class.__name__
|
|
||||||
|
|
||||||
def _to_python(self, value, state):
|
|
||||||
if value:
|
|
||||||
obj = Session.query(self.model_class).get(value)
|
|
||||||
if obj:
|
|
||||||
return obj
|
|
||||||
raise fe.Invalid("{} not found".format(self.model_name), value, state)
|
|
||||||
|
|
||||||
def _from_python(self, value, state):
|
|
||||||
obj = value
|
|
||||||
if not obj:
|
|
||||||
return ''
|
|
||||||
return obj.uuid
|
|
||||||
|
|
||||||
def validate_python(self, value, state):
|
|
||||||
obj = value
|
|
||||||
if obj is not None and not isinstance(obj, self.model_class):
|
|
||||||
raise fe.Invalid("Value must be a valid {} object".format(self.model_name), value, state)
|
|
||||||
|
|
||||||
|
|
||||||
class ValidStore(ModelValidator):
|
|
||||||
"""
|
|
||||||
Validator for store field.
|
|
||||||
"""
|
|
||||||
model_class = model.Store
|
|
||||||
|
|
||||||
|
|
||||||
class ValidCustomer(ModelValidator):
|
|
||||||
"""
|
|
||||||
Validator for customer field.
|
|
||||||
"""
|
|
||||||
model_class = model.Customer
|
|
||||||
|
|
||||||
|
|
||||||
class ValidDepartment(ModelValidator):
|
|
||||||
"""
|
|
||||||
Validator for department field.
|
|
||||||
"""
|
|
||||||
model_class = model.Department
|
|
||||||
|
|
||||||
|
|
||||||
class ValidEmployee(ModelValidator):
|
|
||||||
"""
|
|
||||||
Validator for employee field.
|
|
||||||
"""
|
|
||||||
model_class = model.Employee
|
|
||||||
|
|
||||||
|
|
||||||
class ValidProduct(ModelValidator):
|
|
||||||
"""
|
|
||||||
Validator for product field.
|
|
||||||
"""
|
|
||||||
model_class = model.Product
|
|
||||||
|
|
||||||
|
|
||||||
class ValidUser(ModelValidator):
|
|
||||||
"""
|
|
||||||
Validator for user field.
|
|
||||||
"""
|
|
||||||
model_class = model.User
|
|
||||||
|
|
||||||
|
|
||||||
def valid_email_address(value, field=None):
|
|
||||||
"""
|
|
||||||
FormAlchemy-compatible validation function, which leverages FormEncode
|
|
||||||
under the hood.
|
|
||||||
"""
|
|
||||||
if value:
|
|
||||||
try:
|
|
||||||
return validate_email_address(value, error=True)
|
|
||||||
except Exception as error:
|
|
||||||
raise fa.ValidationError(unicode(error))
|
|
||||||
|
|
||||||
|
|
||||||
def valid_phone_number(value, field=None):
|
|
||||||
"""
|
|
||||||
FormAlchemy-compatible validation function, which leverages FormEncode
|
|
||||||
under the hood.
|
|
||||||
"""
|
|
||||||
if value:
|
|
||||||
try:
|
|
||||||
return validate_phone_number(value, error=True)
|
|
||||||
except Exception as error:
|
|
||||||
raise fa.ValidationError(unicode(error))
|
|
|
@ -1,4 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
<%namespace file="/autocomplete.mako" import="autocomplete" />
|
|
||||||
|
|
||||||
${autocomplete(field_name, service_url, field_value, field_display, width=width, selected=selected, cleared=cleared)}
|
|
|
@ -1,10 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
${h.text(name, value=value)}
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
$('input[name="${name}"]').datepicker({
|
|
||||||
changeYear: ${'true' if change_year else 'false'},
|
|
||||||
dateFormat: 'yy-mm-dd'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,40 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
<% _focus_rendered = False %>
|
|
||||||
|
|
||||||
% for error in fieldset.errors.get(None, []):
|
|
||||||
<div class="fieldset-error">${error}</div>
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
% for field in fieldset.render_fields.itervalues():
|
|
||||||
|
|
||||||
% if field.requires_label:
|
|
||||||
<div class="field-wrapper ${field.name}">
|
|
||||||
% for error in field.errors:
|
|
||||||
<div class="field-error">${error}</div>
|
|
||||||
% endfor
|
|
||||||
${field.label_tag()|n}
|
|
||||||
<div class="field">${field.render()|n}</div>
|
|
||||||
% if 'instructions' in field.metadata:
|
|
||||||
<span class="instructions">${field.metadata['instructions']}</span>
|
|
||||||
% endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
% if not _focus_rendered and (fieldset.focus is True or fieldset.focus is field):
|
|
||||||
% if not field.is_readonly() and getattr(field.renderer, 'needs_focus', True):
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
% if hasattr(field.renderer, 'focus_name'):
|
|
||||||
$('#${field.renderer.focus_name}').focus();
|
|
||||||
% else:
|
|
||||||
$('#${field.renderer.name}').focus();
|
|
||||||
% endif
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<% _focus_rendered = True %>
|
|
||||||
% endif
|
|
||||||
% endif
|
|
||||||
% else:
|
|
||||||
${field.render()|n}
|
|
||||||
% endif
|
|
||||||
|
|
||||||
% endfor
|
|
|
@ -1,7 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
<%namespace file="/forms/lib.mako" import="render_field_readonly" />
|
|
||||||
<div class="fieldset">
|
|
||||||
% for field in fieldset.render_fields.itervalues():
|
|
||||||
${render_field_readonly(field)}
|
|
||||||
% endfor
|
|
||||||
</div>
|
|
|
@ -1,21 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
<div class="form">
|
|
||||||
${h.form(form.action_url, id=form.id or None, method='post', enctype='multipart/form-data')}
|
|
||||||
${form.csrf_token()}
|
|
||||||
|
|
||||||
${form.render_fields()|n}
|
|
||||||
|
|
||||||
% if buttons:
|
|
||||||
${buttons|n}
|
|
||||||
% else:
|
|
||||||
<div class="buttons">
|
|
||||||
${h.submit('create', form.create_label if form.creating else form.update_label)}
|
|
||||||
% if form.creating and form.allow_successive_creates:
|
|
||||||
${h.submit('create_and_continue', form.successive_create_label)}
|
|
||||||
% endif
|
|
||||||
<a href="${form.cancel_url}" class="button">Cancel</a>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
|
|
||||||
${h.end_form()}
|
|
||||||
</div>
|
|
|
@ -1,7 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
<div class="form">
|
|
||||||
${form.render_fields()|n}
|
|
||||||
% if buttons:
|
|
||||||
${buttons|n}
|
|
||||||
% endif
|
|
||||||
</div>
|
|
|
@ -1,10 +0,0 @@
|
||||||
## -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
<%def name="render_field_readonly(field)">
|
|
||||||
% if field.requires_label:
|
|
||||||
<div class="field-wrapper ${field.name}">
|
|
||||||
${field.label_tag()|n}
|
|
||||||
<div class="field">${field.render_readonly()}</div>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
</%def>
|
|
Loading…
Reference in a new issue