Files
@ c2e9fa513d6d
Branch filter:
Location: rattail-project/rattail/rattail/app.py
c2e9fa513d6d
13.5 KiB
text/x-python
Add `get_past_products()` method for custorder batch handler
also add more to `get_product_info()`
also add more to `get_product_info()`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | # -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2021 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/>.
#
################################################################################
"""
App Handler
"""
from __future__ import unicode_literals, absolute_import
import os
# import re
import tempfile
from sqlalchemy import orm
from rattail.util import load_object, pretty_quantity, progress_loop
from rattail.files import temp_path
from rattail.mail import send_email
class AppHandler(object):
"""
Base class and default implementation for top-level Rattail app handler.
aka. "the handler to handle all handlers"
aka. "one handler to bind them all"
"""
default_autocompleters = {
'brands': 'rattail.autocomplete.brands:BrandAutocompleter',
'customers': 'rattail.autocomplete.customers:CustomerAutocompleter',
'customers.neworder': 'rattail.autocomplete.customers:CustomerNewOrderAutocompleter',
'customers.phone': 'rattail.autocomplete.customers:CustomerPhoneAutocompleter',
'employees': 'rattail.autocomplete.employees:EmployeeAutocompleter',
'departments': 'rattail.autocomplete.departments:DepartmentAutocompleter',
'people': 'rattail.autocomplete.people:PersonAutocompleter',
'people.employees': 'rattail.autocomplete.people:PersonEmployeeAutocompleter',
'people.neworder': 'rattail.autocomplete.people:PersonNewOrderAutocompleter',
'products': 'rattail.autocomplete.products:ProductAutocompleter',
'products.all': 'rattail.autocomplete.products:ProductAllAutocompleter',
'products.neworder': 'rattail.autocomplete.products:ProductNewOrderAutocompleter',
'vendors': 'rattail.autocomplete.vendors:VendorAutocompleter',
}
def __init__(self, config):
self.config = config
def get_autocompleter(self, key, **kwargs):
"""
Returns a new :class:`~rattail.autocomplete.base.Autocompleter`
instance corresponding to the given key, e.g. ``'products'``.
The app handler has some hard-coded defaults for the built-in
autocompleters (see ``default_autocompleters`` in the source
code). You can override any of these, and/or add your own
with custom keys, via config, e.g.:
.. code-block:: ini
[rattail]
autocomplete.products = poser.autocomplete.products:ProductAutocompleter
autocomplete.otherthings = poser.autocomplete.things:OtherThingAutocompleter
With the above you can then fetch your custom autocompleter with::
autocompleter = app.get_autocompleter('otherthings')
In any case if it can locate the class, it will create an
instance of it and return that.
:params key: Unique key for the type of autocompleter you
need. Often is a simple string, e.g. ``'customers'`` but
sometimes there may be a "modifier" with it to get an
autocompleter with more specific behavior.
For instance ``'customers.phone'`` would effectively give
you a customer autocompleter but which searched by phone
number instead of customer name.
Note that each key is still a simple string though, and that
must be "unique" in the sense that only one autocompleter
can be configured for each key.
:returns: An :class:`~rattail.autocomplete.base.Autocompleter`
instance if found, otherwise ``None``.
"""
spec = self.config.get('rattail', 'autocomplete.{}'.format(key))
if not spec:
spec = self.default_autocompleters.get(key)
if spec:
return load_object(spec)(self.config)
raise NotImplementedError("cannot locate autocompleter for key: {}".format(key))
def get_auth_handler(self, **kwargs):
if not hasattr(self, 'auth_handler'):
spec = self.config.get('rattail', 'auth.handler',
default='rattail.auth:AuthHandler')
factory = load_object(spec)
self.auth_handler = factory(self.config, **kwargs)
return self.auth_handler
def get_board_handler(self, **kwargs):
if not hasattr(self, 'board_handler'):
from rattail.board import get_board_handler
self.board_handler = get_board_handler(self.config, **kwargs)
return self.board_handler
def get_clientele_handler(self, **kwargs):
if not hasattr(self, 'clientele_handler'):
from rattail.clientele import get_clientele_handler
self.clientele_handler = get_clientele_handler(self.config, **kwargs)
return self.clientele_handler
def get_employment_handler(self, **kwargs):
if not hasattr(self, 'employment_handler'):
from rattail.employment import get_employment_handler
self.employment_handler = get_employment_handler(self.config, **kwargs)
return self.employment_handler
def get_feature_handler(self, **kwargs):
if not hasattr(self, 'feature_handler'):
from rattail.features import FeatureHandler
self.feature_handler = FeatureHandler(self.config, **kwargs)
return self.feature_handler
def get_email_handler(self, **kwargs):
if not hasattr(self, 'email_handler'):
from rattail.mail import get_email_handler
self.email_handler = get_email_handler(self.config, **kwargs)
return self.email_handler
# TODO: is it helpful to expose this? or more confusing?
get_mail_handler = get_email_handler
def get_membership_handler(self, **kwargs):
"""
Returns a reference to the configured Membership Handler.
See also :doc:`rattail-manual:base/handlers/other/membership`.
"""
if not hasattr(self, 'membership_handler'):
spec = self.config.get('rattail', 'membership.handler',
default='rattail.membership:MembershipHandler')
factory = load_object(spec)
self.membership_handler = factory(self.config, **kwargs)
return self.membership_handler
def get_people_handler(self, **kwargs):
"""
Returns a reference to the configured People Handler.
See also :doc:`rattail-manual:base/handlers/other/people`.
"""
if not hasattr(self, 'people_handler'):
spec = self.config.get('rattail', 'people.handler',
default='rattail.people:PeopleHandler')
factory = load_object(spec)
self.people_handler = factory(self.config, **kwargs)
return self.people_handler
def get_products_handler(self, **kwargs):
if not hasattr(self, 'products_handler'):
from rattail.products import get_products_handler
self.products_handler = get_products_handler(self.config, **kwargs)
return self.products_handler
def get_report_handler(self, **kwargs):
if not hasattr(self, 'report_handler'):
from rattail.reporting import get_report_handler
self.report_handler = get_report_handler(self.config, **kwargs)
return self.report_handler
def progress_loop(self, *args, **kwargs):
return progress_loop(*args, **kwargs)
def get_session(self, obj):
"""
Returns the SQLAlchemy session with which the given object is
associated. Simple convenience wrapper around
``sqlalchemy.orm.object_session()``.
"""
return orm.object_session(obj)
def make_session(self, **kwargs):
"""
Creates and returns a new SQLAlchemy session for the Rattail DB.
"""
from rattail.db import Session
return Session(**kwargs)
def cache_model(self, session, model, **kwargs):
"""
Convenience method which invokes
:func:`rattail.db.cache.cache_model()` with the given model
and keyword arguments.
"""
from rattail.db import cache
return cache.cache_model(session, model, **kwargs)
def make_temp_dir(self, **kwargs):
"""
Create a temporary directory. This is mostly a convenience wrapper
around the built-in ``tempfile.mkdtemp()``.
"""
if 'dir' not in kwargs:
workdir = self.config.workdir(require=False)
if workdir:
tmpdir = os.path.join(workdir, 'tmp')
if not os.path.exists(tmpdir):
os.makedirs(tmpdir)
kwargs['dir'] = tmpdir
return tempfile.mkdtemp(**kwargs)
def make_temp_file(self, **kwargs):
"""
Reserve a temporary filename. This is mostly a convenience wrapper
around the built-in ``tempfile.mkstemp()``.
"""
if 'dir' not in kwargs:
workdir = self.config.workdir(require=False)
if workdir:
tmpdir = os.path.join(workdir, 'tmp')
if not os.path.exists(tmpdir):
os.makedirs(tmpdir)
kwargs['dir'] = tmpdir
return temp_path(**kwargs)
def normalize_phone_number(self, number):
"""
Normalize the given phone number, to a "common" format that
can be more easily worked with for sync logic etc.
"""
from rattail.db.util import normalize_phone_number
return normalize_phone_number(number)
def phone_number_is_invalid(self, number):
"""
This method should validate the given phone number string, and if the
number is *not* considered valid, this method should return the reason
as string. If the number is valid, returns ``None``.
"""
# strip non-numeric chars, and make sure we have 10 left
normal = self.normalize_phone_number(number)
if len(normal) != 10:
return "Phone number must have 10 digits"
def format_phone_number(self, number):
"""
Returns a "properly formatted" string based on the given phone number.
"""
from rattail.db.util import format_phone_number
return format_phone_number(number)
def render_currency(self, value, scale=2, **kwargs):
"""
Must return a human-friendly display string for the given currency
value, e.g. ``Decimal('4.20')`` becomes ``"$4.20"``.
"""
if value is not None:
if value < 0:
fmt = "(${{:0,.{}f}})".format(scale)
return fmt.format(0 - value)
fmt = "${{:0,.{}f}}".format(scale)
return fmt.format(value)
def render_quantity(self, value, **kwargs):
"""
Return a human-friendly display string for the given quantity
value, e.g. ``1.000`` becomes ``"1"``.
"""
return pretty_quantity(value, **kwargs)
def render_date(self, value, **kwargs):
"""
Must return a human-friendly display string for the given ``date``
object.
"""
if value is not None:
return value.strftime('%Y-%m-%d')
def render_datetime(self, value, **kwargs):
"""
Must return a human-friendly display string for the given ``datetime``
object.
"""
if value is not None:
return value.strftime('%Y-%m-%d %I:%M:%S %p')
def send_email(self, key, data={}, **kwargs):
"""
Send an email message of the given type.
See :func:`rattail.mail.send_email()` for more info.
"""
send_email(self.config, key, data, **kwargs)
class GenericHandler(object):
"""
Base class for misc. "generic" feature handlers.
Most handlers which exist for sake of business logic, should inherit from
this.
"""
def __init__(self, config, **kwargs):
self.config = config
self.enum = self.config.get_enum()
self.model = self.config.get_model()
self.app = self.config.get_app()
def progress_loop(self, *args, **kwargs):
return self.app.progress_loop(*args, **kwargs)
def get_session(self, obj):
"""
Convenience wrapper around :meth:`AppHandler.get_session()`.
"""
return self.app.get_session(obj)
def make_session(self):
"""
Convenience wrapper around :meth:`AppHandler.make_session()`.
"""
return self.app.make_session()
def cache_model(self, session, model, **kwargs):
"""
Convenience method which invokes :func:`rattail.db.cache.cache_model()`
with the given model and keyword arguments.
"""
return self.app.cache_model(session, model, **kwargs)
def make_app(config, **kwargs):
"""
Create and return the configured :class:`AppHandler` instance.
"""
spec = config.get('rattail', 'app.handler')
if spec:
factory = load_object(spec)
else:
factory = AppHandler
return factory(config, **kwargs)
|