diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 7eb5e2c..0000000 --- a/.pylintrc +++ /dev/null @@ -1,4 +0,0 @@ -# -*- mode: conf; -*- - -[MESSAGES CONTROL] -disable=fixme diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b25e7c..7feebeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,27 +5,6 @@ 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 diff --git a/docs/api/wutta_corepos.db.model.rst b/docs/api/wutta_corepos.db.model.rst deleted file mode 100644 index 9954c44..0000000 --- a/docs/api/wutta_corepos.db.model.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wutta_corepos.db.model`` -========================== - -.. automodule:: wutta_corepos.db.model - :members: diff --git a/docs/api/wutta_corepos.db.rst b/docs/api/wutta_corepos.db.rst deleted file mode 100644 index c04bb3e..0000000 --- a/docs/api/wutta_corepos.db.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wutta_corepos.db`` -==================== - -.. automodule:: wutta_corepos.db - :members: diff --git a/docs/conf.py b/docs/conf.py index f27fe59..4759410 100644 --- a/docs/conf.py +++ b/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://docs.wuttaproject.org/wuttaweb/", None), - "wuttjamaican": ("https://docs.wuttaproject.org/wuttjamaican/", None), + 'wuttaweb': ('https://rattailproject.org/docs/wuttaweb/', None), + 'wuttjamaican': ('https://rattailproject.org/docs/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'] diff --git a/docs/index.rst b/docs/index.rst index 0effd08..4f5d57b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,26 +5,13 @@ Wutta-COREPOS This package adds basic integration with `CORE-POS`_, using `pyCOREPOS`_. -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 +Its main purpose is to setup DB connections for CORE Office, but it +also contains basic readonly web views for some CORE tables. .. _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 @@ -39,8 +26,6 @@ It provides the following: 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 diff --git a/docs/narr/install.rst b/docs/narr/install.rst index fdff505..33e7cd2 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -12,7 +12,8 @@ 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. +related settings. Note that so far, only CORE Office DB connections +are supported. .. code-block:: ini @@ -28,58 +29,4 @@ related settings. [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. diff --git a/pyproject.toml b/pyproject.toml index c252e30..6f77e55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "Wutta-COREPOS" -version = "0.3.0" +version = "0.2.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.5.1", - "WuttJamaican>=0.20.3", + "pyCOREPOS>=0.3.3", + "WuttJamaican>=0.20.1", ] [project.optional-dependencies] web = ["WuttaWeb"] docs = ["Sphinx", "furo"] -tests = ["pylint", "pytest", "pytest-cov", "tox"] +tests = ["pytest-cov", "tox"] [project.entry-points."wutta.app.providers"] diff --git a/src/wutta_corepos/_version.py b/src/wutta_corepos/_version.py index c78b835..7601e62 100644 --- a/src/wutta_corepos/_version.py +++ b/src/wutta_corepos/_version.py @@ -1,9 +1,6 @@ # -*- coding: utf-8; -*- -""" -Package Version -""" from importlib.metadata import version -__version__ = version("Wutta-COREPOS") +__version__ = version('Wutta-COREPOS') diff --git a/src/wutta_corepos/app.py b/src/wutta_corepos/app.py index a937686..7949339 100644 --- a/src/wutta_corepos/app.py +++ b/src/wutta_corepos/app.py @@ -41,11 +41,9 @@ class WuttaCoreposAppProvider(AppProvider): :rtype: :class:`~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", - ) + if not hasattr(self, 'corepos_handler'): + spec = self.config.get(f'{self.appname}.corepos_handler', + default='wutta_corepos.handler:CoreposHandler') factory = self.app.load_object(spec) - self.app.handlers["corepos"] = factory(self.config, **kwargs) - return self.app.handlers["corepos"] + self.corepos_handler = factory(self.config, **kwargs) + return self.corepos_handler diff --git a/src/wutta_corepos/conf.py b/src/wutta_corepos/conf.py index 2c2a309..ef4c917 100644 --- a/src/wutta_corepos/conf.py +++ b/src/wutta_corepos/conf.py @@ -74,97 +74,29 @@ 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`. - - .. 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' - key = "wutta_corepos" - - def configure(self, config): # pylint: disable=empty-docstring + def configure(self, config): """ """ # office_op - from corepos.db.office_op import ( # pylint: disable=import-outside-toplevel - Session as OfficeOpSession, - ) - - engines = get_engines(config, "corepos.db.office_op") + from corepos.db.office_op import Session + engines = get_engines(config, 'corepos.db.office_op') config.core_office_op_engines = engines - config.core_office_op_engine = engines.get("default") - OfficeOpSession.configure(bind=config.core_office_op_engine) + config.core_office_op_engine = engines.get('default') + Session.configure(bind=config.core_office_op_engine) # office_trans - from corepos.db.office_trans import ( # pylint: disable=import-outside-toplevel - Session as OfficeTransSession, - ) - - engines = get_engines(config, "corepos.db.office_trans") + from corepos.db.office_trans import Session + engines = get_engines(config, 'corepos.db.office_trans') config.core_office_trans_engines = engines - config.core_office_trans_engine = engines.get("default") - OfficeTransSession.configure(bind=config.core_office_trans_engine) + config.core_office_trans_engine = engines.get('default') + Session.configure(bind=config.core_office_trans_engine) # office_arch - from corepos.db.office_arch import ( # pylint: disable=import-outside-toplevel - Session as OfficeArchSession, - ) - - engines = get_engines(config, "corepos.db.office_arch") + from corepos.db.office_arch import Session + engines = get_engines(config, 'corepos.db.office_arch') config.core_office_arch_engines = engines - 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() + config.core_office_arch_engine = engines.get('default') + Session.configure(bind=config.core_office_arch_engine) diff --git a/src/wutta_corepos/db/__init__.py b/src/wutta_corepos/db/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/wutta_corepos/db/alembic/versions/0f94089f1af1_initial_user_extension.py b/src/wutta_corepos/db/alembic/versions/0f94089f1af1_initial_user_extension.py deleted file mode 100644 index cc5ffa5..0000000 --- a/src/wutta_corepos/db/alembic/versions/0f94089f1af1_initial_user_extension.py +++ /dev/null @@ -1,44 +0,0 @@ -"""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") diff --git a/src/wutta_corepos/db/model.py b/src/wutta_corepos/db/model.py deleted file mode 100644 index 9dba587..0000000 --- a/src/wutta_corepos/db/model.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- 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 . -# -################################################################################ -""" -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") diff --git a/src/wutta_corepos/handler.py b/src/wutta_corepos/handler.py index 76f0ddc..8b39795 100644 --- a/src/wutta_corepos/handler.py +++ b/src/wutta_corepos/handler.py @@ -38,9 +38,7 @@ 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 ( # pylint: disable=import-outside-toplevel - model, - ) + from corepos.db.office_op import model return model @@ -49,9 +47,7 @@ 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 ( # pylint: disable=import-outside-toplevel - model, - ) + from corepos.db.office_trans import model return model @@ -60,105 +56,47 @@ 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 ( # pylint: disable=import-outside-toplevel - model, - ) + from corepos.db.office_arch import model return model - 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): + 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 ( # pylint: disable=import-outside-toplevel - Session, - ) + from corepos.db.office_op import 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 ( # pylint: disable=import-outside-toplevel - Session, - ) + from corepos.db.office_trans import 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 ( # pylint: disable=import-outside-toplevel - Session, - ) + from corepos.db.office_arch import Session - 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] + if 'bind' not in kwargs: + kwargs['bind'] = self.config.core_office_arch_engines[dbkey] return Session(**kwargs) def get_office_url(self, require=False): @@ -172,12 +110,15 @@ 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 None + return url.rstrip('/') - 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. @@ -193,29 +134,13 @@ 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 None + return f'{office_url}/item/departments/DepartmentEditor.php?did={dept_id}' - 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): + def get_office_likecode_url( + self, + likecode_id, + office_url=None, + require=False): """ Returns the CORE Office URL for a Like Code. @@ -231,10 +156,13 @@ 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 None + return f'{office_url}/item/likecodes/LikeCodeEditor.php?start={likecode_id}' - 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. @@ -250,10 +178,13 @@ 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 None + return f'{office_url}/item/ItemEditorPage.php?searchupc={upc}' - 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. @@ -269,5 +200,4 @@ 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 None + return f'{office_url}/item/vendors/VendorIndexPage.php?vid={vend_id}' diff --git a/src/wutta_corepos/web/__init__.py b/src/wutta_corepos/web/__init__.py index d3a2ac9..71a9477 100644 --- a/src/wutta_corepos/web/__init__.py +++ b/src/wutta_corepos/web/__init__.py @@ -25,5 +25,5 @@ Wutta-COREPOS -- wuttaweb features """ -def includeme(config): # pylint: disable=missing-function-docstring - config.include("wutta_corepos.web.views") +def includeme(config): + config.include('wutta_corepos.web.views') diff --git a/src/wutta_corepos/web/db.py b/src/wutta_corepos/web/db.py index 4c9e292..7faeff2 100644 --- a/src/wutta_corepos/web/db.py +++ b/src/wutta_corepos/web/db.py @@ -49,22 +49,6 @@ 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 @@ -80,15 +64,7 @@ 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 = {} diff --git a/src/wutta_corepos/web/views/__init__.py b/src/wutta_corepos/web/views/__init__.py index c36452d..d4112a2 100644 --- a/src/wutta_corepos/web/views/__init__.py +++ b/src/wutta_corepos/web/views/__init__.py @@ -25,5 +25,5 @@ Wutta-COREPOS -- wuttaweb views """ -def includeme(config): # pylint: disable=missing-function-docstring - config.include("wutta_corepos.web.views.corepos") +def includeme(config): + config.include('wutta_corepos.web.views.corepos') diff --git a/src/wutta_corepos/web/views/corepos/__init__.py b/src/wutta_corepos/web/views/corepos/__init__.py index fff81f8..a78e4ef 100644 --- a/src/wutta_corepos/web/views/corepos/__init__.py +++ b/src/wutta_corepos/web/views/corepos/__init__.py @@ -27,6 +27,6 @@ Views for CORE-POS from .master import CoreOpMasterView -def includeme(config): # pylint: disable=missing-function-docstring - config.include("wutta_corepos.web.views.corepos.members") - config.include("wutta_corepos.web.views.corepos.products") +def includeme(config): + config.include('wutta_corepos.web.views.corepos.members') + config.include('wutta_corepos.web.views.corepos.products') diff --git a/src/wutta_corepos/web/views/corepos/master.py b/src/wutta_corepos/web/views/corepos/master.py index b0d9ab2..817a443 100644 --- a/src/wutta_corepos/web/views/corepos/master.py +++ b/src/wutta_corepos/web/views/corepos/master.py @@ -29,11 +29,10 @@ from wuttaweb.views import MasterView from wutta_corepos.web.db import CoreOpSession -class CoreOpMasterView(MasterView): # pylint: disable=abstract-method +class CoreOpMasterView(MasterView): """ Base class for master views which use the CORE Office 'op' DB. """ - Session = CoreOpSession def __init__(self, request, context=None): diff --git a/src/wutta_corepos/web/views/corepos/members.py b/src/wutta_corepos/web/views/corepos/members.py index ffaffd3..66c967e 100644 --- a/src/wutta_corepos/web/views/corepos/members.py +++ b/src/wutta_corepos/web/views/corepos/members.py @@ -32,7 +32,7 @@ from corepos.db.office_op.model import MemberInfo from wutta_corepos.web.views.corepos import CoreOpMasterView -class MemberView(CoreOpMasterView): # pylint: disable=abstract-method +class MemberView(CoreOpMasterView): """ Master view for :class:`~pycorepos:corepos.db.office_op.model.MemberInfo`; route @@ -43,11 +43,10 @@ class MemberView(CoreOpMasterView): # pylint: disable=abstract-method * ``/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 @@ -55,76 +54,64 @@ class MemberView(CoreOpMasterView): # pylint: disable=abstract-method 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): # pylint: disable=empty-docstring + def get_query(self, session=None): """ """ 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, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): """ """ - 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) - # 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 - ): + def render_customer_attr(self, member, key, value): """ """ customer = member.customers[0] return getattr(customer, key) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - MemberView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name - "MemberView", base["MemberView"] - ) + MemberView = kwargs.get('MemberView', base['MemberView']) MemberView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wutta_corepos/web/views/corepos/products.py b/src/wutta_corepos/web/views/corepos/products.py index 19b016d..e8f33c4 100644 --- a/src/wutta_corepos/web/views/corepos/products.py +++ b/src/wutta_corepos/web/views/corepos/products.py @@ -29,7 +29,7 @@ from corepos.db.office_op.model import Product from wutta_corepos.web.views.corepos import CoreOpMasterView -class ProductView(CoreOpMasterView): # pylint: disable=abstract-method +class ProductView(CoreOpMasterView): """ Master view for :class:`~pycorepos:corepos.db.office_op.model.Product`; route @@ -40,11 +40,10 @@ class ProductView(CoreOpMasterView): # pylint: disable=abstract-method * ``/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 @@ -52,50 +51,47 @@ class ProductView(CoreOpMasterView): # pylint: disable=abstract-method 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, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): """ """ - 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): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - ProductView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name - "ProductView", base["ProductView"] - ) + ProductView = kwargs.get('ProductView', base['ProductView']) ProductView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/tasks.py b/tasks.py index 1eaed3b..101278d 100644 --- a/tasks.py +++ b/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/*') diff --git a/tests/db/test_model.py b/tests/db/test_model.py deleted file mode 100644 index 8d76745..0000000 --- a/tests/db/test_model.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- 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") diff --git a/tests/test_conf.py b/tests/test_conf.py index db26ce3..c957dce 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -13,24 +13,17 @@ 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_lane_op_engine")) - self.assertFalse(hasattr(config, "core_lane_trans_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')) 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.lane_trans.default.url", "sqlite://") + config.setdefault('corepos.db.office_op.default.url', 'sqlite://') ext.configure(config) self.assertIsNotNone(config.core_office_op_engine) - 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://") + self.assertEqual(str(config.core_office_op_engine.url), 'sqlite://') diff --git a/tests/test_handler.py b/tests/test_handler.py index b597044..6dc5077 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -18,95 +18,49 @@ 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() @@ -117,11 +71,9 @@ 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() @@ -130,24 +82,8 @@ 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", - ) - - 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", - ) + 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_likecode_url(self): handler = self.make_handler() @@ -156,24 +92,18 @@ 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() @@ -182,8 +112,5 @@ 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') diff --git a/tests/web/views/corepos/test_members.py b/tests/web/views/corepos/test_members.py index 9c4f557..6055ef9 100644 --- a/tests/web/views/corepos/test_members.py +++ b/tests/web/views/corepos/test_members.py @@ -1,7 +1,5 @@ # -*- coding: utf-8; -*- -from unittest.mock import patch - from sqlalchemy import orm from corepos.db.office_op import model as op_model @@ -11,7 +9,7 @@ from wuttaweb.testing import WebTestCase from wutta_corepos.web.views.corepos import members as mod -class TestMemberView(WebTestCase): +class TestProductView(WebTestCase): def make_view(self): return mod.MemberView(self.request) @@ -29,18 +27,13 @@ class TestMemberView(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) - 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) + self.assertNotIn('first_name', grid.renderers) + view.configure_grid(grid) + self.assertIn('first_name', grid.renderers) 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") diff --git a/tests/web/views/corepos/test_products.py b/tests/web/views/corepos/test_products.py index e2e988e..8870208 100644 --- a/tests/web/views/corepos/test_products.py +++ b/tests/web/views/corepos/test_products.py @@ -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) diff --git a/tox.ini b/tox.ini index 20021b1..0ff0e30 100644 --- a/tox.ini +++ b/tox.ini @@ -3,19 +3,15 @@ envlist = py38, py39, py310, py311 [testenv] -extras = web,tests +extras = 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 = web,docs +extras = docs changedir = docs commands = sphinx-build -b html -d {envtmpdir}/doctrees -W -T . {envtmpdir}/docs