Compare commits
17 commits
Author | SHA1 | Date | |
---|---|---|---|
988ebe29d0 | |||
25b5ca127d | |||
1b4ba2089a | |||
eb9291fce7 | |||
f0ac1d9bd4 | |||
4df38318e3 | |||
4ee5aa5372 | |||
73192a162d | |||
1a9929c734 | |||
e6921c8533 | |||
bf6bf63e68 | |||
d1de2389a5 | |||
34e9528f4b | |||
ce145ed00f | |||
9f779fe60f | |||
![]() |
9946642bf2 | ||
![]() |
93c5f57928 |
29 changed files with 703 additions and 188 deletions
4
.pylintrc
Normal file
4
.pylintrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
# -*- mode: conf; -*-
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=fixme
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -5,6 +5,27 @@ All notable changes to Wutta-COREPOS will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## v0.3.0 (2025-02-20)
|
||||
|
||||
### Feat
|
||||
|
||||
- add app db schema extension, for CoreUser
|
||||
- add support for `lane_op` and `lane_trans` DB sessions, models
|
||||
|
||||
### Fix
|
||||
|
||||
- bump min version for wuttjamaican
|
||||
- bump version requirement for pyCOREPOS
|
||||
- add `get_office_employee_url()` method for corepos handler
|
||||
- add grid links for Members
|
||||
- bump min version for pycorepos dependency
|
||||
|
||||
## v0.2.1 (2025-01-15)
|
||||
|
||||
### Fix
|
||||
|
||||
- add latest schema columns on app startup, unless not supported
|
||||
|
||||
## v0.2.0 (2025-01-13)
|
||||
|
||||
### Feat
|
||||
|
|
6
docs/api/wutta_corepos.db.model.rst
Normal file
6
docs/api/wutta_corepos.db.model.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
``wutta_corepos.db.model``
|
||||
==========================
|
||||
|
||||
.. automodule:: wutta_corepos.db.model
|
||||
:members:
|
6
docs/api/wutta_corepos.db.rst
Normal file
6
docs/api/wutta_corepos.db.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
``wutta_corepos.db``
|
||||
====================
|
||||
|
||||
.. automodule:: wutta_corepos.db
|
||||
:members:
|
28
docs/conf.py
28
docs/conf.py
|
@ -8,32 +8,32 @@
|
|||
|
||||
from importlib.metadata import version as get_version
|
||||
|
||||
project = 'Wutta-COREPOS'
|
||||
copyright = '2025, Lance Edgar'
|
||||
author = 'Lance Edgar'
|
||||
release = get_version('Wutta-COREPOS')
|
||||
project = "Wutta-COREPOS"
|
||||
copyright = "2025, Lance Edgar"
|
||||
author = "Lance Edgar"
|
||||
release = get_version("Wutta-COREPOS")
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.todo',
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.todo",
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
intersphinx_mapping = {
|
||||
'wuttaweb': ('https://rattailproject.org/docs/wuttaweb/', None),
|
||||
'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None),
|
||||
"wuttaweb": ("https://docs.wuttaproject.org/wuttaweb/", None),
|
||||
"wuttjamaican": ("https://docs.wuttaproject.org/wuttjamaican/", None),
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'furo'
|
||||
html_static_path = ['_static']
|
||||
html_theme = "furo"
|
||||
html_static_path = ["_static"]
|
||||
|
|
|
@ -5,13 +5,26 @@ Wutta-COREPOS
|
|||
This package adds basic integration with `CORE-POS`_, using
|
||||
`pyCOREPOS`_.
|
||||
|
||||
Its main purpose is to setup DB connections for CORE Office, but it
|
||||
also contains basic readonly web views for some CORE tables.
|
||||
It provides the following:
|
||||
|
||||
* standard configuration for CORE Office + Lane databases
|
||||
* special :term:`handler` for CORE integration
|
||||
(:class:`~wutta_corepos.handler.CoreposHandler`)
|
||||
* readonly web views for primary CORE Office DB tables
|
||||
* :term:`data model` extension to map
|
||||
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` to CORE
|
||||
Employee
|
||||
|
||||
.. _CORE-POS: https://www.core-pos.com/
|
||||
|
||||
.. _pyCOREPOS: https://pypi.org/project/pyCOREPOS/
|
||||
|
||||
.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen
|
||||
:target: https://github.com/pylint-dev/pylint
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
@ -26,6 +39,8 @@ also contains basic readonly web views for some CORE tables.
|
|||
api/wutta_corepos
|
||||
api/wutta_corepos.app
|
||||
api/wutta_corepos.conf
|
||||
api/wutta_corepos.db
|
||||
api/wutta_corepos.db.model
|
||||
api/wutta_corepos.handler
|
||||
api/wutta_corepos.web
|
||||
api/wutta_corepos.web.db
|
||||
|
|
|
@ -12,8 +12,7 @@ Install the Wutta-COREPOS package to your virtual environment:
|
|||
pip install Wutta-COREPOS
|
||||
|
||||
Edit your :term:`config file` to add CORE-POS DB connection info, and
|
||||
related settings. Note that so far, only CORE Office DB connections
|
||||
are supported.
|
||||
related settings.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
|
@ -29,4 +28,58 @@ are supported.
|
|||
[corepos.db.office_arch]
|
||||
default.url = mysql+mysqlconnector://localhost/trans_archive
|
||||
|
||||
[corepos.db.lane_op]
|
||||
keys = 01, 02
|
||||
01.url = mysql+mysqlconnector://lane01/opdata
|
||||
02.url = mysql+mysqlconnector://lane02/opdata
|
||||
|
||||
[corepos.db.lane_trans]
|
||||
keys = 01, 02
|
||||
01.url = mysql+mysqlconnector://lane01/translog
|
||||
02.url = mysql+mysqlconnector://lane02/translog
|
||||
|
||||
And that's it, the CORE-POS integration is configured.
|
||||
|
||||
|
||||
Schema Extension
|
||||
----------------
|
||||
|
||||
As of writing the only reason to add the schema extension is if you
|
||||
need to map Wutta Users to CORE Employees, for auth (login) purposes.
|
||||
So this section can be skipped if you do not need that.
|
||||
|
||||
This will effectively add the
|
||||
:attr:`~wutta_corepos.db.model.CoreUser.corepos_employee_number`
|
||||
attribute on the
|
||||
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` model.
|
||||
|
||||
First you must override the :term:`app model` with your own. To do
|
||||
this, create your own module (e.g. ``poser.db.model``) to contain::
|
||||
|
||||
from wuttjamaican.db.model import *
|
||||
from wutta_corepos.db.model import *
|
||||
|
||||
Then configure your app model to override the default:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[wutta]
|
||||
model_spec = poser.db.model
|
||||
|
||||
Then configure the Alembic section for schema migrations:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[alembic]
|
||||
script_location = wuttjamaican.db:alembic
|
||||
version_locations = wutta_corepos.db:alembic/versions wuttjamaican.db:alembic/versions
|
||||
|
||||
And finally run the Alembic command to migrate:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
cd /path/to/env
|
||||
bin/alembic -c app/wutta.conf upgrade heads
|
||||
|
||||
That should do it, from then on any changes will be migrated
|
||||
automatically during upgrade.
|
||||
|
|
|
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "Wutta-COREPOS"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Wutta Framework integration for CORE-POS"
|
||||
readme = "README.md"
|
||||
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
||||
|
@ -28,15 +28,15 @@ classifiers = [
|
|||
]
|
||||
requires-python = ">= 3.8"
|
||||
dependencies = [
|
||||
"pyCOREPOS>=0.3.3",
|
||||
"WuttJamaican>=0.20.1",
|
||||
"pyCOREPOS>=0.5.1",
|
||||
"WuttJamaican>=0.20.3",
|
||||
]
|
||||
|
||||
|
||||
[project.optional-dependencies]
|
||||
web = ["WuttaWeb"]
|
||||
docs = ["Sphinx", "furo"]
|
||||
tests = ["pytest-cov", "tox"]
|
||||
tests = ["pylint", "pytest", "pytest-cov", "tox"]
|
||||
|
||||
|
||||
[project.entry-points."wutta.app.providers"]
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Package Version
|
||||
"""
|
||||
|
||||
from importlib.metadata import version
|
||||
|
||||
|
||||
__version__ = version('Wutta-COREPOS')
|
||||
__version__ = version("Wutta-COREPOS")
|
||||
|
|
|
@ -41,9 +41,11 @@ class WuttaCoreposAppProvider(AppProvider):
|
|||
|
||||
:rtype: :class:`~wutta_corepos.handler.CoreposHandler`
|
||||
"""
|
||||
if not hasattr(self, 'corepos_handler'):
|
||||
spec = self.config.get(f'{self.appname}.corepos_handler',
|
||||
default='wutta_corepos.handler:CoreposHandler')
|
||||
if "corepos" not in self.app.handlers:
|
||||
spec = self.config.get(
|
||||
f"{self.appname}.corepos_handler",
|
||||
default="wutta_corepos.handler:CoreposHandler",
|
||||
)
|
||||
factory = self.app.load_object(spec)
|
||||
self.corepos_handler = factory(self.config, **kwargs)
|
||||
return self.corepos_handler
|
||||
self.app.handlers["corepos"] = factory(self.config, **kwargs)
|
||||
return self.app.handlers["corepos"]
|
||||
|
|
|
@ -74,29 +74,97 @@ class WuttaCoreposConfigExtension(WuttaConfigExtension):
|
|||
Dict of ``office_arch`` DB engines. May be empty if no config
|
||||
is found; otherwise there should at least be a ``default`` key
|
||||
defined, corresonding to :data:`core_office_arch_engine`.
|
||||
"""
|
||||
key = 'wutta_corepos'
|
||||
|
||||
def configure(self, config):
|
||||
.. data:: core_lane_op_engine
|
||||
|
||||
Primary engine for the ``lane_op`` DB. May be null if no
|
||||
"default" engine is configured - which is *typical* for a
|
||||
multi-lane environment. See :data:`core_lane_op_engines` for
|
||||
the full set.
|
||||
|
||||
.. data:: core_lane_op_engines
|
||||
|
||||
Dict of ``lane_op`` DB engines. May be empty if no config is
|
||||
found; otherwise keys are typically like ``01`` and ``02`` etc.
|
||||
If present, the ``default`` key will correspond to
|
||||
:data:`core_lane_op_engine`.
|
||||
|
||||
.. data:: core_lane_trans_engine
|
||||
|
||||
Primary engine for the ``lane_trans`` DB. May be null if no
|
||||
"default" engine is configured - which is *typical* for a
|
||||
multi-lane environment. See :data:`core_lane_trans_engines`
|
||||
for the full set.
|
||||
|
||||
.. data:: core_lane_trans_engines
|
||||
|
||||
Dict of ``lane_trans`` DB engines. May be empty if no config
|
||||
is found; otherwise keys are typically like ``01`` and ``02``
|
||||
etc. If present, the ``default`` key will correspond to
|
||||
:data:`core_lane_trans_engine`.
|
||||
"""
|
||||
|
||||
key = "wutta_corepos"
|
||||
|
||||
def configure(self, config): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
|
||||
# office_op
|
||||
from corepos.db.office_op import Session
|
||||
engines = get_engines(config, 'corepos.db.office_op')
|
||||
from corepos.db.office_op import ( # pylint: disable=import-outside-toplevel
|
||||
Session as OfficeOpSession,
|
||||
)
|
||||
|
||||
engines = get_engines(config, "corepos.db.office_op")
|
||||
config.core_office_op_engines = engines
|
||||
config.core_office_op_engine = engines.get('default')
|
||||
Session.configure(bind=config.core_office_op_engine)
|
||||
config.core_office_op_engine = engines.get("default")
|
||||
OfficeOpSession.configure(bind=config.core_office_op_engine)
|
||||
|
||||
# office_trans
|
||||
from corepos.db.office_trans import Session
|
||||
engines = get_engines(config, 'corepos.db.office_trans')
|
||||
from corepos.db.office_trans import ( # pylint: disable=import-outside-toplevel
|
||||
Session as OfficeTransSession,
|
||||
)
|
||||
|
||||
engines = get_engines(config, "corepos.db.office_trans")
|
||||
config.core_office_trans_engines = engines
|
||||
config.core_office_trans_engine = engines.get('default')
|
||||
Session.configure(bind=config.core_office_trans_engine)
|
||||
config.core_office_trans_engine = engines.get("default")
|
||||
OfficeTransSession.configure(bind=config.core_office_trans_engine)
|
||||
|
||||
# office_arch
|
||||
from corepos.db.office_arch import Session
|
||||
engines = get_engines(config, 'corepos.db.office_arch')
|
||||
from corepos.db.office_arch import ( # pylint: disable=import-outside-toplevel
|
||||
Session as OfficeArchSession,
|
||||
)
|
||||
|
||||
engines = get_engines(config, "corepos.db.office_arch")
|
||||
config.core_office_arch_engines = engines
|
||||
config.core_office_arch_engine = engines.get('default')
|
||||
Session.configure(bind=config.core_office_arch_engine)
|
||||
config.core_office_arch_engine = engines.get("default")
|
||||
OfficeArchSession.configure(bind=config.core_office_arch_engine)
|
||||
|
||||
# lane_op
|
||||
from corepos.db.lane_op import ( # pylint: disable=import-outside-toplevel
|
||||
Session as LaneOpSession,
|
||||
)
|
||||
|
||||
engines = get_engines(config, "corepos.db.lane_op")
|
||||
config.core_lane_op_engines = engines
|
||||
config.core_lane_op_engine = engines.get("default")
|
||||
LaneOpSession.configure(bind=config.core_lane_op_engine)
|
||||
|
||||
# lane_trans
|
||||
from corepos.db.lane_trans import ( # pylint: disable=import-outside-toplevel
|
||||
Session as LaneTransSession,
|
||||
)
|
||||
|
||||
engines = get_engines(config, "corepos.db.lane_trans")
|
||||
config.core_lane_trans_engines = engines
|
||||
config.core_lane_trans_engine = engines.get("default")
|
||||
LaneTransSession.configure(bind=config.core_lane_trans_engine)
|
||||
|
||||
# define some schema columns "late" unless not supported
|
||||
if config.get_bool(
|
||||
"corepos.db.office_op.use_latest_columns", default=True, usedb=False
|
||||
):
|
||||
from corepos.db.office_op.model import ( # pylint: disable=import-outside-toplevel
|
||||
use_latest_columns,
|
||||
)
|
||||
|
||||
use_latest_columns()
|
||||
|
|
0
src/wutta_corepos/db/__init__.py
Normal file
0
src/wutta_corepos/db/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
"""initial user extension
|
||||
|
||||
Revision ID: 0f94089f1af1
|
||||
Revises:
|
||||
Create Date: 2025-01-24 21:13:14.359200
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import wuttjamaican.db.util
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "0f94089f1af1"
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = ("wutta_corepos",)
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
|
||||
# corepos_user
|
||||
op.create_table(
|
||||
"corepos_user",
|
||||
sa.Column("uuid", wuttjamaican.db.util.UUID(), nullable=False),
|
||||
sa.Column("corepos_employee_number", sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["uuid"], ["user.uuid"], name=op.f("fk_corepos_user_uuid_user")
|
||||
),
|
||||
sa.PrimaryKeyConstraint("uuid", name=op.f("pk_corepos_user")),
|
||||
sa.UniqueConstraint(
|
||||
"corepos_employee_number",
|
||||
name=op.f("uq_corepos_user_corepos_employee_number"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
# corepos_user
|
||||
op.drop_table("corepos_user")
|
74
src/wutta_corepos/db/model.py
Normal file
74
src/wutta_corepos/db/model.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Wutta-COREPOS -- Wutta Framework integration for CORE-POS
|
||||
# Copyright © 2025 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
#
|
||||
# Wutta Framework 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.
|
||||
#
|
||||
# Wutta Framework 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
|
||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Data models for CORE-POS integration
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
|
||||
from wuttjamaican.db import model
|
||||
|
||||
|
||||
class CoreUser(model.Base): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
CORE-POS extension for
|
||||
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User`.
|
||||
"""
|
||||
|
||||
__tablename__ = "corepos_user"
|
||||
|
||||
uuid = model.uuid_column(sa.ForeignKey("user.uuid"), default=None)
|
||||
user = orm.relationship(
|
||||
model.User,
|
||||
cascade_backrefs=False,
|
||||
doc="""
|
||||
Reference to the
|
||||
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` which
|
||||
this record extends.
|
||||
""",
|
||||
backref=orm.backref(
|
||||
"_corepos",
|
||||
uselist=False,
|
||||
cascade="all, delete-orphan",
|
||||
cascade_backrefs=False,
|
||||
doc="""
|
||||
Reference to the CORE-POS extension record for the user.
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
corepos_employee_number = sa.Column(
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
doc="""
|
||||
``employees.emp_no`` value for the user within CORE-POS.
|
||||
""",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
|
||||
CoreUser.make_proxy(model.User, "_corepos", "corepos_employee_number")
|
|
@ -38,7 +38,9 @@ class CoreposHandler(GenericHandler):
|
|||
Returns the :term:`data model` module for CORE Office 'op' DB,
|
||||
i.e. :mod:`pycorepos:corepos.db.office_op.model`.
|
||||
"""
|
||||
from corepos.db.office_op import model
|
||||
from corepos.db.office_op import ( # pylint: disable=import-outside-toplevel
|
||||
model,
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
|
@ -47,7 +49,9 @@ class CoreposHandler(GenericHandler):
|
|||
Returns the :term:`data model` module for CORE Office 'trans'
|
||||
DB, i.e. :mod:`pycorepos:corepos.db.office_trans.model`.
|
||||
"""
|
||||
from corepos.db.office_trans import model
|
||||
from corepos.db.office_trans import ( # pylint: disable=import-outside-toplevel
|
||||
model,
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
|
@ -56,47 +60,105 @@ class CoreposHandler(GenericHandler):
|
|||
Returns the :term:`data model` module for CORE Office 'arch'
|
||||
DB, i.e. :mod:`pycorepos:corepos.db.office_arch.model`.
|
||||
"""
|
||||
from corepos.db.office_arch import model
|
||||
from corepos.db.office_arch import ( # pylint: disable=import-outside-toplevel
|
||||
model,
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
def make_session_office_op(self, dbkey='default', **kwargs):
|
||||
def get_model_lane_op(self):
|
||||
"""
|
||||
Returns the :term:`data model` module for CORE Lane 'op' DB,
|
||||
i.e. :mod:`pycorepos:corepos.db.lane_op.model`.
|
||||
"""
|
||||
from corepos.db.lane_op import model # pylint: disable=import-outside-toplevel
|
||||
|
||||
return model
|
||||
|
||||
def get_model_lane_trans(self):
|
||||
"""
|
||||
Returns the :term:`data model` module for CORE Lane 'trans'
|
||||
DB, i.e. :mod:`pycorepos:corepos.db.lane_trans.model`.
|
||||
"""
|
||||
from corepos.db.lane_trans import ( # pylint: disable=import-outside-toplevel
|
||||
model,
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
def make_session_office_op(self, dbkey="default", **kwargs):
|
||||
"""
|
||||
Make a new :term:`db session` for the CORE Office 'op' DB.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`pycorepos:corepos.db.office_op.Session`.
|
||||
"""
|
||||
from corepos.db.office_op import Session
|
||||
from corepos.db.office_op import ( # pylint: disable=import-outside-toplevel
|
||||
Session,
|
||||
)
|
||||
|
||||
if 'bind' not in kwargs:
|
||||
kwargs['bind'] = self.config.core_office_op_engines[dbkey]
|
||||
if "bind" not in kwargs:
|
||||
kwargs["bind"] = self.config.core_office_op_engines[dbkey]
|
||||
return Session(**kwargs)
|
||||
|
||||
def make_session_office_trans(self, dbkey='default', **kwargs):
|
||||
def make_session_office_trans(self, dbkey="default", **kwargs):
|
||||
"""
|
||||
Make a new :term:`db session` for the CORE Office 'trans' DB.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`pycorepos:corepos.db.office_trans.Session`.
|
||||
"""
|
||||
from corepos.db.office_trans import Session
|
||||
from corepos.db.office_trans import ( # pylint: disable=import-outside-toplevel
|
||||
Session,
|
||||
)
|
||||
|
||||
if 'bind' not in kwargs:
|
||||
kwargs['bind'] = self.config.core_office_trans_engines[dbkey]
|
||||
if "bind" not in kwargs:
|
||||
kwargs["bind"] = self.config.core_office_trans_engines[dbkey]
|
||||
return Session(**kwargs)
|
||||
|
||||
def make_session_office_arch(self, dbkey='default', **kwargs):
|
||||
def make_session_office_arch(self, dbkey="default", **kwargs):
|
||||
"""
|
||||
Make a new :term:`db session` for the CORE Office 'arch' DB.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`pycorepos:corepos.db.office_arch.Session`.
|
||||
"""
|
||||
from corepos.db.office_arch import Session
|
||||
from corepos.db.office_arch import ( # pylint: disable=import-outside-toplevel
|
||||
Session,
|
||||
)
|
||||
|
||||
if 'bind' not in kwargs:
|
||||
kwargs['bind'] = self.config.core_office_arch_engines[dbkey]
|
||||
if "bind" not in kwargs:
|
||||
kwargs["bind"] = self.config.core_office_arch_engines[dbkey]
|
||||
return Session(**kwargs)
|
||||
|
||||
def make_session_lane_op(self, dbkey="default", **kwargs):
|
||||
"""
|
||||
Make a new :term:`db session` for the CORE Lane 'op' DB.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`pycorepos:corepos.db.lane_op.Session`.
|
||||
"""
|
||||
from corepos.db.lane_op import ( # pylint: disable=import-outside-toplevel
|
||||
Session,
|
||||
)
|
||||
|
||||
if "bind" not in kwargs:
|
||||
kwargs["bind"] = self.config.core_lane_op_engines[dbkey]
|
||||
return Session(**kwargs)
|
||||
|
||||
def make_session_lane_trans(self, dbkey="default", **kwargs):
|
||||
"""
|
||||
Make a new :term:`db session` for the CORE Lane 'trans' DB.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`pycorepos:corepos.db.lane_trans.Session`.
|
||||
"""
|
||||
from corepos.db.lane_trans import ( # pylint: disable=import-outside-toplevel
|
||||
Session,
|
||||
)
|
||||
|
||||
if "bind" not in kwargs:
|
||||
kwargs["bind"] = self.config.core_lane_trans_engines[dbkey]
|
||||
return Session(**kwargs)
|
||||
|
||||
def get_office_url(self, require=False):
|
||||
|
@ -110,15 +172,12 @@ class CoreposHandler(GenericHandler):
|
|||
|
||||
:returns: URL as string.
|
||||
"""
|
||||
url = self.config.get('corepos.office.url', require=require)
|
||||
url = self.config.get("corepos.office.url", require=require)
|
||||
if url:
|
||||
return url.rstrip('/')
|
||||
return url.rstrip("/")
|
||||
return None
|
||||
|
||||
def get_office_department_url(
|
||||
self,
|
||||
dept_id,
|
||||
office_url=None,
|
||||
require=False):
|
||||
def get_office_department_url(self, dept_id, office_url=None, require=False):
|
||||
"""
|
||||
Returns the CORE Office URL for a Department.
|
||||
|
||||
|
@ -134,13 +193,29 @@ class CoreposHandler(GenericHandler):
|
|||
if not office_url:
|
||||
office_url = self.get_office_url(require=require)
|
||||
if office_url:
|
||||
return f'{office_url}/item/departments/DepartmentEditor.php?did={dept_id}'
|
||||
return f"{office_url}/item/departments/DepartmentEditor.php?did={dept_id}"
|
||||
return None
|
||||
|
||||
def get_office_likecode_url(
|
||||
self,
|
||||
likecode_id,
|
||||
office_url=None,
|
||||
require=False):
|
||||
def get_office_employee_url(self, employee_id, office_url=None, require=False):
|
||||
"""
|
||||
Returns the CORE Office URL for an Employee.
|
||||
|
||||
:param employee_id: Employee ID for the URL.
|
||||
|
||||
:param office_url: Root URL from :meth:`get_office_url()`.
|
||||
|
||||
:param require: If true, an error is raised when URL cannot be
|
||||
determined.
|
||||
|
||||
:returns: URL as string.
|
||||
"""
|
||||
if not office_url:
|
||||
office_url = self.get_office_url(require=require)
|
||||
if office_url:
|
||||
return f"{office_url}/admin/Cashiers/CashierEditor.php?emp_no={employee_id}"
|
||||
return None
|
||||
|
||||
def get_office_likecode_url(self, likecode_id, office_url=None, require=False):
|
||||
"""
|
||||
Returns the CORE Office URL for a Like Code.
|
||||
|
||||
|
@ -156,13 +231,10 @@ class CoreposHandler(GenericHandler):
|
|||
if not office_url:
|
||||
office_url = self.get_office_url(require=require)
|
||||
if office_url:
|
||||
return f'{office_url}/item/likecodes/LikeCodeEditor.php?start={likecode_id}'
|
||||
return f"{office_url}/item/likecodes/LikeCodeEditor.php?start={likecode_id}"
|
||||
return None
|
||||
|
||||
def get_office_product_url(
|
||||
self,
|
||||
upc,
|
||||
office_url=None,
|
||||
require=False):
|
||||
def get_office_product_url(self, upc, office_url=None, require=False):
|
||||
"""
|
||||
Returns the CORE Office URL for a Product.
|
||||
|
||||
|
@ -178,13 +250,10 @@ class CoreposHandler(GenericHandler):
|
|||
if not office_url:
|
||||
office_url = self.get_office_url(require=require)
|
||||
if office_url:
|
||||
return f'{office_url}/item/ItemEditorPage.php?searchupc={upc}'
|
||||
return f"{office_url}/item/ItemEditorPage.php?searchupc={upc}"
|
||||
return None
|
||||
|
||||
def get_office_vendor_url(
|
||||
self,
|
||||
vend_id,
|
||||
office_url=None,
|
||||
require=False):
|
||||
def get_office_vendor_url(self, vend_id, office_url=None, require=False):
|
||||
"""
|
||||
Returns the CORE Office URL for a Vendor.
|
||||
|
||||
|
@ -200,4 +269,5 @@ class CoreposHandler(GenericHandler):
|
|||
if not office_url:
|
||||
office_url = self.get_office_url(require=require)
|
||||
if office_url:
|
||||
return f'{office_url}/item/vendors/VendorIndexPage.php?vid={vend_id}'
|
||||
return f"{office_url}/item/vendors/VendorIndexPage.php?vid={vend_id}"
|
||||
return None
|
||||
|
|
|
@ -25,5 +25,5 @@ Wutta-COREPOS -- wuttaweb features
|
|||
"""
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('wutta_corepos.web.views')
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
config.include("wutta_corepos.web.views")
|
||||
|
|
|
@ -49,6 +49,22 @@ in general.
|
|||
.. class:: ExtraCoreArchSessions
|
||||
|
||||
Dict of secondary CORE Office 'arch' DB sessions, if applicable.
|
||||
|
||||
.. class:: CoreLaneOpSession
|
||||
|
||||
Primary web app :term:`db session` for CORE Lane 'op' DB.
|
||||
|
||||
.. class:: CoreLaneTransSession
|
||||
|
||||
Primary web app :term:`db session` for CORE Lane 'trans' DB.
|
||||
|
||||
.. class:: ExtraCoreLaneOpSessions
|
||||
|
||||
Dict of secondary CORE Lane 'op' DB sessions, if applicable.
|
||||
|
||||
.. class:: ExtraCoreLaneTransSessions
|
||||
|
||||
Dict of secondary CORE Lane 'trans' DB sessions, if applicable.
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
|
@ -64,7 +80,15 @@ register(CoreTransSession)
|
|||
CoreArchSession = scoped_session(sessionmaker())
|
||||
register(CoreArchSession)
|
||||
|
||||
CoreLaneOpSession = scoped_session(sessionmaker())
|
||||
register(CoreLaneOpSession)
|
||||
|
||||
CoreLaneTransSession = scoped_session(sessionmaker())
|
||||
register(CoreLaneTransSession)
|
||||
|
||||
# nb. these start out empty but may be populated on app startup
|
||||
ExtraCoreOpSessions = {}
|
||||
ExtraCoreTransSessions = {}
|
||||
ExtraCoreArchSessions = {}
|
||||
ExtraCoreLaneOpSessions = {}
|
||||
ExtraCoreLaneTransSessions = {}
|
||||
|
|
|
@ -25,5 +25,5 @@ Wutta-COREPOS -- wuttaweb views
|
|||
"""
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('wutta_corepos.web.views.corepos')
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
config.include("wutta_corepos.web.views.corepos")
|
||||
|
|
|
@ -27,6 +27,6 @@ Views for CORE-POS
|
|||
from .master import CoreOpMasterView
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('wutta_corepos.web.views.corepos.members')
|
||||
config.include('wutta_corepos.web.views.corepos.products')
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
config.include("wutta_corepos.web.views.corepos.members")
|
||||
config.include("wutta_corepos.web.views.corepos.products")
|
||||
|
|
|
@ -29,10 +29,11 @@ from wuttaweb.views import MasterView
|
|||
from wutta_corepos.web.db import CoreOpSession
|
||||
|
||||
|
||||
class CoreOpMasterView(MasterView):
|
||||
class CoreOpMasterView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Base class for master views which use the CORE Office 'op' DB.
|
||||
"""
|
||||
|
||||
Session = CoreOpSession
|
||||
|
||||
def __init__(self, request, context=None):
|
||||
|
|
|
@ -32,7 +32,7 @@ from corepos.db.office_op.model import MemberInfo
|
|||
from wutta_corepos.web.views.corepos import CoreOpMasterView
|
||||
|
||||
|
||||
class MemberView(CoreOpMasterView):
|
||||
class MemberView(CoreOpMasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for
|
||||
:class:`~pycorepos:corepos.db.office_op.model.MemberInfo`; route
|
||||
|
@ -43,10 +43,11 @@ class MemberView(CoreOpMasterView):
|
|||
* ``/corepos/members/``
|
||||
* ``/corepos/members/XXX``
|
||||
"""
|
||||
|
||||
model_class = MemberInfo
|
||||
model_title = "CORE-POS Member"
|
||||
route_prefix = 'corepos_members'
|
||||
url_prefix = '/corepos/members'
|
||||
route_prefix = "corepos_members"
|
||||
url_prefix = "/corepos/members"
|
||||
|
||||
# nb. this is just for readonly lookup
|
||||
creatable = False
|
||||
|
@ -54,64 +55,76 @@ class MemberView(CoreOpMasterView):
|
|||
deletable = False
|
||||
|
||||
grid_columns = [
|
||||
'card_number',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'street',
|
||||
'city',
|
||||
'state',
|
||||
'zip',
|
||||
'phone',
|
||||
'email',
|
||||
"card_number",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"street",
|
||||
"city",
|
||||
"state",
|
||||
"zip",
|
||||
"phone",
|
||||
"email",
|
||||
]
|
||||
|
||||
filter_defaults = {
|
||||
'card_number': {'active': True, 'verb': 'equal'},
|
||||
'first_name': {'active': True, 'verb': 'contains'},
|
||||
'last_name': {'active': True, 'verb': 'contains'},
|
||||
"card_number": {"active": True, "verb": "equal"},
|
||||
"first_name": {"active": True, "verb": "contains"},
|
||||
"last_name": {"active": True, "verb": "contains"},
|
||||
}
|
||||
|
||||
sort_defaults = 'card_number'
|
||||
sort_defaults = "card_number"
|
||||
|
||||
def get_query(self, session=None):
|
||||
def get_query(self, session=None): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
query = super().get_query(session=session)
|
||||
|
||||
op_model = self.corepos_handler.get_model_office_op()
|
||||
query = query.outerjoin(op_model.CustomerClassic,
|
||||
sa.and_(
|
||||
op_model.CustomerClassic.card_number == op_model.MemberInfo.card_number,
|
||||
op_model.CustomerClassic.person_number == 1,
|
||||
))\
|
||||
.options(orm.joinedload(op_model.MemberInfo.customers))
|
||||
query = query.outerjoin(
|
||||
op_model.CustomerClassic,
|
||||
sa.and_(
|
||||
op_model.CustomerClassic.card_number == op_model.MemberInfo.card_number,
|
||||
op_model.CustomerClassic.person_number == 1,
|
||||
),
|
||||
).options(orm.joinedload(op_model.MemberInfo.customers))
|
||||
|
||||
return query
|
||||
|
||||
def configure_grid(self, g):
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
op_model = self.corepos_handler.get_model_office_op()
|
||||
|
||||
# first_name
|
||||
g.set_renderer('first_name', self.render_customer_attr)
|
||||
g.set_sorter('first_name', op_model.CustomerClassic.first_name)
|
||||
g.set_renderer("first_name", self.render_customer_attr)
|
||||
g.set_sorter("first_name", op_model.CustomerClassic.first_name)
|
||||
|
||||
# last_name
|
||||
g.set_renderer('last_name', self.render_customer_attr)
|
||||
g.set_sorter('last_name', op_model.CustomerClassic.last_name)
|
||||
g.set_renderer("last_name", self.render_customer_attr)
|
||||
g.set_sorter("last_name", op_model.CustomerClassic.last_name)
|
||||
|
||||
def render_customer_attr(self, member, key, value):
|
||||
# links
|
||||
if self.has_perm("view"):
|
||||
g.set_link("card_number")
|
||||
g.set_link("first_name")
|
||||
g.set_link("last_name")
|
||||
|
||||
def render_customer_attr( # pylint: disable=unused-argument,empty-docstring
|
||||
self, member, key, value
|
||||
):
|
||||
""" """
|
||||
customer = member.customers[0]
|
||||
return getattr(customer, key)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
MemberView = kwargs.get('MemberView', base['MemberView'])
|
||||
MemberView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"MemberView", base["MemberView"]
|
||||
)
|
||||
MemberView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -29,7 +29,7 @@ from corepos.db.office_op.model import Product
|
|||
from wutta_corepos.web.views.corepos import CoreOpMasterView
|
||||
|
||||
|
||||
class ProductView(CoreOpMasterView):
|
||||
class ProductView(CoreOpMasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for
|
||||
:class:`~pycorepos:corepos.db.office_op.model.Product`; route
|
||||
|
@ -40,10 +40,11 @@ class ProductView(CoreOpMasterView):
|
|||
* ``/corepos/products/``
|
||||
* ``/corepos/products/XXX``
|
||||
"""
|
||||
|
||||
model_class = Product
|
||||
model_title = "CORE-POS Product"
|
||||
route_prefix = 'corepos_products'
|
||||
url_prefix = '/corepos/products'
|
||||
route_prefix = "corepos_products"
|
||||
url_prefix = "/corepos/products"
|
||||
|
||||
# nb. this is just for readonly lookup
|
||||
creatable = False
|
||||
|
@ -51,47 +52,50 @@ class ProductView(CoreOpMasterView):
|
|||
deletable = False
|
||||
|
||||
labels = {
|
||||
'upc': "UPC",
|
||||
"upc": "UPC",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
'upc',
|
||||
'brand',
|
||||
'description',
|
||||
'size',
|
||||
'department',
|
||||
'vendor',
|
||||
'normal_price',
|
||||
"upc",
|
||||
"brand",
|
||||
"description",
|
||||
"size",
|
||||
"department",
|
||||
"vendor",
|
||||
"normal_price",
|
||||
]
|
||||
|
||||
filter_defaults = {
|
||||
'upc': {'active': True, 'verb': 'contains'},
|
||||
'brand': {'active': True, 'verb': 'contains'},
|
||||
'description': {'active': True, 'verb': 'contains'},
|
||||
"upc": {"active": True, "verb": "contains"},
|
||||
"brand": {"active": True, "verb": "contains"},
|
||||
"description": {"active": True, "verb": "contains"},
|
||||
}
|
||||
|
||||
sort_defaults = 'upc'
|
||||
sort_defaults = "upc"
|
||||
|
||||
def configure_grid(self, g):
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# normal_price
|
||||
g.set_renderer('normal_price', 'currency')
|
||||
g.set_renderer("normal_price", "currency")
|
||||
|
||||
# links
|
||||
g.set_link('upc')
|
||||
g.set_link('brand')
|
||||
g.set_link('description')
|
||||
g.set_link('size')
|
||||
g.set_link("upc")
|
||||
g.set_link("brand")
|
||||
g.set_link("description")
|
||||
g.set_link("size")
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
ProductView = kwargs.get('ProductView', base['ProductView'])
|
||||
ProductView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"ProductView", base["ProductView"]
|
||||
)
|
||||
ProductView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
10
tasks.py
10
tasks.py
|
@ -15,10 +15,10 @@ def release(c, skip_tests=False):
|
|||
Release a new version of Wutta-COREPOS
|
||||
"""
|
||||
if not skip_tests:
|
||||
c.run('pytest')
|
||||
c.run("pytest")
|
||||
|
||||
if os.path.exists('dist'):
|
||||
shutil.rmtree('dist')
|
||||
if os.path.exists("dist"):
|
||||
shutil.rmtree("dist")
|
||||
|
||||
c.run('python -m build --sdist')
|
||||
c.run('twine upload dist/*')
|
||||
c.run("python -m build --sdist")
|
||||
c.run("twine upload dist/*")
|
||||
|
|
16
tests/db/test_model.py
Normal file
16
tests/db/test_model.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from wuttjamaican.testing import ConfigTestCase
|
||||
from wuttjamaican.db.model import User
|
||||
|
||||
from wutta_corepos.db import model as mod
|
||||
|
||||
|
||||
class TestCoreUser(ConfigTestCase):
|
||||
|
||||
def test_str(self):
|
||||
user = User(username="barney")
|
||||
self.assertEqual(str(user), "barney")
|
||||
|
||||
ext = mod.CoreUser(user=user, corepos_employee_number=42)
|
||||
self.assertEqual(str(ext), "barney")
|
|
@ -13,17 +13,24 @@ class TestWuttaCoreposConfigExtension(TestCase):
|
|||
config = WuttaConfig()
|
||||
|
||||
# no engines by default
|
||||
self.assertFalse(hasattr(config, 'core_office_op_engine'))
|
||||
self.assertFalse(hasattr(config, 'core_office_trans_engine'))
|
||||
self.assertFalse(hasattr(config, 'core_office_arch_engine'))
|
||||
self.assertFalse(hasattr(config, "core_office_op_engine"))
|
||||
self.assertFalse(hasattr(config, "core_office_trans_engine"))
|
||||
self.assertFalse(hasattr(config, "core_office_arch_engine"))
|
||||
self.assertFalse(hasattr(config, "core_lane_op_engine"))
|
||||
self.assertFalse(hasattr(config, "core_lane_trans_engine"))
|
||||
ext = mod.WuttaCoreposConfigExtension()
|
||||
ext.configure(config)
|
||||
self.assertIsNone(config.core_office_op_engine)
|
||||
self.assertIsNone(config.core_office_trans_engine)
|
||||
self.assertIsNone(config.core_office_arch_engine)
|
||||
self.assertIsNone(config.core_lane_op_engine)
|
||||
self.assertIsNone(config.core_lane_trans_engine)
|
||||
|
||||
# but config can change that
|
||||
config.setdefault('corepos.db.office_op.default.url', 'sqlite://')
|
||||
config.setdefault("corepos.db.office_op.default.url", "sqlite://")
|
||||
config.setdefault("corepos.db.lane_trans.default.url", "sqlite://")
|
||||
ext.configure(config)
|
||||
self.assertIsNotNone(config.core_office_op_engine)
|
||||
self.assertEqual(str(config.core_office_op_engine.url), 'sqlite://')
|
||||
self.assertEqual(str(config.core_office_op_engine.url), "sqlite://")
|
||||
self.assertIsNotNone(config.core_lane_trans_engine)
|
||||
self.assertEqual(str(config.core_lane_trans_engine.url), "sqlite://")
|
||||
|
|
|
@ -18,49 +18,95 @@ class TestCoreposHandler(ConfigTestCase):
|
|||
|
||||
def test_get_model_office_op(self):
|
||||
from corepos.db.office_op import model
|
||||
|
||||
handler = self.make_handler()
|
||||
op_model = handler.get_model_office_op()
|
||||
self.assertIs(op_model, model)
|
||||
|
||||
def test_get_model_office_trans(self):
|
||||
from corepos.db.office_trans import model
|
||||
|
||||
handler = self.make_handler()
|
||||
trans_model = handler.get_model_office_trans()
|
||||
self.assertIs(trans_model, model)
|
||||
|
||||
def test_get_model_office_arch(self):
|
||||
from corepos.db.office_arch import model
|
||||
|
||||
handler = self.make_handler()
|
||||
arch_model = handler.get_model_office_arch()
|
||||
self.assertIs(arch_model, model)
|
||||
|
||||
def test_get_model_lane_op(self):
|
||||
from corepos.db.lane_op import model
|
||||
|
||||
handler = self.make_handler()
|
||||
op_model = handler.get_model_lane_op()
|
||||
self.assertIs(op_model, model)
|
||||
|
||||
def test_get_model_lane_trans(self):
|
||||
from corepos.db.lane_trans import model
|
||||
|
||||
handler = self.make_handler()
|
||||
trans_model = handler.get_model_lane_trans()
|
||||
self.assertIs(trans_model, model)
|
||||
|
||||
def test_make_session_office_op(self):
|
||||
handler = self.make_handler()
|
||||
engine = sa.create_engine('sqlite://')
|
||||
with patch.object(self.config, 'core_office_op_engines', create=True,
|
||||
new={'default': engine}):
|
||||
engine = sa.create_engine("sqlite://")
|
||||
with patch.object(
|
||||
self.config, "core_office_op_engines", create=True, new={"default": engine}
|
||||
):
|
||||
op_session = handler.make_session_office_op()
|
||||
self.assertIsInstance(op_session, orm.Session)
|
||||
self.assertIs(op_session.bind, engine)
|
||||
|
||||
def test_make_session_office_trans(self):
|
||||
handler = self.make_handler()
|
||||
engine = sa.create_engine('sqlite://')
|
||||
with patch.object(self.config, 'core_office_trans_engines', create=True,
|
||||
new={'default': engine}):
|
||||
engine = sa.create_engine("sqlite://")
|
||||
with patch.object(
|
||||
self.config,
|
||||
"core_office_trans_engines",
|
||||
create=True,
|
||||
new={"default": engine},
|
||||
):
|
||||
trans_session = handler.make_session_office_trans()
|
||||
self.assertIsInstance(trans_session, orm.Session)
|
||||
self.assertIs(trans_session.bind, engine)
|
||||
|
||||
def test_make_session_office_arch(self):
|
||||
handler = self.make_handler()
|
||||
engine = sa.create_engine('sqlite://')
|
||||
with patch.object(self.config, 'core_office_arch_engines', create=True,
|
||||
new={'default': engine}):
|
||||
engine = sa.create_engine("sqlite://")
|
||||
with patch.object(
|
||||
self.config,
|
||||
"core_office_arch_engines",
|
||||
create=True,
|
||||
new={"default": engine},
|
||||
):
|
||||
arch_session = handler.make_session_office_arch()
|
||||
self.assertIsInstance(arch_session, orm.Session)
|
||||
self.assertIs(arch_session.bind, engine)
|
||||
|
||||
def test_make_session_lane_op(self):
|
||||
handler = self.make_handler()
|
||||
engine = sa.create_engine("sqlite://")
|
||||
with patch.object(
|
||||
self.config, "core_lane_op_engines", create=True, new={"default": engine}
|
||||
):
|
||||
op_session = handler.make_session_lane_op()
|
||||
self.assertIsInstance(op_session, orm.Session)
|
||||
self.assertIs(op_session.bind, engine)
|
||||
|
||||
def test_make_session_lane_trans(self):
|
||||
handler = self.make_handler()
|
||||
engine = sa.create_engine("sqlite://")
|
||||
with patch.object(
|
||||
self.config, "core_lane_trans_engines", create=True, new={"default": engine}
|
||||
):
|
||||
trans_session = handler.make_session_lane_trans()
|
||||
self.assertIsInstance(trans_session, orm.Session)
|
||||
self.assertIs(trans_session.bind, engine)
|
||||
|
||||
def test_get_office_url(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
|
@ -71,9 +117,11 @@ class TestCoreposHandler(ConfigTestCase):
|
|||
self.assertRaises(ConfigurationError, handler.get_office_url, require=True)
|
||||
|
||||
# config can specify (traliing slash is stripped)
|
||||
self.config.setdefault('corepos.office.url', 'http://localhost/fannie/')
|
||||
self.assertEqual(handler.get_office_url(), 'http://localhost/fannie')
|
||||
self.assertEqual(handler.get_office_url(require=True), 'http://localhost/fannie')
|
||||
self.config.setdefault("corepos.office.url", "http://localhost/fannie/")
|
||||
self.assertEqual(handler.get_office_url(), "http://localhost/fannie")
|
||||
self.assertEqual(
|
||||
handler.get_office_url(require=True), "http://localhost/fannie"
|
||||
)
|
||||
|
||||
def test_get_office_department_url(self):
|
||||
handler = self.make_handler()
|
||||
|
@ -82,8 +130,24 @@ class TestCoreposHandler(ConfigTestCase):
|
|||
self.assertIsNone(handler.get_office_department_url(7))
|
||||
|
||||
# typical
|
||||
self.config.setdefault('corepos.office.url', 'http://localhost/fannie/')
|
||||
self.assertEqual(handler.get_office_department_url(7), 'http://localhost/fannie/item/departments/DepartmentEditor.php?did=7')
|
||||
self.config.setdefault("corepos.office.url", "http://localhost/fannie/")
|
||||
self.assertEqual(
|
||||
handler.get_office_department_url(7),
|
||||
"http://localhost/fannie/item/departments/DepartmentEditor.php?did=7",
|
||||
)
|
||||
|
||||
def test_get_office_employee_url(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# null
|
||||
self.assertIsNone(handler.get_office_employee_url(7))
|
||||
|
||||
# typical
|
||||
self.config.setdefault("corepos.office.url", "http://localhost/fannie/")
|
||||
self.assertEqual(
|
||||
handler.get_office_employee_url(7),
|
||||
"http://localhost/fannie/admin/Cashiers/CashierEditor.php?emp_no=7",
|
||||
)
|
||||
|
||||
def test_get_office_likecode_url(self):
|
||||
handler = self.make_handler()
|
||||
|
@ -92,18 +156,24 @@ class TestCoreposHandler(ConfigTestCase):
|
|||
self.assertIsNone(handler.get_office_likecode_url(7))
|
||||
|
||||
# typical
|
||||
self.config.setdefault('corepos.office.url', 'http://localhost/fannie/')
|
||||
self.assertEqual(handler.get_office_likecode_url(7), 'http://localhost/fannie/item/likecodes/LikeCodeEditor.php?start=7')
|
||||
self.config.setdefault("corepos.office.url", "http://localhost/fannie/")
|
||||
self.assertEqual(
|
||||
handler.get_office_likecode_url(7),
|
||||
"http://localhost/fannie/item/likecodes/LikeCodeEditor.php?start=7",
|
||||
)
|
||||
|
||||
def test_get_office_product_url(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# null
|
||||
self.assertIsNone(handler.get_office_product_url('07430500132'))
|
||||
self.assertIsNone(handler.get_office_product_url("07430500132"))
|
||||
|
||||
# typical
|
||||
self.config.setdefault('corepos.office.url', 'http://localhost/fannie/')
|
||||
self.assertEqual(handler.get_office_product_url('07430500132'), 'http://localhost/fannie/item/ItemEditorPage.php?searchupc=07430500132')
|
||||
self.config.setdefault("corepos.office.url", "http://localhost/fannie/")
|
||||
self.assertEqual(
|
||||
handler.get_office_product_url("07430500132"),
|
||||
"http://localhost/fannie/item/ItemEditorPage.php?searchupc=07430500132",
|
||||
)
|
||||
|
||||
def test_get_office_vendor_url(self):
|
||||
handler = self.make_handler()
|
||||
|
@ -112,5 +182,8 @@ class TestCoreposHandler(ConfigTestCase):
|
|||
self.assertIsNone(handler.get_office_vendor_url(7))
|
||||
|
||||
# typical
|
||||
self.config.setdefault('corepos.office.url', 'http://localhost/fannie/')
|
||||
self.assertEqual(handler.get_office_vendor_url(7), 'http://localhost/fannie/item/vendors/VendorIndexPage.php?vid=7')
|
||||
self.config.setdefault("corepos.office.url", "http://localhost/fannie/")
|
||||
self.assertEqual(
|
||||
handler.get_office_vendor_url(7),
|
||||
"http://localhost/fannie/item/vendors/VendorIndexPage.php?vid=7",
|
||||
)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlalchemy import orm
|
||||
|
||||
from corepos.db.office_op import model as op_model
|
||||
|
@ -9,7 +11,7 @@ from wuttaweb.testing import WebTestCase
|
|||
from wutta_corepos.web.views.corepos import members as mod
|
||||
|
||||
|
||||
class TestProductView(WebTestCase):
|
||||
class TestMemberView(WebTestCase):
|
||||
|
||||
def make_view(self):
|
||||
return mod.MemberView(self.request)
|
||||
|
@ -27,13 +29,18 @@ class TestProductView(WebTestCase):
|
|||
def test_configure_grid(self):
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=view.model_class)
|
||||
self.assertNotIn('first_name', grid.renderers)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('first_name', grid.renderers)
|
||||
self.assertNotIn("first_name", grid.renderers)
|
||||
self.assertNotIn("first_name", grid.linked_columns)
|
||||
with patch.object(self.request, "is_root", new=True):
|
||||
view.configure_grid(grid)
|
||||
self.assertIn("first_name", grid.renderers)
|
||||
self.assertIn("first_name", grid.linked_columns)
|
||||
|
||||
def test_render_customer_attr(self):
|
||||
view = self.make_view()
|
||||
member = op_model.MemberInfo()
|
||||
customer = op_model.CustomerClassic(first_name="Fred")
|
||||
member.customers.append(customer)
|
||||
self.assertEqual(view.render_customer_attr(member, 'first_name', 'nope'), "Fred")
|
||||
self.assertEqual(
|
||||
view.render_customer_attr(member, "first_name", "nope"), "Fred"
|
||||
)
|
||||
|
|
|
@ -16,6 +16,6 @@ class TestProductView(WebTestCase):
|
|||
def test_configure_grid(self):
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=view.model_class)
|
||||
self.assertNotIn('upc', grid.linked_columns)
|
||||
self.assertNotIn("upc", grid.linked_columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('upc', grid.linked_columns)
|
||||
self.assertIn("upc", grid.linked_columns)
|
||||
|
|
8
tox.ini
8
tox.ini
|
@ -3,15 +3,19 @@
|
|||
envlist = py38, py39, py310, py311
|
||||
|
||||
[testenv]
|
||||
extras = tests
|
||||
extras = web,tests
|
||||
commands = pytest {posargs}
|
||||
|
||||
[testenv:pylint]
|
||||
basepython = python3.11
|
||||
commands = pylint wutta_corepos
|
||||
|
||||
[testenv:coverage]
|
||||
basepython = python3.11
|
||||
commands = pytest --cov=wutta_corepos --cov-report=html --cov-fail-under=100
|
||||
|
||||
[testenv:docs]
|
||||
basepython = python3.11
|
||||
extras = docs
|
||||
extras = web,docs
|
||||
changedir = docs
|
||||
commands = sphinx-build -b html -d {envtmpdir}/doctrees -W -T . {envtmpdir}/docs
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue