diff --git a/.gitignore b/.gitignore index b3006f90..906dc226 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ -*~ -*.pyc .coverage .tox/ -dist/ docs/_build/ htmlcov/ Tailbone.egg-info/ diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c974b3a6..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,683 +0,0 @@ - -# Changelog -All notable changes to Tailbone 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.22.7 (2025-02-19) - -### Fix - -- stop using old config for logo image url on login page -- fix warning msg for deprecated Grid param - -## v0.22.6 (2025-02-01) - -### Fix - -- register vue3 form component for products -> make batch - -## v0.22.5 (2024-12-16) - -### Fix - -- whoops this is latest rattail -- require newer rattail lib -- require newer wuttaweb -- let caller request safe HTML literal for rendered grid table - -## v0.22.4 (2024-11-22) - -### Fix - -- avoid error in product search for duplicated key -- use vmodel for confirm password widget input - -## v0.22.3 (2024-11-19) - -### Fix - -- avoid error for trainwreck query when not a customer - -## v0.22.2 (2024-11-18) - -### Fix - -- use local/custom enum for continuum operations -- add basic master view for Product Costs -- show continuum operation type when viewing version history -- always define `app` attr for ViewSupplement -- avoid deprecated import - -## v0.22.1 (2024-11-02) - -### Fix - -- fix submit button for running problem report -- avoid deprecated grid method - -## v0.22.0 (2024-10-22) - -### Feat - -- add support for new ordering batch from parsed file - -### Fix - -- avoid deprecated method to suggest username - -## v0.21.11 (2024-10-03) - -### Fix - -- custom method for adding grid action -- become/stop root should redirect to previous url - -## v0.21.10 (2024-09-15) - -### Fix - -- update project repo links, kallithea -> forgejo -- use better icon for submit button on login page -- wrap notes text for batch view -- expose datasync consumer batch size via configure page - -## v0.21.9 (2024-08-28) - -### Fix - -- render custom attrs in form component tag - -## v0.21.8 (2024-08-28) - -### Fix - -- ignore session kwarg for `MasterView.make_row_grid()` - -## v0.21.7 (2024-08-28) - -### Fix - -- avoid error when form value cannot be obtained - -## v0.21.6 (2024-08-28) - -### Fix - -- avoid error when grid value cannot be obtained - -## v0.21.5 (2024-08-28) - -### Fix - -- set empty string for "-new-" file configure option - -## v0.21.4 (2024-08-26) - -### Fix - -- handle differing email profile keys for appinfo/configure - -## v0.21.3 (2024-08-26) - -### Fix - -- show non-standard config values for app info configure email - -## v0.21.2 (2024-08-26) - -### Fix - -- refactor waterpark base template to use wutta feedback component -- fix input/output file upload feature for configure pages, per oruga -- tweak how grid data translates to Vue template context -- merge filters into main grid template -- add basic wutta view for users -- some fixes for wutta people view -- various fixes for waterpark theme -- avoid deprecated `component` form kwarg - -## v0.21.1 (2024-08-22) - -### Fix - -- misc. bugfixes per recent changes - -## v0.21.0 (2024-08-22) - -### Feat - -- move "most" filtering logic for grid class to wuttaweb -- inherit from wuttaweb templates for home, login pages -- inherit from wuttaweb for AppInfoView, appinfo/configure template -- add "has output file templates" config option for master view - -### Fix - -- change grid reset-view param name to match wuttaweb -- move "searchable columns" grid feature to wuttaweb -- use wuttaweb to get/render csrf token -- inherit from wuttaweb for appinfo/index template -- prefer wuttaweb config for "home redirect to login" feature -- fix master/index template rendering for waterpark theme -- fix spacing for navbar logo/title in waterpark theme - -## v0.20.1 (2024-08-20) - -### Fix - -- fix default filter verbs logic for workorder status - -## v0.20.0 (2024-08-20) - -### Feat - -- add new 'waterpark' theme, based on wuttaweb w/ vue2 + buefy -- refactor templates to simplify base/page/form structure - -### Fix - -- avoid deprecated reference to app db engine - -## v0.19.3 (2024-08-19) - -### Fix - -- add pager stats to all grid vue data (fixes view history) - -## v0.19.2 (2024-08-19) - -### Fix - -- sort on frontend for appinfo package listing grid -- prefer attr over key lookup when getting model values -- replace all occurrences of `component_studly` => `vue_component` - -## v0.19.1 (2024-08-19) - -### Fix - -- fix broken user auth for web API app - -## v0.19.0 (2024-08-18) - -### Feat - -- move multi-column grid sorting logic to wuttaweb -- move single-column grid sorting logic to wuttaweb - -### Fix - -- fix misc. errors in grid template per wuttaweb -- fix broken permission directives in web api startup - -## v0.18.0 (2024-08-16) - -### Feat - -- move "basic" grid pagination logic to wuttaweb -- inherit from wutta base class for Grid -- inherit most logic from wuttaweb, for GridAction - -### Fix - -- avoid route error in user view, when using wutta people view -- fix some more wutta compat for base template - -## v0.17.0 (2024-08-15) - -### Feat - -- use wuttaweb for `get_liburl()` logic - -## v0.16.1 (2024-08-15) - -### Fix - -- improve wutta People view a bit -- update references to `get_class_hierarchy()` -- tweak template for `people/view_profile` per wutta compat - -## v0.16.0 (2024-08-15) - -### Feat - -- add first wutta-based master, for PersonView -- refactor forms/grids/views/templates per wuttaweb compat - -## v0.15.6 (2024-08-13) - -### Fix - -- avoid `before_render` subscriber hook for web API -- simplify verbiage for batch execution panel - -## v0.15.5 (2024-08-09) - -### Fix - -- assign convenience attrs for all views (config, app, enum, model) - -## v0.15.4 (2024-08-09) - -### Fix - -- avoid bug when checking current theme - -## v0.15.3 (2024-08-08) - -### Fix - -- fix timepicker `parseTime()` when value is null - -## v0.15.2 (2024-08-06) - -### Fix - -- use auth handler, avoid legacy calls for role/perm checks - -## v0.15.1 (2024-08-05) - -### Fix - -- move magic `b` template context var to wuttaweb - -## v0.15.0 (2024-08-05) - -### Feat - -- move more subscriber logic to wuttaweb - -### Fix - -- use wuttaweb logic for `util.get_form_data()` - -## v0.14.5 (2024-08-03) - -### Fix - -- use auth handler instead of deprecated auth functions -- avoid duplicate `partial` param when grid reloads data - -## v0.14.4 (2024-07-18) - -### Fix - -- fix more settings persistence bug(s) for datasync/configure -- fix modals for luigi tasks page, per oruga - -## v0.14.3 (2024-07-17) - -### Fix - -- fix auto-collapse title for viewing trainwreck txn -- allow auto-collapse of header when viewing trainwreck txn - -## v0.14.2 (2024-07-15) - -### Fix - -- add null menu handler, for use with API apps - -## v0.14.1 (2024-07-14) - -### Fix - -- update usage of auth handler, per rattail changes -- fix model reference in menu handler -- fix bug when making "integration" menus - -## v0.14.0 (2024-07-14) - -### Feat - -- move core menu logic to wuttaweb - -## v0.13.2 (2024-07-13) - -### Fix - -- fix logic bug for datasync/config settings save - -## v0.13.1 (2024-07-13) - -### Fix - -- fix settings persistence bug(s) for datasync/configure page - -## v0.13.0 (2024-07-12) - -### Feat - -- begin integrating WuttaWeb as upstream dependency - -### Fix - -- cast enum as list to satisfy deform widget - -## v0.12.1 (2024-07-11) - -### Fix - -- refactor `config.get_model()` => `app.model` - -## v0.12.0 (2024-07-09) - -### Feat - -- drop python 3.6 support, use pyproject.toml (again) - -## v0.11.10 (2024-07-05) - -### Fix - -- make the Members tab optional, for profile view - -## v0.11.9 (2024-07-05) - -### Fix - -- do not show flash message when changing app theme - -- improve collapse panels for butterball theme - -- expand input for butterball theme - -- add xref button to customer profile, for trainwreck txn view - -- add optional Transactions tab for profile view - -## v0.11.8 (2024-07-04) - -### Fix - -- fix grid action icons for datasync/configure, per oruga - -- allow view supplements to add extra links for profile employee tab - -- leverage import handler method to determine command/subcommand - -- add tool to make user account from profile view - -## v0.11.7 (2024-07-04) - -### Fix - -- add stacklevel to deprecation warnings - -- require zope.sqlalchemy >= 1.5 - -- include edit profile email/phone dialogs only if user has perms - -- allow view supplements to add to profile member context - -- cast enum as list to satisfy deform widget - -- expand POD image URL setting input - -## v0.11.6 (2024-07-01) - -### Fix - -- set explicit referrer when changing dbkey - -- remove references, dependency for `six` package - -## v0.11.5 (2024-06-30) - -### Fix - -- allow comma in numeric filter input - -- add custom url prefix if needed, for fanstatic - -- use vue 3.4.31 and oruga 0.8.12 by default - -## v0.11.4 (2024-06-30) - -### Fix - -- start/stop being root should submit POST instead of GET - -- require vendor when making new ordering batch via api - -- don't escape each address for email attempts grid - -## v0.11.3 (2024-06-28) - -### Fix - -- add link to "resolved by" user for pending products - -- handle error when merging 2 records fails - -## v0.11.2 (2024-06-18) - -### Fix - -- hide certain custorder settings if not applicable - -- use different logic for buefy/oruga for product lookup keydown - -- product records should be touchable - -- show flash error message if resolve pending product fails - -## v0.11.1 (2024-06-14) - -### Fix - -- revert back to setup.py + setup.cfg - -## v0.11.0 (2024-06-10) - -### Feat - -- switch from setup.cfg to pyproject.toml + hatchling - -## v0.10.16 (2024-06-10) - -### Feat - -- standardize how app, package versions are determined - -### Fix - -- avoid deprecated config methods for app/node title - -## v0.10.15 (2024-06-07) - -### Fix - -- do *not* Use `pkg_resources` to determine package versions - -## v0.10.14 (2024-06-06) - -### Fix - -- use `pkg_resources` to determine package versions - -## v0.10.13 (2024-06-06) - -### Feat - -- remove old/unused scaffold for use with `pcreate` - -- add 'fanstatic' support for sake of libcache assets - -## v0.10.12 (2024-06-04) - -### Feat - -- require pyramid 2.x; remove 1.x-style auth policies - -- remove version cap for deform - -- set explicit referrer when changing app theme - -- add `` component shim - -- include extra styles from `base_meta` template for butterball - -- include butterball theme by default for new apps - -### Fix - -- fix product lookup component, per butterball - -## v0.10.11 (2024-06-03) - -### Feat - -- fix vue3 refresh bugs for various views - -- fix grid bug for tempmon appliance view, per oruga - -- fix ordering worksheet generator, per butterball - -- fix inventory worksheet generator, per butterball - -## v0.10.10 (2024-06-03) - -### Feat - -- more butterball fixes for "view profile" template - -### Fix - -- fix focus for `` shim component - -## v0.10.9 (2024-06-03) - -### Feat - -- let master view control context menu items for page - -- fix the "new custorder" page for butterball - -### Fix - -- fix panel style for PO vs. Invoice breakdown in receiving batch - -## v0.10.8 (2024-06-02) - -### Feat - -- add styling for checked grid rows, per oruga/butterball - -- fix product view template for oruga/butterball - -- allow per-user custom styles for butterball - -- use oruga 0.8.9 by default - -## v0.10.7 (2024-06-01) - -### Feat - -- add setting to allow decimal quantities for receiving - -- log error if registry has no rattail config - -- add column filters for import/export main grid - -- escape all unsafe html for grid data - -- add speedbumps for delete, set preferred email/phone in profile view - -- fix file upload widget for oruga - -### Fix - -- fix overflow when instance header title is too long (butterball) - -## v0.10.6 (2024-05-29) - -### Feat - -- add way to flag organic products within lookup dialog - -- expose db picker for butterball theme - -- expose quickie lookup for butterball theme - -- fix basic problems with people profile view, per butterball - -## v0.10.5 (2024-05-29) - -### Feat - -- add `` component for oruga - -## v0.10.4 (2024-05-12) - -### Fix - -- fix styles for grid actions, per butterball - -## v0.10.3 (2024-05-10) - -### Fix - -- fix bug with grid date filters - -## v0.10.2 (2024-05-08) - -### Feat - -- remove version restriction for pyramid_beaker dependency - -- rename some attrs etc. for buefy components used with oruga - -- fix "tools" helper for receiving batch view, per oruga - -- more data type fixes for ```` - -- fix "view receiving row" page, per oruga - -- tweak styles for grid action links, per butterball - -### Fix - -- fix employees grid when viewing department (per oruga) - -- fix login "enter" key behavior, per oruga - -- fix button text for autocomplete - -## v0.10.1 (2024-04-28) - -### Feat - -- sort list of available themes - -- update various icon names for oruga compatibility - -- show "View This" button when cloning a record - -- stop including 'falafel' as available theme - -### Fix - -- fix vertical alignment in main menu bar, for butterball - -- fix upgrade execution logic/UI per oruga - -## v0.10.0 (2024-04-28) - -This version bump is to reflect adding support for Vue 3 + Oruga via -the 'butterball' theme. There is likely more work to be done for that -yet, but it mostly works at this point. - -### Feat - -- misc. template and view logic tweaks (applicable to all themes) for - better patterns, consistency etc. - -- add initial support for Vue 3 + Oruga, via "butterball" theme - - -## Older Releases - -Please see `docs/OLDCHANGES.rst` for older release notes. diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 00000000..5471b9c5 --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,3060 @@ + +CHANGELOG +========= + +0.7.16 (2018-06-07) +------------------- + +* Add versioning workaround support for batch actions. + + +0.7.15 (2018-06-05) +------------------- + +* Add integer-specific grid filter. + +* Set filter value renderer when setting enum for grid field. + + +0.7.14 (2018-06-04) +------------------- + +* Show department instead of subdept by default, for products grid. + +* Add support for variance inventory batches, aggregation by product. + +* Set default column renderers for grid based on data types. + +* Expose 'hidden' flag for inventory adjustment reasons. + +* Expose new ``Vendor.abbreviation`` field. + + +0.7.13 (2018-05-31) +------------------- + +* Show 'variance' field when viewing inventory batch row. + + +0.7.12 (2018-05-30) +------------------- + +* Make sure count mode is preserved when making new inventory batch. + +* Add initial support for "variance" inventory batch mode. + +* Fix handling of (missing) password when user is edited. + + +0.7.11 (2018-05-25) +------------------- + +* Add ``Form.__contains__()`` method. + +* Improve default behavior for receiving a purchase batch. + +* Fix label profile type field when editing label batch row. + +* Allow lookup of inventory item by alternate code. + +* Fix rowcount bug when first row added via ordering worksheet. + +* Add "most of" support for truck dump receiving. + +* Add docs for ``MasterView.help_url`` and ``get_help_url()``. + +* Add "Receive 1 CS" button for better efficiency in mobile receiving. + +* Add category name filter for products grid. + +* Increase allowed width for form labels. + +* Add ``allow_zero_all`` flag for inventory batch master. + +* Add buttons to toggle batch 'complete' flag when viewing batch. + +* Hide "create new row" link for batches which are marked complete. + +* Add way to prevent "case" entries for inventory adjustment batch. + +* Add ``MasterView.use_byte_string_filters`` flag for encoding search values. + + +0.7.10 (2018-05-02) +------------------- + +* Add sort/filter for department name, for Categories grid. + + +0.7.9 (2018-04-12) +------------------ + +* Add future mode for vendor catalog batch. + + +0.7.8 (2018-04-09) +------------------ + +* Add awareness for ``Email.dynamic_to`` flag in config UI. + +* Add new vendor catalog row status, render product with hyperlink. + + +0.7.7 (2018-03-23) +------------------ + +* Use 'today' as fallback order date for ordering worksheet. + +* Treat unknown UPC as "product not found" for inventory batch. + +* Refactor inventory batch desktop lookup, to allow for Type 2 UPC logic. + +* Fix default selection bug for store/department time sheet filters. + + +0.7.6 (2018-03-15) +------------------ + +* Fix text area behavior for email recipient fields. + +* Fix autodisable button bug for forms marked as such. + + +0.7.5 (2018-03-12) +------------------ + +* Add desktop support for creating inventory batches. + +* Expose vendor item code for purchase credits. + +* Fix default create logic for vendors, products. + +* Add changelog link for rattail-tempmon in upgrade diff. + +* Add ``disable_submit_button()`` global JS function. + +* Add basic support for making new product on-the-fly during mobile ordering. + + +0.7.4 (2018-02-27) +------------------ + +* Use all "normal" product form fields, for mobile view. + +* Refactor ordering worksheet to use shared logic. + +* Add download path for batch master views. + +* Add basic mobile support for executing batches (with options). + +* Add ``NumberInputWidget`` for ````. + +* Add ``Form.mobile`` flag and set link button styles accordingly. + +* Always show flash-error-style message when form has errors. + +* Use ``Form.submit_label`` if present, or fall back to ``save_label``. + +* Expose ``ship_method`` and ``notes_to_vendor`` for purchase, ordering batch. + +* Bind batch to its execution options schema, when applicable. + +* Don't set order date for new ordering batch when created via mobile. + +* Don't allow row deletion if batch is marked complete. + +* Add logic for editing default phone/email in base master view. + +* Fix bug in users view when person field not present. + + +0.7.3 (2018-02-15) +------------------ + +* More tweaks for python 3. + + +0.7.2 (2018-02-14) +------------------ + +* Refactor all remaining forms to use colander/deform. + +* Coalesce 'forms2' => 'forms' package. + +* Remove dependencies: FormAlchemy, FormEncode, pyramid_simpleform, pyramid_debugtoolbar + +* Misc. cleanup for Python 3. + +* Add generic 'login_as_home' setting. + +* Add tailbone version to base stylesheet URLs. + + +0.7.1 (2018-02-10) +------------------ + +* Make it easier to hide buttons for a form. + +* Let forms choose *not* to auto-disable their cancel button. + +* Add 'newstyle' behavior for ``Form.validate()``. + +* Add some basic ORM object field types for new forms. + +* Make sure each grid has unique set of actions. + +* Add 'gridcore' jQuery plugin, for core behavior. + +* Allow passing arbitrary attrs when rendering grid. + +* Refactor mobile receiving to use colander/deform. + +* Refactor mobile inventory to use colander/deform. + +* Refactor user login, change password to use colander/deform. + +* Fix some bugs with importer batch views. + + +0.7.0 (2018-02-07) +------------------ + +* Coalesce all master views back to single base class. + +* Add ``append()`` and ``replace()`` methods for core Grid class. + +* Show year dropdown by default for jQuery UI date pickers. + +* Don't process file for new batch unless field is present. + +* Add setting for "force home" mobile behavior. + +* Add 'plain' and 'jquery' templates for deform select widget. + +* Add "hidden" concept for form fields. + +* Add ``Form.show_cancel`` flag, for hiding that button. + +* Let each form define its "save" button text. + +* Add master view for ``EmailAttempt``. + +* Avoid "auto disable" button logic for new message form. + +* Add better UPC validation for mobile receiving. + + +0.6.69 (2018-02-01) +------------------- + +* Add proper enum for inventory batch "count mode" filter. + +* Fix bugs when making inventory batch on mobile. + + +0.6.68 (2018-01-31) +------------------- + +* Cap zope.sqlalchemy dependency at pre-1.0. + + +0.6.67 (2018-01-30) +------------------- + +* Fix permission bug when adding row in mobile receiving. + +* Fix mobile logout behavior. + +* Always redirect to mobile home page, if "other" page is refreshed. + + +0.6.66 (2018-01-29) +------------------- + +* Add support for detaching Person from Customer. + +* Allow disabling auto-dismiss of flash messages on mobile. + +* Add ``FieldList`` wrapper for grid columns list. + +* Show "unit cost" column by default, for products grid. + +* Improve case/unit quantity validation for order worksheet. + +* Show new 'confirmed' field for brands table. + +* Add support for extra column(s) in timesheet view table. + +* Add generic "download results as XLSX" feature. + +* Add vendor links in cost grid when viewing product. + +* Show "buttons" when viewing an object, with forms2 (i.e. Execute Batch). + +* Refactor "most" remaining batch views etc. to use master3. + + +0.6.65 (2018-01-24) +------------------- + +* Fix some master3 edit issues for products view. + +* Let custom inventory batch view override logic for mobile UPC scanning. + +* Show new ``cashback`` field for Trainwreck transaction. + +* Add 'delete-instance' class to delete link when viewing a record. + + +0.6.64 (2018-01-22) +------------------- + +* Warn if user "scans" UPC with more than 14 digits, for mobile inventory. + +* Add option for preventing new inventory batch rows for unknown products. + +* Add ``creates_multiple`` flag for master view. + +* Add basic support for per-page help URL. + + +0.6.63 (2018-01-16) +------------------- + +* Fix bug when locating association proxy column. + +* Fix client field when creating / editing tempmon probe. + +* Allow editing of inventory batch count mode and reason code. + + +0.6.62 (2018-01-11) +------------------- + +* Fix dialog button click event when executing price batch (for Chrome). + +* Fix some mobile view URLs. + +* Show case quantity for inventory batch rows. + +* Let custom schema node start out with empty children. + +* Allow passing None to ``Form.set_renderer()``. + + +0.6.61 (2018-01-11) +------------------- + +* Provide some default readonly form field renderers. + +* Fix row query bug when deleting batch row. + + +0.6.60 (2018-01-10) +------------------- + +* Refactor several straggler views to use master3. + +* Add first attempt at master3 for batch views. + + +0.6.59 (2018-01-08) +------------------- + +* Fix bug when printing product label. + + +0.6.58 (2018-01-08) +------------------- + +* Tweak diff styles when viewing upgrade. + + +0.6.57 (2018-01-07) +------------------- + +* Fix some styles for execution options dialog. + +* Show 'static_prices' flag for label batches. + +* Add field name as wrapper class name. + +* Change how select menus are enhanced for batch exec options. + +* Add view for InventoryAdjustmentReason model. + +* Stop setting execution details when multiple batches executed. + +* Add empty default when displaying values in grid. + +* Let grids be paginated even when they have no model class. + +* Exclude JS for refreshing batch unless it's relevant. + +* Tweak conditions for CSV row download link. + +* Add basic support for row grid view links. + +* Refactor away the ``row_route_prefix`` concept. + +* Add ``row_title`` to template context for ``view_row``. + +* Tweak ``diffs.css`` and refactor 'view_version' template to use it. + +* Add basic UI support for "importer batch" feature. + + +0.6.56 (2018-01-05) +------------------- + +* Fix bug when making batch from product query. + + +0.6.55 (2018-01-04) +------------------- + +* Add "price required" flag to product view. + +* Add a bit more flexibility to jquery time input values. + +* Show row count field when viewing vendor catalog batch. + +* Tweak product filter for report code name. + +* Refactor forms logic when making batch from product query. + + +0.6.54 (2017-12-20) +------------------- + +* Provide sane width for filter value dropdowns. + + +0.6.53 (2017-12-19) +------------------- + +* Accept ``value_enum`` kwarg when creating grid filter. + + +0.6.52 (2017-12-08) +------------------- + +* Add transaction "System ID" field for Trainwreck. + +* Add ``Grid.set_sort_defaults()`` method. + +* Change template prefix for vendor catalog batches. + +* Add basic "helptext" support for forms2. + +* Add cleared/selected callbacks for jquery autocomplete in forms2. + +* Add ``Grid.remove_filter()`` method. + +* Add custom schema type for jQuery time picker data. + +* Refactor lots of views to use master3. + + +0.6.51 (2017-12-03) +------------------- + +* Refactor customers view to use master3. + +* Add custom ``FieldList`` class for forms2 field list. + +* Auto-scroll window as needed to ensure drop-down choices are visible. + +* Hide status when creating new purchasing batch. + +* Add "manually priced" awareness to pricing batch UI. + +* Add batch description to page body title. + +* Fix batch row count when bulk-deleting rows. + +* Allow bulk delete of label batch rows. + +* Expose description and notes for label batches. + +* Let batch views allow or deny "execute results" option. + +* Allow "execute results" for inventory batches. + +* Fix permission bug for mobile inventory batch. + +* Expose default address for customers view. + + +0.6.50 (2017-11-21) +------------------- + +* Set widget when defining enum for a form2 field. + +* Add date/time-picker, autocomplete support for forms2 (deform). + +* Add colander magic for association proxy fields. + + +0.6.49 (2017-11-19) +------------------- + +* Improve auto-disable logic for some form buttons. + +* Fix (hack) for editing some department flags. + + +0.6.48 (2017-11-11) +------------------- + +* Accept ``None`` as valid arg for ``Grid.set_filter()``. + + +0.6.47 (2017-11-08) +------------------- + +* Fix manifest to include *.pt deform templates + + +0.6.46 (2017-11-08) +------------------- + +* Add ``json`` to global template context + + +0.6.45 (2017-11-01) +------------------- + +* Add product and personnel flags for Department + +* Add sorters, filters for Product regular, current price + +* Add "text" type for new form fields + +* Add description, notes for pricing batches + + +0.6.44 (2017-10-29) +------------------- + +* Fix join bug for Upgrades table when sorting by executor + + +0.6.43 (2017-10-29) +------------------- + +* Add "make user" button when viewing person w/ no user account + + +0.6.42 (2017-10-28) +------------------- + +* Add cashier info, upload time for Trainwreck transaction views + + +0.6.41 (2017-10-25) +------------------- + +* Add support for validator and required flag, for new forms + +* Use master3 view for datasync changes + + +0.6.40 (2017-10-24) +------------------- + +* Add grid filter which treats empty string as NULL + +* Fix value auto-selection for enum grid filters + +* Add ``item_id`` to trainwreck views + +* Expose ``Person.users`` relationship (readonly) + + +0.6.39 (2017-10-20) +------------------- + +* Fix bug with products view config + + +0.6.38 (2017-10-19) +------------------- + +* Add "local" datetime renderer for new grids, forms + +* Make CSRF protection optional (but on by default) + +* Convert user feedback mechanism to use modal dialog + +* Add 'active' column to Users table view + +* Add "download row results as CSV" feature to master view + +* Add support for setting default field values on new forms + +* Add 'currency' field type for new forms + +* Allow passing ``None`` to ``Grid.set_joiner()`` + + +0.6.37 (2017-09-28) +------------------- + +* Fix data type/size issue with CSV download + +* Don't set batch input file on creation, if no file exists + +* Add "auto-enhance" select field template for deform + +* Add ability to override schema node for custom deform fields + +* Fix deform widget resource inclusion for master/create template + +* Pass form along to ``before_create_flush()`` in master3 + +* Add "populatable" for master views (populating new objects with progress) + +* Add 'duration' type for new form fields + + +0.6.36 (2017-09-15) +------------------- + +* Fix user field rendering when no person associated + +* Add generic support for downloading list results as CSV + +* Tweak title for master view row template + + +0.6.35 (2017-08-30) +------------------- + +* Fix some bugs for rendering upgrade package diffs + + +0.6.34 (2017-08-18) +------------------- + +* Fix mobile inventory template + +* Add extra perms for creating inventory batch w/ different modes + +* Allow batch execution to require options on a per-batch basis + +* Convert more views to master3: + departments, subdepartments, categories, brands, bouncer, customer groups + +* Override deform template for checkbox field; fix label behavior + +* Show all grid actions by default, if there are 3 or less + +* Use shared logic for executing upgrade + + +0.6.33 (2017-08-16) +------------------- + +* Add ``LocalDateTimeFieldRenderer`` for formalchemy + +* Fix auto-disable button on form submit, per Chrome issues + + +0.6.32 (2017-08-15) +------------------- + +* Add generic changelog link for rattail/tailbone packages + +* Let handler delete files when deleting upgrade + +* Add mechanism for user to bulk-change status for purchase credits + +* Tweak how pyramid config is created during app startup, for tests + +* Fix permission used for mobile receiving item lookup + + +0.6.31 (2017-08-13) +------------------- + +* Add show all vs. show diffs for upgrade packages + +* Add initial support for changelog links for upgrade package diffs + +* Add prev/next buttons when viewing upgrade details + +* Merge 'better' theme into base templates + + +0.6.30 (2017-08-12) +------------------- + +* Make product field renderer allow override of link text rendering + + +0.6.29 (2017-08-11) +------------------- + +* Various tweaks to inventory batch logic (zero-all mode etc.) + +* Fix join bug for users grid + +* Flush session once every 1000 records when bulk-deleting + + +0.6.28 (2017-08-09) +------------------- + +* Fix clone config bug for label batches + + +0.6.27 (2017-08-09) +------------------- + +* Improve inventory support, plus "hiding" person data while still using it + +* Fix encoding bug when reading stdout during upgrade + + +0.6.26 (2017-08-09) +------------------- + +* Add awareness of upgrade exit code, success/fail + +* Add support for cloning an upgrade record + +* Add running display of stdout.log when executing upgrade + + +0.6.25 (2017-08-08) +------------------- + +* Specify ``expire_on_commit`` for tailbone db session + + +0.6.24 (2017-08-08) +------------------- + +* Fix bug which caused new empty worked shift when editing time sheet + + +0.6.23 (2017-08-08) +------------------- + +* Fix bulk-delete for batch rows, allow it for pricing batches + +* Fix permission check for deleting single batch rows + +* Fix numeric filter to allow 3 decimal places by default + + +0.6.22 (2017-08-08) +------------------- + +* Remove unwanted import (which broke versioning) + +* Add some links to employees grid + + +0.6.21 (2017-08-08) +------------------- + +* Refactor progress bars somewhat to allow file-based sessions + +* Fix recipients renderer for email settings grid + +* Improve status tracking for upgrades; add package version diff + + +0.6.20 (2017-08-07) +------------------- + +* Record become/stop root user events + +* Make datasync changes bulk-deletable + +* Add basic support for performing / tracking app upgrades + + +0.6.19 (2017-08-04) +------------------- + +* Record basic user login/logout events + +* Expose UserEvent table in UI + + +0.6.18 (2017-08-04) +------------------- + +* Add progress support for bulk deletion + +* Make tempmon readings bulk-deletable + + +0.6.17 (2017-08-04) +------------------- + +* Various view tweaks + + +0.6.16 (2017-08-04) +------------------- + +* Add auto-links for most grids + +* Fix row highlighting for sources panel on product view + + +0.6.15 (2017-08-03) +------------------- + +* Allow product field renderer to suppress hyperlink + +* Add 'data-uuid' attr for mobile grid list items, if applicable + +* Initial (partial) support for mobile ordering + +* Some tweaks to ordering batch views + +* Fix bug when request.user becomes unattached from session (?) + +* Add view for consuming new batch ID + +* Add some links to various grid columns + +* Fix bug in master view_row + + +0.6.14 (2017-08-01) +------------------- + +* Make login template use same logo as home page + +* Fix how we detect grid settings presence in user session + +* Improve verbiage for exception view + +* Fix styles for message compose template + +* Various improvements to batch worksheets, index links etc. + +* Fix batch links when viewing purchase object + +* Add "on order" count to products grid, tweak product notes panel + + +0.6.13 (2017-07-26) +------------------ + +* Allow master view to decide whether each grid checkbox is checked + + +0.6.12 (2017-07-26) +------------------ + +* Add basic support for product inventory and status + +* Stop allowing pre-0.7 SQLAlchemy + + +0.6.11 (2017-07-18) +------------------ + +* Tweak some basic styles for forms/grids + +* Add new v3 master with v2 forms, with colander/deform + + +0.6.10 (2017-07-18) +------------------ + +* Fix grid bug if "current page" becomes invalid + + +0.6.9 (2017-07-15) +------------------ + +* Expose version history for all supported tables + + +0.6.8 (2017-07-14) +------------------ + +* Provide default renderers for SA mapped tables, where possible + +* Add flexible grid class for v3 grids for width=half etc. + +* Final grid refactor; we now have just 'grids' :) + +* Refactor (coalesce) all batch-related templates + + +0.6.7 (2017-07-14) +------------------ + +* Fix master view ``get_effective_data()`` for v3 grids + + +0.6.6 (2017-07-14) +------------------ + +* Fix bug for printing one-off product labels + + +0.6.5 (2017-07-14) +------------------ + +* Fix template/styles for v3 grid views, add purchasing batch status + + +0.6.4 (2017-07-14) +------------------ + +* Add new "v3" grids, refactor all views to use them + + +0.6.3 (2017-07-13) +------------------ + +* Sort mobile receiving batches by ID desc + +* Add initial/basic support for "simple" mobile grid filter w/ radio buttons + +* Add filter support for mobile row grid; plus mark receiving as complete + +* Disable unused Clear button for mobile receiving + +* Add logic for mobile receiving if product not in batch and/or system + +* Prevent mobile receiving actions for batch which is complete or executed + +* Fix bug with mobile receiving UPC lookup; require stronger "create row" perm + +* Stop using popup for expiration date, for mobile receiving + +* Add global key handler for mobile receiving, for scanner wedge input + +* Make all batches support mobile by default + +* Add basic support for viewing inventory batches on mobile + +* Refactor keypad widget for mobile receiving + +* Add unit cost for inventory batches + + +0.6.2 (2017-07-10) +------------------ + +* Fix CS/EA bug for mobile receiving + + +0.6.1 (2017-07-07) +------------------ + +* Switch license to GPL v3 (no longer Affero) + +* Fix broken product image tag, per webhelpers2 + + +0.6.0 (2017-07-06) +------------------ + +Main reason for bumping version is the (re-)addition of data versioning support +using SQLAlchemy-Continuum. This feature has been a long time coming and while +not yet fully implemented, we have a significant head start. + +* Add custom default grid row size for Trainwreck items + +* Make hyperlink optional for employee field renderer + +* Tweak how customer/person relationships are displayed + +* Add initial support for expiration date for mobile receiving + +* Make Person.employee field readonly + +* Rearrange some imports to ensure ``rattail.db.model`` comes last + +* Add basic versioning history support for master view + +* Remove old-style continuum version views + +* Remove all "old-style" (aka. version 1) grids + +* Remove all old-style views: grids, CRUD, versions etc. + +* Refactor to use webhelpers2 etc. instead of older 'webhelpers' + + +0.5.104 (2017-06-22) +-------------------- + +* Add basic views for Trainwreck transactions + +* Add ``AlchemyLocalDateTimeFilter`` + +* Add row count as available column to batch header grids + +* Try to keep batch status updated; display it for handheld batches + +* Tweak display of inventory/label batches to reflect multiple handheld batches + +* Add way to execute multiple handheld batches (search results) at once + +* Fix batch row count when deleting a row + +* Make case/unit quantities prettier within Inventory batch rows grid + +* Sort (alphabetically) device type list field when making new handheld batch + +* Allow bulk row deletion for vendor catalog batches + + +0.5.103 (2017-06-05) +-------------------- + +* Always add key as class to grid column headers; allow literal label + + +0.5.102 (2017-05-30) +-------------------- + +* Remove all views etc. for old-style batches + +* Fix bug when updating Order Form data, if row.po_total is None + + +0.5.101 (2017-05-25) +-------------------- + +* Fix subtle bug when identifying purchase batch row on order form update + +* Remove references to deprecated batch handler methods + +* Add validation for unique name when creating new Setting + +* Simplify page title display for mobile base template + +* Refactor "purchasing" batch views, split off "ordering" + +* Add initial (full-ish) support for mobile receiving views + +* Add support for bulk-delete of Pricing Batches + +* Pad session timeout warning by 10 seconds, to account for drift + +* Add highlight to active row within Order Form view + +* Make 'notes' field use textarea renderer by default, for all batches + +* Add basic ability to download Ordering Batch as Excel spreadsheet + + +0.5.100 (2017-05-18) +-------------------- + +* Allow batch view to override execution failure message + +* Tweak some customer view/field rendering, to allow more customization + +* Remove customer view template (use master default) + +* Add basic support for Trainwreck database connectivity + +* Remove unused 'fake_error' view + +* Add basic 'robots.txt' support to CommonView + +* Cap our pyramid_tm version until we can upgrade to pyramid 1.9 + +* Add daily hour totals when viewing or editing single employee time sheet + +* Let config cause time sheet hours to display as HH.HH for some users + +* Expose full-time flag and start date for employee view + +* Add convenience ``dialog_button()`` JS function + + +0.5.99 (2017-05-05) +------------------- + +* Add allowance for Escape key, in numeric.js + +* Let a batch disallow bulk-deletion of its rows + +* Add basic support for deletion speedbump for row data + +* Remove lower version for Pyramid dependency, but restrict to pre-1.9 + + +0.5.98 (2017-04-18) +------------------- + +* Auto-save time sheet day editor on Enter press if time field is focused + +* Add simple flag to prevent multiple submits for Order Form AJAX + + +0.5.97 (2017-04-04) +------------------- + +* Fix signature for ``MasterView.get_index_url()`` + + +0.5.96 (2017-04-04) +------------------- + +* Tweak logic for registering exception view, to avoid test breakage + +* Add basic paging grid/index support for mobile + +* Tweak field label styles for mobile + +* Allow config to define home page image URL + + +0.5.95 (2017-03-29) +------------------- + +* Tweak organization panel for product view template + +* Add logic to core View class, to force logout if user becomes inactive + +* Detect "backwards" shift when time sheet is edited, alert user + +* Add default view for unhandled exceptions, configure only for production + +* Add basic table listing view, with rough estimate row counts + +* Add 'status' column to vendor cost table in product view + +* Various template standardization tweaks + + +0.5.94 (2017-03-25) +------------------- + +* Add ``CostFieldRenderer`` and tweak product view template + +* Bump margin between grid and header table, i.e. buttons + +* Broad refactor to improve customization of purchase order form etc. + +* Fix route sequence for people autocomplete + +* Fix bugs when checking for 'chuck' in demo mode + +* Add unit item and pack size fields to product view + + +0.5.93 (2017-03-22) +------------------- + +* Add 'is_any' verb to integer grid filters + +* Add more variations of project name when creating via scaffold + +* Various tweaks to the customer and person views/forms + +* Add basic "mobile index" master view, plus support for demo mode + +* Refactor the batch file field renderer somewhat + +* Move ``notfound()`` method to core ``View`` class + +* Add ``BatchMasterView.add_file_field()`` convenience method + +* Add ``extra_main_fields()`` method to product view template + +* Allow config to override jQuery UI version + +* Add master view for Report Output data model + + +0.5.92 (2017-03-14) +------------------- + +* Tweak grid configuration for Employees view + +* Add trailing '?' for employee time sheet when hours are incomplete + + +0.5.91 (2017-03-03) +------------------- + +* Add 'discontinued' flag to product view + + +0.5.90 (2017-03-01) +------------------- + +* Add notes, ingredients to product view + + +0.5.89 (2017-02-24) +------------------- + +* Expose/honor per-role session timeouts + +* Fix daylight savings bug when cloning schedule from previous week + +* Expose notes field for purchasing batches + +* Add some product flags (kosher vegan etc.) to view fieldset + +* Add initial support for native product images + + +0.5.88 (2017-02-21) +------------------- + +* Fix session reference bug in schedule view + + +0.5.87 (2017-02-21) +------------------- + +* Fix bug in DateFieldRenderer when no format specified + + +0.5.86 (2017-02-21) +------------------- + +* Add initial/basic views for customer orders data + +* Be less aggressive when validating schedule edit form POST + + +0.5.85 (2017-02-19) +------------------- + +* Add generic "bulk delete" support to MasterView + +* Add beginnings of mobile receiving views + + +0.5.84 (2017-02-17) +------------------- + +* Tweak progress template to better handle reset to 0% + +* Add ability to merge 2 user accounts + +* Increase size of Roles select when editing a User + +* Add ability to filter Sent Messages by recipient name + + +0.5.83 (2017-02-16) +------------------- + +* Set form id for new purchasing batch page + +* Make sure invoice number is saved when making new purchasing batch + +* Tweak product view page styles (new grids etc.) + +* Add support for client-side session timeout warning + + +0.5.82 (2017-02-14) +------------------- + +* Collapse grid actions if there are only 2 + +* Add master view for generic exports + +* Make some product fields readonly + +* Make datasync changes viewable + +* Redirect to login page when Forbidden happens with anonymous user + +* Tweak styles for Send Message page + +* Tweak form handling for sending a new message, for more customization + +* Advance to password field when Enter pressed on username, login page + +* Add way for ``login_user()`` to set different timeout depending on nature of login + + +0.5.81 (2017-02-11) +------------------- + +* Add config for redirecting user to home page after logout + +* Refactor logic used to login a user, for easier sharing + +* Use ``pretty_hours()`` function where applicable + + +0.5.80 (2017-02-10) +------------------- + +* Tweak renderer for Amount field for DepositLink view + +* Tweak how regular/current price fields are handled for Product view + +* Fix bug in base 'shifts' template if ``weekdays`` not in context + + +0.5.79 (2017-02-09) +------------------- + +* Tweak product view template per rename of case_size field + +* Refactor the Edit Time Sheet view for "autocommit" mode + +* Don't render user field as hyperlink unless so configured + +* Expose 'delay' field in tempmon client views + +* Fix bug when first entry is empty for product on ordering form + + +0.5.78 (2017-02-08) +------------------- + +* Add initial Find Roles/Users by Permission feature + +* Fix sorting bug for Employee Time Sheet view + + +0.5.77 (2017-02-04) +------------------- + +* Invoke timepicker to correct format of user input, for edit schedule/timesheet + + +0.5.76 (2017-02-04) +------------------- + +* Add hyperlink to ``EmployeeFieldRenderer`` + +* Improve the grid for ``WorkedShift`` model a bit + +* Add config flag for disabling option to "Clear Schedule" + + +0.5.75 (2017-02-03) +------------------- + +* Fix probe filter for tempmon readings grid + +* Be explicit about fieldset for pricing batch rows + +* Let project override user authentication for login page + +* Add basic support for per-user session timeout + + +0.5.74 (2017-01-31) +------------------- + +* Refactor schedule / timesheet views for better separation of concerns + + +0.5.73 (2017-01-30) +------------------- + +* Add pyramid_mako dependency, remove minimum version for rattail + +* Add ability to edit employee time sheet + +* Add 'target' kwarg for grid action links + +* Add hyperlink to User field renderer + +* Add min diff threshold param when making price batch from product query + +* Add way for batch views to hide rows with given status code(s) + + +0.5.72 (2017-01-29) +------------------- + +* Add basic support for cloning batches + +* Tweaks to order form template etc., for purchasing batch + +* Let master view with rows prevent sort/filter for row grid + +* Add price diff column to pricing batch row grid + +* Add warning highlight for pricing batch row if can't calculate price + + +0.5.71 (2017-01-24) +------------------- + +* Improve columns, filters for TempMon Readings grid + +* Add ability to merge subdepartments + + +0.5.70 (2017-01-11) +------------------- + +* Fix CSRF token bug with email preview form, refactor to use webhelpers + + +0.5.69 (2017-01-06) +------------------- + +* When making batch from products, build query *before* starting thread + + +0.5.68 (2017-01-03) +------------------- + +* Prefer received quantities over ordered quantities, for Order Form history + + +0.5.67 (2017-01-03) +------------------- + +* Add department UUID to JSON returned for "eligible purchases" when creating batch + +* Set "order date" when creating new receiving batch + +* Add "discarded" flag when receiving DMG/EXP products; add view for purchase credits + +* Fix type error in grid numeric filter + + +0.5.66 (2016-12-30) +------------------- + +* Tweak the "create" screen for purchase batches, for more customization + + +0.5.65 (2016-12-29) +------------------- + +* Fix purchase batch execution, to redirect to Purchase *or* Batch + +* Add extra perms for restricing which 'mode' of purchase batch user can create + +* Refactor Order Form a bit to allow custom history data + + +0.5.64 (2016-12-28) +------------------- + +* Tweak default "numeric" grid filter, to ignore UPC-like values + +* Tweak default filter label for Batch ID + + +0.5.63 (2016-12-28) +------------------- + +* Fix CSRF token bug for bulk-move message forms + + +0.5.62 (2016-12-22) +------------------- + +* Fix CSRF token bug for old-style batch params form + + +0.5.61 (2016-12-21) +------------------- + +* Fix master merge template/forms to include CSRF token + + +0.5.60 (2016-12-20) +------------------- + +* Fix CSRF bug in Ordering Form template, make case quantity pretty + +* Fix some bugs in product view template + +* Update some enum references, render all purchase/batch cases/units fields as quantity + + +0.5.59 (2016-12-19) +------------------- + +* Add ``QuantityFieldRenderer`` + +* Add style for 'half-width' grid + + +0.5.58 (2016-12-16) +------------------- + +* Add ``ValidGPC`` formencode validator + +* Overhaul the Receiving Form to account for "product not found" etc. + +* Auto-append slash to URL when necessary + +* Add "print receiving worksheet" feature, for 'ordered' purchases + +* Add global CSRF protection + +* Tweak some field renderers + +* Overhaul product views a little, per customization needs + + +0.5.57 (2016-12-12) +------------------- + +* Lots of changes for sake of mobile login / user menu etc. + +* Add mobile support for datasync restart + +* Make ``CurrencyFieldRenderer`` inherit from ``FloatFieldRenderer`` + +* Fix session bug in old CRUD views + + +0.5.56 (2016-12-11) +------------------- + +* Show 'enabled' column in grid, fix prefix bug for email profiles + +* Tweak flash message when sending email preview, in case it's disabled + +* Hide first/last name for employee view, unless in readonly mode + +* Add initial mobile templates: base, home, about + + +0.5.55 (2016-12-10) +------------------- + +* Validate for unique tempmon probe config key + +* Add 'restartable tempmon client' conditional logic + + +0.5.54 (2016-12-10) +------------------- + +* Add new 'receiving form' for purchase batches + +* Add support for 'department' field in purchases / batches + +* Add generic 'not on file' product image for use as POD 404 + +* Add logic for handling Ctrl+V / Ctrl+X in numeric.js + + +0.5.53 (2016-12-09) +------------------- + +* Fix bug when editing a data row + + +0.5.52 (2016-12-08) +------------------- + +* Fix permission group label for email bounces + +* Update footer text/link per new about page + + +0.5.51 (2016-12-07) +------------------- + +* Fix permission / grid action bug for email profiles + + +0.5.50 (2016-12-07) +------------------- + +* Tweak tempmon views a little, fix client restart logic + +* Add 'extra_styles' to true base template + +* Add new "bytestring" filter for grids that need it + + +0.5.49 (2016-12-05) +------------------- + +* Allow delete for datasync changes + +* Fix import bugs with tempmon views + +* Use master view's session when creating form + + +0.5.48 (2016-12-05) +------------------- + +* Tweak email config views, to support subject "templates" + +* Refactor tempmon views to leverage rattail-tempmon database + + +0.5.47 (2016-11-30) +------------------- + +* Fix bug in products view class + + +0.5.46 (2016-11-29) +------------------- + +* Add basic 'about' page with some package versions + +* Tweak fields for product view + + +0.5.45 (2016-11-28) +------------------- + +* Fix styles for 'print schedule' page + +* Add permission for bulk-delete of batch data rows + + +0.5.44 (2016-11-22) +------------------- + +* Add some links between employees / people / customers views + +* Add support for pricing batches + +* Add initial views for tempmon clients/probes/readings + + +0.5.43 (2016-11-21) +------------------- + +* Add support for receive/cost mode, purchase relation for purchase batches + +* Bump jquery version + +* Fix bug when downloading batch file + + +0.5.42 (2016-11-20) +------------------- + +* Move ``get_batch_kwargs()`` to ``BatchMasterView`` + + +0.5.41 (2016-11-20) +------------------- + +* Add printer-friendly view for "full" employee schedule + +* Fix some bugs etc. with batch views and templates + + +0.5.40 (2016-11-19) +------------------- + +* Add size, extra link fields to product view template + +* Refactor batch views / templates per rattail framework overhaul + + +0.5.39 (2016-11-14) +------------------- + +* Make POD image for product view a bit more sane + +* Disable save button when creating new object + + +0.5.38 (2016-11-11) +------------------- + +* Tweak default factory for boolean grid filters + +* Add support for more cases + units, more vendor fields, for new purchase batches + + +0.5.37 (2016-11-10) +------------------- + +* Display sequence for product alt codes + +* Change how we determine default 'grid key' for master views + +* Add 'additive fields' concept to merge diff preview + + +0.5.36 (2016-11-09) +------------------- + +* Add historical amounts to new purchase Order Form, allow extra columns etc. + +* Tweak verbiage for merge template etc. + + +0.5.35 (2016-11-08) +------------------- + +* Add support for new Purchase/Batch views, 'create row' master pattern + +* Add basic views for label batches + +* Add support for making new-style batches from products grid query + +* Add initial support for viewing new purchase batch as Order Form + +* Refactor how batch editing is done; don't include rows for that sometimes + + +0.5.34 (2016-11-02) +------------------- + +* Add basic merge feature to ``MasterView`` + + +0.5.33 (2016-10-27) +------------------- + +* Fix template bug when deleting user + +* Tweak default styles for home page + +* Show vendor invoice rows as warning, if they have no case quantity + +* Add 'vendor code' and 'vendor code (any)' filters for products grid + +* Fix bug with how we auto-filter 'deleted' products (?) + + +0.5.32 (2016-10-19) +------------------- + +* Fix / improve progress display somewhat + +* Disable "true delete" button by default, when clicked + +* Fix bug in batch ID field renderer, when displayed for new batch + +* Add ``refresh_after_create`` flag for ``BatchMasterView`` + +* Disable a focus() call in menubar.js which messed with search filter focus + +* Let any 'admin' user elevate to 'root' for full system access + +* Update references to ``request.authenticated_userid`` + + +0.5.31 (2016-10-14) +------------------- + +* Add ability to edit employee schedule + + +0.5.30 (2016-10-10) +------------------- + +* Tweak some things to make demo project more "out of the box" + +* Add registration for 'rattail' template with Pyramid scaffold system + +* Add 'tailbone' to global template context, update 'better' template footer + +* Tweak how tailbone finds rattail config from pyramid settings + +* Remove last references to 'edbob' package + +* Strip whitespace from username field when editing User + +* Fix couple of bugs for vendor catalog views + +* Add size description to inventory report + + +0.5.29 (2016-10-04) +------------------- + +* Add ``code`` field to Category views + +* Add "bulk delete rows" feature to new batches view + + +0.5.28 (2016-09-30) +------------------- + +* Add specific permissions for edit/delete of individual batch rows + + +0.5.27 (2016-09-26) +------------------- + +* Add basic form validation when sending new messages + +* Add "just in time" editable instance check for master view + +* Add "refresh" button when viewing batch + +* Add FormAlchemy-compatible validators for email address, phone number + +* Improve validation for FormAlchemy date field renderer + +* Fix row-level visibility for grid edit action + +* Add a couple of extra verbs to base grid filter class + +* Tweak how a grid filter factory is determined + + +0.5.26 (2016-09-01) +------------------- + +* Add ``MasterView.listable`` flag for disabling grid view + +* Fix permission group label bug for batch views + +* Allow opt-out for "download batch row data as CSV" feature + + +0.5.25 (2016-08-23) +------------------- + +* Tweak how we use DB session to fetch grid settings + +* Add "sub-rows" support to MasterView class + +* Refactor batch views to leverage MasterView sub-rows logic + +* Refactor batch view/edit pages to share some "execution options" logic + +* Add hook to customize timesheet shift rendering + + +0.5.24 (2016-08-17) +------------------- + +* Fix bug in handheld batch view config + + +0.5.23 (2016-08-17) +------------------- + +* Fix bug when viewing batch with no execution options + + +0.5.22 (2016-08-17) +------------------- + +* Fix bug for handheld batch device type field + + +0.5.21 (2016-08-17) +------------------- + +* Add ``MasterView.render()`` method for sake of common context/logic + +* Add "empty" option to enum field renderers, if field allows empty value + +* Add support for system-unique ID in batch views etc. + +* Fix bug when deleting certain batches + +* Fix bug in batch download URL + +* Add basic support for batch execution options + +* Add basic support for new handheld/inventory batches + + +0.5.20 (2016-08-13) +------------------- + +* Add null / not null verbs back to default boolean grid filter + + +0.5.19 (2016-08-12) +------------------- + +* Only show granted permissions when viewing role details + +* Expose 'enabled' flag for email profile/settings + +* Add permissions field when viewing user details + + +0.5.18 (2016-08-10) +------------------- + +* Add ``render_progress()`` method to core view class + +* Add hopefully generic ``FileFieldRenderer`` + + +0.5.17 (2016-08-09) +------------------- + +* Add support for 10-key hyphen/period keys for numeric input fields + + +0.5.16 (2016-08-05) +------------------- + +* Fallback to empty string for email preview recipient, if current user has no address + +* Allow negative sign, decimal point for "numeric" text fields + + +0.5.15 (2016-07-27) +------------------- + +* Add initial attempt at 'better' theme + +* Add ``CodeTextAreaFieldRenderer``, refactor label profile form to use it + + +0.5.14 (2016-07-08) +------------------- + +* Allow extra kwargs to core ``View.redirect()`` method + +* Add awareness of special 'Authenticated' role, in permissions UI etc. + +* Always strip whitespace from label profile 'spec' field input + + +0.5.13 (2016-06-10) +------------------- + +* Hopefully fix some CSS for form field values + +* Add support for viewing single employee's schedule / time sheet + + +0.5.12 (2016-05-11) +------------------- + +* Add support for "full" schedule and time sheet views. + +* Move "full name" to front of Person grid columns. + +* Add rattail config object to ``Session`` kwargs. + + +0.5.11 (2016-05-06) +------------------- + +* Refactor some common FormEncode validators, plus add some more. + +* Tweak styles for jQuery UI selectmenu dropdowns. + +* Tweak timesheet styles, to give rows alternating background color. + +* Disable autocomplete for password fields when editing user. + +* Various incomplete improvements to the timesheet/schedule views. + + +0.5.10 (2016-05-05) +------------------- + +* Refactor timesheet logic, add basic schedule view. + +* Add prev/next/jump week navigation to time sheet, schedule views. + +* Add hyperlinks to product UPC and description, within main grid. + +* Fix bug in roles view. + + +0.5.9 (2016-05-02) +------------------ + +* Remove 'create batch from results' link on products index page. + +* Fix bugs in batch grid URLs. + +* Tweak how empty hours are displayed in time sheet. + + +0.5.8 (2016-05-02) +------------------ + +* Add ``MasterView.listing`` flag, for templates' sake. + +* Overhaul newgrid template header a bit, to improve styles. + +* Move ``Person.display_name`` to top of fieldset when viewing/editing. + +* Add 'testing' image, for background / watermark. + +* Add 'index title' setting to master view. + +* Add auto-hide/show magic to message recipients field when viewing. + +* Add initial support for grid index URLs. + +* Add initial/basic user feedback form support. + +* Stop trying to use PIL when generating product image tag. + + +0.5.7 (2016-04-28) +------------------ + +* Add master views for ``ScheduledShift`` model. + +* Add initial (incomplete) Time Sheet view. + + +0.5.6 (2016-04-25) +------------------ + +* Add views for ``WorkedShift`` model. + + +0.5.5 (2016-04-24) +------------------ + +* Add workarounds for certain display bugs when rendering datetimes. + +* Make currency field renderer display negative amounts in parentheses. + +* Add commas to record/page count in grid footer. + +* Tweak styles for form field labels. + + +0.5.4 (2016-04-12) +------------------ + +* Add support for column header title (tooltip) in new grids. + +* Change default filter type for integer fields, in new grids. + +* Add flag for rendering key value, for enum field renderers. + +* Fix case-sensitivity when sorting permission group labels. + + +0.5.3 (2016-04-05) +------------------ + +* Fix redirect bug when attempting bulk row delete for nonexistent batch. + +* Add comma magic back to ``CurrencyFieldRenderer``. + +* Add the 'is any' verb to default list for most grid filters. + +* Add new ``TimeFieldRenderer``, make it default for ``Time`` fields. + +* Add last-minute check to ensure master views allows deletion. + + +0.5.2 (2016-03-11) +------------------ + +* Make ``tailbone.views.labels`` a subpackage instead of module. + +* Add 'executed' to old batches grid view. + +* Make all timestamps show "raw" by default (with "diff" tooltip). + +* Improve grid filters for datetime fields (smarter verbs). + +* Fix bug where batch creator was being set to current user anytime it was viewed..yikes. + + +0.5.1 (2016-02-27) +------------------ + +* Fix bug when rendering email bounce links. + + +0.5.0 (2016-02-15) +------------------ + +* Refactor products view(s) per new master pattern. + +* Make our ``DateTimeFieldRenderer`` the default for datetime fields. + +* Add new ``BatchMasterView`` for new-style batches. + +* Overhaul vendor catalogs, vendor invoices views to use new batch master class. + +* Refactor some more model views to use MasterView. (depositlink, tax, emailbounce) + +* Make datasync views easier to customize. + + +0.4.42 +------ + +* Add initial reply / reply-all support for messages. + +* Add subscriber hook for setting inbox count in template context. + + +0.4.41 +------ + +* Tweak how we connect a user to a batch, when refreshing. + +* Add 'Move' button to message view template. + + +0.4.40 +------ + +* Make rattail config object use our scoped session, when consulting db. + + +0.4.39 +------ + +* Add support for sending new messages. + + +0.4.38 +------ + +* Add 'password is/not null' filter to users list view. + +* Remove style hack for message grid views. + + +0.4.37 +------ + +* Add 'messages.list' permission, to protect inbox etc. + + +0.4.36 +------ + +* Fix bug when marking batch as executed. + + +0.4.35 +------ + +* Change default form buttons so Cancel is also a button. + +* Add 'Stores' and 'Departments' fields to Employee fieldset. + + +0.4.34 +------ + +* Add 'restart datasync' button to datasync changes list page. + +* Add autocomplete vendor field renderer. + +* Change vendor catalog upload, to allow vendor-less parsers. + +* Stop depending on PIL...for now? + + +0.4.33 +------ + +* Add employee/department relationships to employee and department views. + + +0.4.32 +------ + +* Add edit mode for email "profile" settings. + +* Fix auto-creation of grid sorter, when joined table is involved. + +* Add initial support for 'messages' views. + + +0.4.31 +------ + +* Add speed bump / confirmation page when deleting records. + +* Add "grid tools" to "complete" grid template. + +* Add ``Person.middle_name`` to the fieldset. + + +0.4.30 +------ + +* Add config extension, to record data changes if so configured. + +* Add mailing address to person fieldset. + + +0.4.29 +------ + +* Fix some route names. + + +0.4.28 +------ + +* Use sample data when generating subject for display in email profile settings. + +* Convert (most?) basic views to use master view pattern. + + +0.4.27 +------ + +* Change default sortkey for email profiles list. + +* Add 'To' field to email profile settings grid. + + +0.4.26 +------ + +* Add readonly support for email profile settings. + + +0.4.25 +------ + +* Fix bug when 'edbob.permissions' setting is empty. + +* Tweak some things to get Tailbone working on its own. + +* Let subclass of MasterView override the database Session it uses. + + +0.4.24 +------ + +* Render ``DataSyncChange.obtained`` as humanized timestamp within UI. + + +0.4.23 +------ + +* Delete product costs for vendor when deleting vendor. + +* Work around formalchemy config bug, caused by edbob. + +* Add view to show DataSync changes, for basic troubleshooting. + + +0.4.22 +------ + +* Remove format hack which isn't py2.6-friendly. + + +0.4.21 +------ + +* Add "valueless verbs" concept to grid filters. + +* Tweak labels for new grid filter form buttons. + +* Configure logging when starting up. + +* Add HTML5 doctype to base template. + +* More grid filter improvements; add choice/enum/date value renderers. + +* Treat filter by "contains X Y" as "contains X and contains Y". + +* Tweak layout CSS so page body expands to fill screen. + + +0.4.20 +------ + +* Add ``CurrencyFieldRenderer``. + +* Add basic checkbox support to new grids. + +* Add 'Default Filters' and 'Clear Filters' buttons to new grid filters form. + +* Add "Save Defaults" button so user can save personal defaults for any new grid. + +* Fix bug when rendering hidden field in FA fieldset. + +* Remove some unused styles. + +* Various tweaks to support "late login" idea when uploading new batch. + +* Hard-code old grid pagecount settings, to avoid ``edbob.config``. + +* Refactor app configuration to use ``rattail.config.make_config()``. + +* Tweak label formatter instantiation, per rattail changes. + +* Various tweaks to base batch views. + +* Add ``CustomFieldRenderer`` and ``DateFieldRenderer``. + +* Add ``configure_fieldset()`` stub for master view. + +* Add progress indicator to batch execution. + +* Add ability to download batch row data as CSV. + + +0.4.19 +------ + +* Fix progress template, per jQuery CDN changes. + + +0.4.18 +------ + +* Don't show flash message when user logs in. + +* Add core JS/CSS to base template; use CDN instead of cached files. + +* Add support for "new-style grids" and "model master views", and convert the + following views to use it: roles, users, label profiles, settings. Also + overhaul how permissions are registered in app config. + + +0.4.17 +------ + +* Log warning instead of error when refreshing batch fails. + + +0.4.16 +------ + +* Add initial support for email bounce management. + + +0.4.15 +------ + +* Fix missing import bug. + + +0.4.14 +------ + +* Make anchor tags with 'button' class render as jQuery UI buttons. + +* Tweak ``app.make_rattail_config()`` to allow caller to define some settings. + +* Add ``display_name`` field to employee CRUD view. + +* Allow batch handler to disable the Execute button. + +* Add ``StoreFieldRenderer`` and ``DecimalFieldRenderer``. + +* Tweak how default filter config is handled for batch grid views. + +* Add list of assigned users to role view page. + +* Add products autocomplete view. + +* Add ``rattail_config`` attribute to base ``View`` class. + +* Fix timezone issues with ``util.pretty_datetime()`` function. + +* Add some custom FormEncode validators. + + +0.4.13 +------ + +* Fix query bugs for batch row grid views (add join support). + +* Make vendor field renderer show ID in readonly mode. + +* Change permission requirement for refreshing a batch's data. + +* Add flash message when any batch executes successfully. + +* Add autocomplete view for current employees. + +* Add autocomplete employee field renderer. + +* Fix usage of ``Product.unit_of_measure`` vs. ``Product.weighed``. + + +0.4.12 +------ + +* Fix bug when creating batch from product query. + + +0.4.11 +------ + +* Tweak old-style batch execution call. + + +0.4.10 +------ + +* Add 'fake_error' view to test exception handling. + +* Add ability to view details (i.e. all fields) of a batch row. + +* Fix bulk delete of batch rows, to set 'removed' flag instead. + +* Fix vendor invoice validation bug. + +* Add dept. number and friends to product details page. + +* Add "extra panels" customization hook to product details template. + + +0.4.9 +----- + +* Hide "print labels" column on products list view if so configured. + + +0.4.8 +----- + +* Fix permission for deposit link list/search view. + +* Fix permission for taxes list/search view. + + +0.4.7 +----- + +* Add views for deposit links, taxes; update product view. + +* Add some new vendor and product fields. + +* Add panels to product details view, etc. + +* Fix login so user is sent to their target page after authentication. + +* Don't allow edit of vendor and effective date in catalog batches. + +* Add shared GPC search filter, use it for product batch rows. + +* Add default ``Grid.iter_rows()`` implementation. + +* Add "save" icon and grid column style. + +* Add ``numeric.js`` script for numeric-only text inputs. + +* Add product UPC to JSON output of 'products.search' view. + + +0.4.6 +----- + +* Add vendor catalog batch importer. + +* Add vendor invoice batch importer. + +* Improve data file handling for file batches. + +* Add download feature for file batches. + +* Add better error handling when batch refresh fails, etc. + +* Add some docs for new batch system. + +* Refactor ``app`` module to promote code sharing. + +* Force grid table background to white. + +* Exclude 'deleted' items from reports. + +* Hide deleted field from product details, according to permissions. + +* Fix embedded grid URL query string bug. + + +0.4.5 +----- + +* Add prettier UPCs to ordering worksheet report. + +* Add case pack field to product CRUD form. + + +0.4.4 +----- + +* Add UI support for ``Product.deleted`` column. + + +0.4.3 +----- + +* More versioning support fixes, to allow on or off. + + +0.4.2 +----- + +* Rework versioning support to allow it to be on or off. + + +0.4.1 +----- + +* Only attempt to count versions for versioned models (CRUD views). + + +0.4.0 +----- + +This version primarily got the bump it did because of the addition of support +for SQLAlchemy-Continuum versioning. There were several other minor changes as +well. + +* Add department to field lists for category views. + +* Change default sort for People grid view. + +* Add category to product CRUD view. + +* Add initial versioning support with SQLAlchemy-Continuum. + + +0.3.28 +------ + +* Add unique username check when creating users. + +* Improve UPC search for rows within batches. + +* New batch system... + + +0.3.27 +------ + +* Fix bug with default search filters for SA grids. + +* Fix bug in product search UPC filter. + +* Ugh, add unwanted jQuery libs to progress template. + +* Add support for integer search filters. + + +0.3.26 +------ + +* Use boolean search filter for batch column filters of 'FLAG' type. + + +0.3.25 +------ + +* Make product UPC search view strip non-digit chars from input. + + +0.3.24 +------ + +* Make ``GPCFieldRenderer`` display check digit separate from main barcode + data. + +* Add ``DateTimeFieldRenderer`` to show human-friendly timestamps. + +* Tweak CRUD form buttons a little. + +* Add grid, CRUD views for ``Setting`` model. + +* Update ``base.css`` with various things from other projects. + +* Fix bug with progress template, when error occurs. + + +0.3.23 +------ + +* Fix bugs when configuring database session within threads. + + +0.3.22 +------ + +* Make ``Store.database_key`` field editable. + +* Add explicit session config within batch threads. + +* Remove cap on installed Pyramid version. + +* Change session progress API. + + +0.3.21 +------ + +* Add monospace font for label printer format command. + + +0.3.20 +------ + +* Refactor some label printing stuff, per rattail changes. + + +0.3.19 +------ + +* Add support for ``Product.not_for_sale`` flag. + + +0.3.18 +------ + +* Add explicit file encoding to all Mako templates. + +* Add "active" filter to users view; enable it by default. + + +0.3.17 +------ + +* Add customer phone autocomplete and customer "info" AJAX view. + +* Allow editing ``User.active`` field. + +* Add Person autocomplete view which restricts to employees only. + + +0.3.16 +------ + +* Add product report codes to the UI. + + +0.3.15 +------ + +* Add experimental soundex filter support to the Customers grid. + + +0.3.14 +------ + +* Add event hook for attaching Rattail ``config`` to new requests. + +* Fix vendor filter/sort issues in products grid. + +* Add ``Family`` and ``Product.family`` to the general grid/crud UI. + +* Add POD image support to product view page. + + +0.3.13 +------ + +* Use global ``Session`` from rattail (again). + +* Apply zope transaction to global Tailbone Session class. + + +0.3.12 +------ + +* Fix customer lookup bug in customer detail view. + +* Add ``SessionProgress`` class, and ``progress`` views. + + +0.3.11 +------ + +* Removed reliance on global ``rattail.db.Session`` class. + + +0.3.10 +------ + +* Changed ``UserFieldRenderer`` to leverage ``User.display_name``. + +* Refactored model imports, etc. + + This is in preparation for using database models only from ``rattail`` + (i.e. no ``edbob``). Mostly the model and enum imports were affected. + +* Removed references to ``edbob.enum``. + + +0.3.9 +----- + +* Added forbidden view. + +* Fixed bug with ``request.has_any_perm()``. + +* Made ``SortableAlchemyGridView`` default to full (100%) width. + +* Refactored ``AutocompleteFieldRenderer``. + + Also improved some organization of renderers. + +* Allow overriding form class/factory for CRUD views. + +* Made ``EnumFieldRenderer`` a proper class. + +* Don't sort values in ``EnumFieldRenderer``. + + The dictionaries used to supply enumeration values should be ``OrderedDict`` + instances if sorting is needed. + +* Added ``Product.family`` to CRUD view. + + +0.3.8 +----- + +* Fixed manifest (whoops). + + +0.3.7 +----- + +* Added some autocomplete Javascript magic. + + Not sure how this got missed the first time around. + +* Added ``products.search`` route/view. + + This is for simple AJAX uses. + +* Fixed grid join map bug. + + +0.3.6 +----- + +* Fixed change password template/form. + + +0.3.5 +----- + +* Added ``forms.alchemy`` module and changed CRUD view to use it. + +* Added progress template. + + +0.3.4 +----- + +* Changed vendor filter in product search to find "any vendor". + + I.e. the current filter is *not* restricted to the preferred vendor only. + Probably should still add one (back) for preferred only as well; hence the + commented code. + + +0.3.3 +----- + +* Major overhaul for standalone operation. + + This removes some of the ``edbob`` reliance, as well as borrowing some + templates and styling etc. from Dtail. + + Stop using ``edbob.db.engine``, stop using all edbob templates, etc. + +* Fix authorization policy bug. + + This was really an edge case, but in any event the problem would occur when a + user was logged in, and then that user account was deleted. + +* Added ``global_title()`` to base template. + +* Made logo more easily customizable in login template. + + +0.3.2 +----- + +* Rebranded to Tailbone. + + +0.3.1 +----- + +* Added some tests. + +* Added ``helpers`` module. + + Also added a Pyramid subscriber hook to add the module to the template + renderer context with a key of ``h``. This is nothing really new, but it + overrides the helper provided by ``edbob``, and adds a ``pretty_date()`` + function (which maybe isn't a good idea anyway..?). + +* Added ``simpleform`` wildcard import to ``forms`` module. + +* Added autocomplete view and template. + +* Fixed customer group deletion. + + Now any customer associations are dropped first, to avoid database integrity + errors. + +* Stole grids and grid-based views from ``edbob``. + +* Removed several references to ``edbob``. + +* Replaced ``Grid.clickable`` with ``.viewable``. + + Clickable grid rows seemed to be more irritating than useful. Now a view + icon is shown instead. + +* Added style for grid checkbox cells. + +* Fixed FormAlchemy table rendering when underlying session is not primary. + + This was needed for a grid based on a LOC SMS session. + +* Added grid sort arrow images. + +* Improved query modification logic in alchemy grid views. + +* Overhauled report views to allow easier template customization. + +* Improved product UPC search so check digit is optional. + +* Fixed import issue with ``views.reports`` module. + + +0.3a23 +------ + +* Fixed bugs where edit links were appearing for unprivileged users. + +* Added support for product codes. + + These are shown when viewing a product, and may be used to locate a product + via search filters. + + +0.3a22 +------ + +* Removed ``setup.cfg`` file. + +* Added ``Session`` to ``rattail.pyramid`` namespace. + +* Added Email Address field to Vendor CRUD views. + +* Added extra key lookups for customer and product routes. + + Now the CRUD routes for these objects can leverage UUIDs of various related + objects in addition to the primary object. More should be done with this, + but at least we have a start. + +* Replaced ``forms`` module with subpackage; added some initial goodies (many + of which are currently just imports from ``edbob``). + +* Added/edited various CRUD templates for consistency. + +* Modified several view modules so their Pyramid configuration is more + "extensible." This just means routes and views are defined as two separate + steps, so that derived applications may inherit the route definitions if they + so choose. + +* Added Employee CRUD views; added Email Address field to index view. + +* Updated ``people`` view module so it no longer derives from that of + ``edbob``. + +* Added support for, and some implementations of, extra key lookup abilities to + CRUD views. This allows URLs to use a "natural" key (e.g. Customer ID + instead of UUID), for cases where that is more helpful. + +* Product CRUD now uses autocomplete for Brand field. Also, price fields no + longer appear within an editable fieldset. + +* Within Store index view, default sort is now ID instead of Name. + +* Added Contact and Phone Number fields to Vendor CRUD views; added Contact and + Email Address fields to index view. + + +0.3a21 +------ + +- [feature] Added CRUD view and template. + +- [feature] Added ``AutocompleteView``. + +- [feature] Added Person autocomplete view and User CRUD views. + +- [feature] Added ``id`` and ``status`` fields to Employee grid view. + + +0.3a20 +------ + +- [feature] Sorted the Ordering Worksheet by product brand, description. + +0.3a19 +------ + +- [feature] Made batch creation and execution threads aware of + `sys.excepthook`. Updated both instances to use `rattail.threads.Thread` + instead of `threading.Thread`. This way if an exception occurs within the + thread, the registered handler will be invoked. + +0.3a18 +------ + +- [bug] Label profile editing now uses stripping field renderer to avoid + problems with leading/trailing whitespace. + +- [feature] Added Inventory Worksheet report. + +0.3a17 +------ + +- [feature] Added Brand and Size fields to the Ordering Worksheet. Also + tweaked the template styles slightly, and added the ability to override the + template via config. + +- [feature] Added "preferred only" option to Ordering Worksheet. + +0.3a16 +------ + +- [bug] Fixed bug where requesting deletion of non-existent batch row was + redirecting to a non-existent route. + +0.3a15 +------ + +- [bug] Fixed batch grid and CRUD views so that the execution time shows a + pretty (and local) display instead of 24-hour UTC time. + +0.3a14 +------ + +- [feature] Added some more CRUD. Mostly this was for departments, + subdepartments, brands and products. This was rather ad-hoc and still is + probably far from complete. + +- [general] Changed main batch route. + +- [bug] Fixed label profile templates so they properly handle a missing or + invalid printer spec. + +0.3a13 +------ + +- [bug] Fixed bug which prevented UPC search from working on products screen. + +0.3a12 +------ + +- [general] Fixed namespace packages, per ``setuptools`` documentation. + +- [feature] Added support for ``LabelProfile.visible``. This field may now be + edited, and it is honored when displaying the list of available profiles to + be used for printing from the products page. + +- [bug] Fixed bug where non-numeric data entered in the UPC search field on the + products page was raising an error. + +0.3a11 +------ + +- [bug] Fixed product label printing to handle any uncaught exception, and + report the error message to the end user. + +0.3a10 +------ + +- [general] Updated category views and templates. These were sorely out of + date. + +0.3a9 +----- + +- Add brands autocomplete view. + +- Add departments autocomplete view. + +- Add ID filter to vendors grid. + +0.3a8 +----- + +- Tweak batch progress indicators. + +- Add "Executed" column, filter to batch grid. + +0.3a7 +----- + +- Add ability to restrict batch providers via config. + +0.3a6 +----- + +- Add Vendor CRUD. + +- Add Brand views. + +0.3a5 +----- + +- Added support for GPC data type. + +- Added eager import of ``rattail.sil`` in ``before_render`` hook. + +- Removed ``rattail.pyramid.util`` module. + +- Added initial batch support: views, templates, creation from Product grid. + +- Added support for ``rattail.LabelProfile`` class. + +- Improved Product grid to include filter/sort on Vendor. + +- Cleaned up dependencies. + +- Added ``rattail.pyramid.includeme()``. + +- Added ``CustomerGroup`` CRUD view (read only). + +- Added hot links to ``Customer`` CRUD view. + +- Added ``Store`` index, CRUD views. + +- Updated ``rattail.pyramid.views.includeme()``. + +- Added ``email_preference`` to ``Customer`` CRUD. + +0.3a4 +----- + +- Update grid and CRUD views per changes in ``edbob``. + +0.3a3 +----- + +- Add price field renderers. + +- Add/tweak lots of views for database models. + +- Add label printing to product list view. + +- Add (some of) ``Product`` CRUD. + +0.3a2 +----- + +- Refactor category views. + +0.3a1 +----- + +- Initial port to Rattail v0.3. diff --git a/MANIFEST.in b/MANIFEST.in index a3d57f93..f9845875 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,6 @@ include *.txt include *.rst include *.py -include tailbone/static/robots.txt recursive-include tailbone/static *.js recursive-include tailbone/static *.css recursive-include tailbone/static *.png @@ -11,8 +10,6 @@ recursive-include tailbone/static *.jpg recursive-include tailbone/static *.gif recursive-include tailbone/static *.ico -recursive-include tailbone/static/files * - recursive-include tailbone/templates *.mako recursive-include tailbone/templates *.pt recursive-include tailbone/reports *.mako diff --git a/README.md b/README.rst similarity index 56% rename from README.md rename to README.rst index 74c007f6..0cffc62d 100644 --- a/README.md +++ b/README.rst @@ -1,8 +1,10 @@ -# Tailbone +Tailbone +======== Tailbone is an extensible web application based on Rattail. It provides a "back-office network environment" (BONE) for use in managing retail data. -Please see Rattail's [home page](http://rattailproject.org/) for more -information. +Please see Rattail's `home page`_ for more information. + +.. _home page: http://rattailproject.org/ diff --git a/docs/OLDCHANGES.rst b/docs/OLDCHANGES.rst deleted file mode 100644 index 0a802f40..00000000 --- a/docs/OLDCHANGES.rst +++ /dev/null @@ -1,7539 +0,0 @@ - -CHANGELOG -========= - -NB. this file contains "old" release notes only. for newer releases -see the `CHANGELOG.md` file in the source root folder. - - -0.9.96 (2024-04-25) -------------------- - -* Remove unused code for ``webhelpers2_grid``. - -* Rename setting for custom user css (remove "buefy"). - -* Fix permission checks for root user with pyramid 2.x. - -* Cleanup grid/filters logic a bit. - -* Use normal (not checkbox) button for grid filters. - -* Tweak icon for Download Results button. - -* Use v-model to track selection etc. for download results fields. - -* Allow deleting rows from executed batches. - - -0.9.95 (2024-04-19) -------------------- - -* Fix ASGI websockets when serving on sub-path under site root. - -* Fix raw query to avoid SQLAlchemy 2.x warnings. - -* Remove config "style" from appinfo page. - - -0.9.94 (2024-04-16) -------------------- - -* Fix master template bug when no form in context. - - -0.9.93 (2024-04-16) -------------------- - -* Improve form support for view supplements. - -* Prevent multi-click for grid filters "Save Defaults" button. - -* Fix typo when getting app instance. - - -0.9.92 (2024-04-16) -------------------- - -* Escape underscore char for "contains" query filter. - -* Rename custom ``user_css`` context. - -* Add support for Pyramid 2.x; new security policy. - - -0.9.91 (2024-04-15) -------------------- - -* Avoid uncaught error when updating order batch row quantities. - -* Try to return JSON error when receiving API call fails. - -* Avoid error for tax field when creating new department. - -* Show toast msg instead of silent error, when grid fetch fails. - -* Remove most references to "buefy" name in class methods, template - filenames etc. - - -0.9.90 (2024-04-01) -------------------- - -* Add basic CRUD for Person "preferred first name". - - -0.9.89 (2024-03-27) -------------------- - -* Fix bulk-delete rows for import/export batch. - - -0.9.88 (2024-03-26) -------------------- - -* Update some SQLAlchemy logic per upcoming 2.0 changes. - - -0.9.87 (2023-12-26) -------------------- - -* Auto-disable submit button for login form. - -* Hide single invoice file field for multi-invoice receiving batch. - -* Use common logic to render invoice total for receiving. - -* Expose default custorder discount for Departments. - - -0.9.86 (2023-12-12) -------------------- - -* Use ``ltrim(rtrim())`` instead of just ``trim()`` in grid filters. - - -0.9.85 (2023-12-01) -------------------- - -* Use clientele handler to populate customer dropdown widget. - - -0.9.84 (2023-11-30) -------------------- - -* Provide a way to show enum display text for some version diff fields. - - -0.9.83 (2023-11-30) -------------------- - -* Avoid error when editing a department. - - -0.9.82 (2023-11-19) -------------------- - -* Fix DB picker, theme picker per Buefy conventions. - - -0.9.81 (2023-11-15) -------------------- - -* Log warning instead of error for batch population error. - -* Remove reference to ``pytz`` library. - -* Avoid outright error if user scans barcode for inventory count. - - -0.9.80 (2023-11-05) -------------------- - -* Expose status code for equity payments. - - -0.9.79 (2023-11-01) -------------------- - -* Add button to confirm all costs for receiving. - - -0.9.78 (2023-11-01) -------------------- - -* Use shared logic to get batch handler. - -* Fix config key for default themes list. - - -0.9.77 (2023-11-01) -------------------- - -* Encode values for "between" query filter. - -* Avoid error when rendering version diff. - - -0.9.76 (2023-11-01) -------------------- - -* Fix missing import. - - -0.9.75 (2023-11-01) -------------------- - -* Add deprecation warnings for ambgiguous config keys. - - -0.9.74 (2023-10-30) -------------------- - -* Log warning / avoid error if email profile can't be normalized. - - -0.9.73 (2023-10-29) -------------------- - -* Add way to "ignore" a pending product. - -* Tweak param docs for ``Form.set_validator()``. - -* Remove unused "simple menus" module approach. - - -0.9.72 (2023-10-26) -------------------- - -* Use product lookup component for "resolve pending product" tool. - - -0.9.71 (2023-10-25) -------------------- - -* Fix bug when editing vendor. - -* Show user warning if "add item to custorder" fails. - -* Allow pending product fields to be required, for new custorder. - -* Add price confirm prompt when adding unknown item to custorder. - -* Use ```` for theme picker. - -* Add ``column_only`` kwarg for ``Grid.set_label()`` method. - -* Do not show profile buttons for inactive customer shoppers. - -* Add separate perm for making new custorder for unknown product. - -* Expand the "product lookup" component to include autocomplete. - - -0.9.70 (2023-10-24) -------------------- - -* Fix config file priority for display, and batch subprocess commands. - - -0.9.69 (2023-10-24) -------------------- - -* Allow override of version diff for master views. - -* No need to configure logging. - - -0.9.68 (2023-10-23) -------------------- - -* Expose more permissions for POS. - -* Fix order xlsx download if missing order date. - -* Replace dropdowns with autocomplete, for "find principals by perm". - -* Use ``Grid.make_sorter()`` instead of legacy code. - -* Avoid "None" when rendering product UOM field. - -* Fix default grid filter when "local" date times are involved. - -* Expose new fields for POS batch/row. - -* Remove sorter for "Credits?" column in purchasing batch row grid. - -* Add validation to prevent duplicate files for multi-invoice receiving. - -* Include invoice number for receiving batch row API. - -* Show food stamp tender info for POS batch. - -* Stop using sa-filters for basic grid sorting. - - -0.9.67 (2023-10-12) -------------------- - -* Fix grid sorting when column key/name differ. - -* Expose department tax, FS flag. - -* Add permission for testing error handling at POS. - -* Add some awareness of suspend/resume for POS batch. - -* Fix version child classes for Customers view. - - -0.9.66 (2023-10-11) -------------------- - -* Make grid JS ``loadAsyncData()`` method truly async. - -* Add support for multi-column grid sorting. - -* Add smarts to show display text for some version diff fields. - -* Allow null for FalafelDateTime form fields. - -* Show full version history within the "view" page. - -* Use autocomplete instead of dropdown for grid "add filter". - - -0.9.65 (2023-10-07) -------------------- - -* Avoid deprecated logic for fetching vendor contact email/phone. - -* Add "mark complete" button for inventory batch row entry page. - -* Expose tender ref in POS batch rows; new tender flags. - -* Improve views for taxes, esp. in POS batches. - - -0.9.64 (2023-10-06) -------------------- - -* Fix bug for param helptext in New Report page. - - -0.9.63 (2023-10-06) -------------------- - -* Fix CRUD pages for tempmon clients, probes. - -* Fix bug in POS batch view. - -* Expose permissions for POS, if so configured. - - -0.9.62 (2023-10-04) -------------------- - -* Avoid deprecated ``pretty_hours()`` function. - -* Improve master view ``oneoff_import()`` method. - - -0.9.61 (2023-10-04) -------------------- - -* Use enum to display ``POS_ROW_TYPE``. - -* Expose cash-back flags for tenders. - -* Re-work FalafelDateTime logic a bit. - - -0.9.60 (2023-10-01) -------------------- - -* Do not allow executing custorder if no customer is set. - -* Add clone support for POS batches. - -* Expose views for tenders, more columns for POS batch/rows. - -* Tidy up logic for vendor filtering in products grid. - -* Add support for void rows in POS batch. - - -0.9.59 (2023-09-25) -------------------- - -* Add custom form type/widget for time fields. - - -0.9.58 (2023-09-25) -------------------- - -* Expose POS batch views as "typical". - - -0.9.57 (2023-09-24) -------------------- - -* Show yesterday by default for Trainwreck if so configured. - -* Add ``remove_sorter()`` method for grids. - -* Show "true" (calculated) equity total in members grid. - -* Add basic views for POS batches. - -* Show customer for POS batches. - -* Use header button instead of link for "touch" instance. - - -0.9.56 (2023-09-19) -------------------- - -* Add link to vendor name for receiving batches grid. - -* Prevent catalog/invoice cost edits if receiving batch is complete. - -* Use small text input for receiving cost editor fields. - -* Show catalog/invoice costs as 2-decimal currency in receiving. - - -0.9.55 (2023-09-18) -------------------- - -* Show user warning if receive quick lookup fails. - -* Fix bug for new receiving from scratch via API. - - -0.9.54 (2023-09-17) -------------------- - -* Add "falafel" custom date/time field type and widget. - -* Avoid error when history has blanks for ordering worksheet. - -* Include PO number for receiving batch details via API. - -* Tweaks to improve handling of "missing" items for receiving. - - -0.9.53 (2023-09-16) -------------------- - -* Make member key field readonly when viewing equity payment. - - -0.9.52 (2023-09-15) -------------------- - -* Add basic feature for "grid totals". - - -0.9.51 (2023-09-15) -------------------- - -* Tweak default field list for batch views. - -* Add ``get_rattail_app()`` method for view supplements. - - -0.9.50 (2023-09-12) -------------------- - -* Avoid legacy logic for ``Customer.people`` schema. - -* Show events instead of notes, in field subgrid for custorder item. - - -0.9.49 (2023-09-11) -------------------- - -* Add custom hook for grid "apply filters". - -* Use common POST logic for submitting new customer order. - -* Optionally configure SQLAlchemy Session with ``future=True``. - -* Show related customer orders for Pending Product view. - -* Set stacklevel for all deprecation warnings. - -* Add support for toggling custorder item "flagged". - -* Add support for "mark received" when viewing custorder item. - -* Misc. improvements for custorder views. - - -0.9.48 (2023-09-08) -------------------- - -* Add grid link for equity payment description. - -* Fix msg body display, download link for email bounces. - -* Fix member key display for equity payment form. - - -0.9.47 (2023-09-07) -------------------- - -* Fallback to None when getting values for merge preview. - - -0.9.46 (2023-09-07) -------------------- - -* Improve display for member equity payments. - - -0.9.45 (2023-09-02) -------------------- - -* Add grid filter type for BigInteger columns. - -* Add products API route to fetch label profiles for use w/ printing. - -* Tweaks for cost editing within a receiving batch. - - -0.9.44 (2023-08-31) -------------------- - -* Avoid deprecated ``User.email_address`` property. - -* Preserve URL hash when redirecting in grid "reset to defaults". - - -0.9.43 (2023-08-30) -------------------- - -* Let "new product" batch override type-2 UPC lookup behavior. - - -0.9.42 (2023-08-29) -------------------- - -* When bulk-deleting, skip objects which are not "deletable". - -* Declare "from PO" receiving workflow if applicable, in API. - -* Auto-select text when editing costs for receiving. - -* Include shopper history from parent customer account perspective. - -* Link to product record, for New Product batch row. - -* Fix profile history to show when a CustomerShopperHistory is deleted. - -* Fairly massive overhaul of the Profile view; standardize tabs etc.. - -* Add support for "missing" credit in mobile receiving. - - -0.9.41 (2023-08-08) -------------------- - -* Add common logic to validate employee reference field. - -* Fix HTML rendering for UOM choice options. - -* Fix custom cell click handlers in main buefy grid tables. - - -0.9.40 (2023-08-03) -------------------- - -* Make system key searchable for problem report grid. - - -0.9.39 (2023-07-15) -------------------- - -* Show invoice number for each row in receiving. - -* Tweak display options for tempmon probe readings graph. - - -0.9.38 (2023-07-07) -------------------- - -* Optimize "auto-receive" batch process. - - -0.9.37 (2023-07-03) -------------------- - -* Avoid deprecated product key field getter. - -* Allow "arbitrary" PO attachment to purchase batch. - - -0.9.36 (2023-06-20) -------------------- - -* Include user "active" flag in profile view context. - - -0.9.35 (2023-06-20) -------------------- - -* Add views etc. for member equity payments. - -* Improve merge support for records with no uuid. - -* Turn on quickie person search for CustomerShopper views. - - -0.9.34 (2023-06-17) -------------------- - -* Add basic Shopper tab for profile view. - -* Cleanup some wording in profile view template. - -* Tweak ``SimpleRequestMixin`` to not rely on ``response.data.ok``. - -* Add support for Notes tab in profile view. - -* Add basic support for Person quickie lookup. - -* Hide unwanted revisions for CustomerPerson etc. - -* Fix some things for viewing a member. - - -0.9.33 (2023-06-16) -------------------- - -* Update usage of app handler per upstream changes. - - -0.9.32 (2023-06-16) -------------------- - -* Fix grid filter bug when switching from 'equal' to 'between' verbs. - -* Add users context data for profile view. - -* Join the Person model for Customers grid differently based on config. - - -0.9.31 (2023-06-15) -------------------- - -* Prefer account holder, shoppers over legacy ``Customers.people``. - - -0.9.30 (2023-06-12) -------------------- - -* Add basic support for exposing ``Customer.shoppers``. - -* Move "view history" and related buttons, for person profile view. - -* Consider vendor catalog batch views "typical". - -* Let external customer link buttons be more dynamic, for profile view. - -* Add options for grid results to link straight to Profile view. - -* Change label for Member.person to "Account Holder". - - -0.9.29 (2023-06-06) -------------------- - -* Add "typical" view config, for e.g. Theo and the like. - -* Add customer number filter for People grid. - -* Tweak logic for ``MasterView.get_action_route_kwargs()``. - -* Add "touch" support for Members. - -* Add support for "configured customer/member key". - -* Use *actual* current URL for user feedback msg. - -* Remove old/unused feedback templates. - -* Add basic support for membership types. - -* Add support for version history in person profile view. - - -0.9.28 (2023-06-02) -------------------- - -* Expose mail handler and template paths in email config page. - - -0.9.27 (2023-06-01) -------------------- - -* Share some code for validating vendor field. - -* Save datasync config with new keys, per RattailConfiguration. - - -0.9.26 (2023-05-25) -------------------- - -* Prevent bug in upgrade diff for empty new version. - -* Expose basic way to send test email. - -* Avoid error when filter params not valid. - -* Tweak byjove project generator form. - -* Define essential views for API. - - -0.9.25 (2023-05-18) -------------------- - -* Add initial swagger.json endpoint for API. - -* Add workaround for "share grid link" on insecure sites. - - -0.9.24 (2023-05-16) -------------------- - -* Replace ``setup.py`` contents with ``setup.cfg``. - -* Prevent error in old product search logic. - - -0.9.23 (2023-05-15) -------------------- - -* Get rid of ``newstyle`` flag for ``Form.validate()`` method. - -* Add basic support for managing, and accepting API tokens. - - -0.9.22 (2023-05-13) -------------------- - -* Tweak button wording in "find role by perm" form. - -* Warn user if DB not up to date, in new table wizard. - - -0.9.21 (2023-05-10) -------------------- - -* Move row delete check logic for receiving to batch handler. - - -0.9.20 (2023-05-09) -------------------- - -* Add form config for generating 'shopfoo' projects. - -* Misc. tweaks for "run import job" form. - - -0.9.19 (2023-05-05) -------------------- - -* Massive overhaul of "generate project" feature. - -* Include project views by default, in "essential" views. - - -0.9.18 (2023-05-03) -------------------- - -* Avoid error if tempmon probe has invalid status. - -* Expose, honor the ``prevent_password_change`` flag for Users. - - -0.9.17 (2023-04-17) -------------------- - -* Allow bulk-delete for products grid. - -* Improve global menu search behavior for multiple terms. - - -0.9.16 (2023-03-27) -------------------- - -* Avoid accidental auto-submit of new msg form, for subject field. - -* Add ``has_perm()`` etc. to request during the NewRequest event. - -* Fix table sorting for FK reference column in new table wizard. - -* Overhaul the "find by perm" feature a bit. - - -0.9.15 (2023-03-15) -------------------- - -* Remove version workaround for sphinx. - -* Let providers do DB connection setup for web API. - - -0.9.14 (2023-03-09) -------------------- - -* Fix JSON rendering for Cornice API views. - - -0.9.13 (2023-03-08) -------------------- - -* Remove version cap for cornice, now that we require python3. - - -0.9.12 (2023-03-02) -------------------- - -* Add "equal to any of" verb for string-type grid filters. - -* Allow download results for Trainwreck. - - -0.9.11 (2023-02-24) -------------------- - -* Allow sort/filter by vendor for sample files grid. - - -0.9.10 (2023-02-22) -------------------- - -* Add views for sample vendor files. - - -0.9.9 (2023-02-21) ------------------- - -* Validate vendor for catalog batch upload. - - -0.9.8 (2023-02-20) ------------------- - -* Make ``config`` param more explicit, for GridFilter constructor. - - -0.9.7 (2023-02-14) ------------------- - -* Add dedicated view config methods for "view" and "edit help". - - -0.9.6 (2023-02-12) ------------------- - -* Refactor ``Query.get()`` => ``Session.get()`` per SQLAlchemy 1.4. - - -0.9.5 (2023-02-11) ------------------- - -* Use sa-filters instead of sqlalchemy-filters for API queries. - - -0.9.4 (2023-02-11) ------------------- - -* Remove legacy grid for alt codes in product view. - - -0.9.3 (2023-02-10) ------------------- - -* Add dependency for pyramid_retry. - -* Use latest zope.sqlalchemy package. - -* Fix auto-advance on ENTER for login form. - -* Use label handler to avoid deprecated logic. - -* Remove legacy vendor sources grid for product view. - -* Expose setting for POD image URL. - -* Fix multi-file upload widget bug. - - -0.9.2 (2023-02-03) ------------------- - -* Fix auto-focus username for login form. - - -0.9.1 (2023-02-03) ------------------- - -* Stop including deform JS static files. - - -0.9.0 (2023-02-03) ------------------- - -* Officially drop support for python2. - -* Remove all deprecated jquery and ``use_buefy`` logic. - -* Add new Buefy-specific upgrade template. - -* Replace 'default' theme to match 'falafel'. - -* Allow editing the Department field for a Subdepartment. - -* Refactor the Ordering Worksheet generator, per Buefy. - - -0.8.292 (2023-02-02) --------------------- - -* Always assume ``use_buefy=True`` within main page template. - - -0.8.291 (2023-02-02) --------------------- - -* Fix checkbox behavior for Inventory Worksheet. - -* Form constructor assumes ``use_buefy=True`` by default. - - -0.8.290 (2023-02-02) --------------------- - -* Remove support for Buefy 0.8. - -* Add progress bar page for Buefy theme. - - -0.8.289 (2023-01-30) --------------------- - -* Fix icon for multi-file upload widget. - -* Tweak customer panel header style for new custorder. - -* Add basic API support for printing product labels. - -* Tweak the Ordering Worksheet generator, per Buefy. - -* Refactor the Inventory Worksheet generator, per Buefy. - - -0.8.288 (2023-01-28) --------------------- - -* Tweak import handler form, some fields not required. - -* Tweak styles for Quantity panel when viewing Receiving row. - - -0.8.287 (2023-01-26) --------------------- - -* Fix click event for right-aligned buttons on profile view. - - -0.8.286 (2023-01-18) --------------------- - -* Add some more menu items to default set. - -* Add default view config for Trainwreck. - -* Rename frontend request handler logic to ``SimpleRequestMixin``. - - -0.8.285 (2023-01-18) --------------------- - -* Misc. tweaks for App Details / Configure Menus. - -* Add specific data type options for new table entry form. - -* Add more views, menus to default set. - -* Add way to override particular 'essential' views. - - -0.8.284 (2023-01-15) --------------------- - -* Let the API "rawbytes" response be just that, w/ no file. - -* Fix bug when adding new profile via datasync configure. - -* Add default logic to get merge data for object. - -* Add new handlers, TailboneHandler and MenuHandler. - -* Add full set of default menus. - -* Wrap up steps for new table wizard. - -* Add basic "new model view" wizard. - - -0.8.283 (2023-01-14) --------------------- - -* Tweak how backfill task is launched. - - -0.8.282 (2023-01-13) --------------------- - -* Show basic column info as row grid when viewing Table. - -* Semi-finish logic for writing new table model class to file. - -* Fix "toggle batch complete" for Chrome browser. - -* Revert logic that assumes all themes use buefy. - -* Refactor tempmon dashboard view, for buefy themes. - -* Prevent listing for top-level Messages view. - - -0.8.281 (2023-01-12) --------------------- - -* Add new views for App Info, and Configure App. - - -0.8.280 (2023-01-11) --------------------- - -* Allow all external dependency URLs to be set in config. - - -0.8.279 (2023-01-11) --------------------- - -* Add basic support for receiving from multiple invoice files. - -* Add support for per-item default discount, for new custorder. - -* Fix panel header icon behavior for new custorder. - -* Refactor inventory batch "add row" page, per new theme. - - -0.8.278 (2023-01-08) --------------------- - -* Improve "download rows as XLSX" for importer batch. - - -0.8.277 (2023-01-07) --------------------- - -* Expose, start to honor "units only" setting for products. - - -0.8.276 (2023-01-05) --------------------- - -* Keep aspect ratio for product images in new custorder. - -* Fix template bug for generating report. - -* Show help link when generating or viewing report, if applicable. - -* Use product handler to normalize data for products API. - - -0.8.275 (2023-01-04) --------------------- - -* Allow xref buttons to have "internal" links. - - -0.8.274 (2023-01-02) --------------------- - -* Show only "core" app settings by default. - -* Allow buefy version to be 'latest'. - -* Add beginnings of "New Table" feature. - -* Make invalid email more obvious, in profile view. - -* Expose some settings for Trainwreck DB rotation. - - -0.8.273 (2022-12-28) --------------------- - -* Add support for Buefy 0.9.x. - -* Warn user when luigi is not installed, for relevant view. - -* Fix HUD display when toggling employee status in profile view. - -* Fix checkbox values when re-running a report. - -* Make static files optional, for new tailbone-integration project. - -* Preserve current tab for page reload in profile view. - -* Add cleanup logic for old Beaker session data. - -* Add basic support for editing help info for page, fields. - -* Override document title when upgrading. - -* Filter by person instead of user, for Generated Reports "Created by". - -* Add "direct link" support for master grids. - -* Add support for websockets over HTTP. - -* Fix product image view for python3. - -* Add "global searchbox" for quicker access to main views. - -* Use minified version of vue.js by default, in falafel theme. - - -0.8.272 (2022-12-21) --------------------- - -* Add support for "is row checkable" in grids. - -* Add ``make_status_renderer()`` to MasterView. - -* Expose the ``terms`` field for Vendor CRUD. - - -0.8.271 (2022-12-15) --------------------- - -* Add ``configure_execute_form()`` hook for batch views. - - -0.8.270 (2022-12-10) --------------------- - -* Fix error if no view supplements defined. - - -0.8.269 (2022-12-10) --------------------- - -* Show simple error string, when subprocess batch actions fail. - -* Fix ordering worksheet API for date objects. - -* Add the ViewSupplement concept. - -* Cleanup employees view per new supplements. - -* Add common logic for xref buttons, links when viewing object. - -* Add common logic to determine panel fields for product view. - -* Add xref buttons for Customer, Member tabs in profile view. - -* Suppress error if menu entry has bad route name. - - -0.8.268 (2022-12-07) --------------------- - -* Add support for Beaker >= 1.12.0. - - -0.8.267 (2022-12-06) --------------------- - -* Fix bug when viewing certain receiving batches. - - -0.8.266 (2022-12-06) --------------------- - -* Add simple template hook for "before object helpers". - -* Include email address for current API user info. - -* Add support for editing catalog cost in receiving batch, per new theme. - -* Add receiving workflow as param when making receiving batch. - -* Show invoice cost in receiving batch, if "from scratch". - -* Add support for editing invoice cost in receiving batch, per new theme. - -* Add helptext for "Admin-ish" field when editing Role. - - -0.8.265 (2022-12-01) --------------------- - -* Add way to quickly re-run "any" report. - -* Avoid web config when launching overnight task. - - -0.8.264 (2022-11-28) --------------------- - -* Add prompt dialog when launching overnight task. - -* Fix page title for datasync status. - -* Use newer config strategy for all views. - -* Auto-format phone number when saving for contact records. - - -0.8.263 (2022-11-21) --------------------- - -* Update 'testing' watermark for dev background. - -* Let the Luigi handler take care of removing some DB settings. - - -0.8.262 (2022-11-20) --------------------- - -* Add luigi module/class awareness for overnight tasks. - - -0.8.261 (2022-11-20) --------------------- - -* Allow disabling, or per-day scheduling, of problem reports. - -* Fix how keys are stored for luigi overnight/backfill tasks. - - -0.8.260 (2022-11-18) --------------------- - -* Turn on download results feature for Employees. - - -0.8.259 (2022-11-17) --------------------- - -* Add "between" verb for numeric grid filters. - - -0.8.258 (2022-11-15) --------------------- - -* Let the auth handler manage user merge. - - -0.8.257 (2022-11-03) --------------------- - -* Add template method for rendering row grid component. - -* Use people handler to update address. - -* Fix start_date param for pricing batch upload. - -* Use shared logic for rendering percentage values. - -* Log a warning to troubleshoot luigi restart failure. - -* Show UPC for receiving line item if no product reference. - - -0.8.256 (2022-09-09) --------------------- - -* Add basic per-item discount support for custorders. - -* Make past item lookup optional for custorders. - -* Do not convert date if already a date (for grid filters). - -* Avoid use of ``self.handler`` within batch API views. - - -0.8.255 (2022-09-06) --------------------- - -* Include ``WorkOrder.estimated_total`` for API. - -* Add default normalize logic for API views. - -* Disable "Delete Results" button if no results, for row grid. - -* Move logic for "bulk-delete row objects" into MasterView. - -* Convert value for more date filters; only add condition if valid. - - -0.8.254 (2022-08-30) --------------------- - -* Improve parsing of purchase order quantities. - -* Expose more attrs for new product batch rows. - - -0.8.253 (2022-08-30) --------------------- - -* Convert value for date filter; only add condition if valid. - -* Add 'warning' flash messages to old jquery base template. - -* Add uom fields, configurable template for newproduct batch. - - -0.8.252 (2022-08-25) --------------------- - -* Avoid error when no datasync profiles configured. - -* Add max lengths when editing person name via profile view. - - -0.8.251 (2022-08-24) --------------------- - -* Fix index title for datasync configure page. - -* Add basic support for backfill Luigi tasks. - - -0.8.250 (2022-08-21) --------------------- - -* Add ``render_person_profile()`` method to MasterView. - -* Add way to declare failure for an upgrade. - -* Add websockets progress, "multi-system" support for upgrades. - -* Add global context from handler, for email previews. - -* Allow configuring datasync watcher kwargs. - -* Expose, honor "admin-ish" flag for roles. - - -0.8.249 (2022-08-18) --------------------- - -* Add brief delay before declaring websocket broken. - -* Add basic views for Luigi / overnight tasks. - -* Expose setting for auto-correct when receiving from invoice. - - -0.8.248 (2022-08-17) --------------------- - -* Redirect to custom index URL when user cancels new custorder entry. - -* Add ``get_next_url_after_submit_new_order()`` for customer orders. - -* Add first experiment with websockets, for datasync status page. - -* Allow user feedback to request email reply back. - - -0.8.247 (2022-08-14) --------------------- - -* Avoid double-quotes in field error messages JS code. - -* Add the FormPosterMixin to ProfileInfo component. - -* Fix default help URLs for ordering, receiving. - -* Move handheld batch view module to appropriate location. - -* Refactor usage of ``get_vendor()`` lookup. - -* Consolidate master API view logic. - - -0.8.246 (2022-08-12) --------------------- - -* Couple of API tweaks for work orders. - -* Standardize merge logic when a handler is defined for it. - - -0.8.245 (2022-08-10) --------------------- - -* Add convenience wrapper to make customer field widget, etc.. - -* Some API tweaks to support a byjove app. - -* Tweak flash msg, logging when batch population fails. - -* Log traceback output when batch action subprocess fails. - -* Add initial views for work orders. - -* Fix sequence of events re: grid component creation. - -* Allow download results for Customers grid. - - -0.8.244 (2022-08-08) --------------------- - -* Add separate product grid filters for Category Code, Category Name. - - -0.8.243 (2022-08-08) --------------------- - -* Add button to raise bogus error, for testing email alerts. - -* Make sure "configure" pages use AppHandler to save/delete settings. - -* Expose setting for sendmail failure alerts. - - -0.8.242 (2022-08-07) --------------------- - -* Always show "all" email settings if user has config perm. - - -0.8.241 (2022-08-06) --------------------- - -* Add support for toggling visibility of email profile settings. - - -0.8.240 (2022-08-05) --------------------- - -* Clean up URL routes for row CRUD. - - -0.8.239 (2022-08-04) --------------------- - -* Invalidate config cache when raw setting is deleted. - - -0.8.238 (2022-08-03) --------------------- - -* Improve "touch" logic for employees. - -* Stop using the old ``rattail.db.api.settings`` module. - -* Force cache invalidation when Raw Setting is edited. - - -0.8.237 (2022-07-27) --------------------- - -* Add some more views to potentially include via poser. - -* Misc. improvements for desktop receiving views. - - -0.8.236 (2022-07-25) --------------------- - -* Add setting to expose/hide "active in POS" customer flag. - -* Allow optional row grid title for master view. - -* Add basic/minimal merge support for customers. - -* Assume default vendor for new receiving batch. - -* Add basic edit support for Purchases. - -* Add ``iter(Form)`` logic, to loop through fields. - -* Add "auto-receive all items" support for receiving batch API. - - -0.8.235 (2022-07-22) --------------------- - -* Split out rendering of ``this-page`` component in falafel theme. - -* Allow download of results for common product-related tables. - -* Make caching products optional, when creating vendor catalog batch. - -* Expose the ``complete`` flag for pricing batch. - -* Add ``template_kwargs_clone()`` stub for master view. - -* Misc deform template improvements. - - -0.8.234 (2022-07-18) --------------------- - -* Fix form validation for app settings page w/ buefy theme. - -* Honor default pagesize for all grids, per setting. - -* Add basic "download results" for Subdepartments grid. - -* Add new-style config defaults for BrandView. - - -0.8.233 (2022-06-24) --------------------- - -* Add minimal buefy support for 'percentinput' field widget. - -* Add autocomplete support for subdepartments. - - -0.8.232 (2022-06-14) --------------------- - -* Let default grid page size correspond to first option. - -* Add start date support for "future" pricing batch. - - -0.8.231 (2022-05-15) --------------------- - -* Expose config for identifying supported vendors. - -* Allow restricting to supported vendors only, for Receiving. - - -0.8.230 (2022-05-10) --------------------- - -* Sort roles list when viewing a user. - -* Add grid workarounds when data is list instead of query. - - -0.8.229 (2022-05-03) --------------------- - -* Tweak how family data is displayed. - - -0.8.228 (2022-04-13) --------------------- - -* Fix quotes for field helptext. - -* Flush early when populating batch, to ensure error is shown. - - -0.8.227 (2022-04-04) --------------------- - -* Add touch for report codes. - -* Raise 404 if report not found. - -* Add template kwargs stub for ``view_row()``. - -* Log error when failing to submit new custorder batch. - -* Honor case vs. unit restrictions for new custorder. - -* Tweak where description field is shown for receiving batch. - -* Fix "touch" url for non-standard record types. - - -0.8.226 (2022-03-29) --------------------- - -* Let errors raise when showing poser reports. - - -0.8.225 (2022-03-29) --------------------- - -* Force session flush within try/catch, for batch refresh. - - -0.8.224 (2022-03-25) --------------------- - -* Improve vendor validation for new receiving batch. - -* Use common logic for fetching batch handler. - - -0.8.223 (2022-03-21) --------------------- - -* Show link to txn as field when viewing trainwreck item. - - -0.8.222 (2022-03-17) --------------------- - -* Expose custorder xref markers for trainwreck. - - -0.8.221 (2022-03-16) --------------------- - -* Always show batch params by default when viewing. - -* Show helptext when applicable for "new batch from product query". - -* Make problem report titles searchable in grid. - - -0.8.220 (2022-03-15) --------------------- - -* Log error instead of warning, when batch population fails. - -* Add default help link for Receiving feature. - - -0.8.219 (2022-03-10) --------------------- - -* Cleanup grid filters for vendor catalog batches. - -* Cleanup view config syntax for vendor catalog batch. - -* Add workaround when inserting new fields to form field list. - -* Add ``Form.insert()`` method, to insert field based on index. - -* Default behavior for report chooser should *not* be form/dropdown. - - -0.8.218 (2022-03-08) --------------------- - -* Log warning/traceback when failing to include a configured view. - -* Fix gotcha when defining new provider views. - -* Bump the default Buefy version to 0.8.13. - - -0.8.217 (2022-03-07) --------------------- - -* Add the "provider" concept, let them configure db sessions. - -* Let providers add extra views, options for includes config. - -* Let tailbone providers include static views. - -* Link to email settings profile when viewing email attempt. - - -0.8.216 (2022-03-05) --------------------- - -* Show list of generated reports when viewing Poser Report. - -* Show link back to Poser Report when viewing Generated Report. - -* Always include ``app_title`` in global template rendering context. - -* Update some more view config syntax. - -* Make common web view a bit more common. - -* Improve the Poser Setup page; allow poser dir refresh. - -* Add initial/basic support for configuring "included views". - -* Add ``tailbone.views.essentials`` to include common / "core" views. - -* Add flash message when upgrade execution completes (pass or fail). - - -0.8.215 (2022-03-02) --------------------- - -* Show toast msg instead of alert after sending feedback. - -* Add basic support for Poser reports, list/create. - - -0.8.214 (2022-03-01) --------------------- - -* Params should be readonly when editing batch. - -* Tweak styles for links in object helper panel. - - -0.8.213 (2022-03-01) --------------------- - -* Add simple searchable column support for non-AJAX grids. - -* Fix stdout/stderr fields for upgrade view. - -* Pass query along for download results, so subclass can modify. - -* Avoid making discounts data if missing field, for trainwreck item view. - - -0.8.212 (2022-02-26) --------------------- - -* Add page/way to configure main menus. - - -0.8.211 (2022-02-25) --------------------- - -* Add view template stub for trainwreck transaction. - -* Add auto-filter hyperlinks for batch row status breakdown. - -* Auto-filter hyperlinks for PO vs. invoice breakdown in Receiving. - -* Add grid hyperlinks for trainwreck transaction line items. - -* Use dict instead of custom object to represent menus. - -* Expose "discount type" for Trainwreck line items. - - -0.8.210 (2022-02-20) --------------------- - -* Only show DB picker for permissioned users. - -* Expose some new trainwreck fields; per-item discounts. - -* Show SRP as currency for vendor catalog batch. - - -0.8.209 (2022-02-16) --------------------- - -* Fix progress bar when running problem report. - - -0.8.208 (2022-02-15) --------------------- - -* Allow override of navbar-end element in falafel theme header. - -* Add initial support for editing user preferences. - -* Add FormPosterMixin to WholePage class. - - -0.8.207 (2022-02-13) --------------------- - -* Try out new config defaults function for some views (user, customer). - -* Add highlight for non-active users, customers in grid. - -* Prevent cache for index pages by default, unless configured not to. - -* Cleanup labels for Vendor/Code "preferred" vs. "any" in products grid. - -* Add config for showing ordered vs. shipped amounts when receiving. - -* Tweak how "duration" fields are rendered for grids, forms. - -* New upgrades should be enabled by default. - - -0.8.206 (2022-02-08) --------------------- - -* Add "full lookup" product search modal for new custorder page. - - -0.8.205 (2022-02-05) --------------------- - -* Tweak how product key field is handled for product views. - -* Add some autocomplete workarounds for new vendor catalog batch. - - -0.8.204 (2022-02-04) --------------------- - -* Add ``CustomerGroupAssignment`` to customer version history. - - -0.8.203 (2022-02-01) --------------------- - -* Expose batch params for vendor catalogs. - - -0.8.202 (2022-01-31) --------------------- - -* Make "generate report" the same as "create new generated report". - - -0.8.201 (2022-01-31) --------------------- - -* Show helptext for params when generating new report. - -* Tweak handling of empty params when generating report. - - -0.8.200 (2022-01-31) --------------------- - -* Improve profile link helper for buefy themes. - -* Add project generator support for rattail-integration, tailbone-integration. - - -0.8.199 (2022-01-26) --------------------- - -* Tweak the "auto-receive all" tool for Chrome browser. - - -0.8.198 (2022-01-25) --------------------- - -* Only expose "product" departments within product view dropdowns. - - -0.8.197 (2022-01-19) --------------------- - -* Use buefy input for quickie search. - - -0.8.196 (2022-01-15) --------------------- - -* Use the new label handler. - - -0.8.195 (2022-01-13) --------------------- - -* Strip whitespace for new customer fields, in new custorder page. - - -0.8.194 (2022-01-12) --------------------- - -* Include all static files in manifest. - -* Update usage of ``app.get_email_handler()`` to avoid warnings. - - -0.8.193 (2022-01-10) --------------------- - -* Add buefy support for quick-printing product labels; also speed bump. - -* Add way to set form-wide schema validator. - -* Add progress support when deleting a batch. - -* Expose the Sale, TPR, Current price fields for label batch. - - -0.8.192 (2022-01-08) --------------------- - -* Add configurable template file for vendor catalog batch. - -* Some aesthetic improvements for vendor catalog batch. - -* Several disparate changes needed for vendor catalog improvements. - -* Expose, honor "allow future" setting for vendor catalog batch. - -* Add config for supported vendor catalog parsers. - -* Update some method calls to avoid deprecation warnings. - - -0.8.191 (2022-01-03) --------------------- - -* Fix permission check for input file template links. - -* Remove usage of ``app.get_designated_import_handler()``. - -* Add basic configure page for Trainwreck. - -* Use ``AuthHandler.get_permissions()``. - - -0.8.190 (2021-12-29) --------------------- - -* Show create button on "most" pages for a master view. - -* Expose products setting for type 2 UPC lookup. - -* Add basic "resolve" support for person, product from new custorder. - - -0.8.189 (2021-12-23) --------------------- - -* Add basic "pending product" support for new custorder batch. - -* Improve email bounce view per buefy theme. - - -0.8.188 (2021-12-20) --------------------- - -* Flag discontinued items for main Products grid. - - -0.8.187 (2021-12-20) --------------------- - -* Add common configuration logic for "input file templates". - -* Add some standard CRUD buttons for buefy themes. - - -0.8.186 (2021-12-17) --------------------- - -* Render "pretty" UPC by default, for batch row form fields. - -* Let config decide which versions of vue.js and buefy to use. - - -0.8.185 (2021-12-15) --------------------- - -* Allow for null price when showing price history. - -* Overhaul desktop views for receiving, for efficiency. - -* Add some basic "config" views, to obviate some App Settings. - -* Add "jump to" chooser in App Settings, for various "configure" pages. - -* Fix params field when deleting a report. - -* Add some smarts when making batch execution form schema. - - -0.8.184 (2021-12-09) --------------------- - -* Refactor "receive row" and "declare credit" tools per buefy theme. - -* Allow "auto-receive all items" batch feature in production. - -* Make "view row" prettier for receiving batch, for buefy themes. - -* Add buttons to edit, confirm cost for receiving batch row view. - - -0.8.183 (2021-12-08) --------------------- - -* Add basic views to expose Problem Reports, and run them. - -* Only include ``--runas`` arg if we have a value, for import jobs. - -* Assume default receiving workflow if there is only one. - -* Fix bug when report has no params dict. - - -0.8.182 (2021-12-07) --------------------- - -* Fix form ref bug, for batch execution. - - -0.8.181 (2021-12-07) --------------------- - -* Bugfix. - - -0.8.180 (2021-12-07) --------------------- - -* Add basic import/export handler views, tool to run jobs. - -* Overhaul import handler config etc.: - * add ``MasterView.configurable`` concept, ``/configure.mako`` template - * add new master view for DataSync Threads (needs content) - * tweak view config for DataSync Changes accordingly - * update the Configure DataSync page per ``configurable`` concept - * add new Configure Import/Export page, per ``configurable`` - * add basic views for Raw Permissions - -* Honor "safe for web app" flags for import/export handlers. - -* When viewing report output, show params as proper buefy table. - - -0.8.179 (2021-12-03) --------------------- - -* Expose the Sale Price and TPR Price for product views. - - -0.8.178 (2021-11-29) --------------------- - -* Add page for configuring datasync. - - -0.8.177 (2021-11-28) --------------------- - -* Show current/sale pricing for products in new custorder page. - -* Add simple search filters for past items dialog in new custorder. - - -0.8.176 (2021-11-25) --------------------- - -* Add basic support for receiving from PO with invoice. - -* Don't use multi-select for new report in buefy themes. - - -0.8.175 (2021-11-17) --------------------- - -* Fix bug when product has empty suggested price. - -* Show ordered quantity when viewing costing batch row. - - -0.8.174 (2021-11-14) --------------------- - -* Expose the "sync users" flag for Roles. - - -0.8.173 (2021-11-11) --------------------- - -* Improve error handling when executing a custorder batch. - -* Fix "download results" support for Products. - - -0.8.172 (2021-11-11) --------------------- - -* Add permission for viewing "all" employees. - - -0.8.171 (2021-11-11) --------------------- - -* Add "true margin" to products XLSX export. - -* Add initial ``VersionMasterView`` base class. - -* Add views for ``PendingProduct`` model; also ``DepartmentWidget``. - - -0.8.170 (2021-11-09) --------------------- - -* Fix dynamic content title for "view profile" page. - - -0.8.169 (2021-11-08) --------------------- - -* Use products handler to get image URL. - -* Show some more product attributes in custorder item selection popup. - -* Auto-select Quantity tab when editing item for new custorder. - -* Let user "add past product" when making new custorder. - -* Let handler restrict available invoice parser options. - -* Cleanup grid columns for receiving batches. - -* Fall back to empty string for product regular price. - - -0.8.168 (2021-11-05) --------------------- - -* Make separate method for writing results XLSX file. - -* Add ``render_brand()`` method for MasterView. - -* Add link to download generic template for vendor catalog batch. - - -0.8.167 (2021-11-04) --------------------- - -* Try to prevent caching for any /index (grid) page. - -* Fix product view page when user cannot view version history. - -* Move some custorder logic to handler; allow force-swap of product selection. - -* Honor the "product price may be questionable" flag for new custorder. - -* Show unit price in line items grid for new custorder. - -* Avoid exposing batch params when creating a batch. - - -0.8.166 (2021-11-03) --------------------- - -* Fix the Department filter for Products grid, for jquery themes. - - -0.8.165 (2021-11-02) --------------------- - -* Optionally set the ``sticky-header`` attribute for main buefy grids. - -* Show case qty by default for costing batch rows. - -* Highlight the "did not receive" rows for purchase batch. - -* Improve validation for Person field of User form. - -* Omit "edit" link unless user has perm, for Customer "people" subgrid. - -* Highlight "cannot calculate price" rows for new product batch. - - -0.8.164 (2021-10-20) --------------------- - -* Give custorder batch handler a couple ways to affect adding new items. - -* Refactor to leverage all existing methods of auth handler. - -* Overhaul the autocomplete component, for sake of new custorder. - -* Improve "refresh contact", show new fields in green for custorder. - -* Invoke handler when adding new item to custorder batch. - -* Add basic "price needs confirmation" support for custorder. - -* Clean up the product selection UI for new custorder. - - -0.8.163 (2021-10-14) --------------------- - -* Misc. tweaks for users, roles. - - -0.8.162 (2021-10-14) --------------------- - -* Cleanup form display a bit, for App Settings. - -* Invoke the auth handler to cache user permissions etc. - - -0.8.161 (2021-10-13) --------------------- - -* Add ``debounce()`` wrapper for buefy autocomplete. - -* Leverage the auth handler for main user login. - - -0.8.160 (2021-10-11) --------------------- - -* Stop rounding case/unit cost fields to 2 places for purchase batch. - -* Fix some phone/email bugs for new custorder page. - -* Fix bug when making context for mailing address. - -* Improve display, handling for "add contact info to customer record". - - -0.8.159 (2021-10-10) --------------------- - -* Simplify template context customization for view_profile_buefy. - - -0.8.158 (2021-10-07) --------------------- - -* Add support for "new customer" when creating new custorder. - -* Improve contact name handling for new custorder. - - -0.8.157 (2021-10-06) --------------------- - -* Some tweaks for invoice costing batch views. - -* Add "restrict contact info" features for new custorder batch. - -* Add "contact update request" workflow for new custorder batch. - - -0.8.156 (2021-10-05) --------------------- - -* Show "contact notes" when creating new custorder. - -* Improve phone editing for new custorder. - -* Add button to refresh contact info for new custorder. - -* Overhaul the "Personal" tab of profile view. - -* Refactor the Employee tab of profile view, per better patterns. - - -0.8.155 (2021-10-01) --------------------- - -* Refactor autocomplete view logic to leverage new "autocompleters". - - -0.8.154 (2021-09-30) --------------------- - -* Initial (basic) views for invoice costing batches. - - -0.8.153 (2021-09-28) --------------------- - -* Improve phone/email handling when making new custorder. - -* Avoid "detach person" logic if not supported by view class. - - -0.8.152 (2021-09-27) --------------------- - -* Allow changing status, adding notes for customer order items. - - -0.8.151 (2021-09-27) --------------------- - -* Overhaul new custorder so contact may be either Person or Customer. - -* Add a dropdown of choices to the Department filter for Products grid. - - -0.8.150 (2021-09-26) --------------------- - -* Refactor several "field grids" per Buefy theme. - -* Display the Store field for Customer Orders. - - -0.8.149 (2021-09-25) --------------------- - -* Improve default autocomplete query logic, w/ multiple ILIKE. - -* Add placeholder to customer lookup for new order. - -* Invoke handler for customer autocomplete when making new custorder. - -* Improve "employees" list when viewing a department, for buefy themes. - -* Add products row grid for misc. org table views. - - -0.8.148 (2021-09-22) --------------------- - -* Add way to update Employee ID from profile view. - - -0.8.147 (2021-09-22) --------------------- - -* Add way to override grid action label rendering. - - -0.8.146 (2021-09-21) --------------------- - -* Misc. improvements for customer order views. - - -0.8.145 (2021-09-19) --------------------- - -* Allow setting the "exclusive" sequence of grid filters. - - -0.8.144 (2021-09-16) --------------------- - -* Invoke handler when request is made to merge 2 people. - - -0.8.143 (2021-09-12) --------------------- - -* Add way to customize product autocomplete for new custorder. - - -0.8.142 (2021-09-09) --------------------- - -* Set quantity type when viewing vendor lead times, order intervals. - - -0.8.141 (2021-09-09) --------------------- - -* Add /people API endpoint; allow for "native sort". - -* Allow override of "create" permission in API. - -* Add the ``Grid.remove()`` method, deprecate ``hide_column()`` etc. - -* Improve error handling for purchase batch. - - -0.8.140 (2021-09-01) --------------------- - -* Make it easier to override rendering grid component in master/index. - -* Always show all grid actions...for now. - -* Allow grid columns to be *invisible* (but still present in grid). - -* Improve UI, customization hooks for new custorder batch. - -* Add hover text for vendor ID column of pricing batch row grid. - -* Fix size of roles multi-select when editing user. - -* Allow "touch" action for employees. - - -0.8.139 (2021-08-26) --------------------- - -* Tweak how email preview is sent, and attempt "to" is displayed. - -* Move "merge 2 people" logic into People Handler. - -* Expose "merge request tracking" feature for People data. - -* Allow customization of row 'view' action url. - -* Require explicit opt-in for "clicking grid row checks box" feature. - -* Add ``before_render_index()`` customization hook for MasterView. - - -0.8.138 (2021-08-04) --------------------- - -* Let feedback forms define their own email key. - - -0.8.137 (2021-07-15) --------------------- - -* Set UPC renderer for delproduct batch row. - -* Expose ``pack_size`` for delproduct batch. - - -0.8.136 (2021-06-18) --------------------- - -* Include "is/not null" filters for GPC fields. - - -0.8.135 (2021-06-15) --------------------- - -* Add 'v' prefix for release package diff links. - - -0.8.134 (2021-06-15) --------------------- - -* Allow config to set favicon and header image. - - -0.8.133 (2021-06-11) --------------------- - -* Allow customization of rendering version diff values. - -* Allow direct creation of new label batches. - -* Allow generating project which integrates w/ LOC SMS. - - -0.8.132 (2021-05-03) --------------------- - -* Highlight "has inventory" rows for delete item batch. - -* Add csrftoken to TailboneForm js. - -* Freeze pyramid version at 1.x. - - -0.8.131 (2021-04-12) --------------------- - -* Show current price date range as hover text, for products grid. - -* Make it easier to extend "common" API views. - -* Accept any decimal numbers for API inventory batch counts. - - -0.8.130 (2021-03-30) --------------------- - -* Catch and show error, if one happens when making batch from product query. - -* Expose the new ``Store.archived`` flag. - - -0.8.129 (2021-03-11) --------------------- - -* Add support for ``inactivity_months`` field for delete product batch. - -* Expose new fields for Trainwreck. - -* Fix enum display for customer order status. - - -0.8.128 (2021-03-05) --------------------- - -* Allow per-user stylesheet for Buefy themes. - -* Expose ``date_created`` for delete product batches. - - -0.8.127 (2021-03-02) --------------------- - -* Use end time as default filter, sort for Trainwreck. - -* Avoid encoding values as string, for integer grid filters. - -* Fix message recipients for Reply / Reply-All, with Buefy themes. - -* Handle row click as if checkbox was clicked, for checkable grid. - -* Highlight delete product batch rows with "pending customer orders" status. - -* Add hover text for subdepartment name, in pricing batch row grid. - - -0.8.126 (2021-02-18) --------------------- - -* Allow customization of main Buefy CSS styles, for falafel theme. - -* Add special "contains any of" verb for string-based grid filters. - -* Add special "equal to any of" verb for UPC-related grid filters. - -* Tweaks per "delete products" batch. - -* Misc. tweaks for vendor catalog batch. - -* Add support for "default" trainwreck model. - - -0.8.125 (2021-02-10) --------------------- - -* Fix some permission bugs when showing batch tools etc. - -* Render batch execution description as markdown. - -* Cleanup default display for vendor catalog batches. - -* Make errors more obvious, when running batch commands as subprocess. - -* Add styles for field labels in profile view. - - -0.8.124 (2021-02-04) --------------------- - -* Fix bug when editing a Person. - - -0.8.123 (2021-02-04) --------------------- - -* Fix config defaults for PurchaseView. - -* Add stub methods for ``MasterView.template_kwargs_view()`` etc. - -* Update references to vendor catalog batches etc. - -* Fix display of handheld batch links, when viewing label batch. - -* Prevent updates to batch rows, if batch is immutable. - - -0.8.122 (2021-02-01) --------------------- - -* Normalize naming of all traditional master views. - -* Undo recent ``base.css`` changes for ``

`` tags. - -* Misc. improvements for ordering batches, purchases. - -* Purge things for legacy (jquery) mobile, and unused template themes. - -* Make handler responsible for possible receiving modes. - -* Split "new receiving batch" process into 2 steps: choose, create. - -* Add initial "scanning" feature for Ordering Batches. - -* Add support for "nested" menu items. - -* Add icon for Help button. - - -0.8.121 (2021-01-28) --------------------- - -* Tweak how vendor link is rendered for readonly field. - -* Use "People Handler" to update names, when editing person or user. - - -0.8.120 (2021-01-27) --------------------- - -* Initial support for adding items to, executing customer order batch. - -* Add changelog link for Theo, in upgrade package diff. - -* Hide "collect from wild" button for UOMs unless user has permission. - - -0.8.119 (2021-01-25) --------------------- - -* Don't create new person for new user, if one was selected. - -* Allow newer zope.sqlalchemy package. - -* Add variant transaction logic per zope.sqlalchemy 1.1 changes. - -* Add CSS styles for 'codehilite' a la Pygments. - -* Add feature to generate new features... - -* Add views for "delete product" batch. - -* Set ``self.model`` when constructing new View. - -* Add some generic render methods to MasterView. - -* Add custom ``base.css`` for falafel theme. - -* Add master view for Units of Measure mapping table. - -* Add woocommerce package links for sake of upgrade diff view. - -* Add basic web API app, for simple use cases. - - -0.8.118 (2021-01-10) --------------------- - -* Show node title in header for Login, About pages. - -* Allow changing protected user password when acting as root. - -* Allow specifying the size of a file, for ``readable_size()`` method. - -* Try to show existing filename, for upload widget. - -* Add basic support for "download" and "rawbytes" API views. - - -0.8.117 (2020-12-16) --------------------- - -* Add common "form poster" logic, to make CSRF token/header names configurable. - -* Refactor the feedback form to use common form poster logic. - - -0.8.116 (2020-12-15) --------------------- - -* Add basic views for IFPS PLU Codes. - -* Add very basic support for merging 2 People. - -* Tweak spacing for header logo + title, in falafel theme. - - -0.8.115 (2020-12-04) --------------------- - -* Add the "Employee Status" filter to People grid. - -* Add "is empty" and related verbs, for "string" type grid filters. - -* Assume composite PK when fetching instance for master view. - - -0.8.114 (2020-12-01) --------------------- - -* Misc. tweaks to vendor catalog views. - -* Tweak how an "enum" grid filter is initialized. - -* Add "generic" Employee tab feature, for profile view. - - -0.8.113 (2020-10-13) --------------------- - -* Tweak how global DB session is created. - - -0.8.112 (2020-09-29) --------------------- - -* Add support for "list" type of app settings (w/ textarea). - -* Add feature to "download rows for results" in master index view. - -* Fix "refresh results" for batches, in Buefy theme. - - -0.8.111 (2020-09-25) --------------------- - -* Allow alternate engine to act as 'default' when multiple are available. - -* Fix grid bug when paginator is not involved. - - -0.8.110 (2020-09-24) --------------------- - -* Add ``user_is_protected()`` method to core View class. - -* Change how we protect certain person, employee records. - -* Add global help URL to login template. - -* Fix bug when fetching partial versions data grid. - - -0.8.109 (2020-09-22) --------------------- - -* Add 'warning' class for 'delete' action in b-table grid. - -* Add "worksheet file" pattern for editing batches. - -* Avoid unhelpful error when perm check happens for "re-created" DB user. - -* Prompt user if they try to send email preview w/ no address. - -* Don't expose "timezone" for input when generating 'fabric' project. - -* Add some more field hints when generating 'fabric' project. - -* Show node title in header, for home page. - -* Remove unwanted columns for default Products grid. - - -0.8.108 (2020-09-16) --------------------- - -* Allow custom props for TailboneForm component. - -* Remove some custom field labels for Vendor. - -* Add support for generating new 'fabric' project. - - -0.8.107 (2020-09-14) --------------------- - -* Stop including 'complete' filter by default for purchasing batches. - -* Overhaul project changelog links for upgrade pkg diff table. - -* Add support/views for generating new custom projects, via handler. - - -0.8.106 (2020-09-02) --------------------- - -* Add progress for generating "results as CSV/XLSX" file to download. - -* Use utf8 encoding when downloading results as CSV. - -* Add new/flexible "download results" feature. - -* Fix spacing between components in "grid tools" section. - -* Add support for batch execution options in Buefy themes. - -* Improve auto-handling of "local" timestamps. - -* Expose ``Product.average_weight`` field. - - -0.8.105 (2020-08-21) --------------------- - -* Tweaks for export views, to make more generic. - -* Add config for "global" help URL. - -* Remove ``

`` tag around "no results" for minimal b-table. - -* Allow for unknown/missing "changed by" user for product price history. - -* Add buefy theme support for ordering worksheet. - -* Don't require department by default, for new purchasing batch. - - -0.8.104 (2020-08-17) --------------------- - -* Make "download row results" a bit more generic. - -* Add pagination to price, cost history grids for product view. - - -0.8.103 (2020-08-13) --------------------- - -* Tweak config methods for customer master view. - - -0.8.102 (2020-08-10) --------------------- - -* Improve rendering of ``true_margin`` column for pricing batch row grid. - - -0.8.101 (2020-08-09) --------------------- - -* Fix missing scrollbar when version diff table is too wide for screen. - -* Add basic web views for "new customer order" batches. - -* Tweak the buefy autocomplete component a bit. - -* Add basic/unfinished "new customer order" page/feature. - -* Add ``protected_usernames()`` config function. - -* Add ``model`` to global template context, plus ``h.maxlen()``. - -* Coalesce on ``User.active`` when merging. - -* Expose user reference(s) for employees. - - -0.8.100 (2020-07-30) --------------------- - -* Add more customization hooks for making grid actions in master view. - - -0.8.99 (2020-07-29) -------------------- - -* Add ``self.cloning`` convenience indicator for master view. - -* Use handler ``do_delete()`` method when deleting a batch. - - -0.8.98 (2020-07-26) -------------------- - -* Tweak field label for ``Product.item_id``. - -* Make field list explicit for Department views. - -* Make field list explicit for Store views. - -* Don't allow "execute results" for any batches by default. - -* Fix pagination sync issue with buefy grid tables. - -* Fix permissions wiget bug when creating new role. - -* Tweak "coalesce" logic for merging field data. - - -0.8.97 (2020-06-24) -------------------- - -* Add dropdown, autohide magic when editing Role permissions. - -* Add ability to download roles / permissions matrix as Excel file. - -* Improve support for composite key in master view. - -* Use byte string filters for row grid too. - -* Convert mako directories to list, if it's a string. - - -0.8.96 (2020-06-17) -------------------- - -* Don't allow edit/delete of rows, if master view says so. - - -0.8.95 (2020-05-27) -------------------- - -* Cap version for 'cornice' dependency. - -* Let each grid component have a custom name, if needed. - - -0.8.94 (2020-05-20) -------------------- - -* Expose "shelved" field for pricing batches. - -* Sort available reports by name, if handler doesn't specify. - - -0.8.93 (2020-05-15) -------------------- - -* Parse pip requirements file ourselves, instead of using their internals. - -* Don't auto-include "Guest" role when finding roles w/ permission X. - - -0.8.92 (2020-04-07) -------------------- - -* Allow the home page to include quickie search. - - -0.8.91 (2020-04-06) -------------------- - -* Add "danger" style for "delete" grid row action. - -* Misc. API improvements for sake of mobile receiving. - -* Use proper cornice service registration, for API batch execute etc. - -* Add common permission for sending user feedback. - -* Fix the "change password" form per Buefy theme. - -* Expose the ``Role.notes`` field for view/edit. - -* Add "local only" column to Users grid. - -* Fix row status filter for Import/Export batches. - -* Add "generic" ``render_id_str()`` method to MasterView. - -* Stop raising an error if view doesn't define row grid columns. - -* Add helper function, ``get_csrf_token()``. - -* Add support for "choice" widget, for report params. - -* Allow bulk-delete, merge for Brands table. - -* Move inventory batch view to its proper location. - -* Allow bulk-delete for Inventory Batches. - -* Move "most" inventory batch logic out of view, to underlying handler. - -* Add initial API views for inventory batches. - -* Add basic dashboard page for TempMon. - -* Let config totally disable the old/legacy jQuery mobile app. - -* Defer fetching price, cost history when viewing product details. - - -0.8.90 (2020-03-18) -------------------- - -* Add basic "ordering worksheet" API. - -* Tweak GPC grid filter, to better handle spaces in user input. - -* Only show tables for "public" schema. - -* Remove old/unwanted Vue.js index experiment, for Users table. - -* Misc. changes to User, Role permissions and management thereof. - -* Don't let user delete roles to which they belong, without permission. - -* Prevent deletion of department which still has products. - -* Add sort/filter for Department Name, in Subdepartments grid. - -* Allow "touch" for Department, Subdepartment. - -* Expose ``Customer.number`` field. - -* Add support for "bulk-delete" of Person table. - -* Allow customization for Customers tab of Profile view. - -* Expose default email address, phone number when editing a Person. - -* Add/improve various display of Member data. - - -0.8.89 (2020-03-11) -------------------- - -* Refactor "view profile" page per latest Buefy theme conventions. - -* Move logic for Order Form worksheet into purchase batch handler. - -* Make sure all contact info is "touched" when touching person record. - - -0.8.88 (2020-03-05) -------------------- - -* Fix batch row status breakdown for Buefy themes. - -* Add support for refreshing multiple batches (results) at once. - -* Remove "api." prefix for default route names, in API master views. - -* Allow "touch" for vendor records. - - -0.8.87 (2020-03-02) -------------------- - -* Add new "master" API view class; refactor products and batches to use it. - -* Refactor all API views thus far, to use new v2 master. - -* Use Cornice when registering all "service" API views. - - -0.8.86 (2020-03-01) -------------------- - -* Add toggle complete, more normalized row fields for odering batch API. - -* Return employee_uuid along with user info, from API. - -* Add support for executing ordering batches via API. - -* Fix how we fetch employee history, for profile view. - -* Cleanup main version history views for Buefy theme. - -* Fix product price, cost history dialogs, for Buefy theme. - -* Fix some basic product editing features. - - -0.8.85 (2020-02-26) -------------------- - -* Overhaul the /ordering batch API somewhat; update docs. - -* Tweak ``save_edit_row_form()`` of purchase batch view, to leverage handler. - -* Tweak ``worksheet_update()`` of ordering batch view, to leverage handler. - -* Fix "edit row" logic for ordering batch. - -* Raise 404 not found instead of error, when user is not employee. - -* Send batch params as part of normalized API. - - -0.8.84 (2020-02-21) -------------------- - -* Add API view for changing current user password. - -* Return new user permissions when logging in via API. - - -0.8.83 (2020-02-12) -------------------- - -* Use new ``Email.obtain_sample_data()`` method when generating preview. - -* Add some custom display logic for "current price" in pricing batch. - -* Fix email preview for TXT templates on python3. - -* Allow override of "email key" for user feedback, sent via API. - -* Add way to prevent user login via API, per custom logic. - -* Add common ``get_user_info()`` method for all API views. - -* Return package names as list, from "about" page from API. - - -0.8.82 (2020-02-03) -------------------- - -* Fix vendor ID/name for Excel download of pricing batch rows. - -* Add red highlight for SRP breach, for generic product batch. - -* Make sure falafel theme is somewhat available by default. - - -0.8.81 (2020-01-28) -------------------- - -* Include regular price changes, for current price history dialog. - -* Allow populate of new pricing batch from products w/ "SRP breach". - -* Tweak how we import pip internal things, for upgrade view. - -* Sort report options by name, when choosing which to generate. - -* Add warning for "price breaches SRP" rows in pricing batch. - - -0.8.80 (2020-01-20) -------------------- - -* Hide the SRP history link for new buefy themes. - -* Add regular price history dialog for product view. - -* Add support for Row Status Breakdown, for Import/Export batches. - -* Cleanup "diff" table for importer batch row view, per Buefy theme. - -* Highlight SRP in red, if reg price is greater. - -* Expose batch ID, sequence for datasync change queue. - -* Add "current price history" dialog for product view. - -* Add "cost history" dialog for product view. - - -0.8.79 (2020-01-06) -------------------- - -* Move "delete results" logic for master grid. - - -0.8.78 (2020-01-02) -------------------- - -* Add ``Grid.set_filters_sequence()`` convenience method. - -* Add dialog for viewing product SRP history. - - -0.8.77 (2019-12-04) -------------------- - -* Use currency formatting for costs in vendor catalog batch. - - -0.8.76 (2019-12-02) -------------------- - -* Allow update of row unit cost directly from receiving batch view. - -* Show vendor item code in receiving batch row grid. - -* Expose catalog cost, allow updating, for receiving batch rows. - -* Add API view for marking "receiving complete" for receiving batch. - -* Allow override of user authentication logic for API. - -* Add API views for admin user to become / stop being "root". - - -0.8.75 (2019-11-19) -------------------- - -* Filter by receiving mode, for receiving batch API. - - -0.8.74 (2019-11-15) -------------------- - -* Add support for label batch "quick entry" API. - -* Add support for "toggle complete" for batch API. - -* Add some API views for receiving, and vendor autocomplete. - -* Move "quick entry" logic for purchase batch, into rattail handler. - -* Provide background color when first checking API session. - - -0.8.73 (2019-11-08) -------------------- - -* Assume "local only" flag should be ON by default, for new objects. - -* Bump default Buefy version to 0.8.2. - -* Always store CSRF token for each page in Vue.js theme. - -* Refactor "make batch from products query" per Vue.js theme. - -* Add Vue.js support for "enable / disable selected" grid feature. - -* Add Vue.js support for "delete selected" grid feature. - -* Improve checkbox click handling support for grids. - -* Improve/fix some views for Messages per Vue.js theme. - -* Add some padding above/below form fields (for Vue.js). - -* Use "warning" status for pricing batch rows, where product not found. - -* Refactor "send new message" form, esp. recipients field, per Vue.js. - -* Allow rendering of "raw" datetime as ISO date. - -* Add very basic API views for label batches. - -* Fallback to referrer if form has no cancel button URL. - -* Fix merge feature for master index grid. - - -0.8.72 (2019-10-25) -------------------- - -* Allow bulk delete of New Product batch rows. - -* Don't bug out if can't update roles for user. - - -0.8.71 (2019-10-23) -------------------- - -* Improve default behavior for clone operation. - -* Add config flag to "force unit item" for inventory batch. - -* Fix JS bug for graph view of tempmon probe readings. - - -0.8.70 (2019-10-17) -------------------- - -* Don't bug out if stores, departments fields aren't present for Employee. - - -0.8.69 (2019-10-15) -------------------- - -* Fix buefy grid pager bug. - -* Fix permissions for add/edit/delete notes from people profile view. - - -0.8.68 (2019-10-14) -------------------- - -* Use ``self.has_perm()`` within MasterView. - -* Only show action URL if present, for Buefy grid rows. - -* Show active flag for users mini-grid on Role view page. - - -0.8.67 (2019-10-12) -------------------- - -* Fix URL for user, for feedback email. - -* Add "is false or null" verb for boolean grid filters. - -* Move label batch views to ``tailbone.views.batch.labels``. - -* Allow bulk-delete for some common batches. - -* Move vendor catalog batch views to ``tailbone.views.batch.vendorcatalog``. - -* Expose the "is preferred vendor" flag for vendor catalog batches. - -* Move vendor invoice batch views to ``tailbone.views.batch.vendorinvoice``. - -* Expose unit cost diff for vendor invoice batch rows. - -* Honor configured db key sequence; let config hide some db keys from UI. - - -0.8.66 (2019-10-08) -------------------- - -* Fix label bug for grid filter with value choices dropdown. - - -0.8.65 (2019-10-07) -------------------- - -* Add support for "local only" Person, User, plus related security. - - -0.8.64 (2019-10-04) -------------------- - -* Add ``forbidden()`` convenience method to core View class. - - -0.8.63 (2019-10-02) -------------------- - -* Fix "progress" behavior for upgrade page. - - -0.8.62 (2019-09-25) -------------------- - -* Add core ``View.make_progress()`` method. - - -0.8.61 (2019-09-24) -------------------- - -* Use ``simple_error()`` from rattail, for showing some error messages. - -* Honor kwargs used for ``MasterView.get_index_url()``. - -* Fix progress page so it effectively fetches progress data synchronously. - -* Show "image not found" placeholder image for products which have none. - - -0.8.60 (2019-09-09) -------------------- - -* Show product image from database, if it exists. - -* Let config turn off display of "POD" image from products. - - -0.8.59 (2019-09-09) -------------------- - -* Let a grid have custom ajax data url. - -* Set default max height, width for app logo. - -* Hopefully fix "single store" behavior when make a new ordering batch. - -* Add basic support for create and update actions in API views. - -* Tweak how we detect JSON request body instead of POST params. - -* Add basic support for "between" verb, for date range grid filter. - -* Add basic API view for user feedback. - -* Add basic API view for "about" page. - -* Include ``short_name`` in field list returned by /session API. - -* Return current user permissions when session is checked via API. - -* Tweak return value for /customers API. - -* Cleanup styles for login form. - -* Add /products API endpoint, enable basic filter support for API views. - -* Add basic API endpoints for /ordering-batch. - -* Don't show Delete Row button for executed batch, on jquery mobile site. - -* Include tax1 thru tax3 flags in form fields for product view page. - -* Prevent text wrap for pricing panel fields on product view page. - -* Fix rendering of "handheld batches" field for inventory batch view. - -* Fix various templates for generating reports, per Buefy. - -* Fix 'about' page template for Buefy themes. - - -0.8.58 (2019-08-21) -------------------- - -* Provide today's date as context for profile view. - -* Tweak login page logo style for jQuery (non-Buefy) themes. - - -0.8.57 (2019-08-05) -------------------- - -* Remove unused "login tips" for demo. - -* Fix form handling for user feedback. - -* Fix "last sold" field rendering for product view. - - -0.8.56 (2019-08-04) -------------------- - -* Fix home and login pages for Buefy theme. - - -0.8.55 (2019-08-04) -------------------- - -* Allow "touch" for Person records. - -* Refactor Buefy templates to use WholePage and ThisPage components. - -* Highlight former Employee records as red/warning. - - -0.8.54 (2019-07-31) -------------------- - -* Freeze Buefy version at pre-0.8.0. - - -0.8.53 (2019-07-30) -------------------- - -* Add proper support for composite primary key, in MasterView. - - -0.8.52 (2019-07-25) -------------------- - -* Add 'disabled' prop for Buefy datepicker. - -* Add perm for editing employee history from profile view. - -* Add "multi-engine" support for Trainwreck transaction views. - -* Cleanup 'phone' filter/sort logic for Employees grid. - - -0.8.51 (2019-07-13) -------------------- - -* Add basic "DB picker" support, for views which allow multiple engines. - -* Include employee history data in context for "view profile". - -* Add custom permissions for People "profile" view. - -* Use latest version of Buefy by default, for falafel theme. - -* Send URL for viewing employee, along to profile page template. - - -0.8.50 (2019-07-09) -------------------- - -* Add way to hide "view profile" helper for customer view. - -* Add ``render_customer()`` method for MasterView. - -* When creating an export, set creator to current user. - -* Add basic "downloadable" support for ExportMasterView. - -* Remove unwanted "export has file" logic for ExportMasterView. - -* Refactor feedback dialog for Buefy themes. - -* Add support for general "view click handler" for ```` element. - - -0.8.49 (2019-07-01) -------------------- - -* Fix product view template per Buefy refactoring. - - -0.8.48 (2019-07-01) -------------------- - -* Clear checked rows when refreshing async grid data. - - -0.8.47 (2019-07-01) -------------------- - -* Allow "touch" for customer records. - -* Add ``NumericInputWidget`` for use with Buefy themes. - -* Expose a way to embed "raw" data values within Buefy grid data. - -* Add 'duration_hours' type for grid column display. - -* Make sure grid action links preserve white-space. - - -0.8.46 (2019-06-25) -------------------- - -* Only expose "Make User" button when viewing a person. - -* Fix PO total calculation bug for mobile ordering. - -* Fix "edit row" icon for batch row grids, for Buefy themes. - -* Refactor all Buefy form submit buttons, per Chrome behavior. - - -0.8.45 (2019-06-18) -------------------- - -* Fix inheritance issue with "view row" master template. - - -0.8.44 (2019-06-18) -------------------- - -* Add generic ``/page.mako`` template. - -* Add Buefy support for "execute results" from core batch grid view. - -* Pull the grid tools to the right, for Buefy. - -* Fix click behavior for all/diffs package links in upgrade view. - -* Refactor form/page component structure for Buefy/Vue.js. - - -0.8.43 (2019-06-16) -------------------- - -* Refactor tempmon probe view template, per Buefy. - -* Refactor tempmon probe graph view per Buefy. - -* Use once-button for tempmon client restart. - -* Fix package diff table for upgrade view template, per Buefy. - -* Assign client IP address to session, for sake of data versioning. - -* Use locale formatting for some numbers in the Buefy grid. - -* Buefy support for "mark batch as (in)complete". - - -0.8.42 (2019-06-14) -------------------- - -* Fix some response headers per python 3. - -* Make person, created by fields readonly when editing Person Note. - - -0.8.41 (2019-06-13) -------------------- - -* Add ``json_response()`` convenience method for all views. - -* Add ```` element template for simple grids with "static" data. - -* Improve props handling for ```` component. - -* Fall back to parsing request body as JSON for form data. - -* Basic support for maintaining PersonNote data from profile view. - -* Fix permissions styles for view/edit of User, Role. - -* Turn on bulk-delete feature for Raw Settings view. - -* Add a generic "user" field renderer to master view. - -* Fix "current value" for ```` element in e.g. edit form views. - -* Use ```` in more places, where appropriate. - -* Update calculated PO totals for purchasing batch, when editing row. - -* Add support for Buefy autocomplete. - -* More Buefy tweaks, for file upload, and "edit batch" generally. - -* Tweak structure of "view product" page to support Buefy, context menu. - -* Add support for "simple confirm" of object deletion. - -* Add some vendor fields for product Excel download. - - -0.8.40 (2019-06-03) -------------------- - -* Add ``verbose`` flag for ``util.raw_datetime()`` rendering. - -* Add basic master view for PersonNote data model. - -* Make email preview buttons use primary color. - -* Add basic Buefy support for batch refresh, execute buttons. - -* Add basic/generic Buefy support to the Form class. - -* Add custom ``tailbone-datepicker`` component for Buefy. - -* Let view template define how to render "row grid tools". - -* Move logic used to determine if current request should use Buefy. - -* Allow inherited theme to set location of Vue.js, Buefy etc. - -* Add "full justify" for grid filter pseudo-column elements. - -* Expose per-page size picker for Buefy grids. - -* Add basic Buefy support for default SelectWidget template. - -* Add Buefy support for enum grid filters. - -* Add ```` component for Buefy templates. - -* Add basic Buefy support for "Make User" button when viewing Person. - -* Make Buefy grids use proper Vue.js component structure. - -* Assume forms support Buefy if theme does; fix basic CRUD views. - -* Fix Buefy "row grids" when viewing parent; add basic file upload support. - -* Refactor "edit printer settings" view for Label Profile. - -* Add Buefy panels support for "view product" page. - -* Allow bulk row delete for generic products batch. - -* also "lots more changes" for sake of Buefy support... - - -0.8.39 (2019-05-09) -------------------- - -* Expose params and type key for report output. - -* Clean up falafel theme, move some parts to root template path. - -* Allow choosing report from simple list, when generating new. - -* Force unicode string behavior for left/right arrow thingies. - -* Must still define "jquery theme" for falafel theme, for now. - -* Add support for "quickie" search in falafel theme. - -* Fix sorting info bug when Buefy grid doesn't support it. - -* Make "view profile" buttons use "primary" color. - -* Add ``simple_field()`` def for base falafel template. - -* Align pseudo-columns for grid filters; let app settings define widths. - -* Tweak how we disable grid filter options. - -* Add basic Buefy form support when generating reports. - -* Add basic/generic email validator logic. - - -0.8.38 (2019-05-07) -------------------- - -* Add basic support for "quickie" search. - -* Add basic Buefy support for row grids. - -* Add basic Buefy support for merging 2 objects. - - -0.8.37 (2019-05-05) -------------------- - -* Add basic Buefy support for full "profile" view for Person. - - -0.8.36 (2019-05-03) -------------------- - -* Add basic support for "touching" a data record object. - - -0.8.35 (2019-04-30) -------------------- - -* Add filter for Vendor ID in Pricing Batch row grid. - -* Pass batch execution kwargs when doing that via subprocess. - - -0.8.34 (2019-04-25) -------------------- - -* Don't assume grid model class declares its title. - - -0.8.33 (2019-04-25) -------------------- - -* Add "most of" Buefy support for grid filters. - -* Add Buefy support for email preview buttons. - -* Improve logic used to determine if current theme supports Buefy. - -* Add basic Buefy support for App Settings page. - -* Add views for "new product" batches. - -* Fix auto-disable action for new message form. - -* Declare row fields for vendor catalog batches. - -* Add "created by" and "executed by" grid filters for all batch views. - -* Expose new code fields for pricing batch. - -* Add basic Buefy support for "find user/role with permission X". - -* Improve default people "profile" view somewhat. - -* Add support for generic "product" batch type. - -* Fix some issues with progress "socket" workaround for batches. - -* Allow config to specify grid "page size" options. - -* Add ``render_person()`` convenience method for MasterView. - - -0.8.32 (2019-04-12) -------------------- - -* Can finally assume "simple" menus by default. - -* Add custom grid filter for phone number fields. - -* Add ``raw_datetime()`` function to ``tailbone.helpers`` module. - -* Add "profile" view, for viewing *all* details of a given person at once. - -* Add "view profile" object helper for all person-related views. - -* Hopefully fix style bug when new filter is added to grid. - - -0.8.31 (2019-04-02) -------------------- - -* Require invoice parser selection for new truck dump child from invoice. - -* Make sure user sees "receive row" page on mobile, after scanning UPC. - -* Use shipped instead of ordered, for receiving authority. - -* Add ``move_before()`` convenience method for ``GridFilterSet``. - - -0.8.30 (2019-03-29) -------------------- - -* Add smarts for some more projects in the upgraded packages links. - -* Add basic "Buefy" support for grids (master index view). - -* Remove 'number' column for Customers grid by default. - -* Add feature for generating new report of arbitrary type and params. - -* Fix rendering bug when ``price.multiple`` is null. - -* Fix HTML escaping bug when rendering products with pack price. - -* Don't allow deletion of some receiving data rows on mobile. - -* Add validation when "declaring credit" for receiving batch row. - -* Add proper hamburger menu for falafel theme. - -* Add icon for Feedback button, in falafel theme. - - -0.8.29 (2019-03-21) -------------------- - -* Allow width of object helper panel to grow. - - -0.8.28 (2019-03-14) -------------------- - -* Tweak how batch handler is invoked to remove row. - -* Add mobile alert when receiving product for 2nd time. - -* Honor enum sort order where possible, for grid filter values. - -* Add basic "receive row" desktop view for receiving batches. - -* Add "declare credit" UI for receiving batch rows. - - -0.8.27 (2019-03-11) -------------------- - -* Fix some unicode literals for base template. - - -0.8.26 (2019-03-11) -------------------- - -* Expose "true cost" and "true margin" columns for products grid. - -* Use configured background color for 'bobcat' theme. - -* Add view, edit links to vue.js users index. - -* Fix navbar, footer background to match custom body background (bobcat theme). - -* Fix layout issues for bobcat theme, so footer sticks to bottom. - -* Fix login page styles for bobcat theme. - -* Refactor template ``content_title()`` and prev/next buttons feature. - -* Add basic 'dodo' theme. - -* Allow apps to set background color per request. - -* Add 'falafel' theme, based on bobcat. - -* Begin to customize grid filters, for 'falafel' theme. - -* Fix PO unit cost calculation for ordering row, batch. - - -0.8.25 (2019-03-08) -------------------- - -* Show grid link even when value is "false-ish". - -* Only objectify address data if present. - -* Improve display of purchase credit data. - -* Expose new "calculated" invoice totals for receiving batch, rows. - - -0.8.24 (2019-03-06) -------------------- - -* Add "plain" date widget. - -* Invoke handler when marking batch as (in)complete. - -* Add new "receive row" view for mobile receiving; invokes handler. - -* Remove 'truck_dump' field from mobile receiving batch view. - -* Add "truck dump status" fields to receiving batch views. - -* Add ability to sort by Credits? column for receiving batch rows. - -* Add mobile support for basic "feedback" dialog. - -* Tweak the "incomplete" row filter for mobile receiving batch. - - -0.8.23 (2019-02-22) -------------------- - -* Add basic support for "mobile edit" of records. - -* Add basic support for editing address for a "contact" record. - -* Add ``unique_id()`` validator method to Customer view. - -* Declare "is contact" for the Customers view. - -* Allow vendor field to be dropdown, for mobile ordering/receiving. - -* Treat empty string as null, for app settings field values. - - -0.8.22 (2019-02-14) -------------------- - -* Improve validator for "percent" input widget. - -* Refactor email settings/preview views to use email handler. - - -0.8.21 (2019-02-12) -------------------- - -* Remove usage of ``colander.timeparse()`` function. - - -0.8.20 (2019-02-08) -------------------- - -* Introduce support for "children first" truck dump receiving. - - -0.8.19 (2019-02-06) -------------------- - -* Add support for downloading batch rows as XLSX file. - - -0.8.18 (2019-02-05) -------------------- - -* Add support for "delete set" feature for main object index view. - -* Use app node title setting for base template. - -* Improve user form handling, to prevent unwanted Person creation. - -* Add support for background color app setting. - -* Add generic support for "enable/disable selection" of grid records. - - -0.8.17 (2019-01-31) -------------------- - -* Improve rendering of ``enabled`` field for tempmon clients, probes. - - -0.8.16 (2019-01-28) -------------------- - -* Update tempmon UI now that ``enabled`` flags are really datetime in DB. - - -0.8.15 (2019-01-24) -------------------- - -* Fix response header value, per python3. - - -0.8.14 (2019-01-23) -------------------- - -* Use empty string for "missing" department name, for ordering worksheet. - - -0.8.13 (2019-01-22) -------------------- - -* Include ``robots.txt`` in the manifest. - - -0.8.12 (2019-01-21) -------------------- - -* Log details of one-off label printing error, when they occur. - -* Fix Excel download of ordering batch, per python3. - - -0.8.11 (2019-01-17) -------------------- - -* Convert all datetime values to localtime, for "download rows as CSV". - - -0.8.10 (2019-01-11) -------------------- - -* Fix products grid query when filter/sort has multiple ProductCost joins. - - -0.8.9 (2019-01-10) ------------------- - -* Tweak batch view template "object helpers" for easier customization. - -* Let batch view customize logic for marking batch as (in)complete. - -* Make command configurable, for restarting tempmon-client. - - -0.8.8 (2019-01-08) ------------------- - -* Add custom widget for "percent" field. - - -0.8.7 (2019-01-07) ------------------- - -* Fix styles for master view_row template. - -* Turn off messaging-related menus by default. - - -0.8.6 (2019-01-02) ------------------- - -* Expose ``vendor_id`` column in pricing batch row grid. - -* Only allow POST method for executing "results" for batch grid. - - -0.8.5 (2019-01-01) ------------------- - -* Add basic master view for Members table. - - -0.8.4 (2018-12-19) ------------------- - -* Add ``object_helpers()`` def to master/view template. - -* Add ``oneoff_import()`` helper method to MasterView class. - -* Fix some styles, per flexbox layout changes. - -* Add ability to make new pricing batch from input data file. - -* Clean up some inventory batch UI logic; prefer units by default. - -* Add 'unit_cost' to Excel download for Products grid. - -* Expose subdepartment for pricing batch rows. - -* Add 'percent' as field type for Form; fix rendering of 'percent' for Grid. - -* Expose label profile selection when editing label batch. - -* Make sure custom field labels are shown for batch execution dialog. - - -0.8.3 (2018-12-14) ------------------- - -* Fix some layout styles for master edit template. - - -0.8.2 (2018-12-13) ------------------- - -* Refactor product view template to use flexbox styles. - - -0.8.1 (2018-12-10) ------------------- - -* Expose new "sync me" flag for LabelProfile settings. - - -0.8.0 (2018-12-02) ------------------- - -This version begins the "serious" efforts in pursuit of REST API, Vue.js, Bulma -and related technologies. - -* Use sqlalchemy-filters package for REST API collection_get. - -* Refactor API collection_get to work with vue-tables-2. - -* Remove some relationship fields when creating new Person. - -* Fix bug in receiving template when truck dump not enabled. - -* Tweak default "model title" logic for master view. - -* Add better support for "make import batch from file" pattern. - -* Fix download filename when it contains spaces. - -* Add "min % diff" option for pricing batch from products query. - -* Allow override of products query when making batch from it. - -* Use empty string instead of null as fallback value, for pricing rows CSV. - -* Add very basic Vue.js grid/index experiment for Users table. - -* Add patterns for joining tables in API list methods. - -* Add template "theme" feature, albeit global. - -* Clean up how we configure DB sessions on app startup. - -* Add description, notes to default form_fields for batch views. - -* Add basic 'excite-bike' theme. - -* Use Bulma CSS and some components for 'bobcat' theme. - -* Add basic support for "simple menus". - -* Refactor default theme re: "context menu" and "object helper" styles. - -* Use 4 decimal places when calculating hours for worked shift excel download. - -* Expose ``old_price_margin`` field for pricing batch rows. - - -0.7.50 (2018-11-19) -------------------- - -* Add simple price fields for product XLSX results download. - -* Add "200 per page" option for UI table grids. - -* Add department, subdepartment "name" columns for products XLSX download. - -* Allow override of template for custom create views. - -* Expose new ``Customer.wholesale`` flag. - -* Add vendor id, name to row CSV download for pricing batch. - -* Expose ``suggested_price``, ``price_diff_percent``, ``margin_diff`` for - pricing batch row. - - -0.7.49 (2018-11-08) -------------------- - -* Detect non-numeric entry when locating row for purchase batch. - -* Remove unwanted style for "email setting description" field. - -* Add ``Grid.hide_columns()`` convenience method. - -* Make sure status field is readonly when creating new batch. - -* Display "suggested price" when viewing product details. - - -0.7.48 (2018-11-07) -------------------- - -* Add initial ``tailbone.api`` subpackage, with some basic API views. Note - that this API is meant to be ran as a separate app so we can better leverage - Cornice features. - -* Add client IP address to user feedback email. - - -0.7.47 (2018-10-25) -------------------- - -* Try to configure the 'pyramid_retry' package, if available. - -* Add more time range options for viewing tempmon probe readings as graph. - -* Add button for restarting filemon. - - -0.7.46 (2018-10-24) -------------------- - -* Allow individual App Settings to not be required; allow null. - -* Add ``MasterView.render_product()``; fix edit for pricing batch row. - -* Add ability to "transform" TD parent row from pack to unit item. - - -0.7.45 (2018-10-19) -------------------- - -* Add very basic support for viewing tempmon probe readings as graph. - - -0.7.44 (2018-10-19) -------------------- - -* Don't include LargeBinary properties in default colander schema. - - -0.7.43 (2018-10-19) -------------------- - -* Add new timeout fields for tempmon probe. - -* Customize template for viewing probe details. - -* Add support for new Tempmon Appliance table, etc. - -* Add basic image upload support for tempmon appliances. - -* Add thumbnail images to Appliances grid. - -* Hopefully, let the Grid class generate a default list of columns. - -* Don't include grid filters for LargeBinary columns. - - -0.7.42 (2018-10-18) -------------------- - -* Fix a dialog button for Chrome. - - -0.7.41 (2018-10-17) -------------------- - -* Cache user permissions upon "new request" event. - -* Add basic Excel download support for Products table. - - -0.7.40 (2018-10-13) -------------------- - -* Add "hours as decimal" hover text for some HH:MM timesheet values. - - -0.7.39 (2018-10-09) -------------------- - -* Fix bug when non-numeric entry given for mobile inventory "quick row". - -* Show tempmon readings when viewing client or probe. - -* Auto-disable button when sending email preview. - -* Add some helptext for various tempmon fields. - -* Allow override of jquery for base templates, desktop and mobile. - -* Improve "length" (hours) column for Worked Shifts grid. - -* Add basic Excel download support for raw worked shifts. - - -0.7.38 (2018-10-03) -------------------- - -* Add support for "archived" flag in Tempmon Client views. - -* Expose notes field for tempmon client and probe views. - -* Expose new ``disk_type`` field for tempmon client views. - -* Tweak how receiving rows are looked up when adding to the batch. - - -0.7.37 (2018-09-27) -------------------- - -* Restrict (temporarily I hope) webhelpers2_grid to 0.1. - - -0.7.36 (2018-09-26) -------------------- - -* Leverage alternate code also, for mobile product quick lookup. - -* Misc. UI improvements for truck dump receiving on desktop. - -* Add speedbump by default when deleting any "row" record. - -* Expose ``item_entry`` field for receiving batch row. - -* Capture user input for mobile receiving, and move some lookup logic. - - -0.7.35 (2018-09-20) -------------------- - -* Fix batch row status breakdown, for rows with no status. - - -0.7.34 (2018-09-20) -------------------- - -* Add unique check for "name" when creating new Role. - -* Fix bug when editing truck dump child batch row quantities. - -* Add setting to show/hide product image for mobile purchasing/receiving. - -* Show red background for mobile receiving if product not found. - -* Add quick-receive 1EA, 3EA, 6EA for mobile receiving. - -* Fix how we check config for mobile "quick receive" feature. - -* Do quick lookup by vendor item code, alt code for mobile receiving. - -* Fix price fields, add pref. vendor/cost fields for mobile product view. - -* Add simple row status breakdown when viewing batch. - -* Only show mobile "quick receive" buttons if product is identifiable. - - -0.7.33 (2018-09-10) -------------------- - -* Fix default (status) filter for Employees grid. - - -0.7.32 (2018-08-24) -------------------- - -* Add "quick receive all" support for mobile receiving. - -* Refactor sqlerror tween to add support for pyramid_retry. - -* Honor view logic when displaying Delete Row button for mobile receiving. - - -0.7.31 (2018-08-14) -------------------- - -* Make sure we refresh batch status when adding a new row. - -* Hide 'ordered' columns for truck dump parent row grid. - -* Add support for editing "claim" quantities for truck dump child row. - -* Use invoice total, PO total as fallback, for mobile receiving list. - -* Show links to claiming rows for truck dump parent row. - -* Add "quick lookup" for mobile Products page. - - -0.7.30 (2018-07-31) -------------------- - -* Don't configure versioning when making the app. - - -0.7.29 (2018-07-30) -------------------- - -* Various tweaks for arbitrary model view with "rows". - - -0.7.28 (2018-07-26) -------------------- - -* Let mobile form declare if/how to auto-focus a field. - -* Assign purchase to new receiving batch via uuid instead of object ref. - -* Fix permission group label for Ordering Batches. - -* Redirect to "view parent" after deleting a row. - - -0.7.27 (2018-07-19) -------------------- - -* Use upload time as default filter/sort for Trainwreck transactions. - -* Add initial support for mobile "quick row" feature, for ordering. - -* Add product grid filters for "on hand", "on order". - -* Don't make customer ID readonly when editing. - -* Fix Person.customers readonly field for python 3. - -* Traverse master class hierarchy to collect all defined labels. - -* Add 'person' column for customers grid. - -* Fix how we check file size when reading stdout for upgrade. - -* Add runtime ``mobile`` flag for ``MasterView``. - -* Improve basic mobile views for customers, people. - -* Refactor mobile receiving to use "quick row" feature. - -* Improve support for "receive from scratch" workflow, esp. for mobile. - -* Add (admin-friendly!) view to manage some App Settings. - -* Add (restore?) basic support for mobile receiving from PO. - -* Expose status etc. when editing upgrade; rename Email Settings. - - -0.7.26 (2018-07-11) -------------------- - -* Force user to count "units" and not "packs" for inventory batch. - -* Fix bug for inventory batch when product not found. - -* Sort mobile receiving rows by last modified instead of sequence. - -* Tweak default page title for master view. - -* Show "truck dump" info for applicable receiving batch page title. - -* Highlight purchasing batch rows with "case quantity differs" status. - -* Improve how cases/units, uom are handled for mobile receiving. - -* Add "?" for daily time sheet total if partial shift present. - -* Fix cancel button for progress page. - - -0.7.25 (2018-07-09) -------------------- - -* Fix enum values for customer email preference grid filter. - -* Tweak field ordering for customer form. - -* Remove deprecated "edbob" settings. - -* Improve basic support for unit/pack info when viewing product details. - - -0.7.24 (2018-07-03) -------------------- - -* Tweak how some "pack item" fields are displayed when viewing product. - - -0.7.23 (2018-07-03) -------------------- - -* Don't read upgrade progress file if size hasn't changed. - -* Fix batch file download link URL. - -* Fix batch action kwargs, so 'action' can be a handler kwarg. - - -0.7.22 (2018-06-29) -------------------- - -* Consider any integer greater than PG allows, to be invalid grid filter value. - - -0.7.21 (2018-06-28) -------------------- - -* Fix bug when populating new batch. - -* Allow zero quantity for inventory batch rows. - -* Allow editing of unit cost for inventory batch row. - -* Add overflow validation for cases/units in inventory batch desktop form. - -* Add ``credit_total`` column for purchase credits grid. - -* Don't aggregate product for mobile truck dump receiving. - -* Be smarter about when we sort receiving batch by most recent (for mobile). - -* Accept invoice number when adding truck dump child from invoice file. - -* Add highlight for "cost not found" rows in purchasing batch. - -* Fix email preview logic per python 3. - -* Improve basic support for adding new product. - -* Show department column for receiving batch rows. - -* Fix how "unknown product" row is added to receiving batch. - - -0.7.20 (2018-06-27) -------------------- - -* Fix input validation for integer grid filter. - - -0.7.19 (2018-06-14) -------------------- - -* Change how date fields are handled within grid filters. - -* Add workaround for using pip 10.0 "internal" API in upgrades view. - - -0.7.18 (2018-06-14) -------------------- - -* Auto-size columns for Excel results download. - -* Add Excel results download for categories, report codes. - -* Use "known" label if possible when making new grid filters. - -* Expose new ``exempt_from_gross_sales`` flags. - - -0.7.17 (2018-06-09) -------------------- - -* Allow products view to set some labels in costs grid. - -* Let config override ``sys.prefix`` when launching batch commands in subprocess. - - -0.7.16 (2018-06-07) -------------------- - -* Add versioning workaround support for batch actions. - - -0.7.15 (2018-06-05) -------------------- - -* Add integer-specific grid filter. - -* Set filter value renderer when setting enum for grid field. - - -0.7.14 (2018-06-04) -------------------- - -* Show department instead of subdept by default, for products grid. - -* Add support for variance inventory batches, aggregation by product. - -* Set default column renderers for grid based on data types. - -* Expose 'hidden' flag for inventory adjustment reasons. - -* Expose new ``Vendor.abbreviation`` field. - - -0.7.13 (2018-05-31) -------------------- - -* Show 'variance' field when viewing inventory batch row. - - -0.7.12 (2018-05-30) -------------------- - -* Make sure count mode is preserved when making new inventory batch. - -* Add initial support for "variance" inventory batch mode. - -* Fix handling of (missing) password when user is edited. - - -0.7.11 (2018-05-25) -------------------- - -* Add ``Form.__contains__()`` method. - -* Improve default behavior for receiving a purchase batch. - -* Fix label profile type field when editing label batch row. - -* Allow lookup of inventory item by alternate code. - -* Fix rowcount bug when first row added via ordering worksheet. - -* Add "most of" support for truck dump receiving. - -* Add docs for ``MasterView.help_url`` and ``get_help_url()``. - -* Add "Receive 1 CS" button for better efficiency in mobile receiving. - -* Add category name filter for products grid. - -* Increase allowed width for form labels. - -* Add ``allow_zero_all`` flag for inventory batch master. - -* Add buttons to toggle batch 'complete' flag when viewing batch. - -* Hide "create new row" link for batches which are marked complete. - -* Add way to prevent "case" entries for inventory adjustment batch. - -* Add ``MasterView.use_byte_string_filters`` flag for encoding search values. - - -0.7.10 (2018-05-02) -------------------- - -* Add sort/filter for department name, for Categories grid. - - -0.7.9 (2018-04-12) ------------------- - -* Add future mode for vendor catalog batch. - - -0.7.8 (2018-04-09) ------------------- - -* Add awareness for ``Email.dynamic_to`` flag in config UI. - -* Add new vendor catalog row status, render product with hyperlink. - - -0.7.7 (2018-03-23) ------------------- - -* Use 'today' as fallback order date for ordering worksheet. - -* Treat unknown UPC as "product not found" for inventory batch. - -* Refactor inventory batch desktop lookup, to allow for Type 2 UPC logic. - -* Fix default selection bug for store/department time sheet filters. - - -0.7.6 (2018-03-15) ------------------- - -* Fix text area behavior for email recipient fields. - -* Fix autodisable button bug for forms marked as such. - - -0.7.5 (2018-03-12) ------------------- - -* Add desktop support for creating inventory batches. - -* Expose vendor item code for purchase credits. - -* Fix default create logic for vendors, products. - -* Add changelog link for rattail-tempmon in upgrade diff. - -* Add ``disable_submit_button()`` global JS function. - -* Add basic support for making new product on-the-fly during mobile ordering. - - -0.7.4 (2018-02-27) ------------------- - -* Use all "normal" product form fields, for mobile view. - -* Refactor ordering worksheet to use shared logic. - -* Add download path for batch master views. - -* Add basic mobile support for executing batches (with options). - -* Add ``NumberInputWidget`` for ````. - -* Add ``Form.mobile`` flag and set link button styles accordingly. - -* Always show flash-error-style message when form has errors. - -* Use ``Form.submit_label`` if present, or fall back to ``save_label``. - -* Expose ``ship_method`` and ``notes_to_vendor`` for purchase, ordering batch. - -* Bind batch to its execution options schema, when applicable. - -* Don't set order date for new ordering batch when created via mobile. - -* Don't allow row deletion if batch is marked complete. - -* Add logic for editing default phone/email in base master view. - -* Fix bug in users view when person field not present. - - -0.7.3 (2018-02-15) ------------------- - -* More tweaks for python 3. - - -0.7.2 (2018-02-14) ------------------- - -* Refactor all remaining forms to use colander/deform. - -* Coalesce 'forms2' => 'forms' package. - -* Remove dependencies: FormAlchemy, FormEncode, pyramid_simpleform, pyramid_debugtoolbar - -* Misc. cleanup for Python 3. - -* Add generic 'login_as_home' setting. - -* Add tailbone version to base stylesheet URLs. - - -0.7.1 (2018-02-10) ------------------- - -* Make it easier to hide buttons for a form. - -* Let forms choose *not* to auto-disable their cancel button. - -* Add 'newstyle' behavior for ``Form.validate()``. - -* Add some basic ORM object field types for new forms. - -* Make sure each grid has unique set of actions. - -* Add 'gridcore' jQuery plugin, for core behavior. - -* Allow passing arbitrary attrs when rendering grid. - -* Refactor mobile receiving to use colander/deform. - -* Refactor mobile inventory to use colander/deform. - -* Refactor user login, change password to use colander/deform. - -* Fix some bugs with importer batch views. - - -0.7.0 (2018-02-07) ------------------- - -* Coalesce all master views back to single base class. - -* Add ``append()`` and ``replace()`` methods for core Grid class. - -* Show year dropdown by default for jQuery UI date pickers. - -* Don't process file for new batch unless field is present. - -* Add setting for "force home" mobile behavior. - -* Add 'plain' and 'jquery' templates for deform select widget. - -* Add "hidden" concept for form fields. - -* Add ``Form.show_cancel`` flag, for hiding that button. - -* Let each form define its "save" button text. - -* Add master view for ``EmailAttempt``. - -* Avoid "auto disable" button logic for new message form. - -* Add better UPC validation for mobile receiving. - - -0.6.69 (2018-02-01) -------------------- - -* Add proper enum for inventory batch "count mode" filter. - -* Fix bugs when making inventory batch on mobile. - - -0.6.68 (2018-01-31) -------------------- - -* Cap zope.sqlalchemy dependency at pre-1.0. - - -0.6.67 (2018-01-30) -------------------- - -* Fix permission bug when adding row in mobile receiving. - -* Fix mobile logout behavior. - -* Always redirect to mobile home page, if "other" page is refreshed. - - -0.6.66 (2018-01-29) -------------------- - -* Add support for detaching Person from Customer. - -* Allow disabling auto-dismiss of flash messages on mobile. - -* Add ``FieldList`` wrapper for grid columns list. - -* Show "unit cost" column by default, for products grid. - -* Improve case/unit quantity validation for order worksheet. - -* Show new 'confirmed' field for brands table. - -* Add support for extra column(s) in timesheet view table. - -* Add generic "download results as XLSX" feature. - -* Add vendor links in cost grid when viewing product. - -* Show "buttons" when viewing an object, with forms2 (i.e. Execute Batch). - -* Refactor "most" remaining batch views etc. to use master3. - - -0.6.65 (2018-01-24) -------------------- - -* Fix some master3 edit issues for products view. - -* Let custom inventory batch view override logic for mobile UPC scanning. - -* Show new ``cashback`` field for Trainwreck transaction. - -* Add 'delete-instance' class to delete link when viewing a record. - - -0.6.64 (2018-01-22) -------------------- - -* Warn if user "scans" UPC with more than 14 digits, for mobile inventory. - -* Add option for preventing new inventory batch rows for unknown products. - -* Add ``creates_multiple`` flag for master view. - -* Add basic support for per-page help URL. - - -0.6.63 (2018-01-16) -------------------- - -* Fix bug when locating association proxy column. - -* Fix client field when creating / editing tempmon probe. - -* Allow editing of inventory batch count mode and reason code. - - -0.6.62 (2018-01-11) -------------------- - -* Fix dialog button click event when executing price batch (for Chrome). - -* Fix some mobile view URLs. - -* Show case quantity for inventory batch rows. - -* Let custom schema node start out with empty children. - -* Allow passing None to ``Form.set_renderer()``. - - -0.6.61 (2018-01-11) -------------------- - -* Provide some default readonly form field renderers. - -* Fix row query bug when deleting batch row. - - -0.6.60 (2018-01-10) -------------------- - -* Refactor several straggler views to use master3. - -* Add first attempt at master3 for batch views. - - -0.6.59 (2018-01-08) -------------------- - -* Fix bug when printing product label. - - -0.6.58 (2018-01-08) -------------------- - -* Tweak diff styles when viewing upgrade. - - -0.6.57 (2018-01-07) -------------------- - -* Fix some styles for execution options dialog. - -* Show 'static_prices' flag for label batches. - -* Add field name as wrapper class name. - -* Change how select menus are enhanced for batch exec options. - -* Add view for InventoryAdjustmentReason model. - -* Stop setting execution details when multiple batches executed. - -* Add empty default when displaying values in grid. - -* Let grids be paginated even when they have no model class. - -* Exclude JS for refreshing batch unless it's relevant. - -* Tweak conditions for CSV row download link. - -* Add basic support for row grid view links. - -* Refactor away the ``row_route_prefix`` concept. - -* Add ``row_title`` to template context for ``view_row``. - -* Tweak ``diffs.css`` and refactor 'view_version' template to use it. - -* Add basic UI support for "importer batch" feature. - - -0.6.56 (2018-01-05) -------------------- - -* Fix bug when making batch from product query. - - -0.6.55 (2018-01-04) -------------------- - -* Add "price required" flag to product view. - -* Add a bit more flexibility to jquery time input values. - -* Show row count field when viewing vendor catalog batch. - -* Tweak product filter for report code name. - -* Refactor forms logic when making batch from product query. - - -0.6.54 (2017-12-20) -------------------- - -* Provide sane width for filter value dropdowns. - - -0.6.53 (2017-12-19) -------------------- - -* Accept ``value_enum`` kwarg when creating grid filter. - - -0.6.52 (2017-12-08) -------------------- - -* Add transaction "System ID" field for Trainwreck. - -* Add ``Grid.set_sort_defaults()`` method. - -* Change template prefix for vendor catalog batches. - -* Add basic "helptext" support for forms2. - -* Add cleared/selected callbacks for jquery autocomplete in forms2. - -* Add ``Grid.remove_filter()`` method. - -* Add custom schema type for jQuery time picker data. - -* Refactor lots of views to use master3. - - -0.6.51 (2017-12-03) -------------------- - -* Refactor customers view to use master3. - -* Add custom ``FieldList`` class for forms2 field list. - -* Auto-scroll window as needed to ensure drop-down choices are visible. - -* Hide status when creating new purchasing batch. - -* Add "manually priced" awareness to pricing batch UI. - -* Add batch description to page body title. - -* Fix batch row count when bulk-deleting rows. - -* Allow bulk delete of label batch rows. - -* Expose description and notes for label batches. - -* Let batch views allow or deny "execute results" option. - -* Allow "execute results" for inventory batches. - -* Fix permission bug for mobile inventory batch. - -* Expose default address for customers view. - - -0.6.50 (2017-11-21) -------------------- - -* Set widget when defining enum for a form2 field. - -* Add date/time-picker, autocomplete support for forms2 (deform). - -* Add colander magic for association proxy fields. - - -0.6.49 (2017-11-19) -------------------- - -* Improve auto-disable logic for some form buttons. - -* Fix (hack) for editing some department flags. - - -0.6.48 (2017-11-11) -------------------- - -* Accept ``None`` as valid arg for ``Grid.set_filter()``. - - -0.6.47 (2017-11-08) -------------------- - -* Fix manifest to include ``*.pt`` deform templates - - -0.6.46 (2017-11-08) -------------------- - -* Add ``json`` to global template context - - -0.6.45 (2017-11-01) -------------------- - -* Add product and personnel flags for Department - -* Add sorters, filters for Product regular, current price - -* Add "text" type for new form fields - -* Add description, notes for pricing batches - - -0.6.44 (2017-10-29) -------------------- - -* Fix join bug for Upgrades table when sorting by executor - - -0.6.43 (2017-10-29) -------------------- - -* Add "make user" button when viewing person w/ no user account - - -0.6.42 (2017-10-28) -------------------- - -* Add cashier info, upload time for Trainwreck transaction views - - -0.6.41 (2017-10-25) -------------------- - -* Add support for validator and required flag, for new forms - -* Use master3 view for datasync changes - - -0.6.40 (2017-10-24) -------------------- - -* Add grid filter which treats empty string as NULL - -* Fix value auto-selection for enum grid filters - -* Add ``item_id`` to trainwreck views - -* Expose ``Person.users`` relationship (readonly) - - -0.6.39 (2017-10-20) -------------------- - -* Fix bug with products view config - - -0.6.38 (2017-10-19) -------------------- - -* Add "local" datetime renderer for new grids, forms - -* Make CSRF protection optional (but on by default) - -* Convert user feedback mechanism to use modal dialog - -* Add 'active' column to Users table view - -* Add "download row results as CSV" feature to master view - -* Add support for setting default field values on new forms - -* Add 'currency' field type for new forms - -* Allow passing ``None`` to ``Grid.set_joiner()`` - - -0.6.37 (2017-09-28) -------------------- - -* Fix data type/size issue with CSV download - -* Don't set batch input file on creation, if no file exists - -* Add "auto-enhance" select field template for deform - -* Add ability to override schema node for custom deform fields - -* Fix deform widget resource inclusion for master/create template - -* Pass form along to ``before_create_flush()`` in master3 - -* Add "populatable" for master views (populating new objects with progress) - -* Add 'duration' type for new form fields - - -0.6.36 (2017-09-15) -------------------- - -* Fix user field rendering when no person associated - -* Add generic support for downloading list results as CSV - -* Tweak title for master view row template - - -0.6.35 (2017-08-30) -------------------- - -* Fix some bugs for rendering upgrade package diffs - - -0.6.34 (2017-08-18) -------------------- - -* Fix mobile inventory template - -* Add extra perms for creating inventory batch w/ different modes - -* Allow batch execution to require options on a per-batch basis - -* Convert more views to master3: - departments, subdepartments, categories, brands, bouncer, customer groups - -* Override deform template for checkbox field; fix label behavior - -* Show all grid actions by default, if there are 3 or less - -* Use shared logic for executing upgrade - - -0.6.33 (2017-08-16) -------------------- - -* Add ``LocalDateTimeFieldRenderer`` for formalchemy - -* Fix auto-disable button on form submit, per Chrome issues - - -0.6.32 (2017-08-15) -------------------- - -* Add generic changelog link for rattail/tailbone packages - -* Let handler delete files when deleting upgrade - -* Add mechanism for user to bulk-change status for purchase credits - -* Tweak how pyramid config is created during app startup, for tests - -* Fix permission used for mobile receiving item lookup - - -0.6.31 (2017-08-13) -------------------- - -* Add show all vs. show diffs for upgrade packages - -* Add initial support for changelog links for upgrade package diffs - -* Add prev/next buttons when viewing upgrade details - -* Merge 'better' theme into base templates - - -0.6.30 (2017-08-12) -------------------- - -* Make product field renderer allow override of link text rendering - - -0.6.29 (2017-08-11) -------------------- - -* Various tweaks to inventory batch logic (zero-all mode etc.) - -* Fix join bug for users grid - -* Flush session once every 1000 records when bulk-deleting - - -0.6.28 (2017-08-09) -------------------- - -* Fix clone config bug for label batches - - -0.6.27 (2017-08-09) -------------------- - -* Improve inventory support, plus "hiding" person data while still using it - -* Fix encoding bug when reading stdout during upgrade - - -0.6.26 (2017-08-09) -------------------- - -* Add awareness of upgrade exit code, success/fail - -* Add support for cloning an upgrade record - -* Add running display of stdout.log when executing upgrade - - -0.6.25 (2017-08-08) -------------------- - -* Specify ``expire_on_commit`` for tailbone db session - - -0.6.24 (2017-08-08) -------------------- - -* Fix bug which caused new empty worked shift when editing time sheet - - -0.6.23 (2017-08-08) -------------------- - -* Fix bulk-delete for batch rows, allow it for pricing batches - -* Fix permission check for deleting single batch rows - -* Fix numeric filter to allow 3 decimal places by default - - -0.6.22 (2017-08-08) -------------------- - -* Remove unwanted import (which broke versioning) - -* Add some links to employees grid - - -0.6.21 (2017-08-08) -------------------- - -* Refactor progress bars somewhat to allow file-based sessions - -* Fix recipients renderer for email settings grid - -* Improve status tracking for upgrades; add package version diff - - -0.6.20 (2017-08-07) -------------------- - -* Record become/stop root user events - -* Make datasync changes bulk-deletable - -* Add basic support for performing / tracking app upgrades - - -0.6.19 (2017-08-04) -------------------- - -* Record basic user login/logout events - -* Expose UserEvent table in UI - - -0.6.18 (2017-08-04) -------------------- - -* Add progress support for bulk deletion - -* Make tempmon readings bulk-deletable - - -0.6.17 (2017-08-04) -------------------- - -* Various view tweaks - - -0.6.16 (2017-08-04) -------------------- - -* Add auto-links for most grids - -* Fix row highlighting for sources panel on product view - - -0.6.15 (2017-08-03) -------------------- - -* Allow product field renderer to suppress hyperlink - -* Add 'data-uuid' attr for mobile grid list items, if applicable - -* Initial (partial) support for mobile ordering - -* Some tweaks to ordering batch views - -* Fix bug when request.user becomes unattached from session (?) - -* Add view for consuming new batch ID - -* Add some links to various grid columns - -* Fix bug in master view_row - - -0.6.14 (2017-08-01) -------------------- - -* Make login template use same logo as home page - -* Fix how we detect grid settings presence in user session - -* Improve verbiage for exception view - -* Fix styles for message compose template - -* Various improvements to batch worksheets, index links etc. - -* Fix batch links when viewing purchase object - -* Add "on order" count to products grid, tweak product notes panel - - -0.6.13 (2017-07-26) -------------------- - -* Allow master view to decide whether each grid checkbox is checked - - -0.6.12 (2017-07-26) -------------------- - -* Add basic support for product inventory and status - -* Stop allowing pre-0.7 SQLAlchemy - - -0.6.11 (2017-07-18) -------------------- - -* Tweak some basic styles for forms/grids - -* Add new v3 master with v2 forms, with colander/deform - - -0.6.10 (2017-07-18) -------------------- - -* Fix grid bug if "current page" becomes invalid - - -0.6.9 (2017-07-15) ------------------- - -* Expose version history for all supported tables - - -0.6.8 (2017-07-14) ------------------- - -* Provide default renderers for SA mapped tables, where possible - -* Add flexible grid class for v3 grids for width=half etc. - -* Final grid refactor; we now have just 'grids' :) - -* Refactor (coalesce) all batch-related templates - - -0.6.7 (2017-07-14) ------------------- - -* Fix master view ``get_effective_data()`` for v3 grids - - -0.6.6 (2017-07-14) ------------------- - -* Fix bug for printing one-off product labels - - -0.6.5 (2017-07-14) ------------------- - -* Fix template/styles for v3 grid views, add purchasing batch status - - -0.6.4 (2017-07-14) ------------------- - -* Add new "v3" grids, refactor all views to use them - - -0.6.3 (2017-07-13) ------------------- - -* Sort mobile receiving batches by ID desc - -* Add initial/basic support for "simple" mobile grid filter w/ radio buttons - -* Add filter support for mobile row grid; plus mark receiving as complete - -* Disable unused Clear button for mobile receiving - -* Add logic for mobile receiving if product not in batch and/or system - -* Prevent mobile receiving actions for batch which is complete or executed - -* Fix bug with mobile receiving UPC lookup; require stronger "create row" perm - -* Stop using popup for expiration date, for mobile receiving - -* Add global key handler for mobile receiving, for scanner wedge input - -* Make all batches support mobile by default - -* Add basic support for viewing inventory batches on mobile - -* Refactor keypad widget for mobile receiving - -* Add unit cost for inventory batches - - -0.6.2 (2017-07-10) ------------------- - -* Fix CS/EA bug for mobile receiving - - -0.6.1 (2017-07-07) ------------------- - -* Switch license to GPL v3 (no longer Affero) - -* Fix broken product image tag, per webhelpers2 - - -0.6.0 (2017-07-06) ------------------- - -Main reason for bumping version is the (re-)addition of data versioning support -using SQLAlchemy-Continuum. This feature has been a long time coming and while -not yet fully implemented, we have a significant head start. - -* Add custom default grid row size for Trainwreck items - -* Make hyperlink optional for employee field renderer - -* Tweak how customer/person relationships are displayed - -* Add initial support for expiration date for mobile receiving - -* Make Person.employee field readonly - -* Rearrange some imports to ensure ``rattail.db.model`` comes last - -* Add basic versioning history support for master view - -* Remove old-style continuum version views - -* Remove all "old-style" (aka. version 1) grids - -* Remove all old-style views: grids, CRUD, versions etc. - -* Refactor to use webhelpers2 etc. instead of older 'webhelpers' - - -0.5.104 (2017-06-22) --------------------- - -* Add basic views for Trainwreck transactions - -* Add ``AlchemyLocalDateTimeFilter`` - -* Add row count as available column to batch header grids - -* Try to keep batch status updated; display it for handheld batches - -* Tweak display of inventory/label batches to reflect multiple handheld batches - -* Add way to execute multiple handheld batches (search results) at once - -* Fix batch row count when deleting a row - -* Make case/unit quantities prettier within Inventory batch rows grid - -* Sort (alphabetically) device type list field when making new handheld batch - -* Allow bulk row deletion for vendor catalog batches - - -0.5.103 (2017-06-05) --------------------- - -* Always add key as class to grid column headers; allow literal label - - -0.5.102 (2017-05-30) --------------------- - -* Remove all views etc. for old-style batches - -* Fix bug when updating Order Form data, if row.po_total is None - - -0.5.101 (2017-05-25) --------------------- - -* Fix subtle bug when identifying purchase batch row on order form update - -* Remove references to deprecated batch handler methods - -* Add validation for unique name when creating new Setting - -* Simplify page title display for mobile base template - -* Refactor "purchasing" batch views, split off "ordering" - -* Add initial (full-ish) support for mobile receiving views - -* Add support for bulk-delete of Pricing Batches - -* Pad session timeout warning by 10 seconds, to account for drift - -* Add highlight to active row within Order Form view - -* Make 'notes' field use textarea renderer by default, for all batches - -* Add basic ability to download Ordering Batch as Excel spreadsheet - - -0.5.100 (2017-05-18) --------------------- - -* Allow batch view to override execution failure message - -* Tweak some customer view/field rendering, to allow more customization - -* Remove customer view template (use master default) - -* Add basic support for Trainwreck database connectivity - -* Remove unused 'fake_error' view - -* Add basic 'robots.txt' support to CommonView - -* Cap our pyramid_tm version until we can upgrade to pyramid 1.9 - -* Add daily hour totals when viewing or editing single employee time sheet - -* Let config cause time sheet hours to display as HH.HH for some users - -* Expose full-time flag and start date for employee view - -* Add convenience ``dialog_button()`` JS function - - -0.5.99 (2017-05-05) -------------------- - -* Add allowance for Escape key, in numeric.js - -* Let a batch disallow bulk-deletion of its rows - -* Add basic support for deletion speedbump for row data - -* Remove lower version for Pyramid dependency, but restrict to pre-1.9 - - -0.5.98 (2017-04-18) -------------------- - -* Auto-save time sheet day editor on Enter press if time field is focused - -* Add simple flag to prevent multiple submits for Order Form AJAX - - -0.5.97 (2017-04-04) -------------------- - -* Fix signature for ``MasterView.get_index_url()`` - - -0.5.96 (2017-04-04) -------------------- - -* Tweak logic for registering exception view, to avoid test breakage - -* Add basic paging grid/index support for mobile - -* Tweak field label styles for mobile - -* Allow config to define home page image URL - - -0.5.95 (2017-03-29) -------------------- - -* Tweak organization panel for product view template - -* Add logic to core View class, to force logout if user becomes inactive - -* Detect "backwards" shift when time sheet is edited, alert user - -* Add default view for unhandled exceptions, configure only for production - -* Add basic table listing view, with rough estimate row counts - -* Add 'status' column to vendor cost table in product view - -* Various template standardization tweaks - - -0.5.94 (2017-03-25) -------------------- - -* Add ``CostFieldRenderer`` and tweak product view template - -* Bump margin between grid and header table, i.e. buttons - -* Broad refactor to improve customization of purchase order form etc. - -* Fix route sequence for people autocomplete - -* Fix bugs when checking for 'chuck' in demo mode - -* Add unit item and pack size fields to product view - - -0.5.93 (2017-03-22) -------------------- - -* Add 'is_any' verb to integer grid filters - -* Add more variations of project name when creating via scaffold - -* Various tweaks to the customer and person views/forms - -* Add basic "mobile index" master view, plus support for demo mode - -* Refactor the batch file field renderer somewhat - -* Move ``notfound()`` method to core ``View`` class - -* Add ``BatchMasterView.add_file_field()`` convenience method - -* Add ``extra_main_fields()`` method to product view template - -* Allow config to override jQuery UI version - -* Add master view for Report Output data model - - -0.5.92 (2017-03-14) -------------------- - -* Tweak grid configuration for Employees view - -* Add trailing '?' for employee time sheet when hours are incomplete - - -0.5.91 (2017-03-03) -------------------- - -* Add 'discontinued' flag to product view - - -0.5.90 (2017-03-01) -------------------- - -* Add notes, ingredients to product view - - -0.5.89 (2017-02-24) -------------------- - -* Expose/honor per-role session timeouts - -* Fix daylight savings bug when cloning schedule from previous week - -* Expose notes field for purchasing batches - -* Add some product flags (kosher vegan etc.) to view fieldset - -* Add initial support for native product images - - -0.5.88 (2017-02-21) -------------------- - -* Fix session reference bug in schedule view - - -0.5.87 (2017-02-21) -------------------- - -* Fix bug in DateFieldRenderer when no format specified - - -0.5.86 (2017-02-21) -------------------- - -* Add initial/basic views for customer orders data - -* Be less aggressive when validating schedule edit form POST - - -0.5.85 (2017-02-19) -------------------- - -* Add generic "bulk delete" support to MasterView - -* Add beginnings of mobile receiving views - - -0.5.84 (2017-02-17) -------------------- - -* Tweak progress template to better handle reset to 0% - -* Add ability to merge 2 user accounts - -* Increase size of Roles select when editing a User - -* Add ability to filter Sent Messages by recipient name - - -0.5.83 (2017-02-16) -------------------- - -* Set form id for new purchasing batch page - -* Make sure invoice number is saved when making new purchasing batch - -* Tweak product view page styles (new grids etc.) - -* Add support for client-side session timeout warning - - -0.5.82 (2017-02-14) -------------------- - -* Collapse grid actions if there are only 2 - -* Add master view for generic exports - -* Make some product fields readonly - -* Make datasync changes viewable - -* Redirect to login page when Forbidden happens with anonymous user - -* Tweak styles for Send Message page - -* Tweak form handling for sending a new message, for more customization - -* Advance to password field when Enter pressed on username, login page - -* Add way for ``login_user()`` to set different timeout depending on nature of login - - -0.5.81 (2017-02-11) -------------------- - -* Add config for redirecting user to home page after logout - -* Refactor logic used to login a user, for easier sharing - -* Use ``pretty_hours()`` function where applicable - - -0.5.80 (2017-02-10) -------------------- - -* Tweak renderer for Amount field for DepositLink view - -* Tweak how regular/current price fields are handled for Product view - -* Fix bug in base 'shifts' template if ``weekdays`` not in context - - -0.5.79 (2017-02-09) -------------------- - -* Tweak product view template per rename of case_size field - -* Refactor the Edit Time Sheet view for "autocommit" mode - -* Don't render user field as hyperlink unless so configured - -* Expose 'delay' field in tempmon client views - -* Fix bug when first entry is empty for product on ordering form - - -0.5.78 (2017-02-08) -------------------- - -* Add initial Find Roles/Users by Permission feature - -* Fix sorting bug for Employee Time Sheet view - - -0.5.77 (2017-02-04) -------------------- - -* Invoke timepicker to correct format of user input, for edit schedule/timesheet - - -0.5.76 (2017-02-04) -------------------- - -* Add hyperlink to ``EmployeeFieldRenderer`` - -* Improve the grid for ``WorkedShift`` model a bit - -* Add config flag for disabling option to "Clear Schedule" - - -0.5.75 (2017-02-03) -------------------- - -* Fix probe filter for tempmon readings grid - -* Be explicit about fieldset for pricing batch rows - -* Let project override user authentication for login page - -* Add basic support for per-user session timeout - - -0.5.74 (2017-01-31) -------------------- - -* Refactor schedule / timesheet views for better separation of concerns - - -0.5.73 (2017-01-30) -------------------- - -* Add pyramid_mako dependency, remove minimum version for rattail - -* Add ability to edit employee time sheet - -* Add 'target' kwarg for grid action links - -* Add hyperlink to User field renderer - -* Add min diff threshold param when making price batch from product query - -* Add way for batch views to hide rows with given status code(s) - - -0.5.72 (2017-01-29) -------------------- - -* Add basic support for cloning batches - -* Tweaks to order form template etc., for purchasing batch - -* Let master view with rows prevent sort/filter for row grid - -* Add price diff column to pricing batch row grid - -* Add warning highlight for pricing batch row if can't calculate price - - -0.5.71 (2017-01-24) -------------------- - -* Improve columns, filters for TempMon Readings grid - -* Add ability to merge subdepartments - - -0.5.70 (2017-01-11) -------------------- - -* Fix CSRF token bug with email preview form, refactor to use webhelpers - - -0.5.69 (2017-01-06) -------------------- - -* When making batch from products, build query *before* starting thread - - -0.5.68 (2017-01-03) -------------------- - -* Prefer received quantities over ordered quantities, for Order Form history - - -0.5.67 (2017-01-03) -------------------- - -* Add department UUID to JSON returned for "eligible purchases" when creating batch - -* Set "order date" when creating new receiving batch - -* Add "discarded" flag when receiving DMG/EXP products; add view for purchase credits - -* Fix type error in grid numeric filter - - -0.5.66 (2016-12-30) -------------------- - -* Tweak the "create" screen for purchase batches, for more customization - - -0.5.65 (2016-12-29) -------------------- - -* Fix purchase batch execution, to redirect to Purchase *or* Batch - -* Add extra perms for restricing which 'mode' of purchase batch user can create - -* Refactor Order Form a bit to allow custom history data - - -0.5.64 (2016-12-28) -------------------- - -* Tweak default "numeric" grid filter, to ignore UPC-like values - -* Tweak default filter label for Batch ID - - -0.5.63 (2016-12-28) -------------------- - -* Fix CSRF token bug for bulk-move message forms - - -0.5.62 (2016-12-22) -------------------- - -* Fix CSRF token bug for old-style batch params form - - -0.5.61 (2016-12-21) -------------------- - -* Fix master merge template/forms to include CSRF token - - -0.5.60 (2016-12-20) -------------------- - -* Fix CSRF bug in Ordering Form template, make case quantity pretty - -* Fix some bugs in product view template - -* Update some enum references, render all purchase/batch cases/units fields as quantity - - -0.5.59 (2016-12-19) -------------------- - -* Add ``QuantityFieldRenderer`` - -* Add style for 'half-width' grid - - -0.5.58 (2016-12-16) -------------------- - -* Add ``ValidGPC`` formencode validator - -* Overhaul the Receiving Form to account for "product not found" etc. - -* Auto-append slash to URL when necessary - -* Add "print receiving worksheet" feature, for 'ordered' purchases - -* Add global CSRF protection - -* Tweak some field renderers - -* Overhaul product views a little, per customization needs - - -0.5.57 (2016-12-12) -------------------- - -* Lots of changes for sake of mobile login / user menu etc. - -* Add mobile support for datasync restart - -* Make ``CurrencyFieldRenderer`` inherit from ``FloatFieldRenderer`` - -* Fix session bug in old CRUD views - - -0.5.56 (2016-12-11) -------------------- - -* Show 'enabled' column in grid, fix prefix bug for email profiles - -* Tweak flash message when sending email preview, in case it's disabled - -* Hide first/last name for employee view, unless in readonly mode - -* Add initial mobile templates: base, home, about - - -0.5.55 (2016-12-10) -------------------- - -* Validate for unique tempmon probe config key - -* Add 'restartable tempmon client' conditional logic - - -0.5.54 (2016-12-10) -------------------- - -* Add new 'receiving form' for purchase batches - -* Add support for 'department' field in purchases / batches - -* Add generic 'not on file' product image for use as POD 404 - -* Add logic for handling Ctrl+V / Ctrl+X in numeric.js - - -0.5.53 (2016-12-09) -------------------- - -* Fix bug when editing a data row - - -0.5.52 (2016-12-08) -------------------- - -* Fix permission group label for email bounces - -* Update footer text/link per new about page - - -0.5.51 (2016-12-07) -------------------- - -* Fix permission / grid action bug for email profiles - - -0.5.50 (2016-12-07) -------------------- - -* Tweak tempmon views a little, fix client restart logic - -* Add 'extra_styles' to true base template - -* Add new "bytestring" filter for grids that need it - - -0.5.49 (2016-12-05) -------------------- - -* Allow delete for datasync changes - -* Fix import bugs with tempmon views - -* Use master view's session when creating form - - -0.5.48 (2016-12-05) -------------------- - -* Tweak email config views, to support subject "templates" - -* Refactor tempmon views to leverage rattail-tempmon database - - -0.5.47 (2016-11-30) -------------------- - -* Fix bug in products view class - - -0.5.46 (2016-11-29) -------------------- - -* Add basic 'about' page with some package versions - -* Tweak fields for product view - - -0.5.45 (2016-11-28) -------------------- - -* Fix styles for 'print schedule' page - -* Add permission for bulk-delete of batch data rows - - -0.5.44 (2016-11-22) -------------------- - -* Add some links between employees / people / customers views - -* Add support for pricing batches - -* Add initial views for tempmon clients/probes/readings - - -0.5.43 (2016-11-21) -------------------- - -* Add support for receive/cost mode, purchase relation for purchase batches - -* Bump jquery version - -* Fix bug when downloading batch file - - -0.5.42 (2016-11-20) -------------------- - -* Move ``get_batch_kwargs()`` to ``BatchMasterView`` - - -0.5.41 (2016-11-20) -------------------- - -* Add printer-friendly view for "full" employee schedule - -* Fix some bugs etc. with batch views and templates - - -0.5.40 (2016-11-19) -------------------- - -* Add size, extra link fields to product view template - -* Refactor batch views / templates per rattail framework overhaul - - -0.5.39 (2016-11-14) -------------------- - -* Make POD image for product view a bit more sane - -* Disable save button when creating new object - - -0.5.38 (2016-11-11) -------------------- - -* Tweak default factory for boolean grid filters - -* Add support for more cases + units, more vendor fields, for new purchase batches - - -0.5.37 (2016-11-10) -------------------- - -* Display sequence for product alt codes - -* Change how we determine default 'grid key' for master views - -* Add 'additive fields' concept to merge diff preview - - -0.5.36 (2016-11-09) -------------------- - -* Add historical amounts to new purchase Order Form, allow extra columns etc. - -* Tweak verbiage for merge template etc. - - -0.5.35 (2016-11-08) -------------------- - -* Add support for new Purchase/Batch views, 'create row' master pattern - -* Add basic views for label batches - -* Add support for making new-style batches from products grid query - -* Add initial support for viewing new purchase batch as Order Form - -* Refactor how batch editing is done; don't include rows for that sometimes - - -0.5.34 (2016-11-02) -------------------- - -* Add basic merge feature to ``MasterView`` - - -0.5.33 (2016-10-27) -------------------- - -* Fix template bug when deleting user - -* Tweak default styles for home page - -* Show vendor invoice rows as warning, if they have no case quantity - -* Add 'vendor code' and 'vendor code (any)' filters for products grid - -* Fix bug with how we auto-filter 'deleted' products (?) - - -0.5.32 (2016-10-19) -------------------- - -* Fix / improve progress display somewhat - -* Disable "true delete" button by default, when clicked - -* Fix bug in batch ID field renderer, when displayed for new batch - -* Add ``refresh_after_create`` flag for ``BatchMasterView`` - -* Disable a focus() call in menubar.js which messed with search filter focus - -* Let any 'admin' user elevate to 'root' for full system access - -* Update references to ``request.authenticated_userid`` - - -0.5.31 (2016-10-14) -------------------- - -* Add ability to edit employee schedule - - -0.5.30 (2016-10-10) -------------------- - -* Tweak some things to make demo project more "out of the box" - -* Add registration for 'rattail' template with Pyramid scaffold system - -* Add 'tailbone' to global template context, update 'better' template footer - -* Tweak how tailbone finds rattail config from pyramid settings - -* Remove last references to 'edbob' package - -* Strip whitespace from username field when editing User - -* Fix couple of bugs for vendor catalog views - -* Add size description to inventory report - - -0.5.29 (2016-10-04) -------------------- - -* Add ``code`` field to Category views - -* Add "bulk delete rows" feature to new batches view - - -0.5.28 (2016-09-30) -------------------- - -* Add specific permissions for edit/delete of individual batch rows - - -0.5.27 (2016-09-26) -------------------- - -* Add basic form validation when sending new messages - -* Add "just in time" editable instance check for master view - -* Add "refresh" button when viewing batch - -* Add FormAlchemy-compatible validators for email address, phone number - -* Improve validation for FormAlchemy date field renderer - -* Fix row-level visibility for grid edit action - -* Add a couple of extra verbs to base grid filter class - -* Tweak how a grid filter factory is determined - - -0.5.26 (2016-09-01) -------------------- - -* Add ``MasterView.listable`` flag for disabling grid view - -* Fix permission group label bug for batch views - -* Allow opt-out for "download batch row data as CSV" feature - - -0.5.25 (2016-08-23) -------------------- - -* Tweak how we use DB session to fetch grid settings - -* Add "sub-rows" support to MasterView class - -* Refactor batch views to leverage MasterView sub-rows logic - -* Refactor batch view/edit pages to share some "execution options" logic - -* Add hook to customize timesheet shift rendering - - -0.5.24 (2016-08-17) -------------------- - -* Fix bug in handheld batch view config - - -0.5.23 (2016-08-17) -------------------- - -* Fix bug when viewing batch with no execution options - - -0.5.22 (2016-08-17) -------------------- - -* Fix bug for handheld batch device type field - - -0.5.21 (2016-08-17) -------------------- - -* Add ``MasterView.render()`` method for sake of common context/logic - -* Add "empty" option to enum field renderers, if field allows empty value - -* Add support for system-unique ID in batch views etc. - -* Fix bug when deleting certain batches - -* Fix bug in batch download URL - -* Add basic support for batch execution options - -* Add basic support for new handheld/inventory batches - - -0.5.20 (2016-08-13) -------------------- - -* Add null / not null verbs back to default boolean grid filter - - -0.5.19 (2016-08-12) -------------------- - -* Only show granted permissions when viewing role details - -* Expose 'enabled' flag for email profile/settings - -* Add permissions field when viewing user details - - -0.5.18 (2016-08-10) -------------------- - -* Add ``render_progress()`` method to core view class - -* Add hopefully generic ``FileFieldRenderer`` - - -0.5.17 (2016-08-09) -------------------- - -* Add support for 10-key hyphen/period keys for numeric input fields - - -0.5.16 (2016-08-05) -------------------- - -* Fallback to empty string for email preview recipient, if current user has no address - -* Allow negative sign, decimal point for "numeric" text fields - - -0.5.15 (2016-07-27) -------------------- - -* Add initial attempt at 'better' theme - -* Add ``CodeTextAreaFieldRenderer``, refactor label profile form to use it - - -0.5.14 (2016-07-08) -------------------- - -* Allow extra kwargs to core ``View.redirect()`` method - -* Add awareness of special 'Authenticated' role, in permissions UI etc. - -* Always strip whitespace from label profile 'spec' field input - - -0.5.13 (2016-06-10) -------------------- - -* Hopefully fix some CSS for form field values - -* Add support for viewing single employee's schedule / time sheet - - -0.5.12 (2016-05-11) -------------------- - -* Add support for "full" schedule and time sheet views. - -* Move "full name" to front of Person grid columns. - -* Add rattail config object to ``Session`` kwargs. - - -0.5.11 (2016-05-06) -------------------- - -* Refactor some common FormEncode validators, plus add some more. - -* Tweak styles for jQuery UI selectmenu dropdowns. - -* Tweak timesheet styles, to give rows alternating background color. - -* Disable autocomplete for password fields when editing user. - -* Various incomplete improvements to the timesheet/schedule views. - - -0.5.10 (2016-05-05) -------------------- - -* Refactor timesheet logic, add basic schedule view. - -* Add prev/next/jump week navigation to time sheet, schedule views. - -* Add hyperlinks to product UPC and description, within main grid. - -* Fix bug in roles view. - - -0.5.9 (2016-05-02) ------------------- - -* Remove 'create batch from results' link on products index page. - -* Fix bugs in batch grid URLs. - -* Tweak how empty hours are displayed in time sheet. - - -0.5.8 (2016-05-02) ------------------- - -* Add ``MasterView.listing`` flag, for templates' sake. - -* Overhaul newgrid template header a bit, to improve styles. - -* Move ``Person.display_name`` to top of fieldset when viewing/editing. - -* Add 'testing' image, for background / watermark. - -* Add 'index title' setting to master view. - -* Add auto-hide/show magic to message recipients field when viewing. - -* Add initial support for grid index URLs. - -* Add initial/basic user feedback form support. - -* Stop trying to use PIL when generating product image tag. - - -0.5.7 (2016-04-28) ------------------- - -* Add master views for ``ScheduledShift`` model. - -* Add initial (incomplete) Time Sheet view. - - -0.5.6 (2016-04-25) ------------------- - -* Add views for ``WorkedShift`` model. - - -0.5.5 (2016-04-24) ------------------- - -* Add workarounds for certain display bugs when rendering datetimes. - -* Make currency field renderer display negative amounts in parentheses. - -* Add commas to record/page count in grid footer. - -* Tweak styles for form field labels. - - -0.5.4 (2016-04-12) ------------------- - -* Add support for column header title (tooltip) in new grids. - -* Change default filter type for integer fields, in new grids. - -* Add flag for rendering key value, for enum field renderers. - -* Fix case-sensitivity when sorting permission group labels. - - -0.5.3 (2016-04-05) ------------------- - -* Fix redirect bug when attempting bulk row delete for nonexistent batch. - -* Add comma magic back to ``CurrencyFieldRenderer``. - -* Add the 'is any' verb to default list for most grid filters. - -* Add new ``TimeFieldRenderer``, make it default for ``Time`` fields. - -* Add last-minute check to ensure master views allows deletion. - - -0.5.2 (2016-03-11) ------------------- - -* Make ``tailbone.views.labels`` a subpackage instead of module. - -* Add 'executed' to old batches grid view. - -* Make all timestamps show "raw" by default (with "diff" tooltip). - -* Improve grid filters for datetime fields (smarter verbs). - -* Fix bug where batch creator was being set to current user anytime it was viewed..yikes. - - -0.5.1 (2016-02-27) ------------------- - -* Fix bug when rendering email bounce links. - - -0.5.0 (2016-02-15) ------------------- - -* Refactor products view(s) per new master pattern. - -* Make our ``DateTimeFieldRenderer`` the default for datetime fields. - -* Add new ``BatchMasterView`` for new-style batches. - -* Overhaul vendor catalogs, vendor invoices views to use new batch master class. - -* Refactor some more model views to use MasterView. (depositlink, tax, emailbounce) - -* Make datasync views easier to customize. - - -0.4.42 ------- - -* Add initial reply / reply-all support for messages. - -* Add subscriber hook for setting inbox count in template context. - - -0.4.41 ------- - -* Tweak how we connect a user to a batch, when refreshing. - -* Add 'Move' button to message view template. - - -0.4.40 ------- - -* Make rattail config object use our scoped session, when consulting db. - - -0.4.39 ------- - -* Add support for sending new messages. - - -0.4.38 ------- - -* Add 'password is/not null' filter to users list view. - -* Remove style hack for message grid views. - - -0.4.37 ------- - -* Add 'messages.list' permission, to protect inbox etc. - - -0.4.36 ------- - -* Fix bug when marking batch as executed. - - -0.4.35 ------- - -* Change default form buttons so Cancel is also a button. - -* Add 'Stores' and 'Departments' fields to Employee fieldset. - - -0.4.34 ------- - -* Add 'restart datasync' button to datasync changes list page. - -* Add autocomplete vendor field renderer. - -* Change vendor catalog upload, to allow vendor-less parsers. - -* Stop depending on PIL...for now? - - -0.4.33 ------- - -* Add employee/department relationships to employee and department views. - - -0.4.32 ------- - -* Add edit mode for email "profile" settings. - -* Fix auto-creation of grid sorter, when joined table is involved. - -* Add initial support for 'messages' views. - - -0.4.31 ------- - -* Add speed bump / confirmation page when deleting records. - -* Add "grid tools" to "complete" grid template. - -* Add ``Person.middle_name`` to the fieldset. - - -0.4.30 ------- - -* Add config extension, to record data changes if so configured. - -* Add mailing address to person fieldset. - - -0.4.29 ------- - -* Fix some route names. - - -0.4.28 ------- - -* Use sample data when generating subject for display in email profile settings. - -* Convert (most?) basic views to use master view pattern. - - -0.4.27 ------- - -* Change default sortkey for email profiles list. - -* Add 'To' field to email profile settings grid. - - -0.4.26 ------- - -* Add readonly support for email profile settings. - - -0.4.25 ------- - -* Fix bug when 'edbob.permissions' setting is empty. - -* Tweak some things to get Tailbone working on its own. - -* Let subclass of MasterView override the database Session it uses. - - -0.4.24 ------- - -* Render ``DataSyncChange.obtained`` as humanized timestamp within UI. - - -0.4.23 ------- - -* Delete product costs for vendor when deleting vendor. - -* Work around formalchemy config bug, caused by edbob. - -* Add view to show DataSync changes, for basic troubleshooting. - - -0.4.22 ------- - -* Remove format hack which isn't py2.6-friendly. - - -0.4.21 ------- - -* Add "valueless verbs" concept to grid filters. - -* Tweak labels for new grid filter form buttons. - -* Configure logging when starting up. - -* Add HTML5 doctype to base template. - -* More grid filter improvements; add choice/enum/date value renderers. - -* Treat filter by "contains X Y" as "contains X and contains Y". - -* Tweak layout CSS so page body expands to fill screen. - - -0.4.20 ------- - -* Add ``CurrencyFieldRenderer``. - -* Add basic checkbox support to new grids. - -* Add 'Default Filters' and 'Clear Filters' buttons to new grid filters form. - -* Add "Save Defaults" button so user can save personal defaults for any new grid. - -* Fix bug when rendering hidden field in FA fieldset. - -* Remove some unused styles. - -* Various tweaks to support "late login" idea when uploading new batch. - -* Hard-code old grid pagecount settings, to avoid ``edbob.config``. - -* Refactor app configuration to use ``rattail.config.make_config()``. - -* Tweak label formatter instantiation, per rattail changes. - -* Various tweaks to base batch views. - -* Add ``CustomFieldRenderer`` and ``DateFieldRenderer``. - -* Add ``configure_fieldset()`` stub for master view. - -* Add progress indicator to batch execution. - -* Add ability to download batch row data as CSV. - - -0.4.19 ------- - -* Fix progress template, per jQuery CDN changes. - - -0.4.18 ------- - -* Don't show flash message when user logs in. - -* Add core JS/CSS to base template; use CDN instead of cached files. - -* Add support for "new-style grids" and "model master views", and convert the - following views to use it: roles, users, label profiles, settings. Also - overhaul how permissions are registered in app config. - - -0.4.17 ------- - -* Log warning instead of error when refreshing batch fails. - - -0.4.16 ------- - -* Add initial support for email bounce management. - - -0.4.15 ------- - -* Fix missing import bug. - - -0.4.14 ------- - -* Make anchor tags with 'button' class render as jQuery UI buttons. - -* Tweak ``app.make_rattail_config()`` to allow caller to define some settings. - -* Add ``display_name`` field to employee CRUD view. - -* Allow batch handler to disable the Execute button. - -* Add ``StoreFieldRenderer`` and ``DecimalFieldRenderer``. - -* Tweak how default filter config is handled for batch grid views. - -* Add list of assigned users to role view page. - -* Add products autocomplete view. - -* Add ``rattail_config`` attribute to base ``View`` class. - -* Fix timezone issues with ``util.pretty_datetime()`` function. - -* Add some custom FormEncode validators. - - -0.4.13 ------- - -* Fix query bugs for batch row grid views (add join support). - -* Make vendor field renderer show ID in readonly mode. - -* Change permission requirement for refreshing a batch's data. - -* Add flash message when any batch executes successfully. - -* Add autocomplete view for current employees. - -* Add autocomplete employee field renderer. - -* Fix usage of ``Product.unit_of_measure`` vs. ``Product.weighed``. - - -0.4.12 ------- - -* Fix bug when creating batch from product query. - - -0.4.11 ------- - -* Tweak old-style batch execution call. - - -0.4.10 ------- - -* Add 'fake_error' view to test exception handling. - -* Add ability to view details (i.e. all fields) of a batch row. - -* Fix bulk delete of batch rows, to set 'removed' flag instead. - -* Fix vendor invoice validation bug. - -* Add dept. number and friends to product details page. - -* Add "extra panels" customization hook to product details template. - - -0.4.9 ------ - -* Hide "print labels" column on products list view if so configured. - - -0.4.8 ------ - -* Fix permission for deposit link list/search view. - -* Fix permission for taxes list/search view. - - -0.4.7 ------ - -* Add views for deposit links, taxes; update product view. - -* Add some new vendor and product fields. - -* Add panels to product details view, etc. - -* Fix login so user is sent to their target page after authentication. - -* Don't allow edit of vendor and effective date in catalog batches. - -* Add shared GPC search filter, use it for product batch rows. - -* Add default ``Grid.iter_rows()`` implementation. - -* Add "save" icon and grid column style. - -* Add ``numeric.js`` script for numeric-only text inputs. - -* Add product UPC to JSON output of 'products.search' view. - - -0.4.6 ------ - -* Add vendor catalog batch importer. - -* Add vendor invoice batch importer. - -* Improve data file handling for file batches. - -* Add download feature for file batches. - -* Add better error handling when batch refresh fails, etc. - -* Add some docs for new batch system. - -* Refactor ``app`` module to promote code sharing. - -* Force grid table background to white. - -* Exclude 'deleted' items from reports. - -* Hide deleted field from product details, according to permissions. - -* Fix embedded grid URL query string bug. - - -0.4.5 ------ - -* Add prettier UPCs to ordering worksheet report. - -* Add case pack field to product CRUD form. - - -0.4.4 ------ - -* Add UI support for ``Product.deleted`` column. - - -0.4.3 ------ - -* More versioning support fixes, to allow on or off. - - -0.4.2 ------ - -* Rework versioning support to allow it to be on or off. - - -0.4.1 ------ - -* Only attempt to count versions for versioned models (CRUD views). - - -0.4.0 ------ - -This version primarily got the bump it did because of the addition of support -for SQLAlchemy-Continuum versioning. There were several other minor changes as -well. - -* Add department to field lists for category views. - -* Change default sort for People grid view. - -* Add category to product CRUD view. - -* Add initial versioning support with SQLAlchemy-Continuum. - - -0.3.28 ------- - -* Add unique username check when creating users. - -* Improve UPC search for rows within batches. - -* New batch system... - - -0.3.27 ------- - -* Fix bug with default search filters for SA grids. - -* Fix bug in product search UPC filter. - -* Ugh, add unwanted jQuery libs to progress template. - -* Add support for integer search filters. - - -0.3.26 ------- - -* Use boolean search filter for batch column filters of 'FLAG' type. - - -0.3.25 ------- - -* Make product UPC search view strip non-digit chars from input. - - -0.3.24 ------- - -* Make ``GPCFieldRenderer`` display check digit separate from main barcode - data. - -* Add ``DateTimeFieldRenderer`` to show human-friendly timestamps. - -* Tweak CRUD form buttons a little. - -* Add grid, CRUD views for ``Setting`` model. - -* Update ``base.css`` with various things from other projects. - -* Fix bug with progress template, when error occurs. - - -0.3.23 ------- - -* Fix bugs when configuring database session within threads. - - -0.3.22 ------- - -* Make ``Store.database_key`` field editable. - -* Add explicit session config within batch threads. - -* Remove cap on installed Pyramid version. - -* Change session progress API. - - -0.3.21 ------- - -* Add monospace font for label printer format command. - - -0.3.20 ------- - -* Refactor some label printing stuff, per rattail changes. - - -0.3.19 ------- - -* Add support for ``Product.not_for_sale`` flag. - - -0.3.18 ------- - -* Add explicit file encoding to all Mako templates. - -* Add "active" filter to users view; enable it by default. - - -0.3.17 ------- - -* Add customer phone autocomplete and customer "info" AJAX view. - -* Allow editing ``User.active`` field. - -* Add Person autocomplete view which restricts to employees only. - - -0.3.16 ------- - -* Add product report codes to the UI. - - -0.3.15 ------- - -* Add experimental soundex filter support to the Customers grid. - - -0.3.14 ------- - -* Add event hook for attaching Rattail ``config`` to new requests. - -* Fix vendor filter/sort issues in products grid. - -* Add ``Family`` and ``Product.family`` to the general grid/crud UI. - -* Add POD image support to product view page. - - -0.3.13 ------- - -* Use global ``Session`` from rattail (again). - -* Apply zope transaction to global Tailbone Session class. - - -0.3.12 ------- - -* Fix customer lookup bug in customer detail view. - -* Add ``SessionProgress`` class, and ``progress`` views. - - -0.3.11 ------- - -* Removed reliance on global ``rattail.db.Session`` class. - - -0.3.10 ------- - -* Changed ``UserFieldRenderer`` to leverage ``User.display_name``. - -* Refactored model imports, etc. - - This is in preparation for using database models only from ``rattail`` - (i.e. no ``edbob``). Mostly the model and enum imports were affected. - -* Removed references to ``edbob.enum``. - - -0.3.9 ------ - -* Added forbidden view. - -* Fixed bug with ``request.has_any_perm()``. - -* Made ``SortableAlchemyGridView`` default to full (100%) width. - -* Refactored ``AutocompleteFieldRenderer``. - - Also improved some organization of renderers. - -* Allow overriding form class/factory for CRUD views. - -* Made ``EnumFieldRenderer`` a proper class. - -* Don't sort values in ``EnumFieldRenderer``. - - The dictionaries used to supply enumeration values should be ``OrderedDict`` - instances if sorting is needed. - -* Added ``Product.family`` to CRUD view. - - -0.3.8 ------ - -* Fixed manifest (whoops). - - -0.3.7 ------ - -* Added some autocomplete Javascript magic. - - Not sure how this got missed the first time around. - -* Added ``products.search`` route/view. - - This is for simple AJAX uses. - -* Fixed grid join map bug. - - -0.3.6 ------ - -* Fixed change password template/form. - - -0.3.5 ------ - -* Added ``forms.alchemy`` module and changed CRUD view to use it. - -* Added progress template. - - -0.3.4 ------ - -* Changed vendor filter in product search to find "any vendor". - - I.e. the current filter is *not* restricted to the preferred vendor only. - Probably should still add one (back) for preferred only as well; hence the - commented code. - - -0.3.3 ------ - -* Major overhaul for standalone operation. - - This removes some of the ``edbob`` reliance, as well as borrowing some - templates and styling etc. from Dtail. - - Stop using ``edbob.db.engine``, stop using all edbob templates, etc. - -* Fix authorization policy bug. - - This was really an edge case, but in any event the problem would occur when a - user was logged in, and then that user account was deleted. - -* Added ``global_title()`` to base template. - -* Made logo more easily customizable in login template. - - -0.3.2 ------ - -* Rebranded to Tailbone. - - -0.3.1 ------ - -* Added some tests. - -* Added ``helpers`` module. - - Also added a Pyramid subscriber hook to add the module to the template - renderer context with a key of ``h``. This is nothing really new, but it - overrides the helper provided by ``edbob``, and adds a ``pretty_date()`` - function (which maybe isn't a good idea anyway..?). - -* Added ``simpleform`` wildcard import to ``forms`` module. - -* Added autocomplete view and template. - -* Fixed customer group deletion. - - Now any customer associations are dropped first, to avoid database integrity - errors. - -* Stole grids and grid-based views from ``edbob``. - -* Removed several references to ``edbob``. - -* Replaced ``Grid.clickable`` with ``.viewable``. - - Clickable grid rows seemed to be more irritating than useful. Now a view - icon is shown instead. - -* Added style for grid checkbox cells. - -* Fixed FormAlchemy table rendering when underlying session is not primary. - - This was needed for a grid based on a LOC SMS session. - -* Added grid sort arrow images. - -* Improved query modification logic in alchemy grid views. - -* Overhauled report views to allow easier template customization. - -* Improved product UPC search so check digit is optional. - -* Fixed import issue with ``views.reports`` module. - - -0.3a23 ------- - -* Fixed bugs where edit links were appearing for unprivileged users. - -* Added support for product codes. - - These are shown when viewing a product, and may be used to locate a product - via search filters. - - -0.3a22 ------- - -* Removed ``setup.cfg`` file. - -* Added ``Session`` to ``rattail.pyramid`` namespace. - -* Added Email Address field to Vendor CRUD views. - -* Added extra key lookups for customer and product routes. - - Now the CRUD routes for these objects can leverage UUIDs of various related - objects in addition to the primary object. More should be done with this, - but at least we have a start. - -* Replaced ``forms`` module with subpackage; added some initial goodies (many - of which are currently just imports from ``edbob``). - -* Added/edited various CRUD templates for consistency. - -* Modified several view modules so their Pyramid configuration is more - "extensible." This just means routes and views are defined as two separate - steps, so that derived applications may inherit the route definitions if they - so choose. - -* Added Employee CRUD views; added Email Address field to index view. - -* Updated ``people`` view module so it no longer derives from that of - ``edbob``. - -* Added support for, and some implementations of, extra key lookup abilities to - CRUD views. This allows URLs to use a "natural" key (e.g. Customer ID - instead of UUID), for cases where that is more helpful. - -* Product CRUD now uses autocomplete for Brand field. Also, price fields no - longer appear within an editable fieldset. - -* Within Store index view, default sort is now ID instead of Name. - -* Added Contact and Phone Number fields to Vendor CRUD views; added Contact and - Email Address fields to index view. - - -0.3a21 ------- - -- [feature] Added CRUD view and template. - -- [feature] Added ``AutocompleteView``. - -- [feature] Added Person autocomplete view and User CRUD views. - -- [feature] Added ``id`` and ``status`` fields to Employee grid view. - - -0.3a20 ------- - -- [feature] Sorted the Ordering Worksheet by product brand, description. - -0.3a19 ------- - -- [feature] Made batch creation and execution threads aware of - `sys.excepthook`. Updated both instances to use `rattail.threads.Thread` - instead of `threading.Thread`. This way if an exception occurs within the - thread, the registered handler will be invoked. - -0.3a18 ------- - -- [bug] Label profile editing now uses stripping field renderer to avoid - problems with leading/trailing whitespace. - -- [feature] Added Inventory Worksheet report. - -0.3a17 ------- - -- [feature] Added Brand and Size fields to the Ordering Worksheet. Also - tweaked the template styles slightly, and added the ability to override the - template via config. - -- [feature] Added "preferred only" option to Ordering Worksheet. - -0.3a16 ------- - -- [bug] Fixed bug where requesting deletion of non-existent batch row was - redirecting to a non-existent route. - -0.3a15 ------- - -- [bug] Fixed batch grid and CRUD views so that the execution time shows a - pretty (and local) display instead of 24-hour UTC time. - -0.3a14 ------- - -- [feature] Added some more CRUD. Mostly this was for departments, - subdepartments, brands and products. This was rather ad-hoc and still is - probably far from complete. - -- [general] Changed main batch route. - -- [bug] Fixed label profile templates so they properly handle a missing or - invalid printer spec. - -0.3a13 ------- - -- [bug] Fixed bug which prevented UPC search from working on products screen. - -0.3a12 ------- - -- [general] Fixed namespace packages, per ``setuptools`` documentation. - -- [feature] Added support for ``LabelProfile.visible``. This field may now be - edited, and it is honored when displaying the list of available profiles to - be used for printing from the products page. - -- [bug] Fixed bug where non-numeric data entered in the UPC search field on the - products page was raising an error. - -0.3a11 ------- - -- [bug] Fixed product label printing to handle any uncaught exception, and - report the error message to the end user. - -0.3a10 ------- - -- [general] Updated category views and templates. These were sorely out of - date. - -0.3a9 ------ - -- Add brands autocomplete view. - -- Add departments autocomplete view. - -- Add ID filter to vendors grid. - -0.3a8 ------ - -- Tweak batch progress indicators. - -- Add "Executed" column, filter to batch grid. - -0.3a7 ------ - -- Add ability to restrict batch providers via config. - -0.3a6 ------ - -- Add Vendor CRUD. - -- Add Brand views. - -0.3a5 ------ - -- Added support for GPC data type. - -- Added eager import of ``rattail.sil`` in ``before_render`` hook. - -- Removed ``rattail.pyramid.util`` module. - -- Added initial batch support: views, templates, creation from Product grid. - -- Added support for ``rattail.LabelProfile`` class. - -- Improved Product grid to include filter/sort on Vendor. - -- Cleaned up dependencies. - -- Added ``rattail.pyramid.includeme()``. - -- Added ``CustomerGroup`` CRUD view (read only). - -- Added hot links to ``Customer`` CRUD view. - -- Added ``Store`` index, CRUD views. - -- Updated ``rattail.pyramid.views.includeme()``. - -- Added ``email_preference`` to ``Customer`` CRUD. - -0.3a4 ------ - -- Update grid and CRUD views per changes in ``edbob``. - -0.3a3 ------ - -- Add price field renderers. - -- Add/tweak lots of views for database models. - -- Add label printing to product list view. - -- Add (some of) ``Product`` CRUD. - -0.3a2 ------ - -- Refactor category views. - -0.3a1 ------ - -- Initial port to Rattail v0.3. diff --git a/docs/api/api/batch/core.rst b/docs/api/api/batch/core.rst deleted file mode 100644 index 48d34315..00000000 --- a/docs/api/api/batch/core.rst +++ /dev/null @@ -1,15 +0,0 @@ - -``tailbone.api.batch.core`` -=========================== - -.. automodule:: tailbone.api.batch.core - -.. autoclass:: APIBatchMixin - -.. autoclass:: APIBatchView - -.. autoclass:: APIBatchRowView - - .. autoattribute:: editable - - .. autoattribute:: supports_quick_entry diff --git a/docs/api/api/batch/ordering.rst b/docs/api/api/batch/ordering.rst deleted file mode 100644 index 4b07e1f2..00000000 --- a/docs/api/api/batch/ordering.rst +++ /dev/null @@ -1,41 +0,0 @@ - -``tailbone.api.batch.ordering`` -=============================== - -.. automodule:: tailbone.api.batch.ordering - -.. autoclass:: OrderingBatchViews - - .. autoattribute:: collection_url_prefix - - .. autoattribute:: object_url_prefix - - .. autoattribute:: model_class - - .. autoattribute:: route_prefix - - .. autoattribute:: permission_prefix - - .. autoattribute:: default_handler_spec - - .. automethod:: base_query - - .. automethod:: create_object - -.. autoclass:: OrderingBatchRowViews - - .. autoattribute:: collection_url_prefix - - .. autoattribute:: object_url_prefix - - .. autoattribute:: model_class - - .. autoattribute:: route_prefix - - .. autoattribute:: permission_prefix - - .. autoattribute:: default_handler_spec - - .. autoattribute:: supports_quick_entry - - .. automethod:: update_object diff --git a/docs/api/db.rst b/docs/api/db.rst deleted file mode 100644 index ace21b68..00000000 --- a/docs/api/db.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.db`` -=============== - -.. automodule:: tailbone.db - :members: diff --git a/docs/api/diffs.rst b/docs/api/diffs.rst deleted file mode 100644 index fb1bba71..00000000 --- a/docs/api/diffs.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.diffs`` -================== - -.. automodule:: tailbone.diffs - :members: diff --git a/docs/api/forms.widgets.rst b/docs/api/forms.widgets.rst deleted file mode 100644 index 33316903..00000000 --- a/docs/api/forms.widgets.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.forms.widgets`` -========================== - -.. automodule:: tailbone.forms.widgets - :members: diff --git a/docs/api/grids.core.rst b/docs/api/grids.core.rst deleted file mode 100644 index 60155cb2..00000000 --- a/docs/api/grids.core.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.grids.core`` -======================= - -.. automodule:: tailbone.grids.core - :members: diff --git a/docs/api/progress.rst b/docs/api/progress.rst deleted file mode 100644 index 83685d47..00000000 --- a/docs/api/progress.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.progress`` -===================== - -.. automodule:: tailbone.progress - :members: diff --git a/docs/api/subscribers.rst b/docs/api/subscribers.rst index d28a1b15..abafe0c9 100644 --- a/docs/api/subscribers.rst +++ b/docs/api/subscribers.rst @@ -3,4 +3,5 @@ ======================== .. automodule:: tailbone.subscribers - :members: + +.. autofunction:: add_rattail_config_attribute_to_request diff --git a/docs/api/util.rst b/docs/api/util.rst deleted file mode 100644 index 35e66ed3..00000000 --- a/docs/api/util.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.util`` -================= - -.. automodule:: tailbone.util - :members: diff --git a/docs/api/views/batch.vendorcatalog.rst b/docs/api/views/batch.vendorcatalog.rst deleted file mode 100644 index 4df51685..00000000 --- a/docs/api/views/batch.vendorcatalog.rst +++ /dev/null @@ -1,10 +0,0 @@ - -``tailbone.views.batch.vendorcatalog`` -====================================== - -.. automodule:: tailbone.views.batch.vendorcatalog - -.. autoclass:: VendorCatalogsView - :members: - -.. autofunction:: includeme diff --git a/docs/api/views/core.rst b/docs/api/views/core.rst deleted file mode 100644 index 8a68f33f..00000000 --- a/docs/api/views/core.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.views.core`` -======================= - -.. automodule:: tailbone.views.core - :members: diff --git a/docs/api/views/master.rst b/docs/api/views/master.rst index e7de7170..bf505b6c 100644 --- a/docs/api/views/master.rst +++ b/docs/api/views/master.rst @@ -81,12 +81,6 @@ override when defining your subclass. override this for certain views, if so that should be done within :meth:`get_help_url()`. - .. attribute:: MasterView.version_diff_factory - - Optional factory to use for version diff objects. By default - this is *not set* but a subclass is free to set it. See also - :meth:`get_version_diff_factory()`. - Methods to Override ------------------- @@ -94,8 +88,6 @@ Methods to Override The following is a list of methods which you can override when defining your subclass. - .. automethod:: MasterView.editable_instance - .. .. automethod:: MasterView.get_settings .. automethod:: MasterView.get_csv_fields @@ -103,24 +95,3 @@ subclass. .. automethod:: MasterView.get_csv_row .. automethod:: MasterView.get_help_url - - .. automethod:: MasterView.get_model_key - - .. automethod:: MasterView.get_version_diff_enums - - .. automethod:: MasterView.get_version_diff_factory - - .. automethod:: MasterView.make_version_diff - - .. automethod:: MasterView.title_for_version - - -Support Methods ---------------- - -The following is a list of methods you should (probably) not need to -override, but may find useful: - - .. automethod:: MasterView.default_edit_url - - .. automethod:: MasterView.get_action_route_kwargs diff --git a/docs/api/views/members.rst b/docs/api/views/members.rst deleted file mode 100644 index 6a9e9168..00000000 --- a/docs/api/views/members.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``tailbone.views.members`` -========================== - -.. automodule:: tailbone.views.members - :members: diff --git a/docs/api/views/purchasing.batch.rst b/docs/api/views/purchasing.batch.rst deleted file mode 100644 index 9bb62c8b..00000000 --- a/docs/api/views/purchasing.batch.rst +++ /dev/null @@ -1,9 +0,0 @@ - -``tailbone.views.purchasing.batch`` -=================================== - -.. automodule:: tailbone.views.purchasing.batch - -.. autoclass:: PurchasingBatchView - - .. automethod:: save_edit_row_form diff --git a/docs/api/views/purchasing.ordering.rst b/docs/api/views/purchasing.ordering.rst deleted file mode 100644 index 38d46b07..00000000 --- a/docs/api/views/purchasing.ordering.rst +++ /dev/null @@ -1,15 +0,0 @@ - -``tailbone.views.purchasing.ordering`` -====================================== - -.. automodule:: tailbone.views.purchasing.ordering - -.. autoclass:: OrderingBatchView - - .. autoattribute:: model_class - - .. autoattribute:: default_handler_spec - - .. automethod:: configure_row_form - - .. automethod:: worksheet_update diff --git a/docs/api/views/vendors.catalogs.rst b/docs/api/views/vendors.catalogs.rst new file mode 100644 index 00000000..432966e7 --- /dev/null +++ b/docs/api/views/vendors.catalogs.rst @@ -0,0 +1,10 @@ + +``tailbone.views.vendors.catalogs`` +=================================== + +.. automodule:: tailbone.views.vendors.catalogs + +.. autoclass:: VendorCatalogsView + :members: + +.. autofunction:: includeme diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index bbf94f4b..00000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,8 +0,0 @@ - -Changelog Archive -================= - -.. toctree:: - :maxdepth: 1 - - OLDCHANGES diff --git a/docs/conf.py b/docs/conf.py index ade4c92a..7d3e5831 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,21 +1,38 @@ -# Configuration file for the Sphinx documentation builder. +# -*- coding: utf-8; -*- # -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html +# Tailbone documentation build configuration file, created by +# sphinx-quickstart on Sat Feb 15 23:15:27 2014. +# +# This file is exec()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import sys +import os -from importlib.metadata import version as get_version +import sphinx_rtd_theme -project = 'Tailbone' -copyright = '2010 - 2024, Lance Edgar' -author = 'Lance Edgar' -release = get_version('Tailbone') +exec(open(os.path.join(os.pardir, 'tailbone', '_version.py')).read()) -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', @@ -23,30 +40,237 @@ extensions = [ 'sphinx.ext.viewcode', ] -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - intersphinx_mapping = { - 'rattail': ('https://docs.wuttaproject.org/rattail/', None), 'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None), - 'wuttaweb': ('https://docs.wuttaproject.org/wuttaweb/', None), - 'wuttjamaican': ('https://docs.wuttaproject.org/wuttjamaican/', None), } -# allow todo entries to show up -todo_include_todos = True +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Tailbone' +copyright = u'2010 - 2018, Lance Edgar' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +# version = '0.3' +version = '.'.join(__version__.split('.')[:2]) +# The full version, including alpha/beta/rc tags. +release = __version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +# -- Options for HTML output ---------------------------------------------- -html_theme = 'furo' -html_static_path = ['_static'] +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# html_theme = 'classic' +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None -#html_logo = 'images/rattail_avatar.png' +html_logo = 'images/rattail_avatar.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None # Output file base name for HTML help builder. -#htmlhelp_basename = 'Tailbonedoc' +htmlhelp_basename = 'Tailbonedoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'Tailbone.tex', u'Tailbone Documentation', + u'Lance Edgar', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'tailbone', u'Tailbone Documentation', + [u'Lance Edgar'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Tailbone', u'Tailbone Documentation', + u'Lance Edgar', 'Tailbone', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst index d964086f..f26d4019 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -42,32 +42,12 @@ Package API: .. toctree:: :maxdepth: 1 - api/api/batch/core - api/api/batch/ordering - api/db - api/diffs api/forms - api/forms.widgets api/grids - api/grids.core - api/progress api/subscribers - api/util api/views/batch - api/views/batch.vendorcatalog - api/views/core api/views/master - api/views/members - api/views/purchasing.batch - api/views/purchasing.ordering - - -Changelog: - -.. toctree:: - :maxdepth: 1 - - changelog + api/views/vendors.catalogs Documentation To-Do diff --git a/docs/structure.rst b/docs/structure.rst index 5585f71a..b741475e 100644 --- a/docs/structure.rst +++ b/docs/structure.rst @@ -117,6 +117,7 @@ of course supply the web app layer. │   │   │   └── foobatch/ │   │   ├── customers/ │   │   ├── menu.mako + │   │   ├── mobile/ │   │   └── products/ │   └── views/ │   ├── __init__.py diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index a7214a8e..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,103 +0,0 @@ - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - - -[project] -name = "Tailbone" -version = "0.22.7" -description = "Backoffice Web Application for Rattail" -readme = "README.md" -authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] -license = {text = "GNU GPL v3+"} -classifiers = [ - "Development Status :: 4 - Beta", - "Environment :: Web Environment", - "Framework :: Pyramid", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Office/Business", - "Topic :: Software Development :: Libraries :: Python Modules", -] -requires-python = ">= 3.8" -dependencies = [ - "asgiref", - "colander", - "ColanderAlchemy", - "cornice", - "cornice-swagger", - "deform", - "humanize", - "Mako", - "markdown", - "openpyxl", - "paginate", - "paginate_sqlalchemy", - "passlib", - "Pillow", - "pyramid>=2", - "pyramid_beaker", - "pyramid_deform", - "pyramid_exclog", - "pyramid_fanstatic", - "pyramid_mako", - "pyramid_retry", - "pyramid_tm", - "rattail[db,bouncer]>=0.20.1", - "sa-filters", - "simplejson", - "transaction", - "waitress", - "WebHelpers2", - "WuttaWeb>=0.21.0", - "zope.sqlalchemy>=1.5", -] - - -[project.optional-dependencies] -docs = ["Sphinx", "furo"] -tests = ["coverage", "mock", "pytest", "pytest-cov"] - - -[project.entry-points."paste.app_factory"] -main = "tailbone.app:main" -webapi = "tailbone.webapi:main" - - -[project.entry-points."rattail.cleaners"] -beaker = "tailbone.cleanup:BeakerCleaner" - - -[project.entry-points."rattail.config.extensions"] -tailbone = "tailbone.config:ConfigExtension" - - -[project.urls] -Homepage = "https://rattailproject.org" -Repository = "https://forgejo.wuttaproject.org/rattail/tailbone" -Issues = "https://forgejo.wuttaproject.org/rattail/tailbone/issues" -Changelog = "https://forgejo.wuttaproject.org/rattail/tailbone/src/branch/master/CHANGELOG.md" - - -[tool.commitizen] -version_provider = "pep621" -tag_format = "v$version" -update_changelog_on_bump = true - - -[tool.nosetests] -nocapture = 1 -cover-package = "tailbone" -cover-erase = 1 -cover-html = 1 -cover-html-dir = "htmlcov" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..7712ec72 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,6 @@ +[nosetests] +nocapture = 1 +cover-package = tailbone +cover-erase = 1 +cover-html = 1 +cover-html-dir = htmlcov diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..98cb4b9f --- /dev/null +++ b/setup.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2018 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# Rattail. If not, see . +# +################################################################################ +""" +Setup script for Tailbone +""" + +from __future__ import unicode_literals, absolute_import + +import os.path +from setuptools import setup, find_packages + + +here = os.path.abspath(os.path.dirname(__file__)) +exec(open(os.path.join(here, 'tailbone', '_version.py')).read()) +README = open(os.path.join(here, 'README.rst')).read() + + +requires = [ + # + # Version numbers within comments below have specific meanings. + # Basically the 'low' value is a "soft low," and 'high' a "soft high." + # In other words: + # + # If either a 'low' or 'high' value exists, the primary point to be + # made about the value is that it represents the most current (stable) + # version available for the package (assuming typical public access + # methods) whenever this project was started and/or documented. + # Therefore: + # + # If a 'low' version is present, you should know that attempts to use + # versions of the package significantly older than the 'low' version + # may not yield happy results. (A "hard" high limit may or may not be + # indicated by a true version requirement.) + # + # Similarly, if a 'high' version is present, and especially if this + # project has laid dormant for a while, you may need to refactor a bit + # when attempting to support a more recent version of the package. (A + # "hard" low limit should be indicated by a true version requirement + # when a 'high' version is present.) + # + # In any case, developers and other users are encouraged to play + # outside the lines with regard to these soft limits. If bugs are + # encountered then they should be filed as such. + # + # package # low high + + # TODO: Pyramid 1.9 looks like it breaks us..? playing it safe for now.. + 'pyramid<1.9', # 1.3b2 1.8.3 + + # apparently 2.0 removes the retry support, in which case we then need + # pyramid_retry .. but that requires pyramid 1.9 ... + 'pyramid_tm<2.0', # 0.3 1.1.1 + + # TODO: why do we need to cap this? breaks tailbone.db zope stuff somehow + 'zope.sqlalchemy<1.0', # 0.7 0.7.7 + + 'ColanderAlchemy', # 0.3.3 + 'deform', # 2.0.4 + 'humanize', # 0.5.1 + 'Mako', # 0.6.2 + 'openpyxl', # 2.4.7 + 'paginate', # 0.5.6 + 'paginate_sqlalchemy', # 0.2.0 + 'passlib', # 1.7.1 + 'pyramid_beaker>=0.6', # 0.6.1 + 'pyramid_deform', # 0.2 + 'pyramid_exclog', # 0.6 + 'pyramid_mako', # 1.0.2 + 'rattail[db,bouncer]', # 0.5.0 + 'six', # 1.10.0 + 'transaction', # 1.2.0 + 'waitress', # 0.8.1 + 'WebHelpers2', # 2.0 + 'webhelpers2_grid', # 0.1 + 'WTForms', # 2.1 +] + + +extras = { + + 'docs': [ + # + # package # low high + + 'Sphinx', # 1.2 + 'sphinx-rtd-theme', # 0.2.4 + ], + + 'tests': [ + # + # package # low high + + 'coverage', # 3.6 + 'fixture', # 1.5 + 'mock', # 1.0.1 + 'nose', # 1.3.0 + ], +} + + +setup( + name = "Tailbone", + version = __version__, + author = "Lance Edgar", + author_email = "lance@edbob.org", + url = "http://rattailproject.org/", + license = "GNU GPL v3", + description = "Backoffice Web Application for Rattail", + long_description = README, + + classifiers = [ + 'Development Status :: 4 - Beta', + 'Environment :: Web Environment', + 'Framework :: Pyramid', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Office/Business', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + + install_requires = requires, + extras_require = extras, + tests_require = ['Tailbone[tests]'], + test_suite = 'nose.collector', + + packages = find_packages(exclude=['tests.*', 'tests']), + include_package_data = True, + zip_safe = False, + + entry_points = { + + 'paste.app_factory': [ + 'main = tailbone.app:main', + ], + + 'rattail.config.extensions': [ + 'tailbone = tailbone.config:ConfigExtension', + ], + + 'pyramid.scaffold': [ + 'rattail = tailbone.scaffolds:RattailTemplate', + ], + }, +) diff --git a/tailbone/_version.py b/tailbone/_version.py index 7095f6c8..9751bfdf 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,9 +1,3 @@ # -*- coding: utf-8; -*- -try: - from importlib.metadata import version -except ImportError: - from importlib_metadata import version - - -__version__ = version('Tailbone') +__version__ = '0.7.16' diff --git a/tailbone/api/__init__.py b/tailbone/api/__init__.py deleted file mode 100644 index 1fae059f..00000000 --- a/tailbone/api/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API -""" - -from __future__ import unicode_literals, absolute_import - -from .core import APIView, api -from .master import APIMasterView, SortColumn -# TODO: remove this -from .master2 import APIMasterView2 - - -def includeme(config): - config.include('tailbone.api.common') - config.include('tailbone.api.auth') - config.include('tailbone.api.customers') - config.include('tailbone.api.upgrades') - config.include('tailbone.api.users') diff --git a/tailbone/api/auth.py b/tailbone/api/auth.py deleted file mode 100644 index a710e30d..00000000 --- a/tailbone/api/auth.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Auth Views -""" - -from cornice import Service - -from tailbone.api import APIView, api -from tailbone.db import Session -from tailbone.auth import login_user, logout_user - - -class AuthenticationView(APIView): - - @api - def check_session(self): - """ - View to serve as "no-op" / ping action to check current user's session. - This will establish a server-side web session for the user if none - exists. Note that this also resets the user's session timer. - """ - data = {'ok': True, 'permissions': []} - if self.request.user: - data['user'] = self.get_user_info(self.request.user) - data['permissions'] = list(self.request.user_permissions) - - # background color may be set per-request, by some apps - if hasattr(self.request, 'background_color') and self.request.background_color: - data['background_color'] = self.request.background_color - else: # otherwise we use the one from config - data['background_color'] = self.rattail_config.get( - 'tailbone', 'background_color') - - # TODO: this seems the best place to return some global app - # settings, but maybe not desirable in all cases..in which - # case should caller need to ask for these explicitly? or - # make a different call altogether to get them..? - app = self.get_rattail_app() - customer_handler = app.get_clientele_handler() - data['settings'] = { - 'customer_field_dropdown': customer_handler.choice_uses_dropdown(), - } - - return data - - @api - def login(self): - """ - API login view. - """ - if self.request.method == 'OPTIONS': - return self.request.response - - username = self.request.json.get('username') - password = self.request.json.get('password') - if not (username and password): - return {'error': "Invalid username or password"} - - # make sure credentials are valid - user = self.authenticate_user(username, password) - if not user: - return {'error': "Invalid username or password"} - - # is there some reason this user should not login? - error = self.why_cant_user_login(user) - if error: - return {'error': error} - - app = self.get_rattail_app() - auth = app.get_auth_handler() - - login_user(self.request, user) - return { - 'ok': True, - 'user': self.get_user_info(user), - 'permissions': list(auth.get_permissions(Session(), user)), - } - - def authenticate_user(self, username, password): - app = self.get_rattail_app() - auth = app.get_auth_handler() - return auth.authenticate_user(Session(), username, password) - - def why_cant_user_login(self, user): - """ - This method is given a ``User`` instance, which represents someone who - is just now trying to login, and has already cleared the basic hurdle - of providing the correct credentials for a user on file. This method - is responsible then, for further verification that this user *should* - in fact be allowed to login to this app node. If the method determines - a reason the user should *not* be allowed to login, then it should - return that reason as a simple string. - """ - - @api - def logout(self): - """ - API logout view. - """ - if self.request.method == 'OPTIONS': - return self.request.response - - logout_user(self.request) - return {'ok': True} - - @api - def become_root(self): - """ - Elevate the current request to 'root' for full system access. - """ - if not self.request.is_admin: - raise self.forbidden() - self.request.user.record_event(self.enum.USER_EVENT_BECOME_ROOT) - self.request.session['is_root'] = True - return { - 'ok': True, - 'user': self.get_user_info(self.request.user), - } - - @api - def stop_root(self): - """ - Lower the current request from 'root' back to normal access. - """ - if not self.request.is_admin: - raise self.forbidden() - self.request.user.record_event(self.enum.USER_EVENT_STOP_ROOT) - self.request.session['is_root'] = False - return { - 'ok': True, - 'user': self.get_user_info(self.request.user), - } - - @api - def change_password(self): - """ - View which allows a user to change their password. - """ - if self.request.method == 'OPTIONS': - return self.request.response - - if not self.request.user: - raise self.forbidden() - - if self.request.user.prevent_password_change and not self.request.is_root: - raise self.forbidden() - - data = self.request.json_body - - # first make sure "current" password is accurate - if not self.authenticate_user(self.request.user, data['current_password']): - return {'error': "The current/old password you provided is incorrect"} - - # okay then, set new password - auth = self.app.get_auth_handler() - auth.set_user_password(self.request.user, data['new_password']) - return { - 'ok': True, - 'user': self.get_user_info(self.request.user), - } - - @classmethod - def defaults(cls, config): - cls._auth_defaults(config) - - @classmethod - def _auth_defaults(cls, config): - - # session - check_session = Service(name='check_session', path='/session') - check_session.add_view('GET', 'check_session', klass=cls) - config.add_cornice_service(check_session) - - # login - login = Service(name='login', path='/login') - login.add_view('POST', 'login', klass=cls) - config.add_cornice_service(login) - - # logout - logout = Service(name='logout', path='/logout') - logout.add_view('POST', 'logout', klass=cls) - config.add_cornice_service(logout) - - # become root - become_root = Service(name='become_root', path='/become-root') - become_root.add_view('POST', 'become_root', klass=cls) - config.add_cornice_service(become_root) - - # stop root - stop_root = Service(name='stop_root', path='/stop-root') - stop_root.add_view('POST', 'stop_root', klass=cls) - config.add_cornice_service(stop_root) - - # change password - change_password = Service(name='change_password', path='/change-password') - change_password.add_view('POST', 'change_password', klass=cls) - config.add_cornice_service(change_password) - - -def defaults(config, **kwargs): - base = globals() - - AuthenticationView = kwargs.get('AuthenticationView', base['AuthenticationView']) - AuthenticationView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/batch/__init__.py b/tailbone/api/batch/__init__.py deleted file mode 100644 index bdf58438..00000000 --- a/tailbone/api/batch/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2019 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Batches -""" - -from __future__ import unicode_literals, absolute_import - -from .core import APIBatchView, APIBatchRowView, BatchAPIMasterView diff --git a/tailbone/api/batch/core.py b/tailbone/api/batch/core.py deleted file mode 100644 index f7bc9333..00000000 --- a/tailbone/api/batch/core.py +++ /dev/null @@ -1,360 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Batch Views -""" - -import logging -import warnings - -from cornice import Service - -from tailbone.api import APIMasterView - - -log = logging.getLogger(__name__) - - -class APIBatchMixin(object): - """ - Base class for all API views which are meant to handle "batch" *and/or* - "batch row" data. - """ - - def get_batch_class(self): - model_class = self.get_model_class() - if hasattr(model_class, '__batch_class__'): - return model_class.__batch_class__ - return model_class - - def get_handler(self): - """ - Returns a `BatchHandler` instance for the view. All (?) custom batch - API views should define a default handler class; however this may in all - (?) cases be overridden by config also. The specific setting required - to do so will depend on the 'key' for the type of batch involved, e.g. - assuming the 'vendor_catalog' batch: - - .. code-block:: ini - - [rattail.batch] - vendor_catalog.handler = myapp.batch.vendorcatalog:CustomCatalogHandler - - Note that the 'key' for a batch is generally the same as its primary - table name, although technically it is whatever value returns from the - ``batch_key`` attribute of the main batch model class. - """ - app = self.get_rattail_app() - key = self.get_batch_class().batch_key - return app.get_batch_handler(key, default=self.default_handler_spec) - - -class APIBatchView(APIBatchMixin, APIMasterView): - """ - Base class for all API views which are meant to handle "batch" *and/or* - "batch row" data. - """ - supports_toggle_complete = False - supports_execute = False - - def __init__(self, request, **kwargs): - super(APIBatchView, self).__init__(request, **kwargs) - self.batch_handler = self.get_handler() - - @property - def handler(self): - warnings.warn("the `handler` property is deprecated; " - "please use `batch_handler` instead", - DeprecationWarning, stacklevel=2) - return self.batch_handler - - def normalize(self, batch): - app = self.get_rattail_app() - created = app.localtime(batch.created, from_utc=True) - - executed = None - if batch.executed: - executed = app.localtime(batch.executed, from_utc=True) - - return { - 'uuid': batch.uuid, - '_str': str(batch), - 'id': batch.id, - 'id_str': batch.id_str, - 'description': batch.description, - 'notes': batch.notes, - 'params': batch.params or {}, - 'rowcount': batch.rowcount, - 'created': str(created), - 'created_display': self.pretty_datetime(created), - 'created_by_uuid': batch.created_by.uuid, - 'created_by_display': str(batch.created_by), - 'complete': batch.complete, - 'status_code': batch.status_code, - 'status_display': batch.STATUS.get(batch.status_code, - str(batch.status_code)), - 'executed': str(executed) if executed else None, - 'executed_display': self.pretty_datetime(executed) if executed else None, - 'executed_by_uuid': batch.executed_by_uuid, - 'executed_by_display': str(batch.executed_by or ''), - 'mutable': self.batch_handler.is_mutable(batch), - } - - def create_object(self, data): - """ - Create a new object instance and populate it with the given data. - - Here we'll invoke the handler for actual batch creation, instead of - typical logic used for simple records. - """ - user = self.request.user - kwargs = dict(data) - kwargs['user'] = user - batch = self.batch_handler.make_batch(self.Session(), **kwargs) - if self.batch_handler.should_populate(batch): - self.batch_handler.do_populate(batch, user) - return batch - - def update_object(self, batch, data): - """ - Logic for updating a main object record. - - Here we want to make sure we set "created by" to the current user, when - creating a new batch. - """ - # we're only concerned with *new* batches here - if not batch.uuid: - - # assign creator; initialize row count - batch.created_by_uuid = self.request.user.uuid - if batch.rowcount is None: - batch.rowcount = 0 - - # then go ahead with usual logic - return super(APIBatchView, self).update_object(batch, data) - - def mark_complete(self): - """ - Mark the given batch as "complete". - """ - batch = self.get_object() - - if batch.executed: - return {'error': "Batch {} has already been executed: {}".format( - batch.id_str, batch.description)} - - if batch.complete: - return {'error': "Batch {} is already marked complete: {}".format( - batch.id_str, batch.description)} - - batch.complete = True - return self._get(obj=batch) - - def mark_incomplete(self): - """ - Mark the given batch as "incomplete". - """ - batch = self.get_object() - - if batch.executed: - return {'error': "Batch {} has already been executed: {}".format( - batch.id_str, batch.description)} - - if not batch.complete: - return {'error': "Batch {} is already marked incomplete: {}".format( - batch.id_str, batch.description)} - - batch.complete = False - return self._get(obj=batch) - - def execute(self): - """ - Execute the given batch. - """ - batch = self.get_object() - - if batch.executed: - return {'error': "Batch {} has already been executed: {}".format( - batch.id_str, batch.description)} - - kwargs = dict(self.request.json_body) - kwargs.pop('user', None) - kwargs.pop('progress', None) - result = self.batch_handler.do_execute(batch, self.request.user, **kwargs) - return {'ok': bool(result), 'batch': self.normalize(batch)} - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._batch_defaults(config) - - @classmethod - def _batch_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - collection_url_prefix = cls.get_collection_url_prefix() - object_url_prefix = cls.get_object_url_prefix() - - if cls.supports_toggle_complete: - - # mark complete - mark_complete = Service(name='{}.mark_complete'.format(route_prefix), - path='{}/{{uuid}}/mark-complete'.format(object_url_prefix)) - mark_complete.add_view('POST', 'mark_complete', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(mark_complete) - - # mark incomplete - mark_incomplete = Service(name='{}.mark_incomplete'.format(route_prefix), - path='{}/{{uuid}}/mark-incomplete'.format(object_url_prefix)) - mark_incomplete.add_view('POST', 'mark_incomplete', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(mark_incomplete) - - if cls.supports_execute: - - # execute batch - execute = Service(name='{}.execute'.format(route_prefix), - path='{}/{{uuid}}/execute'.format(object_url_prefix)) - execute.add_view('POST', 'execute', klass=cls, - permission='{}.execute'.format(permission_prefix)) - config.add_cornice_service(execute) - - -# TODO: deprecate / remove this -BatchAPIMasterView = APIBatchView - - -class APIBatchRowView(APIBatchMixin, APIMasterView): - """ - Base class for all API views which are meant to handle "batch rows" data. - """ - editable = False - supports_quick_entry = False - - def __init__(self, request, **kwargs): - super(APIBatchRowView, self).__init__(request, **kwargs) - self.batch_handler = self.get_handler() - - @property - def handler(self): - warnings.warn("the `handler` property is deprecated; " - "please use `batch_handler` instead", - DeprecationWarning, stacklevel=2) - return self.batch_handler - - def normalize(self, row): - batch = row.batch - return { - 'uuid': row.uuid, - '_str': str(row), - '_parent_str': str(batch), - '_parent_uuid': batch.uuid, - 'batch_uuid': batch.uuid, - 'batch_id': batch.id, - 'batch_id_str': batch.id_str, - 'batch_description': batch.description, - 'batch_complete': batch.complete, - 'batch_executed': bool(batch.executed), - 'batch_mutable': self.batch_handler.is_mutable(batch), - 'sequence': row.sequence, - 'status_code': row.status_code, - 'status_display': row.STATUS.get(row.status_code, str(row.status_code)), - } - - def update_object(self, row, data): - """ - Supplements the default logic as follows: - - Invokes the batch handler's ``refresh_row()`` method after updating the - row's field data per usual. - """ - if not self.batch_handler.is_mutable(row.batch): - return {'error': "Batch is not mutable"} - - # update row per usual - row = super(APIBatchRowView, self).update_object(row, data) - - # okay now we apply handler refresh logic - self.batch_handler.refresh_row(row) - return row - - def delete_object(self, row): - """ - Overrides the default logic as follows: - - Delegates deletion of the row to the batch handler. - """ - self.batch_handler.do_remove_row(row) - - def quick_entry(self): - """ - View for handling "quick entry" user input, for a batch. - """ - data = self.request.json_body - - uuid = data['batch_uuid'] - batch = self.Session.get(self.get_batch_class(), uuid) - if not batch: - raise self.notfound() - - entry = data['quick_entry'] - - try: - row = self.batch_handler.quick_entry(self.Session(), batch, entry) - except Exception as error: - log.warning("quick entry failed for '%s' batch %s: %s", - self.batch_handler.batch_key, batch.id_str, entry, - exc_info=True) - msg = str(error) - if not msg and isinstance(error, NotImplementedError): - msg = "Feature is not implemented" - return {'error': msg} - - if not row: - return {'error': "Could not identify product"} - - self.Session.flush() - result = self._get(obj=row) - result['ok'] = True - return result - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._batch_row_defaults(config) - - @classmethod - def _batch_row_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - collection_url_prefix = cls.get_collection_url_prefix() - - if cls.supports_quick_entry: - - # quick entry - quick_entry = Service(name='{}.quick_entry'.format(route_prefix), - path='{}/quick-entry'.format(collection_url_prefix)) - quick_entry.add_view('POST', 'quick_entry', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(quick_entry) diff --git a/tailbone/api/batch/inventory.py b/tailbone/api/batch/inventory.py deleted file mode 100644 index 22b67e54..00000000 --- a/tailbone/api/batch/inventory.py +++ /dev/null @@ -1,200 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Inventory Batches -""" - -import decimal - -import sqlalchemy as sa - -from rattail import pod -from rattail.db.model import InventoryBatch, InventoryBatchRow - -from cornice import Service - -from tailbone.api.batch import APIBatchView, APIBatchRowView - - -class InventoryBatchViews(APIBatchView): - - model_class = InventoryBatch - default_handler_spec = 'rattail.batch.inventory:InventoryBatchHandler' - route_prefix = 'inventory' - permission_prefix = 'batch.inventory' - collection_url_prefix = '/inventory-batches' - object_url_prefix = '/inventory-batch' - supports_toggle_complete = True - - def normalize(self, batch): - data = super().normalize(batch) - - data['mode'] = batch.mode - data['mode_display'] = self.enum.INVENTORY_MODE.get(batch.mode) - if data['mode_display'] is None and batch.mode is not None: - data['mode_display'] = str(batch.mode) - - data['reason_code'] = batch.reason_code - - return data - - def count_modes(self): - """ - Retrieve info about the available batch count modes. - """ - permission_prefix = self.get_permission_prefix() - if self.request.is_root: - modes = self.batch_handler.get_count_modes() - else: - modes = self.batch_handler.get_allowed_count_modes( - self.Session(), self.request.user, - permission_prefix=permission_prefix) - return modes - - def adjustment_reasons(self): - """ - Retrieve info about the available "reasons" for inventory adjustment - batches. - """ - raw_reasons = self.batch_handler.get_adjustment_reasons(self.Session()) - reasons = [] - for reason in raw_reasons: - reasons.append({ - 'uuid': reason.uuid, - 'code': reason.code, - 'description': reason.description, - 'hidden': reason.hidden, - }) - return reasons - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._batch_defaults(config) - cls._inventory_defaults(config) - - @classmethod - def _inventory_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - collection_url_prefix = cls.get_collection_url_prefix() - - # get count modes - count_modes = Service(name='{}.count_modes'.format(route_prefix), - path='{}/count-modes'.format(collection_url_prefix)) - count_modes.add_view('GET', 'count_modes', klass=cls, - permission='{}.list'.format(permission_prefix)) - config.add_cornice_service(count_modes) - - # get adjustment reasons - adjustment_reasons = Service(name='{}.adjustment_reasons'.format(route_prefix), - path='{}/adjustment-reasons'.format(collection_url_prefix)) - adjustment_reasons.add_view('GET', 'adjustment_reasons', klass=cls, - permission='{}.list'.format(permission_prefix)) - config.add_cornice_service(adjustment_reasons) - - -class InventoryBatchRowViews(APIBatchRowView): - - model_class = InventoryBatchRow - default_handler_spec = 'rattail.batch.inventory:InventoryBatchHandler' - route_prefix = 'inventory.rows' - permission_prefix = 'batch.inventory' - collection_url_prefix = '/inventory-batch-rows' - object_url_prefix = '/inventory-batch-row' - editable = True - supports_quick_entry = True - - def normalize(self, row): - batch = row.batch - data = super().normalize(row) - app = self.get_rattail_app() - - data['item_id'] = row.item_id - data['upc'] = str(row.upc) - data['upc_pretty'] = row.upc.pretty() if row.upc else None - data['brand_name'] = row.brand_name - data['description'] = row.description - data['size'] = row.size - data['full_description'] = row.product.full_description if row.product else row.description - data['image_url'] = pod.get_image_url(self.rattail_config, row.upc) if row.upc else None - data['case_quantity'] = app.render_quantity(row.case_quantity or 1) - - data['cases'] = row.cases - data['units'] = row.units - data['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA' - data['quantity_display'] = "{} {}".format( - app.render_quantity(row.cases or row.units), - 'CS' if row.cases else data['unit_uom']) - - data['allow_cases'] = self.batch_handler.allow_cases(batch) - - return data - - def update_object(self, row, data): - """ - Supplements the default logic as follows: - - Converts certain fields within the data, to proper "native" types. - """ - data = dict(data) - - # convert some data types as needed - if 'cases' in data: - if data['cases'] == '': - data['cases'] = None - elif data['cases']: - data['cases'] = decimal.Decimal(data['cases']) - if 'units' in data: - if data['units'] == '': - data['units'] = None - elif data['units']: - data['units'] = decimal.Decimal(data['units']) - - # update row per usual - try: - row = super().update_object(row, data) - except sa.exc.DataError as error: - # detect when user scans barcode for cases/units field - if hasattr(error, 'orig'): - orig = type(error.orig) - if hasattr(orig, '__name__'): - # nb. this particular error is from psycopg2 - if orig.__name__ == 'NumericValueOutOfRange': - return {'error': "Numeric value out of range"} - raise - return row - - -def defaults(config, **kwargs): - base = globals() - - InventoryBatchViews = kwargs.get('InventoryBatchViews', base['InventoryBatchViews']) - InventoryBatchViews.defaults(config) - - InventoryBatchRowViews = kwargs.get('InventoryBatchRowViews', base['InventoryBatchRowViews']) - InventoryBatchRowViews.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/batch/labels.py b/tailbone/api/batch/labels.py deleted file mode 100644 index 4f154b21..00000000 --- a/tailbone/api/batch/labels.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Label Batches -""" - -from rattail.db import model - -from tailbone.api.batch import APIBatchView, APIBatchRowView - - -class LabelBatchViews(APIBatchView): - - model_class = model.LabelBatch - default_handler_spec = 'rattail.batch.labels:LabelBatchHandler' - route_prefix = 'labelbatchviews' - permission_prefix = 'labels.batch' - collection_url_prefix = '/label-batches' - object_url_prefix = '/label-batch' - supports_toggle_complete = True - - -class LabelBatchRowViews(APIBatchRowView): - - model_class = model.LabelBatchRow - default_handler_spec = 'rattail.batch.labels:LabelBatchHandler' - route_prefix = 'api.label_batch_rows' - permission_prefix = 'labels.batch' - collection_url_prefix = '/label-batch-rows' - object_url_prefix = '/label-batch-row' - supports_quick_entry = True - - def normalize(self, row): - batch = row.batch - data = super().normalize(row) - - data['item_id'] = row.item_id - data['upc'] = str(row.upc) - data['upc_pretty'] = row.upc.pretty() if row.upc else None - data['brand_name'] = row.brand_name - data['description'] = row.description - data['size'] = row.size - data['full_description'] = row.product.full_description if row.product else row.description - return data - - -def defaults(config, **kwargs): - base = globals() - - LabelBatchViews = kwargs.get('LabelBatchViews', base['LabelBatchViews']) - LabelBatchViews.defaults(config) - - LabelBatchRowViews = kwargs.get('LabelBatchRowViews', base['LabelBatchRowViews']) - LabelBatchRowViews.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/batch/ordering.py b/tailbone/api/batch/ordering.py deleted file mode 100644 index 204be8ad..00000000 --- a/tailbone/api/batch/ordering.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Ordering Batches - -These views expose the basic CRUD interface to "ordering" batches, for the web -API. -""" - -import datetime -import logging - -import sqlalchemy as sa - -from rattail.db.model import PurchaseBatch, PurchaseBatchRow - -from cornice import Service - -from tailbone.api.batch import APIBatchView, APIBatchRowView - - -log = logging.getLogger(__name__) - - -class OrderingBatchViews(APIBatchView): - - model_class = PurchaseBatch - default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler' - route_prefix = 'orderingbatchviews' - permission_prefix = 'ordering' - collection_url_prefix = '/ordering-batches' - object_url_prefix = '/ordering-batch' - supports_toggle_complete = True - supports_execute = True - - def base_query(self): - """ - Modifies the default logic as follows: - - Adds a condition to the query, to ensure only purchase batches with - "ordering" mode are returned. - """ - model = self.model - query = super().base_query() - query = query.filter(model.PurchaseBatch.mode == self.enum.PURCHASE_BATCH_MODE_ORDERING) - return query - - def normalize(self, batch): - data = super().normalize(batch) - - data['vendor_uuid'] = batch.vendor.uuid - data['vendor_display'] = str(batch.vendor) - - data['department_uuid'] = batch.department_uuid - data['department_display'] = str(batch.department) if batch.department else None - - data['po_total_calculated_display'] = "${:0.2f}".format(batch.po_total_calculated or 0) - data['ship_method'] = batch.ship_method - data['notes_to_vendor'] = batch.notes_to_vendor - return data - - def create_object(self, data): - """ - Modifies the default logic as follows: - - Sets the mode to "ordering" for the new batch. - """ - data = dict(data) - if not data.get('vendor_uuid'): - raise ValueError("You must specify the vendor") - data['mode'] = self.enum.PURCHASE_BATCH_MODE_ORDERING - batch = super().create_object(data) - return batch - - def worksheet(self): - """ - Returns primary data for the Ordering Worksheet view. - """ - batch = self.get_object() - if batch.executed: - raise self.forbidden() - - app = self.get_rattail_app() - - # TODO: much of the logic below was copied from the traditional master - # view for ordering batches. should maybe let them share it somehow? - - # organize existing batch rows by product - order_items = {} - for row in batch.active_rows(): - order_items[row.product_uuid] = row - - # organize vendor catalog costs by dept / subdept - departments = {} - costs = self.batch_handler.get_order_form_costs(self.Session(), batch.vendor) - costs = self.batch_handler.sort_order_form_costs(costs) - costs = list(costs) # we must have a stable list for the rest of this - self.batch_handler.decorate_order_form_costs(batch, costs) - for cost in costs: - - department = cost.product.department - if department: - department_dict = departments.setdefault(department.uuid, { - 'uuid': department.uuid, - 'number': department.number, - 'name': department.name, - }) - else: - if None not in departments: - departments[None] = { - 'uuid': None, - 'number': None, - 'name': "", - } - department_dict = departments[None] - - subdepartments = department_dict.setdefault('subdepartments', {}) - - subdepartment = cost.product.subdepartment - if subdepartment: - subdepartment_dict = subdepartments.setdefault(subdepartment.uuid, { - 'uuid': subdepartment.uuid, - 'number': subdepartment.number, - 'name': subdepartment.name, - }) - else: - if None not in subdepartments: - subdepartments[None] = { - 'uuid': None, - 'number': None, - 'name': "", - } - subdepartment_dict = subdepartments[None] - - subdept_costs = subdepartment_dict.setdefault('costs', []) - product = cost.product - subdept_costs.append({ - 'uuid': cost.uuid, - 'upc': str(product.upc), - 'upc_pretty': product.upc.pretty() if product.upc else None, - 'brand_name': product.brand.name if product.brand else None, - 'description': product.description, - 'size': product.size, - 'case_size': cost.case_size, - 'uom_display': "LB" if product.weighed else "EA", - 'vendor_item_code': cost.code, - 'preference': cost.preference, - 'preferred': cost.preference == 1, - 'unit_cost': cost.unit_cost, - 'unit_cost_display': "${:0.2f}".format(cost.unit_cost) if cost.unit_cost is not None else "", - # TODO - # 'cases_ordered': None, - # 'units_ordered': None, - # 'po_total': None, - # 'po_total_display': None, - }) - - # sort the (sub)department groupings - sorted_departments = [] - for dept in sorted(departments.values(), key=lambda d: d['name']): - dept['subdepartments'] = sorted(dept['subdepartments'].values(), - key=lambda s: s['name']) - sorted_departments.append(dept) - - # fetch recent purchase history, sort/pad for template convenience - history = self.batch_handler.get_order_form_history(batch, costs, 6) - for i in range(6 - len(history)): - history.append(None) - history = list(reversed(history)) - # must convert some date objects to string, for JSON sake - for h in history: - if not h: - continue - purchase = h.get('purchase') - if purchase: - dt = purchase.get('date_ordered') - if dt and isinstance(dt, datetime.date): - purchase['date_ordered'] = app.render_date(dt) - dt = purchase.get('date_received') - if dt and isinstance(dt, datetime.date): - purchase['date_received'] = app.render_date(dt) - - return { - 'batch': self.normalize(batch), - 'departments': departments, - 'sorted_departments': sorted_departments, - 'history': history, - } - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._batch_defaults(config) - cls._ordering_batch_defaults(config) - - @classmethod - def _ordering_batch_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - object_url_prefix = cls.get_object_url_prefix() - - # worksheet - worksheet = Service(name='{}.worksheet'.format(route_prefix), - path='{}/{{uuid}}/worksheet'.format(object_url_prefix)) - worksheet.add_view('GET', 'worksheet', klass=cls, - permission='{}.worksheet'.format(permission_prefix)) - config.add_cornice_service(worksheet) - - -class OrderingBatchRowViews(APIBatchRowView): - - model_class = PurchaseBatchRow - default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler' - route_prefix = 'ordering.rows' - permission_prefix = 'ordering' - collection_url_prefix = '/ordering-batch-rows' - object_url_prefix = '/ordering-batch-row' - supports_quick_entry = True - editable = True - - def normalize(self, row): - data = super().normalize(row) - app = self.get_rattail_app() - batch = row.batch - - data['item_id'] = row.item_id - data['upc'] = str(row.upc) - data['upc_pretty'] = row.upc.pretty() if row.upc else None - data['brand_name'] = row.brand_name - data['description'] = row.description - data['size'] = row.size - data['full_description'] = row.product.full_description if row.product else row.description - - # # only provide image url if so configured - # if self.rattail_config.getbool('rattail.batch', 'purchase.mobile_images', default=True): - # data['image_url'] = pod.get_image_url(self.rattail_config, row.upc) if row.upc else None - - # unit_uom can vary by product - data['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA' - - data['case_quantity'] = row.case_quantity - data['cases_ordered'] = row.cases_ordered - data['units_ordered'] = row.units_ordered - data['cases_ordered_display'] = app.render_quantity(row.cases_ordered or 0, empty_zero=False) - data['units_ordered_display'] = app.render_quantity(row.units_ordered or 0, empty_zero=False) - - data['po_unit_cost'] = row.po_unit_cost - data['po_unit_cost_display'] = "${:0.2f}".format(row.po_unit_cost) if row.po_unit_cost is not None else None - data['po_total_calculated'] = row.po_total_calculated - data['po_total_calculated_display'] = "${:0.2f}".format(row.po_total_calculated) if row.po_total_calculated is not None else None - data['status_code'] = row.status_code - data['status_display'] = row.STATUS.get(row.status_code, str(row.status_code)) - - return data - - def update_object(self, row, data): - """ - Overrides the default logic as follows: - - So far, we only allow updating the ``cases_ordered`` and/or - ``units_ordered`` quantities; therefore ``data`` should have one or - both of those keys. - - This data is then passed to the - :meth:`~rattail:rattail.batch.purchase.PurchaseBatchHandler.update_row_quantity()` - method of the batch handler. - - Note that the "normal" logic for this method is not invoked at all. - """ - if not self.batch_handler.is_mutable(row.batch): - return {'error': "Batch is not mutable"} - - try: - self.batch_handler.update_row_quantity(row, **data) - self.Session.flush() - except Exception as error: - log.warning("update_row_quantity failed", exc_info=True) - if isinstance(error, sa.exc.DataError) and hasattr(error, 'orig'): - error = str(error.orig) - else: - error = str(error) - return {'error': error} - - return row - - -def defaults(config, **kwargs): - base = globals() - - OrderingBatchViews = kwargs.get('OrderingBatchViews', base['OrderingBatchViews']) - OrderingBatchViews.defaults(config) - - OrderingBatchRowViews = kwargs.get('OrderingBatchRowViews', base['OrderingBatchRowViews']) - OrderingBatchRowViews.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/batch/receiving.py b/tailbone/api/batch/receiving.py deleted file mode 100644 index b23bff55..00000000 --- a/tailbone/api/batch/receiving.py +++ /dev/null @@ -1,492 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Receiving Batches -""" - -import logging - -import humanize -import sqlalchemy as sa - -from rattail.db.model import PurchaseBatch, PurchaseBatchRow - -from cornice import Service -from deform import widget as dfwidget - -from tailbone import forms -from tailbone.api.batch import APIBatchView, APIBatchRowView -from tailbone.forms.receiving import ReceiveRow - - -log = logging.getLogger(__name__) - - -class ReceivingBatchViews(APIBatchView): - - model_class = PurchaseBatch - default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler' - route_prefix = 'receivingbatchviews' - permission_prefix = 'receiving' - collection_url_prefix = '/receiving-batches' - object_url_prefix = '/receiving-batch' - supports_toggle_complete = True - supports_execute = True - - def base_query(self): - model = self.app.model - query = super().base_query() - query = query.filter(model.PurchaseBatch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING) - return query - - def normalize(self, batch): - data = super().normalize(batch) - - data['vendor_uuid'] = batch.vendor.uuid - data['vendor_display'] = str(batch.vendor) - - data['department_uuid'] = batch.department_uuid - data['department_display'] = str(batch.department) if batch.department else None - - data['po_number'] = batch.po_number - data['po_total'] = batch.po_total - data['invoice_total'] = batch.invoice_total - data['invoice_total_calculated'] = batch.invoice_total_calculated - - data['can_auto_receive'] = self.batch_handler.can_auto_receive(batch) - - return data - - def create_object(self, data): - data = dict(data) - - # all about receiving mode here - data['mode'] = self.enum.PURCHASE_BATCH_MODE_RECEIVING - - # assume "receive from PO" if given a PO key - if data.get('purchase_key'): - data['workflow'] = 'from_po' - - return super().create_object(data) - - def auto_receive(self): - """ - View which handles auto-marking as received, all items within - a pending batch. - """ - batch = self.get_object() - self.batch_handler.auto_receive_all_items(batch) - return self._get(obj=batch) - - def mark_receiving_complete(self): - """ - Mark the given batch as "receiving complete". - """ - batch = self.get_object() - - if batch.executed: - return {'error': "Batch {} has already been executed: {}".format( - batch.id_str, batch.description)} - - if batch.complete: - return {'error': "Batch {} is already marked complete: {}".format( - batch.id_str, batch.description)} - - if batch.receiving_complete: - return {'error': "Receiving is already complete for batch {}: {}".format( - batch.id_str, batch.description)} - - batch.receiving_complete = True - return self._get(obj=batch) - - def eligible_purchases(self): - model = self.app.model - uuid = self.request.params.get('vendor_uuid') - vendor = self.Session.get(model.Vendor, uuid) if uuid else None - if not vendor: - return {'error': "Vendor not found"} - - purchases = self.batch_handler.get_eligible_purchases( - vendor, self.enum.PURCHASE_BATCH_MODE_RECEIVING) - - purchases = [self.normalize_eligible_purchase(p) - for p in purchases] - - return {'purchases': purchases} - - def normalize_eligible_purchase(self, purchase): - return self.batch_handler.normalize_eligible_purchase(purchase) - - def render_eligible_purchase(self, purchase): - return self.batch_handler.render_eligible_purchase(purchase) - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._batch_defaults(config) - cls._receiving_batch_defaults(config) - - @classmethod - def _receiving_batch_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - collection_url_prefix = cls.get_collection_url_prefix() - object_url_prefix = cls.get_object_url_prefix() - - # auto_receive - auto_receive = Service(name='{}.auto_receive'.format(route_prefix), - path='{}/{{uuid}}/auto-receive'.format(object_url_prefix)) - auto_receive.add_view('GET', 'auto_receive', klass=cls, - permission='{}.auto_receive'.format(permission_prefix)) - config.add_cornice_service(auto_receive) - - # mark_receiving_complete - mark_receiving_complete = Service(name='{}.mark_receiving_complete'.format(route_prefix), - path='{}/{{uuid}}/mark-receiving-complete'.format(object_url_prefix)) - mark_receiving_complete.add_view('POST', 'mark_receiving_complete', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(mark_receiving_complete) - - # eligible purchases - eligible_purchases = Service(name='{}.eligible_purchases'.format(route_prefix), - path='{}/eligible-purchases'.format(collection_url_prefix)) - eligible_purchases.add_view('GET', 'eligible_purchases', klass=cls, - permission='{}.create'.format(permission_prefix)) - config.add_cornice_service(eligible_purchases) - - -class ReceivingBatchRowViews(APIBatchRowView): - - model_class = PurchaseBatchRow - default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler' - route_prefix = 'receiving.rows' - permission_prefix = 'receiving' - collection_url_prefix = '/receiving-batch-rows' - object_url_prefix = '/receiving-batch-row' - supports_quick_entry = True - - def make_filter_spec(self): - model = self.app.model - filters = super().make_filter_spec() - if filters: - - # must translate certain convenience filters - orig_filters, filters = filters, [] - for filtr in orig_filters: - - # # is_received - # # NOTE: this is only relevant for truck dump or "from scratch" - # if filtr['field'] == 'is_received' and filtr['op'] == 'eq' and filtr['value'] is True: - # filters.extend([ - # {'or': [ - # {'field': 'cases_received', 'op': '!=', 'value': 0}, - # {'field': 'units_received', 'op': '!=', 'value': 0}, - # ]}, - # ]) - - # is_incomplete - if filtr['field'] == 'is_incomplete' and filtr['op'] == 'eq' and filtr['value'] is True: - # looking for any rows with "ordered" quantity, but where the - # status does *not* signify a "settled" row so to speak - # TODO: would be nice if we had a simple flag to leverage? - filters.extend([ - {'or': [ - {'field': 'cases_ordered', 'op': '!=', 'value': 0}, - {'field': 'units_ordered', 'op': '!=', 'value': 0}, - ]}, - {'field': 'status_code', 'op': 'not_in', 'value': [ - model.PurchaseBatchRow.STATUS_OK, - model.PurchaseBatchRow.STATUS_PRODUCT_NOT_FOUND, - model.PurchaseBatchRow.STATUS_CASE_QUANTITY_DIFFERS, - ]}, - ]) - - # is_invalid - elif filtr['field'] == 'is_invalid' and filtr['op'] == 'eq' and filtr['value'] is True: - filters.extend([ - {'field': 'status_code', 'op': 'in', 'value': [ - model.PurchaseBatchRow.STATUS_PRODUCT_NOT_FOUND, - model.PurchaseBatchRow.STATUS_COST_NOT_FOUND, - model.PurchaseBatchRow.STATUS_CASE_QUANTITY_UNKNOWN, - model.PurchaseBatchRow.STATUS_CASE_QUANTITY_DIFFERS, - ]}, - ]) - - # is_unexpected - elif filtr['field'] == 'is_unexpected' and filtr['op'] == 'eq' and filtr['value'] is True: - # looking for any rows which do *not* have "ordered/shipped" quantity - filters.extend([ - {'and': [ - {'or': [ - {'field': 'cases_ordered', 'op': 'is_null'}, - {'field': 'cases_ordered', 'op': '==', 'value': 0}, - ]}, - {'or': [ - {'field': 'units_ordered', 'op': 'is_null'}, - {'field': 'units_ordered', 'op': '==', 'value': 0}, - ]}, - {'or': [ - {'field': 'cases_shipped', 'op': 'is_null'}, - {'field': 'cases_shipped', 'op': '==', 'value': 0}, - ]}, - {'or': [ - {'field': 'units_shipped', 'op': 'is_null'}, - {'field': 'units_shipped', 'op': '==', 'value': 0}, - ]}, - {'or': [ - # but "unexpected" also implies we have some confirmed amount(s) - {'field': 'cases_received', 'op': '!=', 'value': 0}, - {'field': 'units_received', 'op': '!=', 'value': 0}, - {'field': 'cases_damaged', 'op': '!=', 'value': 0}, - {'field': 'units_damaged', 'op': '!=', 'value': 0}, - {'field': 'cases_expired', 'op': '!=', 'value': 0}, - {'field': 'units_expired', 'op': '!=', 'value': 0}, - ]}, - ]}, - ]) - - # is_damaged - elif filtr['field'] == 'is_damaged' and filtr['op'] == 'eq' and filtr['value'] is True: - filters.extend([ - {'or': [ - {'field': 'cases_damaged', 'op': '!=', 'value': 0}, - {'field': 'units_damaged', 'op': '!=', 'value': 0}, - ]}, - ]) - - # is_expired - elif filtr['field'] == 'is_expired' and filtr['op'] == 'eq' and filtr['value'] is True: - filters.extend([ - {'or': [ - {'field': 'cases_expired', 'op': '!=', 'value': 0}, - {'field': 'units_expired', 'op': '!=', 'value': 0}, - ]}, - ]) - - # is_missing - elif filtr['field'] == 'is_missing' and filtr['op'] == 'eq' and filtr['value'] is True: - filters.extend([ - {'or': [ - {'field': 'cases_missing', 'op': '!=', 'value': 0}, - {'field': 'units_missing', 'op': '!=', 'value': 0}, - ]}, - ]) - - else: # just some filter, use as-is - filters.append(filtr) - - return filters - - def normalize(self, row): - data = super().normalize(row) - model = self.app.model - - batch = row.batch - prodder = self.app.get_products_handler() - - data['product_uuid'] = row.product_uuid - data['item_id'] = row.item_id - data['upc'] = str(row.upc) - data['upc_pretty'] = row.upc.pretty() if row.upc else None - data['brand_name'] = row.brand_name - data['description'] = row.description - data['size'] = row.size - data['full_description'] = row.product.full_description if row.product else row.description - - # only provide image url if so configured - if self.rattail_config.getbool('rattail.batch', 'purchase.mobile_images', default=True): - data['image_url'] = prodder.get_image_url(product=row.product, upc=row.upc) - - # unit_uom can vary by product - data['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA' - - data['case_quantity'] = row.case_quantity - data['order_quantities_known'] = batch.order_quantities_known - - data['cases_ordered'] = row.cases_ordered - data['units_ordered'] = row.units_ordered - - data['cases_shipped'] = row.cases_shipped - data['units_shipped'] = row.units_shipped - - data['cases_received'] = row.cases_received - data['units_received'] = row.units_received - - data['cases_damaged'] = row.cases_damaged - data['units_damaged'] = row.units_damaged - - data['cases_expired'] = row.cases_expired - data['units_expired'] = row.units_expired - - data['cases_missing'] = row.cases_missing - data['units_missing'] = row.units_missing - - cases, units = self.batch_handler.get_unconfirmed_counts(row) - data['cases_unconfirmed'] = cases - data['units_unconfirmed'] = units - - data['po_unit_cost'] = row.po_unit_cost - data['po_total'] = row.po_total - - data['invoice_number'] = row.invoice_number - data['invoice_unit_cost'] = row.invoice_unit_cost - data['invoice_total'] = row.invoice_total - data['invoice_total_calculated'] = row.invoice_total_calculated - - data['allow_cases'] = self.batch_handler.allow_cases() - - data['quick_receive'] = self.rattail_config.getbool( - 'rattail.batch', 'purchase.mobile_quick_receive', - default=True) - - if batch.order_quantities_known: - data['quick_receive_all'] = self.rattail_config.getbool( - 'rattail.batch', 'purchase.mobile_quick_receive_all', - default=False) - - # TODO: this was copied from regular view receive_row() method; should merge - if data['quick_receive'] and data.get('quick_receive_all'): - if data['allow_cases']: - data['quick_receive_uom'] = 'CS' - raise NotImplementedError("TODO: add CS support for quick_receive_all") - else: - data['quick_receive_uom'] = data['unit_uom'] - accounted_for = self.batch_handler.get_units_accounted_for(row) - remainder = self.batch_handler.get_units_ordered(row) - accounted_for - - if accounted_for: - # some product accounted for; button should receive "remainder" only - if remainder: - remainder = self.app.render_quantity(remainder) - data['quick_receive_quantity'] = remainder - data['quick_receive_text'] = "Receive Remainder ({} {})".format( - remainder, data['unit_uom']) - else: - # unless there is no remainder, in which case disable it - data['quick_receive'] = False - - else: # nothing yet accounted for, button should receive "all" - if not remainder: - log.warning("quick receive remainder is empty for row %s", row.uuid) - remainder = self.app.render_quantity(remainder) - data['quick_receive_quantity'] = remainder - data['quick_receive_text'] = "Receive ALL ({} {})".format( - remainder, data['unit_uom']) - - data['unexpected_alert'] = None - if batch.order_quantities_known and not row.cases_ordered and not row.units_ordered: - warn = True - if batch.is_truck_dump_parent() and row.product: - uuids = [child.uuid for child in batch.truck_dump_children] - if uuids: - count = self.Session.query(model.PurchaseBatchRow)\ - .filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\ - .filter(model.PurchaseBatchRow.product == row.product)\ - .count() - if count: - warn = False - if warn: - data['unexpected_alert'] = "This item was NOT on the original purchase order." - - # TODO: surely the caller of API should determine this flag? - # maybe alert user if they've already received some of this product - alert_received = self.rattail_config.getbool('tailbone', 'receiving.alert_already_received', - default=False) - if alert_received: - data['received_alert'] = None - if self.batch_handler.get_units_confirmed(row): - msg = "You have already received some of this product; last update was {}.".format( - humanize.naturaltime(self.app.make_utc() - row.modified)) - data['received_alert'] = msg - - return data - - def receive(self): - """ - View which handles "receiving" against a particular batch row. - """ - model = self.app.model - - # first do basic input validation - schema = ReceiveRow().bind(session=self.Session()) - form = forms.Form(schema=schema, request=self.request) - # TODO: this seems hacky, but avoids "complex" date value parsing - form.set_widget('expiration_date', dfwidget.TextInputWidget()) - if not form.validate(): - log.warning("form did not validate: %s", - form.make_deform_form().error) - return {'error': "Form did not validate"} - - # fetch / validate row object - row = self.Session.get(model.PurchaseBatchRow, form.validated['row']) - if row is not self.get_object(): - return {'error': "Specified row does not match the route!"} - - # handler takes care of the row receiving logic for us - kwargs = dict(form.validated) - del kwargs['row'] - try: - self.batch_handler.receive_row(row, **kwargs) - self.Session.flush() - except Exception as error: - log.warning("receive() failed", exc_info=True) - if isinstance(error, sa.exc.DataError) and hasattr(error, 'orig'): - error = str(error.orig) - else: - error = str(error) - return {'error': error} - - return self._get(obj=row) - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._batch_row_defaults(config) - cls._receiving_batch_row_defaults(config) - - @classmethod - def _receiving_batch_row_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - object_url_prefix = cls.get_object_url_prefix() - - # receive (row) - receive = Service(name='{}.receive'.format(route_prefix), - path='{}/{{uuid}}/receive'.format(object_url_prefix)) - receive.add_view('POST', 'receive', klass=cls, - permission='{}.edit_row'.format(permission_prefix)) - config.add_cornice_service(receive) - - -def defaults(config, **kwargs): - base = globals() - - ReceivingBatchViews = kwargs.get('ReceivingBatchViews', base['ReceivingBatchViews']) - ReceivingBatchViews.defaults(config) - - ReceivingBatchRowViews = kwargs.get('ReceivingBatchRowViews', base['ReceivingBatchRowViews']) - ReceivingBatchRowViews.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/common.py b/tailbone/api/common.py deleted file mode 100644 index 6cacfb06..00000000 --- a/tailbone/api/common.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - "Common" Views -""" - -from collections import OrderedDict - -from rattail.util import get_pkg_version - -from cornice import Service -from cornice.service import get_services -from cornice_swagger import CorniceSwagger - -from tailbone import forms -from tailbone.forms.common import Feedback -from tailbone.api import APIView, api -from tailbone.db import Session - - -class CommonView(APIView): - """ - Misc. "common" views for the API. - - .. attribute:: feedback_email_key - - This is the email key which will be used when sending "user feedback" - email. Default value is ``'user_feedback'``. - """ - feedback_email_key = 'user_feedback' - - @api - def about(self): - """ - Generic view to show "about project" info page. - """ - packages = self.get_packages() - return { - 'project_title': self.get_project_title(), - 'project_version': self.get_project_version(), - 'packages': packages, - 'package_names': list(packages), - } - - def get_project_title(self): - app = self.get_rattail_app() - return app.get_title() - - def get_project_version(self): - app = self.get_rattail_app() - return app.get_version() - - def get_packages(self): - """ - Should return the full set of packages which should be displayed on the - 'about' page. - """ - return OrderedDict([ - ('rattail', get_pkg_version('rattail')), - ('Tailbone', get_pkg_version('Tailbone')), - ]) - - @api - def feedback(self): - """ - View to handle user feedback form submits. - """ - app = self.get_rattail_app() - model = self.model - # TODO: this logic was copied from tailbone.views.common and is largely - # identical; perhaps should merge somehow? - schema = Feedback().bind(session=Session()) - form = forms.Form(schema=schema, request=self.request) - if form.validate(): - data = dict(form.validated) - - # figure out who the sending user is, if any - if self.request.user: - data['user'] = self.request.user - elif data['user']: - data['user'] = Session.get(model.User, data['user']) - - # TODO: should provide URL to view user - if data['user']: - data['user_url'] = '#' # TODO: could get from config? - - data['client_ip'] = self.request.client_addr - email_key = data['email_key'] or self.feedback_email_key - app.send_email(email_key, data=data) - return {'ok': True} - - return {'error': "Form did not validate!"} - - def swagger(self): - doc = CorniceSwagger(get_services()) - app = self.get_rattail_app() - spec = doc.generate(f"{app.get_node_title()} API docs", - app.get_version(), - base_path='/api') # TODO - return spec - - @classmethod - def defaults(cls, config): - cls._common_defaults(config) - - @classmethod - def _common_defaults(cls, config): - rattail_config = config.registry.settings.get('rattail_config') - app = rattail_config.get_app() - - # about - about = Service(name='about', path='/about') - about.add_view('GET', 'about', klass=cls) - config.add_cornice_service(about) - - # feedback - feedback = Service(name='feedback', path='/feedback') - feedback.add_view('POST', 'feedback', klass=cls, - permission='common.feedback') - config.add_cornice_service(feedback) - - # swagger - swagger = Service(name='swagger', - path='/swagger.json', - description=f"OpenAPI documentation for {app.get_title()}") - swagger.add_view('GET', 'swagger', klass=cls, - permission='common.api_swagger') - config.add_cornice_service(swagger) - - -def defaults(config, **kwargs): - base = globals() - - CommonView = kwargs.get('CommonView', base['CommonView']) - CommonView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/core.py b/tailbone/api/core.py deleted file mode 100644 index 0d8eec32..00000000 --- a/tailbone/api/core.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Core Views -""" - -from tailbone.views import View - - -def api(view_meth): - """ - Common decorator for all API views. Ideally this would not be needed..but - for now, alas, it is. - """ - def wrapped(view, *args, **kwargs): - - # TODO: why doesn't this work here...? (instead we have to repeat this - # code in lots of other places) - # if view.request.method == 'OPTIONS': - # return view.request.response - - # invoke the view logic first, since presumably it may involve a - # redirect in which case we don't really need to add the CSRF token. - # main known use case for this is the /logout endpoint - if that gets - # hit then the "current" (old) session will be destroyed, in which case - # we can't use the token from that, but instead must generate a new one. - result = view_meth(view, *args, **kwargs) - - # explicitly set CSRF token cookie, unless OPTIONS request - # TODO: why doesn't pyramid do this for us again? - if view.request.method != 'OPTIONS': - view.request.response.set_cookie(name='XSRF-TOKEN', - value=view.request.session.get_csrf_token()) - - return result - - return wrapped - - -class APIView(View): - """ - Base class for all API views. - """ - - def pretty_datetime(self, dt): - if not dt: - return "" - return dt.strftime('%Y-%m-%d @ %I:%M %p') - - def get_user_info(self, user): - """ - This method is present on *all* API views, and is meant to provide a - single means of obtaining "common" user info, for return to the caller. - Such info may be returned in several places, e.g. upon login but also - in the "check session" call, or e.g. as part of a broader return value - from any other call. - - :returns: Dictionary of user info data, ready for JSON serialization. - - Note that you should *not* (usually) override this method in any view, - but instead configure a "supplemental" function which can then add or - replace info entries. Config for that looks like e.g.: - - .. code-block:: ini - - [tailbone.api] - extra_user_info = poser.web.api.util:extra_user_info - - Note that the above config assumes a simple *function* defined in your - ``util`` module; such a function would look like e.g.:: - - def extra_user_info(request, user, **info): - # add favorite color - info['favorite_color'] = 'green' - # override display name - info['display_name'] = "TODO" - # remove short_name - info.pop('short_name', None) - return info - """ - app = self.get_rattail_app() - auth = app.get_auth_handler() - - # basic / default info - is_admin = auth.user_is_admin(user) - employee = app.get_employee(user) - info = { - 'uuid': user.uuid, - 'username': user.username, - 'display_name': user.display_name, - 'short_name': auth.get_short_display_name(user), - 'is_admin': is_admin, - 'is_root': is_admin and self.request.session.get('is_root', False), - 'employee_uuid': employee.uuid if employee else None, - 'email_address': app.get_contact_email_address(user), - } - - # maybe get/use "extra" info - extra = self.rattail_config.get('tailbone.api', 'extra_user_info', - usedb=False) - if extra: - extra = app.load_object(extra) - info = extra(self.request, user, **info) - - return info diff --git a/tailbone/api/customers.py b/tailbone/api/customers.py deleted file mode 100644 index 85d28c24..00000000 --- a/tailbone/api/customers.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Customer Views -""" - -from rattail.db import model - -from tailbone.api import APIMasterView - - -class CustomerView(APIMasterView): - """ - API views for Customer data - """ - model_class = model.Customer - collection_url_prefix = '/customers' - object_url_prefix = '/customer' - supports_autocomplete = True - autocomplete_fieldname = 'name' - - def normalize(self, customer): - return { - 'uuid': customer.uuid, - '_str': str(customer), - 'id': customer.id, - 'number': customer.number, - 'name': customer.name, - } - - -def defaults(config, **kwargs): - base = globals() - - CustomerView = kwargs.get('CustomerView', base['CustomerView']) - CustomerView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/essentials.py b/tailbone/api/essentials.py deleted file mode 100644 index 7b151578..00000000 --- a/tailbone/api/essentials.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Essential views for convenient includes -""" - - -def defaults(config, **kwargs): - mod = lambda spec: kwargs.get(spec, spec) - - config.include(mod('tailbone.api.auth')) - config.include(mod('tailbone.api.common')) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/master.py b/tailbone/api/master.py deleted file mode 100644 index 551d6428..00000000 --- a/tailbone/api/master.py +++ /dev/null @@ -1,618 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Master View -""" - -import json - -from rattail.db.util import get_fieldnames - -from cornice import resource, Service - -from tailbone.api import APIView -from tailbone.db import Session -from tailbone.util import SortColumn - - -class APIMasterView(APIView): - """ - Base class for data model REST API views. - """ - listable = True - creatable = True - viewable = True - editable = True - deletable = True - supports_autocomplete = False - supports_download = False - supports_rawbytes = False - - @property - def Session(self): - return Session - - @classmethod - def get_model_class(cls): - if hasattr(cls, 'model_class'): - return cls.model_class - raise NotImplementedError("must set `model_class` for {}".format(cls.__name__)) - - @classmethod - def get_normalized_model_name(cls): - if hasattr(cls, 'normalized_model_name'): - return cls.normalized_model_name - return cls.get_model_class().__name__.lower() - - @classmethod - def get_route_prefix(cls): - """ - Returns a prefix which (by default) applies to all routes provided by - this view class. - """ - prefix = getattr(cls, 'route_prefix', None) - if prefix: - return prefix - model_name = cls.get_normalized_model_name() - return '{}s'.format(model_name) - - @classmethod - def get_permission_prefix(cls): - """ - Returns a prefix which (by default) applies to all permissions - leveraged by this view class. - """ - prefix = getattr(cls, 'permission_prefix', None) - if prefix: - return prefix - return cls.get_route_prefix() - - @classmethod - def get_collection_url_prefix(cls): - """ - Returns a prefix which (by default) applies to all "collection" URLs - provided by this view class. - """ - prefix = getattr(cls, 'collection_url_prefix', None) - if prefix: - return prefix - return '/{}'.format(cls.get_route_prefix()) - - @classmethod - def get_object_url_prefix(cls): - """ - Returns a prefix which (by default) applies to all "object" URLs - provided by this view class. - """ - prefix = getattr(cls, 'object_url_prefix', None) - if prefix: - return prefix - return '/{}'.format(cls.get_route_prefix()) - - @classmethod - def get_object_key(cls): - if hasattr(cls, 'object_key'): - return cls.object_key - return cls.get_normalized_model_name() - - @classmethod - def get_collection_key(cls): - if hasattr(cls, 'collection_key'): - return cls.collection_key - return '{}s'.format(cls.get_object_key()) - - @classmethod - def establish_method(cls, method_name): - """ - Establish the given HTTP method for this Cornice Resource. - - Cornice will auto-register any class methods for a resource, if they - are named according to what it expects (i.e. 'get', 'collection_get' - etc.). Tailbone API tries to make things automagical for the sake of - e.g. Poser logic, but in this case if we predefine all of these methods - and then some subclass view wants to *not* allow one, it's not clear - how to "undefine" it per se. Or at least, the more straightforward - thing (I think) is to not define such a method in the first place, if - it was not wanted. - - Enter ``establish_method()``, which is what finally "defines" each - resource method according to what the subclass has declared via its - various attributes (:attr:`creatable`, :attr:`deletable` etc.). - - Note that you will not likely have any need to use this - ``establish_method()`` yourself! But we describe its purpose here, for - clarity. - """ - def method(self): - internal_method = getattr(self, '_{}'.format(method_name)) - return internal_method() - - setattr(cls, method_name, method) - - def make_filter_spec(self): - if not self.request.GET.has_key('filters'): - return [] - - filters = json.loads(self.request.GET.getone('filters')) - return filters - - def make_sort_spec(self): - - # we prefer a "native sort" - if self.request.GET.has_key('nativeSort'): - return json.loads(self.request.GET.getone('nativeSort')) - - # these params are based on 'vuetable-2' - # https://www.vuetable.com/guide/sorting.html#initial-sorting-order - if 'sort' in self.request.params: - sort = self.request.params['sort'] - sortkey, sortdir = sort.split('|') - if sortdir != 'desc': - sortdir = 'asc' - return [ - { - # 'model': self.model_class.__name__, - 'field': sortkey, - 'direction': sortdir, - }, - ] - - # these params are based on 'vue-tables-2' - # https://github.com/matfish2/vue-tables-2#server-side - if 'orderBy' in self.request.params and 'ascending' in self.request.params: - sortcol = self.interpret_sortcol(self.request.params['orderBy']) - if sortcol: - spec = { - 'field': sortcol.field_name, - 'direction': 'asc' if self.config.parse_bool(self.request.params['ascending']) else 'desc', - } - if sortcol.model_name: - spec['model'] = sortcol.model_name - return [spec] - - def interpret_sortcol(self, order_by): - """ - This must return a ``SortColumn`` object based on parsing of the given - ``order_by`` string, which is "raw" as received from the client. - - Please override as necessary, but in all cases you should invoke - :meth:`sortcol()` to obtain your return value. Default behavior - for this method is to simply do (only) that:: - - return self.sortcol(order_by) - - Note that you can also return ``None`` here, if the given ``order_by`` - string does not represent a valid sort. - """ - return self.sortcol(order_by) - - def sortcol(self, field_name, model_name=None): - """ - Return a simple ``SortColumn`` object which denotes the field and - optionally, the model, to be used when sorting. - """ - if not model_name: - model_name = self.model_class.__name__ - return SortColumn(field_name, model_name) - - def join_for_sort_spec(self, query, sort_spec): - """ - This should apply any joins needed on the given query, to accommodate - requested sorting as per ``sort_spec`` - which will be non-empty but - otherwise no claims are made regarding its contents. - - Please override as necessary, but in all cases you should return a - query, either untouched or else with join(s) applied. - """ - model_name = sort_spec[0].get('model') - return self.join_for_sort_model(query, model_name) - - def join_for_sort_model(self, query, model_name): - """ - This should apply any joins needed on the given query, to accommodate - requested sorting on a field associated with the given model. - - Please override as necessary, but in all cases you should return a - query, either untouched or else with join(s) applied. - """ - return query - - def make_pagination_spec(self): - - # these params are based on 'vuetable-2' - # https://github.com/ratiw/vuetable-2-tutorial/wiki/prerequisite#sample-api-endpoint - if 'page' in self.request.params and 'per_page' in self.request.params: - page = self.request.params['page'] - per_page = self.request.params['per_page'] - if page.isdigit() and per_page.isdigit(): - return int(page), int(per_page) - - # these params are based on 'vue-tables-2' - # https://github.com/matfish2/vue-tables-2#server-side - if 'page' in self.request.params and 'limit' in self.request.params: - page = self.request.params['page'] - limit = self.request.params['limit'] - if page.isdigit() and limit.isdigit(): - return int(page), int(limit) - - def base_query(self): - cls = self.get_model_class() - query = self.Session.query(cls) - return query - - def get_fieldnames(self): - if not hasattr(self, '_fieldnames'): - self._fieldnames = get_fieldnames( - self.rattail_config, self.model_class, - columns=True, proxies=True, relations=False) - return self._fieldnames - - def normalize(self, obj): - data = {'_str': str(obj)} - - for field in self.get_fieldnames(): - data[field] = getattr(obj, field) - - return data - - def _collection_get(self): - from sa_filters import apply_filters, apply_sort, apply_pagination - - query = self.base_query() - context = {} - - # maybe filter query - filter_spec = self.make_filter_spec() - if filter_spec: - query = apply_filters(query, filter_spec) - - # maybe sort query - sort_spec = self.make_sort_spec() - if sort_spec: - query = self.join_for_sort_spec(query, sort_spec) - query = apply_sort(query, sort_spec) - - # maybe paginate query - pagination_spec = self.make_pagination_spec() - if pagination_spec: - number, size = pagination_spec - query, pagination = apply_pagination(query, page_number=number, page_size=size) - - # these properties are based on 'vuetable-2' - # https://www.vuetable.com/guide/pagination.html#how-the-pagination-component-works - context['total'] = pagination.total_results - context['per_page'] = pagination.page_size - context['current_page'] = pagination.page_number - context['last_page'] = pagination.num_pages - context['from'] = pagination.page_size * (pagination.page_number - 1) + 1 - to = pagination.page_size * (pagination.page_number - 1) + pagination.page_size - if to > pagination.total_results: - context['to'] = pagination.total_results - else: - context['to'] = to - - # these properties are based on 'vue-tables-2' - # https://github.com/matfish2/vue-tables-2#server-side - context['count'] = pagination.total_results - - objects = [self.normalize(obj) for obj in query] - - # TODO: test this for ratbob! - context[self.get_collection_key()] = objects - - # these properties are based on 'vue-tables-2' - # https://github.com/matfish2/vue-tables-2#server-side - context['data'] = objects - if 'count' not in context: - context['count'] = len(objects) - - return context - - def get_object(self, uuid=None): - if not uuid: - uuid = self.request.matchdict['uuid'] - - obj = self.Session.get(self.get_model_class(), uuid) - if obj: - return obj - - raise self.notfound() - - def _get(self, obj=None, uuid=None): - if not obj: - obj = self.get_object(uuid=uuid) - key = self.get_object_key() - normal = self.normalize(obj) - return {key: normal, 'data': normal} - - def _collection_post(self): - """ - Default method for actually processing a POST request for the - collection, aka. "create new object". - """ - # assume our data comes only from request JSON body - data = self.request.json_body - - # add instance to session, and return data for it - try: - obj = self.create_object(data) - except Exception as error: - return self.json_response({'error': str(error)}) - else: - self.Session.flush() - return self._get(obj) - - def create_object(self, data): - """ - Create a new object instance and populate it with the given data. - - Note that this method by default will only populate *simple* fields, so - you may need to subclass and override to add more complex field logic. - """ - # create new instance of model class - cls = self.get_model_class() - obj = cls() - - # "update" new object with given data - obj = self.update_object(obj, data) - - # that's all we can do here, subclass must override if more needed - self.Session.add(obj) - return obj - - def _post(self, uuid=None): - """ - Default method for actually processing a POST request for an object, - aka. "update existing object". - """ - if not uuid: - uuid = self.request.matchdict['uuid'] - obj = self.Session.get(self.get_model_class(), uuid) - if not obj: - raise self.notfound() - - # assume our data comes only from request JSON body - data = self.request.json_body - - # try to update data for object, returning error as necessary - obj = self.update_object(obj, data) - if isinstance(obj, dict) and 'error' in obj: - return {'error': obj['error']} - - # return data for object - self.Session.flush() - return self._get(obj) - - def update_object(self, obj, data): - """ - Update the given object instance with the given data. - - Note that this method by default will only update *simple* fields, so - you may need to subclass and override to add more complex field logic. - """ - # set values for simple fields only - for key, value in data.items(): - if hasattr(obj, key): - # TODO: what about datetime, decimal etc.? - setattr(obj, key, value) - - # that's all we can do here, subclass must override if more needed - return obj - - ############################## - # delete - ############################## - - def _delete(self): - """ - View to handle DELETE action for an existing record/object. - """ - obj = self.get_object() - self.delete_object(obj) - - def delete_object(self, obj): - """ - Delete the object, or mark it as deleted, or whatever you need to do. - """ - # flush immediately to force any pending integrity errors etc. - self.Session.delete(obj) - self.Session.flush() - - ############################## - # download - ############################## - - def download(self): - """ - GET view allowing for download of a single file, which is attached to a - given record. - """ - obj = self.get_object() - - filename = self.request.GET.get('filename', None) - if not filename: - raise self.notfound() - path = self.download_path(obj, filename) - - response = self.file_response(path) - return response - - def download_path(self, obj, filename): - """ - Should return absolute path on disk, for the given object and filename. - Result will be used to return a file response to client. - """ - raise NotImplementedError - - def rawbytes(self): - """ - GET view allowing for direct access to the raw bytes of a file, which - is attached to a given record. Basically the same as 'download' except - this does not come as an attachment. - """ - obj = self.get_object() - - # TODO: is this really needed? - # filename = self.request.GET.get('filename', None) - # if filename: - # path = self.download_path(obj, filename) - # return self.file_response(path, attachment=False) - - return self.rawbytes_response(obj) - - def rawbytes_response(self, obj): - raise NotImplementedError - - ############################## - # autocomplete - ############################## - - def autocomplete(self): - """ - View which accepts a single ``term`` param, and returns a list of - autocomplete results to match. - """ - term = self.request.params.get('term', '').strip() - term = self.prepare_autocomplete_term(term) - if not term: - return [] - - results = self.get_autocomplete_data(term) - return [{'label': self.autocomplete_display(x), - 'value': self.autocomplete_value(x)} - for x in results] - - @property - def autocomplete_fieldname(self): - raise NotImplementedError("You must define `autocomplete_fieldname` " - "attribute for API view class: {}".format( - self.__class__)) - - def autocomplete_display(self, obj): - return getattr(obj, self.autocomplete_fieldname) - - def autocomplete_value(self, obj): - return obj.uuid - - def get_autocomplete_data(self, term): - query = self.make_autocomplete_query(term) - return query.all() - - def make_autocomplete_query(self, term): - model_class = self.get_model_class() - query = self.Session.query(model_class) - query = self.filter_autocomplete_query(query) - - field = getattr(model_class, self.autocomplete_fieldname) - query = query.filter(field.ilike('%%%s%%' % term))\ - .order_by(field) - - return query - - def filter_autocomplete_query(self, query): - return query - - def prepare_autocomplete_term(self, term): - """ - If necessary, massage the incoming search term for use with the - autocomplete query. - """ - return term - - @classmethod - def defaults(cls, config): - cls._defaults(config) - - @classmethod - def _defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - collection_url_prefix = cls.get_collection_url_prefix() - object_url_prefix = cls.get_object_url_prefix() - - # first, the primary resource API - - # list/search - if cls.listable: - cls.establish_method('collection_get') - resource.add_view(cls.collection_get, permission='{}.list'.format(permission_prefix)) - - # create - if cls.creatable: - cls.establish_method('collection_post') - if hasattr(cls, 'permission_to_create'): - permission = cls.permission_to_create - else: - permission = '{}.create'.format(permission_prefix) - resource.add_view(cls.collection_post, permission=permission) - - # view - if cls.viewable: - cls.establish_method('get') - resource.add_view(cls.get, permission='{}.view'.format(permission_prefix)) - - # edit - if cls.editable: - cls.establish_method('post') - resource.add_view(cls.post, permission='{}.edit'.format(permission_prefix)) - - # delete - if cls.deletable: - cls.establish_method('delete') - resource.add_view(cls.delete, permission='{}.delete'.format(permission_prefix)) - - # register primary resource API via cornice - object_resource = resource.add_resource( - cls, - collection_path=collection_url_prefix, - # TODO: probably should allow for other (composite?) key fields - path='{}/{{uuid}}'.format(object_url_prefix)) - config.add_cornice_resource(object_resource) - - # now for some more "custom" things, which are still somewhat generic - - # autocomplete - if cls.supports_autocomplete: - autocomplete = Service(name='{}.autocomplete'.format(route_prefix), - path='{}/autocomplete'.format(collection_url_prefix)) - autocomplete.add_view('GET', 'autocomplete', klass=cls, - permission='{}.list'.format(permission_prefix)) - config.add_cornice_service(autocomplete) - - # download - if cls.supports_download: - download = Service(name='{}.download'.format(route_prefix), - # TODO: probably should allow for other (composite?) key fields - path='{}/{{uuid}}/download'.format(object_url_prefix)) - download.add_view('GET', 'download', klass=cls, - permission='{}.download'.format(permission_prefix)) - config.add_cornice_service(download) - - # rawbytes - if cls.supports_rawbytes: - rawbytes = Service(name='{}.rawbytes'.format(route_prefix), - # TODO: probably should allow for other (composite?) key fields - path='{}/{{uuid}}/rawbytes'.format(object_url_prefix)) - rawbytes.add_view('GET', 'rawbytes', klass=cls, - permission='{}.download'.format(permission_prefix)) - config.add_cornice_service(rawbytes) diff --git a/tailbone/api/master2.py b/tailbone/api/master2.py deleted file mode 100644 index 4a5abb3e..00000000 --- a/tailbone/api/master2.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Master View (v2) -""" - -from __future__ import unicode_literals, absolute_import - -import warnings - -from tailbone.api import APIMasterView - - -class APIMasterView2(APIMasterView): - """ - Base class for data model REST API views. - """ - - def __init__(self, request, context=None): - warnings.warn("APIMasterView2 class is deprecated; please use " - "APIMasterView instead", - DeprecationWarning, stacklevel=2) - super(APIMasterView2, self).__init__(request, context=context) diff --git a/tailbone/api/people.py b/tailbone/api/people.py deleted file mode 100644 index f7c08dfa..00000000 --- a/tailbone/api/people.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Person Views -""" - -from rattail.db import model - -from tailbone.api import APIMasterView - - -class PersonView(APIMasterView): - """ - API views for Person data - """ - model_class = model.Person - permission_prefix = 'people' - collection_url_prefix = '/people' - object_url_prefix = '/person' - - def normalize(self, person): - return { - 'uuid': person.uuid, - '_str': str(person), - 'first_name': person.first_name, - 'last_name': person.last_name, - 'display_name': person.display_name, - } - - -def defaults(config, **kwargs): - base = globals() - - PersonView = kwargs.get('PersonView', base['PersonView']) - PersonView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/products.py b/tailbone/api/products.py deleted file mode 100644 index 3f29ff54..00000000 --- a/tailbone/api/products.py +++ /dev/null @@ -1,220 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Product Views -""" - -import logging - -import sqlalchemy as sa -from sqlalchemy import orm - -from cornice import Service - -from rattail.db import model - -from tailbone.api import APIMasterView - - -log = logging.getLogger(__name__) - - -class ProductView(APIMasterView): - """ - API views for Product data - """ - model_class = model.Product - collection_url_prefix = '/products' - object_url_prefix = '/product' - supports_autocomplete = True - - def __init__(self, request, context=None): - super(ProductView, self).__init__(request, context=context) - app = self.get_rattail_app() - self.products_handler = app.get_products_handler() - - def normalize(self, product): - - # get what we can from handler - data = self.products_handler.normalize_product(product, fields=[ - 'brand_name', - 'full_description', - 'department_name', - 'unit_price_display', - 'sale_price', - 'sale_price_display', - 'sale_ends', - 'sale_ends_display', - 'tpr_price', - 'tpr_price_display', - 'tpr_ends', - 'tpr_ends_display', - 'current_price', - 'current_price_display', - 'current_ends', - 'current_ends_display', - 'vendor_name', - 'costs', - 'image_url', - ]) - - # but must supplement - cost = product.cost - data.update({ - 'upc': str(product.upc), - 'scancode': product.scancode, - 'item_id': product.item_id, - 'item_type': product.item_type, - 'status_code': product.status_code, - 'default_unit_cost': cost.unit_cost if cost else None, - 'default_unit_cost_display': "${:0.2f}".format(cost.unit_cost) if cost and cost.unit_cost is not None else None, - }) - - return data - - def make_autocomplete_query(self, term): - query = self.Session.query(model.Product)\ - .outerjoin(model.Brand)\ - .filter(sa.or_( - model.Brand.name.ilike('%{}%'.format(term)), - model.Product.description.ilike('%{}%'.format(term)))) - - if not self.request.has_perm('products.view_deleted'): - query = query.filter(model.Product.deleted == False) - - query = query.order_by(model.Brand.name, - model.Product.description)\ - .options(orm.joinedload(model.Product.brand)) - return query - - def autocomplete_display(self, product): - return product.full_description - - def quick_lookup(self): - """ - View for handling "quick lookup" user input, for index page. - """ - data = self.request.GET - entry = data['entry'] - - product = self.products_handler.locate_product_for_entry(self.Session(), - entry) - if not product: - return {'error': "Product not found"} - - return {'ok': True, - 'product': self.normalize(product)} - - def label_profiles(self): - """ - Returns the set of label profiles available for use with - printing label for product. - """ - app = self.get_rattail_app() - label_handler = app.get_label_handler() - model = self.model - - profiles = [] - for profile in label_handler.get_label_profiles(self.Session()): - profiles.append({ - 'uuid': profile.uuid, - 'description': profile.description, - }) - - return {'label_profiles': profiles} - - def print_labels(self): - app = self.get_rattail_app() - label_handler = app.get_label_handler() - model = self.model - data = self.request.json_body - - uuid = data.get('label_profile_uuid') - profile = self.Session.get(model.LabelProfile, uuid) if uuid else None - if not profile: - return {'error': "Label profile not found"} - - uuid = data.get('product_uuid') - product = self.Session.get(model.Product, uuid) if uuid else None - if not product: - return {'error': "Product not found"} - - try: - quantity = int(data.get('quantity')) - except: - return {'error': "Quantity must be integer"} - - printer = label_handler.get_printer(profile) - if not printer: - return {'error': "Couldn't get printer from label profile"} - - try: - printer.print_labels([({'product': product}, quantity)]) - except Exception as error: - log.warning("error occurred while printing labels", exc_info=True) - return {'error': str(error)} - - return {'ok': True} - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._product_defaults(config) - - @classmethod - def _product_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - collection_url_prefix = cls.get_collection_url_prefix() - - # quick lookup - quick_lookup = Service(name='{}.quick_lookup'.format(route_prefix), - path='{}/quick-lookup'.format(collection_url_prefix)) - quick_lookup.add_view('GET', 'quick_lookup', klass=cls, - permission='{}.list'.format(permission_prefix)) - config.add_cornice_service(quick_lookup) - - # label profiles - label_profiles = Service(name=f'{route_prefix}.label_profiles', - path=f'{collection_url_prefix}/label-profiles') - label_profiles.add_view('GET', 'label_profiles', klass=cls, - permission=f'{permission_prefix}.print_labels') - config.add_cornice_service(label_profiles) - - # print labels - print_labels = Service(name='{}.print_labels'.format(route_prefix), - path='{}/print-labels'.format(collection_url_prefix)) - print_labels.add_view('POST', 'print_labels', klass=cls, - permission='{}.print_labels'.format(permission_prefix)) - config.add_cornice_service(print_labels) - - -def defaults(config, **kwargs): - base = globals() - - ProductView = kwargs.get('ProductView', base['ProductView']) - ProductView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/upgrades.py b/tailbone/api/upgrades.py deleted file mode 100644 index 467c8a0d..00000000 --- a/tailbone/api/upgrades.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Upgrade Views -""" - -from rattail.db import model - -from tailbone.api import APIMasterView - - -class UpgradeView(APIMasterView): - """ - REST API views for Upgrade model. - """ - model_class = model.Upgrade - collection_url_prefix = '/upgrades' - object_url_prefix = '/upgrades' - - def normalize(self, upgrade): - data = { - 'created': upgrade.created.isoformat(), - 'description': upgrade.description, - 'enabled': upgrade.enabled, - 'executed': upgrade.executed.isoformat() if upgrade.executed else None, - # 'executed_by': - } - if upgrade.status_code is None: - data['status_code'] = None - else: - data['status_code'] = self.enum.UPGRADE_STATUS.get(upgrade.status_code, - str(upgrade.status_code)) - return data - - -def defaults(config, **kwargs): - base = globals() - - UpgradeView = kwargs.get('UpgradeView', base['UpgradeView']) - UpgradeView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/users.py b/tailbone/api/users.py deleted file mode 100644 index a6bcad57..00000000 --- a/tailbone/api/users.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - User Views -""" - -from rattail.db import model - -from tailbone.api import APIMasterView - - -class UserView(APIMasterView): - """ - API views for User data - """ - model_class = model.User - collection_url_prefix = '/users' - object_url_prefix = '/user' - - def normalize(self, user): - return { - 'uuid': user.uuid, - 'username': user.username, - 'person_display_name': (user.person.display_name or '') if user.person else '', - 'active': user.active, - } - - def interpret_sortcol(self, order_by): - if order_by == 'person_display_name': - return self.sortcol('Person', 'display_name') - return self.sortcol(order_by) - - def join_for_sort_model(self, query, model_name): - if model_name == 'Person': - query = query.outerjoin(model.Person) - return query - - def update_object(self, user, data): - # TODO: should ensure prevent_password_change is respected - return super(UserView, self).update_object(user, data) - - -def defaults(config, **kwargs): - base = globals() - - UserView = kwargs.get('UserView', base['UserView']) - UserView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/vendors.py b/tailbone/api/vendors.py deleted file mode 100644 index 64311b1b..00000000 --- a/tailbone/api/vendors.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Vendor Views -""" - -from rattail.db import model - -from tailbone.api import APIMasterView - - -class VendorView(APIMasterView): - - model_class = model.Vendor - collection_url_prefix = '/vendors' - object_url_prefix = '/vendor' - supports_autocomplete = True - autocomplete_fieldname = 'name' - - def normalize(self, vendor): - return { - 'uuid': vendor.uuid, - '_str': str(vendor), - 'id': vendor.id, - 'name': vendor.name, - } - - -def defaults(config, **kwargs): - base = globals() - - VendorView = kwargs.get('VendorView', base['VendorView']) - VendorView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/api/workorders.py b/tailbone/api/workorders.py deleted file mode 100644 index 19def6c4..00000000 --- a/tailbone/api/workorders.py +++ /dev/null @@ -1,234 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Web API - Work Order Views -""" - -import datetime - -from rattail.db.model import WorkOrder - -from cornice import Service - -from tailbone.api import APIMasterView - - -class WorkOrderView(APIMasterView): - - model_class = WorkOrder - collection_url_prefix = '/workorders' - object_url_prefix = '/workorder' - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - app = self.get_rattail_app() - self.workorder_handler = app.get_workorder_handler() - - def normalize(self, workorder): - data = super().normalize(workorder) - data.update({ - 'customer_name': workorder.customer.name, - 'status_label': self.enum.WORKORDER_STATUS[workorder.status_code], - 'date_submitted': str(workorder.date_submitted or ''), - 'date_received': str(workorder.date_received or ''), - 'date_released': str(workorder.date_released or ''), - 'date_delivered': str(workorder.date_delivered or ''), - }) - return data - - def create_object(self, data): - - # invoke the handler instead of normal API CRUD logic - workorder = self.workorder_handler.make_workorder(self.Session(), **data) - return workorder - - def update_object(self, workorder, data): - date_fields = [ - 'date_submitted', - 'date_received', - 'date_released', - 'date_delivered', - ] - - # coerce date field values to proper datetime.date objects - for field in date_fields: - if field in data: - if data[field] == '': - data[field] = None - elif not isinstance(data[field], datetime.date): - date = datetime.datetime.strptime(data[field], '%Y-%m-%d').date() - data[field] = date - - # coerce status code value to proper integer - if 'status_code' in data: - data['status_code'] = int(data['status_code']) - - return super().update_object(workorder, data) - - def status_codes(self): - """ - Retrieve all info about possible work order status codes. - """ - return self.workorder_handler.status_codes() - - def receive(self): - """ - Sets work order status to "received". - """ - workorder = self.get_object() - self.workorder_handler.receive(workorder) - self.Session.flush() - return self.normalize(workorder) - - def await_estimate(self): - """ - Sets work order status to "awaiting estimate confirmation". - """ - workorder = self.get_object() - self.workorder_handler.await_estimate(workorder) - self.Session.flush() - return self.normalize(workorder) - - def await_parts(self): - """ - Sets work order status to "awaiting parts". - """ - workorder = self.get_object() - self.workorder_handler.await_parts(workorder) - self.Session.flush() - return self.normalize(workorder) - - def work_on_it(self): - """ - Sets work order status to "working on it". - """ - workorder = self.get_object() - self.workorder_handler.work_on_it(workorder) - self.Session.flush() - return self.normalize(workorder) - - def release(self): - """ - Sets work order status to "released". - """ - workorder = self.get_object() - self.workorder_handler.release(workorder) - self.Session.flush() - return self.normalize(workorder) - - def deliver(self): - """ - Sets work order status to "delivered". - """ - workorder = self.get_object() - self.workorder_handler.deliver(workorder) - self.Session.flush() - return self.normalize(workorder) - - def cancel(self): - """ - Sets work order status to "canceled". - """ - workorder = self.get_object() - self.workorder_handler.cancel(workorder) - self.Session.flush() - return self.normalize(workorder) - - @classmethod - def defaults(cls, config): - cls._defaults(config) - cls._workorder_defaults(config) - - @classmethod - def _workorder_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - collection_url_prefix = cls.get_collection_url_prefix() - object_url_prefix = cls.get_object_url_prefix() - - # status codes - status_codes = Service(name='{}.status_codes'.format(route_prefix), - path='{}/status-codes'.format(collection_url_prefix)) - status_codes.add_view('GET', 'status_codes', klass=cls, - permission='{}.list'.format(permission_prefix)) - config.add_cornice_service(status_codes) - - # receive - receive = Service(name='{}.receive'.format(route_prefix), - path='{}/{{uuid}}/receive'.format(object_url_prefix)) - receive.add_view('POST', 'receive', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(receive) - - # await estimate confirmation - await_estimate = Service(name='{}.await_estimate'.format(route_prefix), - path='{}/{{uuid}}/await-estimate'.format(object_url_prefix)) - await_estimate.add_view('POST', 'await_estimate', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(await_estimate) - - # await parts - await_parts = Service(name='{}.await_parts'.format(route_prefix), - path='{}/{{uuid}}/await-parts'.format(object_url_prefix)) - await_parts.add_view('POST', 'await_parts', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(await_parts) - - # work on it - work_on_it = Service(name='{}.work_on_it'.format(route_prefix), - path='{}/{{uuid}}/work-on-it'.format(object_url_prefix)) - work_on_it.add_view('POST', 'work_on_it', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(work_on_it) - - # release - release = Service(name='{}.release'.format(route_prefix), - path='{}/{{uuid}}/release'.format(object_url_prefix)) - release.add_view('POST', 'release', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(release) - - # deliver - deliver = Service(name='{}.deliver'.format(route_prefix), - path='{}/{{uuid}}/deliver'.format(object_url_prefix)) - deliver.add_view('POST', 'deliver', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(deliver) - - # cancel - cancel = Service(name='{}.cancel'.format(route_prefix), - path='{}/{{uuid}}/cancel'.format(object_url_prefix)) - cancel.add_view('POST', 'cancel', klass=cls, - permission='{}.edit'.format(permission_prefix)) - config.add_cornice_service(cancel) - - -def defaults(config, **kwargs): - base = globals() - - WorkOrderView = kwargs.get('WorkOrderView', base['WorkOrderView']) - WorkOrderView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tailbone/app.py b/tailbone/app.py index d2d0c5ef..60808cc9 100644 --- a/tailbone/app.py +++ b/tailbone/app.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -24,23 +24,24 @@ Application Entry Point """ +from __future__ import unicode_literals, absolute_import + import os +import warnings -from sqlalchemy.orm import sessionmaker, scoped_session - -from wuttjamaican.util import parse_list +import sqlalchemy as sa +import rattail.db from rattail.config import make_config from rattail.exceptions import ConfigurationError +from rattail.db.config import get_engines, configure_versioning +from rattail.db.types import GPCType from pyramid.config import Configurator -from zope.sqlalchemy import register +from pyramid.authentication import SessionAuthenticationPolicy import tailbone.db -from tailbone.auth import TailboneSecurityPolicy -from tailbone.config import csrf_token_name, csrf_header_name -from tailbone.util import get_effective_theme, get_theme_template_path -from tailbone.providers import get_all_providers +from tailbone.auth import TailboneAuthorizationPolicy def make_rattail_config(settings): @@ -54,48 +55,45 @@ def make_rattail_config(settings): # available for web requests later path = settings.get('rattail.config') if not path or not os.path.exists(path): - raise ConfigurationError("Please set 'rattail.config' in [app:main] section of config " - "to the path of your config file. Lame, but necessary.") + path = settings.get('edbob.config') + if not path or not os.path.exists(path): + raise ConfigurationError("Please set 'rattail.config' in [app:main] section of config " + "to the path of your config file. Lame, but necessary.") + warnings.warn("[app:main] setting 'edbob.config' is deprecated; " + "please use 'rattail.config' setting instead", + DeprecationWarning) rattail_config = make_config(path) settings['rattail_config'] = rattail_config + rattail_config.configure_logging() - # nb. this is for compaibility with wuttaweb - settings['wutta_config'] = rattail_config + rattail_engines = settings.get('rattail_engines') + if not rattail_engines: - # must import all sqlalchemy models before things get rolling, - # otherwise can have errors about continuum TransactionMeta class - # not yet mapped, when relevant pages are first requested... - # cf. https://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/database/sqlalchemy.html#importing-all-sqlalchemy-models - # hat tip to https://stackoverflow.com/a/59241485 - if getattr(rattail_config, 'tempmon_engine', None): - from rattail_tempmon.db import model as tempmon_model, Session as TempmonSession - tempmon_session = TempmonSession() - tempmon_session.query(tempmon_model.Appliance).first() - tempmon_session.close() + # Load all Rattail database engines from config, and store in settings + # dict. This is necessary e.g. in the case of a host server, to have + # access to its subordinate store servers. + rattail_engines = get_engines(rattail_config) + settings['rattail_engines'] = rattail_engines - # configure database sessions - if hasattr(rattail_config, 'appdb_engine'): - tailbone.db.Session.configure(bind=rattail_config.appdb_engine) - if hasattr(rattail_config, 'trainwreck_engine'): - tailbone.db.TrainwreckSession.configure(bind=rattail_config.trainwreck_engine) + # Configure the database session classes. Note that most of the time we'll + # be using the Tailbone Session, but occasionally (e.g. within batch + # processing threads) we want the Rattail Session. The reason is that + # during normal request processing, the Tailbone Session is preferable as + # it includes Zope Transaction magic. Within an explicitly-spawned thread + # however, this is *not* desirable. + rattail.db.Session.configure(bind=rattail_engines['default']) + tailbone.db.Session.configure(bind=rattail_engines['default']) if hasattr(rattail_config, 'tempmon_engine'): tailbone.db.TempmonSession.configure(bind=rattail_config.tempmon_engine) - - # maybe set "future" behavior for SQLAlchemy - if rattail_config.getbool('rattail.db', 'sqlalchemy_future_mode', usedb=False): - tailbone.db.Session.configure(future=True) - - # create session wrappers for each "extra" Trainwreck engine - for key, engine in rattail_config.trainwreck_engines.items(): - if key != 'default': - Session = scoped_session(sessionmaker(bind=engine)) - register(Session) - tailbone.db.ExtraTrainwreckSessions[key] = Session + if hasattr(rattail_config, 'trainwreck_engine'): + tailbone.db.TrainwreckSession.configure(bind=rattail_config.trainwreck_engine) # Make sure rattail config object uses our scoped session, to avoid # unnecessary connections (and pooling limits). rattail_config._session_factory = lambda: (tailbone.db.Session(), False) + # Configure (or not) Continuum versioning. + configure_versioning(rattail_config) return rattail_config @@ -105,12 +103,7 @@ def provide_postgresql_settings(settings): this enables retrying transactions a second time, in an attempt to gracefully handle database restarts. """ - try: - import pyramid_retry - except ImportError: - settings.setdefault('tm.attempts', 2) - else: - settings.setdefault('retry.attempts', 2) + settings.setdefault('tm.attempts', 2) class Root(dict): @@ -128,197 +121,42 @@ def make_pyramid_config(settings, configure_csrf=True): """ Make a Pyramid config object from the given settings. """ - rattail_config = settings['rattail_config'] - config = settings.pop('pyramid_config', None) if config: config.set_root_factory(Root) else: - - # declare this web app of the "classic" variety - settings.setdefault('tailbone.classic', 'true') - - # we want the new themes feature! - establish_theme(settings) - - settings.setdefault('fanstatic.versioning', 'true') settings.setdefault('pyramid_deform.template_search_path', 'tailbone:templates/deform') config = Configurator(settings=settings, root_factory=Root) - # add rattail config directly to registry, for access throughout the app - config.registry['rattail_config'] = rattail_config - # configure user authorization / authentication - config.set_security_policy(TailboneSecurityPolicy()) + config.set_authorization_policy(TailboneAuthorizationPolicy()) + config.set_authentication_policy(SessionAuthenticationPolicy()) - # maybe require CSRF token protection + # always require CSRF token protection if configure_csrf: - config.set_default_csrf_options(require_csrf=True, - token=csrf_token_name(rattail_config), - header=csrf_header_name(rattail_config)) + config.set_default_csrf_options(require_csrf=True, token='_csrf') # Bring in some Pyramid goodies. config.include('tailbone.beaker') config.include('pyramid_deform') - config.include('pyramid_fanstatic') config.include('pyramid_mako') config.include('pyramid_tm') - # TODO: this may be a good idea some day, if wanting to leverage - # deform resources for component JS? cf. also base.mako template - # # override default script mapping for deform - # from deform import Field - # from deform.widget import ResourceRegistry, default_resources - # registry = ResourceRegistry(use_defaults=False) - # for key in default_resources: - # registry.set_js_resources(key, None, {'js': []}) - # Field.set_default_resource_registry(registry) + # Add some permissions magic. + config.add_directive('add_tailbone_permission_group', 'tailbone.auth.add_permission_group') + config.add_directive('add_tailbone_permission', 'tailbone.auth.add_permission') - # bring in the pyramid_retry logic, if available - # TODO: pretty soon we can require this package, hopefully.. - try: - import pyramid_retry - except ImportError: - pass - else: - config.include('pyramid_retry') - - # fetch all tailbone providers - providers = get_all_providers(rattail_config) - for provider in providers.values(): - - # configure DB sessions associated with transaction manager - provider.configure_db_sessions(rattail_config, config) - - # add any static includes - includes = provider.get_static_includes() - if includes: - for spec in includes: - config.include(spec) - - # add some permissions magic - config.add_directive('add_wutta_permission_group', - 'wuttaweb.auth.add_permission_group') - config.add_directive('add_wutta_permission', - 'wuttaweb.auth.add_permission') - # TODO: deprecate / remove these - config.add_directive('add_tailbone_permission_group', - 'wuttaweb.auth.add_permission_group') - config.add_directive('add_tailbone_permission', - 'wuttaweb.auth.add_permission') - - # and some similar magic for certain master views - config.add_directive('add_tailbone_index_page', 'tailbone.app.add_index_page') - config.add_directive('add_tailbone_config_page', 'tailbone.app.add_config_page') - config.add_directive('add_tailbone_model_view', 'tailbone.app.add_model_view') - config.add_directive('add_tailbone_view_supplement', 'tailbone.app.add_view_supplement') - - config.add_directive('add_tailbone_websocket', 'tailbone.app.add_websocket') + # TODO: This can finally be removed once all CRUD/index views have been + # converted to use the new master view etc. + for label, perms in settings.get('edbob.permissions', []): + groupkey = label.lower().replace(' ', '_') + config.add_tailbone_permission_group(groupkey, label) + for key, label in perms: + config.add_tailbone_permission(groupkey, key, label) return config -def add_websocket(config, name, view, attr=None): - """ - Register a websocket entry point for the app. - """ - def action(): - rattail_config = config.registry.settings['rattail_config'] - rattail_app = rattail_config.get_app() - - if isinstance(view, str): - view_callable = rattail_app.load_object(view) - else: - view_callable = view - view_callable = view_callable(config) - if attr: - view_callable = getattr(view_callable, attr) - - # register route - path = '/ws/{}'.format(name) - route_name = 'ws.{}'.format(name) - config.add_route(route_name, path, static=True) - - # register view callable - websockets = config.registry.setdefault('tailbone_websockets', {}) - websockets[path] = view_callable - - config.action('tailbone-add-websocket-{}'.format(name), action, - # nb. since this action adds routes, it must happen - # sooner in the order than it normally would, hence - # we declare that - order=-20) - - -def add_index_page(config, route_name, label, permission): - """ - Register a config page for the app. - """ - def action(): - pages = config.get_settings().get('tailbone_index_pages', []) - pages.append({'label': label, 'route': route_name, - 'permission': permission}) - config.add_settings({'tailbone_index_pages': pages}) - config.action(None, action) - - -def add_config_page(config, route_name, label, permission): - """ - Register a config page for the app. - """ - def action(): - pages = config.get_settings().get('tailbone_config_pages', []) - pages.append({'label': label, 'route': route_name, - 'permission': permission}) - config.add_settings({'tailbone_config_pages': pages}) - config.action(None, action) - - -def add_model_view(config, model_name, label, route_prefix, permission_prefix): - """ - Register a model view for the app. - """ - def action(): - all_views = config.get_settings().get('tailbone_model_views', {}) - - model_views = all_views.setdefault(model_name, []) - model_views.append({ - 'label': label, - 'route_prefix': route_prefix, - 'permission_prefix': permission_prefix, - }) - - config.add_settings({'tailbone_model_views': all_views}) - - config.action(None, action) - - -def add_view_supplement(config, route_prefix, cls): - """ - Register a master view supplement for the app. - """ - def action(): - supplements = config.get_settings().get('tailbone_view_supplements', {}) - supplements.setdefault(route_prefix, []).append(cls) - config.add_settings({'tailbone_view_supplements': supplements}) - config.action(None, action) - - -def establish_theme(settings): - rattail_config = settings['rattail_config'] - - theme = get_effective_theme(rattail_config) - settings['tailbone.theme'] = theme - - directories = settings['mako.directories'] - if isinstance(directories, str): - directories = parse_list(directories) - - path = get_theme_template_path(rattail_config) - directories.insert(0, path) - settings['mako.directories'] = directories - - def configure_postgresql(pyramid_config): """ Add some PostgreSQL-specific tweaks to the final app config. Specifically, @@ -332,8 +170,7 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings.setdefault('mako.directories', ['tailbone:templates', - 'wuttaweb:templates']) + settings.setdefault('mako.directories', ['tailbone:templates']) rattail_config = make_rattail_config(settings) pyramid_config = make_pyramid_config(settings) pyramid_config.include('tailbone') diff --git a/tailbone/asgi.py b/tailbone/asgi.py deleted file mode 100644 index 1afbe12a..00000000 --- a/tailbone/asgi.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -ASGI App Utilities -""" - -import os -import configparser -import logging - -from rattail.util import load_object - -from asgiref.wsgi import WsgiToAsgi - - -log = logging.getLogger(__name__) - - -class TailboneWsgiToAsgi(WsgiToAsgi): - """ - Custom WSGI -> ASGI wrapper, to add routing for websockets. - """ - - async def __call__(self, scope, *args, **kwargs): - protocol = scope['type'] - path = scope['path'] - - # strip off the root path, if non-empty. needed for serving - # under /poser or anything other than true site root - root_path = scope['root_path'] - if root_path and path.startswith(root_path): - path = path[len(root_path):] - - if protocol == 'websocket': - websockets = self.wsgi_application.registry.get( - 'tailbone_websockets', {}) - if path in websockets: - await websockets[path](scope, *args, **kwargs) - - try: - await super().__call__(scope, *args, **kwargs) - except ValueError as e: - # The developer may wish to improve handling of this exception. - # See https://github.com/Pylons/pyramid_cookbook/issues/225 and - # https://asgi.readthedocs.io/en/latest/specs/www.html#websocket - pass - except Exception as e: - raise e - - -def make_asgi_app(main_app=None): - """ - This function returns an ASGI application. - """ - path = os.environ.get('TAILBONE_ASGI_CONFIG') - if not path: - raise RuntimeError("You must define TAILBONE_ASGI_CONFIG env variable.") - - # make a config parser good enough to load pyramid settings - configdir = os.path.dirname(path) - parser = configparser.ConfigParser(defaults={'__file__': path, - 'here': configdir}) - - # read the config file - parser.read(path) - - # parse the settings needed for pyramid app - settings = dict(parser.items('app:main')) - - if isinstance(main_app, str): - make_wsgi_app = load_object(main_app) - elif callable(main_app): - make_wsgi_app = main_app - else: - if main_app: - log.warning("specified main app of unknown type: %s", main_app) - make_wsgi_app = load_object('tailbone.app:main') - - # construct a pyramid app "per usual" - app = make_wsgi_app({}, **settings) - - # then wrap it with ASGI - return TailboneWsgiToAsgi(app) - - -def asgi_main(): - """ - This function returns an ASGI application. - """ - return make_asgi_app() diff --git a/tailbone/auth.py b/tailbone/auth.py index 95bf90ba..9db292ad 100644 --- a/tailbone/auth.py +++ b/tailbone/auth.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -24,31 +24,32 @@ Authentication & Authorization """ +from __future__ import unicode_literals, absolute_import + import logging -import re -from wuttjamaican.util import UNSPECIFIED +from rattail import enum +from rattail.util import prettify, NOTSET -from pyramid.security import remember, forget +from zope.interface import implementer +from pyramid.interfaces import IAuthorizationPolicy +from pyramid.security import remember, forget, Everyone, Authenticated -from wuttaweb.auth import WuttaSecurityPolicy from tailbone.db import Session log = logging.getLogger(__name__) -def login_user(request, user, timeout=UNSPECIFIED): +def login_user(request, user, timeout=NOTSET): """ Perform the steps necessary to login the given user. Note that this returns a ``headers`` dict which you should pass to the redirect. """ - config = request.rattail_config - app = config.get_app() - user.record_event(app.enum.USER_EVENT_LOGIN) + user.record_event(enum.USER_EVENT_LOGIN) headers = remember(request, user.uuid) - if timeout is UNSPECIFIED: - timeout = session_timeout_for_user(config, user) + if timeout is NOTSET: + timeout = session_timeout_for_user(user) log.debug("setting session timeout for '{}' to {}".format(user.username, timeout)) set_session_timeout(request, timeout) return headers @@ -59,28 +60,24 @@ def logout_user(request): Perform the logout action for the given request. Note that this returns a ``headers`` dict which you should pass to the redirect. """ - app = request.rattail_config.get_app() user = request.user if user: - user.record_event(app.enum.USER_EVENT_LOGOUT) + user.record_event(enum.USER_EVENT_LOGOUT) request.session.delete() request.session.invalidate() headers = forget(request) return headers -def session_timeout_for_user(config, user): +def session_timeout_for_user(user): """ Returns the "max" session timeout for the user, according to roles """ - app = config.get_app() - auth = app.get_auth_handler() + from rattail.db.auth import authenticated_role - authenticated = auth.get_role_authenticated(Session()) - roles = user.roles + [authenticated] + roles = user.roles + [authenticated_role(Session())] timeouts = [role.session_timeout for role in roles if role.session_timeout is not None] - if timeouts and 0 not in timeouts: return max(timeouts) @@ -92,42 +89,53 @@ def set_session_timeout(request, timeout): request.session['_timeout'] = timeout or None -class TailboneSecurityPolicy(WuttaSecurityPolicy): +@implementer(IAuthorizationPolicy) +class TailboneAuthorizationPolicy(object): - def __init__(self, db_session=None, api_mode=False, **kwargs): - kwargs['db_session'] = db_session or Session() - super().__init__(**kwargs) - self.api_mode = api_mode + def permits(self, context, principals, permission): + from rattail.db import model + from rattail.db.auth import has_permission - def load_identity(self, request): - config = request.registry.settings.get('rattail_config') - app = config.get_app() - user = None + for userid in principals: + if userid not in (Everyone, Authenticated): + if context.request.user and context.request.user.uuid == userid: + return context.request.has_perm(permission) + else: + assert False # should no longer happen..right? + user = Session.query(model.User).get(userid) + if user: + if has_permission(Session(), user, permission): + return True + if Everyone in principals: + return has_permission(Session(), None, permission) + return False - if self.api_mode: + def principals_allowed_by_permission(self, context, permission): + raise NotImplementedError - # determine/load user from header token if present - credentials = request.headers.get('Authorization') - if credentials: - match = re.match(r'^Bearer (\S+)$', credentials) - if match: - token = match.group(1) - auth = app.get_auth_handler() - user = auth.authenticate_user_token(self.db_session, token) - if not user: +def add_permission_group(config, key, label=None, overwrite=True): + """ + Add a permission group to the app configuration. + """ + def action(): + perms = config.get_settings().get('tailbone_permissions', {}) + if key not in perms or overwrite: + group = perms.setdefault(key, {'key': key}) + group['label'] = label or prettify(key) + config.add_settings({'tailbone_permissions': perms}) + config.action(None, action) - # fetch user uuid from current session - uuid = self.session_helper.authenticated_userid(request) - if not uuid: - return - # fetch user object from db - model = app.model - user = self.db_session.get(model.User, uuid) - if not user: - return - - # this user is responsible for data changes in current request - self.db_session.set_continuum_user(user) - return user +def add_permission(config, groupkey, key, label=None): + """ + Add a permission to the app configuration. + """ + def action(): + perms = config.get_settings().get('tailbone_permissions', {}) + group = perms.setdefault(groupkey, {'key': groupkey}) + group.setdefault('label', prettify(groupkey)) + perm = group.setdefault('perms', {}).setdefault(key, {'key': key}) + perm['label'] = label or prettify(key) + config.add_settings({'tailbone_permissions': perms}) + config.action(None, action) diff --git a/tailbone/beaker.py b/tailbone/beaker.py index 25a450df..1f7f20c5 100644 --- a/tailbone/beaker.py +++ b/tailbone/beaker.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8; -*- +# -*- coding: utf-8 -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -27,12 +27,10 @@ Note that most of the code for this module was copied from the beaker and pyramid_beaker projects. """ +from __future__ import unicode_literals, absolute_import + import time -from pkg_resources import parse_version -from rattail.util import get_pkg_version - -import beaker from beaker.session import Session from beaker.util import coerce_session_params from pyramid.settings import asbool @@ -47,10 +45,6 @@ class TailboneSession(Session): def load(self): "Loads the data from this session from persistent storage" - - # are we using older version of beaker? - old_beaker = parse_version(get_pkg_version('beaker')) < parse_version('1.12') - self.namespace = self.namespace_class(self.id, data_dir=self.data_dir, digest_filenames=False, @@ -66,12 +60,8 @@ class TailboneSession(Session): try: session_data = self.namespace['session'] - if old_beaker: - if (session_data is not None and self.encrypt_key): - session_data = self._decrypt_data(session_data) - else: # beaker >= 1.12 - if session_data is not None: - session_data = self._decrypt_data(session_data) + if (session_data is not None and self.encrypt_key): + session_data = self._decrypt_data(session_data) # Memcached always returns a key, its None when its not # present @@ -100,7 +90,6 @@ class TailboneSession(Session): # for this module entirely... timeout = session_data.get('_timeout', self.timeout) if timeout is not None and \ - '_accessed_time' in session_data and \ now - session_data['_accessed_time'] > timeout: timed_out = True else: @@ -114,6 +103,9 @@ class TailboneSession(Session): # Update the current _accessed_time session_data['_accessed_time'] = now + # Set the path if applicable + if '_path' in session_data: + self._path = session_data['_path'] self.update(session_data) self.accessed_dict = session_data.copy() finally: diff --git a/tailbone/cleanup.py b/tailbone/cleanup.py deleted file mode 100644 index 0ed5d026..00000000 --- a/tailbone/cleanup.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Cleanup logic -""" - -from __future__ import unicode_literals, absolute_import - -import os -import logging -import time - -from rattail.cleanup import Cleaner - - -log = logging.getLogger(__name__) - - -class BeakerCleaner(Cleaner): - """ - Cleanup logic for old Beaker session files. - """ - - def get_session_dir(self): - session_dir = self.config.get('rattail.cleanup', 'beaker.session_dir') - if session_dir and os.path.isdir(session_dir): - return session_dir - - session_dir = os.path.join(self.config.appdir(), 'sessions') - if os.path.isdir(session_dir): - return session_dir - - def cleanup(self, session, dry_run=False, progress=None, **kwargs): - session_dir = self.get_session_dir() - if not session_dir: - return - - data_dir = os.path.join(session_dir, 'data') - lock_dir = os.path.join(session_dir, 'lock') - - # looking for files older than X days - days = self.config.getint('rattail.cleanup', - 'beaker.session_cutoff_days', - default=30) - cutoff = time.time() - 3600 * 24 * days - - for topdir in (data_dir, lock_dir): - if not os.path.isdir(topdir): - continue - - for dirpath, dirnames, filenames in os.walk(topdir): - for fname in filenames: - path = os.path.join(dirpath, fname) - ts = os.path.getmtime(path) - if ts <= cutoff: - if dry_run: - log.debug("would delete file: %s", path) - else: - os.remove(path) - log.debug("deleted file: %s", path) diff --git a/tailbone/config.py b/tailbone/config.py index 8392ba0a..51293a26 100644 --- a/tailbone/config.py +++ b/tailbone/config.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8; -*- +# -*- coding: utf-8 -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -24,16 +24,15 @@ Rattail config extension for Tailbone """ -import warnings - -from wuttjamaican.conf import WuttaConfigExtension +from __future__ import unicode_literals, absolute_import +from rattail.config import ConfigExtension as BaseExtension from rattail.db.config import configure_session from tailbone.db import Session -class ConfigExtension(WuttaConfigExtension): +class ConfigExtension(BaseExtension): """ Rattail config extension for Tailbone. Does the following: @@ -48,31 +47,3 @@ class ConfigExtension(WuttaConfigExtension): def configure(self, config): Session.configure(rattail_config=config) configure_session(config, Session) - - # provide default theme selection - config.setdefault('tailbone', 'themes.keys', 'default, butterball') - config.setdefault('tailbone', 'themes.expose_picker', 'true') - - # override oruga detection - config.setdefault('wuttaweb.oruga_detector.spec', 'tailbone.util:should_use_oruga') - - -def csrf_token_name(config): - return config.get('tailbone', 'csrf_token_name', default='_csrf') - - -def csrf_header_name(config): - return config.get('tailbone', 'csrf_header_name', default='X-CSRF-TOKEN') - - -def global_help_url(config): - return config.get('tailbone', 'global_help_url') - - -def protected_usernames(config): - return config.getlist('tailbone', 'protected_usernames') - - -def should_expose_websockets(config): - return config.getbool('tailbone', 'expose_websockets', - usedb=False, default=False) diff --git a/tailbone/db.py b/tailbone/db.py index 8b37f399..041f750e 100644 --- a/tailbone/db.py +++ b/tailbone/db.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8; -*- +# -*- coding: utf-8 -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -21,9 +21,11 @@ # ################################################################################ """ -Database sessions etc. +Database Stuff """ +from __future__ import unicode_literals, absolute_import + import sqlalchemy as sa from zope.sqlalchemy import datamanager import sqlalchemy_continuum as continuum @@ -33,37 +35,24 @@ from rattail.db import SessionBase from rattail.db.continuum import versioning_manager -Session = scoped_session(sessionmaker(class_=SessionBase, rattail_config=None, expire_on_commit=False)) +Session = scoped_session(sessionmaker(class_=SessionBase, rattail_config=None, rattail_record_changes=False, expire_on_commit=False)) # not necessarily used, but here if you need it TempmonSession = scoped_session(sessionmaker()) TrainwreckSession = scoped_session(sessionmaker()) -# empty dict for now, this must populated on app startup (if needed) -ExtraTrainwreckSessions = {} - class TailboneSessionDataManager(datamanager.SessionDataManager): - """ - Integrate a top level sqlalchemy session transaction into a zope - transaction + """Integrate a top level sqlalchemy session transaction into a zope transaction One phase variant. .. note:: - - This class appears to be necessary in order for the - SQLAlchemy-Continuum integration to work alongside the Zope - transaction integration. - - It subclasses - ``zope.sqlalchemy.datamanager.SessionDataManager`` but injects - some SQLAlchemy-Continuum logic within :meth:`tpc_vote()`, and - is sort of monkey-patched into the mix. + This class appears to be necessary in order for the Continuum + integration to work alongside the Zope transaction integration. """ def tpc_vote(self, trans): - """ """ # for a one phase data manager commit last in tpc_vote if self.tx is not None: # there may have been no work to do @@ -75,42 +64,25 @@ class TailboneSessionDataManager(datamanager.SessionDataManager): self._finish('committed') -def join_transaction( - session, - initial_state=datamanager.STATUS_ACTIVE, - transaction_manager=datamanager.zope_transaction.manager, - keep_session=False, -): - """ - Join a session to a transaction using the appropriate datamanager. +def join_transaction(session, initial_state=datamanager.STATUS_ACTIVE, transaction_manager=datamanager.zope_transaction.manager, keep_session=False): + """Join a session to a transaction using the appropriate datamanager. - It is safe to call this multiple times, if the session is already - joined then it just returns. + It is safe to call this multiple times, if the session is already joined + then it just returns. - `initial_state` is either STATUS_ACTIVE, STATUS_INVALIDATED or - STATUS_READONLY + `initial_state` is either STATUS_ACTIVE, STATUS_INVALIDATED or STATUS_READONLY - If using the default initial status of STATUS_ACTIVE, you must - ensure that mark_changed(session) is called when data is written - to the database. + If using the default initial status of STATUS_ACTIVE, you must ensure that + mark_changed(session) is called when data is written to the database. - The ZopeTransactionExtesion SessionExtension can be used to ensure - that this is called automatically after session write operations. + The ZopeTransactionExtesion SessionExtension can be used to ensure that this is + called automatically after session write operations. .. note:: - - This function appears to be necessary in order for the - SQLAlchemy-Continuum integration to work alongside the Zope - transaction integration. - - It overrides ``zope.sqlalchemy.datamanager.join_transaction()`` - to ensure the custom :class:`TailboneSessionDataManager` is - used, and is sort of monkey-patched into the mix. + This function is copied from upstream, and tweaked so that our custom + :class:`TailboneSessionDataManager` will be used. """ - # the upstream internals of this function has changed a little over time. - # unfortunately for us, that means we must include each variant here. - - if datamanager._SESSION_STATE.get(session, None) is None: + if datamanager._SESSION_STATE.get(id(session), None) is None: if session.twophase: DataManager = datamanager.TwoPhaseSessionDataManager else: @@ -118,74 +90,44 @@ def join_transaction( DataManager(session, initial_state, transaction_manager, keep_session=keep_session) -class ZopeTransactionEvents(datamanager.ZopeTransactionEvents): - """ - Record that a flush has occurred on a session's connection. This - allows the DataManager to rollback rather than commit on read only - transactions. +class ZopeTransactionExtension(datamanager.ZopeTransactionExtension): + """Record that a flush has occurred on a session's connection. This allows + the DataManager to rollback rather than commit on read only transactions. .. note:: - - This class appears to be necessary in order for the - SQLAlchemy-Continuum integration to work alongside the Zope - transaction integration. - - It subclasses - ``zope.sqlalchemy.datamanager.ZopeTransactionEvents`` but - overrides various methods to ensure the custom - :func:`join_transaction()` is called, and is sort of - monkey-patched into the mix. + This class is copied from upstream, and tweaked so that our custom + :func:`join_transaction()` will be used. """ def after_begin(self, session, transaction, connection): - """ """ - join_transaction(session, self.initial_state, - self.transaction_manager, self.keep_session) + join_transaction(session, self.initial_state, self.transaction_manager, self.keep_session) def after_attach(self, session, instance): - """ """ - join_transaction(session, self.initial_state, - self.transaction_manager, self.keep_session) - - def join_transaction(self, session): - """ """ - join_transaction(session, self.initial_state, - self.transaction_manager, self.keep_session) + join_transaction(session, self.initial_state, self.transaction_manager, self.keep_session) -def register( - session, - initial_state=datamanager.STATUS_ACTIVE, - transaction_manager=datamanager.zope_transaction.manager, - keep_session=False, -): - """ - Register ZopeTransaction listener events on the given Session or - Session factory/class. +def register(session, initial_state=datamanager.STATUS_ACTIVE, + transaction_manager=datamanager.zope_transaction.manager, keep_session=False): + """Register ZopeTransaction listener events on the + given Session or Session factory/class. - This function requires at least SQLAlchemy 0.7 and makes use of - the newer sqlalchemy.event package in order to register event - listeners on the given Session. + This function requires at least SQLAlchemy 0.7 and makes use + of the newer sqlalchemy.event package in order to register event listeners + on the given Session. The session argument here may be a Session class or subclass, a - sessionmaker or scoped_session instance, or a specific Session - instance. Event listening will be specific to the scope of the - type of argument passed, including specificity to its subclass as - well as its identity. + sessionmaker or scoped_session instance, or a specific Session instance. + Event listening will be specific to the scope of the type of argument + passed, including specificity to its subclass as well as its identity. .. note:: - - This function appears to be necessary in order for the - SQLAlchemy-Continuum integration to work alongside the Zope - transaction integration. - - It overrides ``zope.sqlalchemy.datamanager.regsiter()`` to - ensure the custom :class:`ZopeTransactionEvents` is used. + This function is copied from upstream, and tweaked so that our custom + :class:`ZopeTransactionExtension` will be used. """ from sqlalchemy import event - ext = ZopeTransactionEvents( - initial_state=initial_state, + ext = ZopeTransactionExtension( + initial_state=initial_state, transaction_manager=transaction_manager, keep_session=keep_session, ) @@ -197,9 +139,6 @@ def register( event.listen(session, "after_bulk_delete", ext.after_bulk_delete) event.listen(session, "before_commit", ext.before_commit) - if datamanager.SA_GE_14: - event.listen(session, "do_orm_execute", ext.do_orm_execute) - register(Session) register(TempmonSession) diff --git a/tailbone/diffs.py b/tailbone/diffs.py index 2e582b15..595dbfc9 100644 --- a/tailbone/diffs.py +++ b/tailbone/diffs.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -24,8 +24,7 @@ Tools for displaying data diffs """ -import sqlalchemy as sa -import sqlalchemy_continuum as continuum +from __future__ import unicode_literals, absolute_import from pyramid.renderers import render from webhelpers2.html import HTML @@ -34,43 +33,16 @@ from webhelpers2.html import HTML class Diff(object): """ Core diff class. In sore need of documentation. - - You must provide the old and new data sets, and the set of - relevant fields as well, if they cannot be easily introspected. - - :param old_data: Dict of "old" data values. - - :param new_data: Dict of "old" data values. - - :param fields: Sequence of relevant field names. Note that - both data dicts are expected to have keys which match these - field names. If you do not specify the fields then they - will (hopefully) be introspected from the old or new data - sets; however this will not work if they are both empty. - - :param monospace: If true, this flag will cause the value - columns to be rendered in monospace font. This is assumed - to be helpful when comparing "raw" data values which are - shown as e.g. ``repr(val)``. - - :param enums: Optional dict of enums for use when displaying field - values. If specified, keys should be field names and values - should be enum dicts. """ - def __init__(self, old_data, new_data, columns=None, fields=None, enums=None, - render_field=None, render_value=None, nature='dirty', - monospace=False, extra_row_attrs=None): + def __init__(self, old_data, new_data, columns=None, fields=None, render_field=None, render_value=None, monospace=False): self.old_data = old_data self.new_data = new_data self.columns = columns or ["field name", "old value", "new value"] self.fields = fields or self.make_fields() - self.enums = enums or {} self._render_field = render_field or self.render_field_default self.render_value = render_value or self.render_value_default - self.nature = nature self.monospace = monospace - self.extra_row_attrs = extra_row_attrs def make_fields(self): return sorted(set(self.old_data) | set(self.new_data), key=lambda x: x.lower()) @@ -89,32 +61,6 @@ class Diff(object): context['diff'] = self return HTML.literal(render(template, context)) - def get_row_attrs(self, field): - """ - Returns a *rendered* set of extra attributes for the ```` element - for the given field. May be an empty string, or a snippet of HTML - attribute syntax, e.g.: - - .. code-block:: none - - class="diff" foo="bar" - - If you wish to supply additional attributes, please define - :attr:`extra_row_attrs`, which can be either a static dict, or a - callable returning a dict. - """ - attrs = {} - if self.values_differ(field): - attrs['class'] = 'diff' - - if self.extra_row_attrs: - if callable(self.extra_row_attrs): - attrs.update(self.extra_row_attrs(field, attrs)) - else: - attrs.update(self.extra_row_attrs) - - return HTML.render_attrs(attrs) - def render_field(self, field): return self._render_field(field, self) @@ -131,161 +77,3 @@ class Diff(object): def render_new_value(self, field): value = self.new_value(field) return self.render_value(field, value) - - -class VersionDiff(Diff): - """ - Special diff class, for use with version history views. Note that - while based on :class:`Diff`, this class uses a different - signature for the constructor. - - :param version: Reference to a Continuum version record (object). - - :param \*args: Typical usage will not require positional args - beyond the ``version`` param, in which case ``old_data`` and - ``new_data`` params will be auto-determined based on the - ``version``. But if you specify positional args then nothing - automatic is done, they are passed as-is to the parent - :class:`Diff` constructor. - - :param \*\*kwargs: Remaining kwargs are passed as-is to the - :class:`Diff` constructor. - """ - - def __init__(self, version, *args, **kwargs): - self.version = version - self.mapper = sa.inspect(continuum.parent_class(type(self.version))) - self.version_mapper = sa.inspect(type(self.version)) - self.title = kwargs.pop('title', None) - - if 'nature' not in kwargs: - if version.previous and version.operation_type == continuum.Operation.DELETE: - kwargs['nature'] = 'deleted' - elif version.previous: - kwargs['nature'] = 'dirty' - else: - kwargs['nature'] = 'new' - - if 'fields' not in kwargs: - kwargs['fields'] = self.get_default_fields() - - if not args: - old_data = {} - new_data = {} - for field in kwargs['fields']: - if version.previous: - old_data[field] = getattr(version.previous, field) - new_data[field] = getattr(version, field) - args = (old_data, new_data) - - super().__init__(*args, **kwargs) - - def get_default_fields(self): - fields = sorted(self.version_mapper.columns.keys()) - - unwanted = [ - 'transaction_id', - 'end_transaction_id', - 'operation_type', - ] - - return [field for field in fields - if field not in unwanted] - - def render_version_value(self, field, value, version): - """ - Render the cell value text for the given version/field info. - - Note that this method is used to render both sides of the diff - (before and after values). - - :param field: Name of the field, as string. - - :param value: Raw value for the field, as obtained from ``version``. - - :param version: Reference to the Continuum version object. - - :returns: Rendered text as string, or ``None``. - """ - text = HTML.tag('span', c=[repr(value)], - style='font-family: monospace;') - - # assume the enum display is all we need, if enum exists for the field - if field in self.enums: - - # but skip the enum display if None - display = self.enums[field].get(value) - if display is None and value is None: - return text - - # otherwise show enum display to the right of raw value - display = self.enums[field].get(value, str(value)) - return HTML.tag('span', c=[ - text, - HTML.tag('span', c=[display], - style='margin-left: 2rem; font-style: italic; font-weight: bold;'), - ]) - - # next we look for a relationship and may render the foreign object - for prop in self.mapper.relationships: - if prop.uselist: - continue - - for col in prop.local_columns: - if col.name != field: - continue - - if not hasattr(version, prop.key): - continue - - if col in self.mapper.primary_key: - continue - - ref = getattr(version, prop.key) - if ref: - ref = getattr(ref, 'version_parent', None) - if ref: - return HTML.tag('span', c=[ - text, - HTML.tag('span', c=[str(ref)], - style='margin-left: 2rem; font-style: italic; font-weight: bold;'), - ]) - - return text - - def render_old_value(self, field): - if self.nature == 'new': - return '' - value = self.old_value(field) - return self.render_version_value(field, value, self.version.previous) - - def render_new_value(self, field): - if self.nature == 'deleted': - return '' - value = self.new_value(field) - return self.render_version_value(field, value, self.version) - - def as_struct(self): - values = {} - for field in self.fields: - values[field] = {'before': self.render_old_value(field), - 'after': self.render_new_value(field)} - - operation = None - if self.version.operation_type == continuum.Operation.INSERT: - operation = 'INSERT' - elif self.version.operation_type == continuum.Operation.UPDATE: - operation = 'UPDATE' - elif self.version.operation_type == continuum.Operation.DELETE: - operation = 'DELETE' - else: - operation = self.version.operation_type - - return { - 'key': id(self.version), - 'model_title': self.title, - 'operation': operation, - 'diff_class': self.nature, - 'fields': self.fields, - 'values': values, - } diff --git a/tailbone/exceptions.py b/tailbone/exceptions.py deleted file mode 100644 index 3468562a..00000000 --- a/tailbone/exceptions.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Tailbone Exceptions -""" - -from rattail.exceptions import RattailError - - -class TailboneError(RattailError): - """ - Base class for all Tailbone exceptions. - """ - - -class TailboneJSONFieldError(TailboneError): - """ - Error raised when JSON serialization of a form field results in an error. - This is just a simple wrapper, to make the error message more helpful for - the developer. - """ - - def __init__(self, field, error): - self.field = field - self.error = error - - def __str__(self): - return ("Failed to serialize field '{}' as JSON! " - "Original error was: {}".format(self.field, self.error)) diff --git a/tailbone/forms/__init__.py b/tailbone/forms/__init__.py index 34b34a6c..7b412dca 100644 --- a/tailbone/forms/__init__.py +++ b/tailbone/forms/__init__.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -24,7 +24,8 @@ Forms Library """ -# nb. import widgets before types, b/c types may refer to widgets -from . import widgets +from __future__ import unicode_literals, absolute_import + from . import types -from .core import Form, SimpleFileImport +from . import widgets +from .core import Form diff --git a/tailbone/forms/common.py b/tailbone/forms/common.py deleted file mode 100644 index 6183d17f..00000000 --- a/tailbone/forms/common.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -Common Forms -""" - -from rattail.db import model - -import colander - - -@colander.deferred -def validate_user(node, kw): - session = kw['session'] - def validate(node, value): - user = session.get(model.User, value) - if not user: - raise colander.Invalid(node, "User not found") - return user.uuid - return validate - - -class Feedback(colander.Schema): - """ - Form schema for user feedback. - """ - email_key = colander.SchemaNode(colander.String(), - missing=colander.null) - - referrer = colander.SchemaNode(colander.String()) - - user = colander.SchemaNode(colander.String(), - missing=colander.null, - validator=validate_user) - - user_name = colander.SchemaNode(colander.String(), - missing=colander.null) - - please_reply_to = colander.SchemaNode(colander.String(), - missing=colander.null) - - message = colander.SchemaNode(colander.String()) diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py index 4024557b..8401729a 100644 --- a/tailbone/forms/core.py +++ b/tailbone/forms/core.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2024 Lance Edgar +# Copyright © 2010-2018 Lance Edgar # # This file is part of Rattail. # @@ -24,19 +24,18 @@ Forms Core """ -import hashlib -import json -import logging -import warnings -from collections import OrderedDict +from __future__ import unicode_literals, absolute_import +import datetime +import logging + +import six import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.ext.associationproxy import AssociationProxy, ASSOCIATION_PROXY -from wuttjamaican.util import UNSPECIFIED -from rattail.util import pretty_boolean -from rattail.db.util import get_fieldnames +from rattail.time import localtime +from rattail.util import prettify, pretty_boolean, pretty_hours, pretty_quantity import colander import deform @@ -47,15 +46,9 @@ from pyramid_deform import SessionFileUploadTempStore from pyramid.renderers import render from webhelpers2.html import tags, HTML -from wuttaweb.util import FieldList, get_form_data, make_json_safe - -from tailbone.db import Session -from tailbone.util import raw_datetime, render_markdown -from tailbone.forms import types -from tailbone.forms.widgets import (ReadonlyWidget, PlainDateWidget, - JQueryDateWidget, JQueryTimeWidget, - FileUploadWidget, MultiFileUploadWidget) -from tailbone.exceptions import TailboneJSONFieldError +from tailbone.util import raw_datetime +from . import types +from .widgets import ReadonlyWidget, JQueryDateWidget, JQueryTimeWidget log = logging.getLogger(__name__) @@ -116,14 +109,22 @@ class CustomSchemaNode(SQLAlchemySchemaNode): for the given association proxy field name. Typically this will refer to the "extension" model class. """ - return get_association_proxy_target(self.inspector, field) + proxy = self.association_proxy(field) + if proxy: + proxy_target = self.inspector.get_property(proxy.target_collection) + if isinstance(proxy_target, orm.RelationshipProperty) and not proxy_target.uselist: + return proxy_target def association_proxy_column(self, field): """ Returns the property on the proxy target class, for the column which is reflected by the proxy. """ - return get_association_proxy_column(self.inspector, field) + proxy_target = self.association_proxy_target(field) + if proxy_target: + prop = proxy_target.mapper.get_property(field) + if isinstance(prop, orm.ColumnProperty) and isinstance(prop.columns[0], sa.Column): + return prop def supported_association_proxy(self, field): """ @@ -212,21 +213,13 @@ class CustomSchemaNode(SQLAlchemySchemaNode): excludes = [] if isinstance(prop, orm.RelationshipProperty): for next_prop in prop.mapper.iterate_properties: - - # don't include secondary relationships if isinstance(next_prop, orm.RelationshipProperty): excludes.append(next_prop.key) - # don't include fields of binary type - elif isinstance(next_prop, orm.ColumnProperty): - for column in next_prop.columns: - if isinstance(column.type, sa.LargeBinary): - excludes.append(next_prop.key) - if excludes: overrides['excludes'] = excludes - return super().get_schema_from_relationship(prop, overrides) + return super(CustomSchemaNode, self).get_schema_from_relationship(prop, overrides) def dictify(self, obj): """ Return a dictified version of `obj` using schema information. @@ -235,7 +228,7 @@ class CustomSchemaNode(SQLAlchemySchemaNode): This method was copied from upstream and modified to add automatic handling of "association proxy" fields. """ - dict_ = super().dictify(obj) + dict_ = super(CustomSchemaNode, self).dictify(obj) for node in self: name = node.name @@ -328,32 +321,26 @@ class Form(object): """ Base class for all forms. """ - save_label = "Submit" + save_label = "Save" update_label = "Save" show_cancel = True auto_disable = True auto_disable_save = True auto_disable_cancel = True - def __init__(self, fields=None, schema=None, request=None, readonly=False, readonly_fields=[], - model_instance=None, model_class=None, appstruct=UNSPECIFIED, nodes={}, enums={}, labels={}, - assume_local_times=False, renderers=None, renderer_kwargs={}, - hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None, - action_url=None, cancel_url=None, - vue_tagname=None, - vuejs_component_kwargs=None, vuejs_field_converters={}, json_data={}, included_templates={}, - # TODO: ugh this is getting out hand! - can_edit_help=False, edit_help_url=None, route_prefix=None, - **kwargs - ): + def __init__(self, fields=None, schema=None, request=None, mobile=False, readonly=False, readonly_fields=[], + model_instance=None, model_class=None, nodes={}, enums={}, labels={}, renderers=None, + hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, + action_url=None, cancel_url=None): + self.fields = None if fields is not None: self.set_fields(fields) self.schema = schema if self.fields is None and self.schema: self.set_fields([f.name for f in self.schema]) - self.grouping = None self.request = request + self.mobile = mobile self.readonly = readonly self.readonly_fields = set(readonly_fields or []) self.model_instance = model_instance @@ -362,99 +349,22 @@ class Form(object): self.model_class = type(self.model_instance) if self.model_class and self.fields is None: self.set_fields(self.make_fields()) - self.appstruct = appstruct self.nodes = nodes or {} self.enums = enums or {} self.labels = labels or {} - self.assume_local_times = assume_local_times if renderers is None and self.model_class: self.renderers = self.make_renderers() else: self.renderers = renderers or {} - self.renderer_kwargs = renderer_kwargs or {} self.hidden = hidden or {} self.widgets = widgets or {} self.defaults = defaults or {} self.validators = validators or {} self.required = required or {} self.helptext = helptext or {} - self.dynamic_helptext = {} - self.focus_spec = focus_spec self.action_url = action_url self.cancel_url = cancel_url - # vue_tagname - self.vue_tagname = vue_tagname - if not self.vue_tagname and kwargs.get('component'): - warnings.warn("component kwarg is deprecated for Form(); " - "please use vue_tagname param instead", - DeprecationWarning, stacklevel=2) - self.vue_tagname = kwargs['component'] - if not self.vue_tagname: - self.vue_tagname = 'tailbone-form' - - self.vuejs_component_kwargs = vuejs_component_kwargs or {} - self.vuejs_field_converters = vuejs_field_converters or {} - self.json_data = json_data or {} - self.included_templates = included_templates or {} - self.can_edit_help = can_edit_help - self.edit_help_url = edit_help_url - self.route_prefix = route_prefix - - self.button_icon_submit = kwargs.get('button_icon_submit', 'save') - - def __iter__(self): - return iter(self.fields) - - @property - def vue_component(self): - """ - String name for the Vue component, e.g. ``'TailboneGrid'``. - - This is a generated value based on :attr:`vue_tagname`. - """ - words = self.vue_tagname.split('-') - return ''.join([word.capitalize() for word in words]) - - @property - def component(self): - """ - DEPRECATED - use :attr:`vue_tagname` instead. - """ - warnings.warn("Form.component is deprecated; " - "please use vue_tagname instead", - DeprecationWarning, stacklevel=2) - return self.vue_tagname - - @property - def component_studly(self): - """ - DEPRECATED - use :attr:`vue_component` instead. - """ - warnings.warn("Form.component_studly is deprecated; " - "please use vue_component instead", - DeprecationWarning, stacklevel=2) - return self.vue_component - - def get_button_label_submit(self): - """ """ - if hasattr(self, '_button_label_submit'): - return self._button_label_submit - - label = getattr(self, 'submit_label', None) - if label: - return label - - return self.save_label - - def set_button_label_submit(self, value): - """ """ - self._button_label_submit = value - - # wutta compat - button_label_submit = property(get_button_label_submit, - set_button_label_submit) - def __contains__(self, item): return item in self.fields @@ -468,11 +378,19 @@ class Form(object): if not self.model_class: raise ValueError("Must define model_class to use make_fields()") - return get_fieldnames(self.request.rattail_config, self.model_class, - columns=True, proxies=True, relations=True) + mapper = orm.class_mapper(self.model_class) - def set_grouping(self, items): - self.grouping = OrderedDict(items) + # first add primary column fields + fields = FieldList([prop.key for prop in mapper.iterate_properties + if not prop.key.startswith('_') + and prop.key != 'versions']) + + # then add association proxy fields + for key, desc in sa.inspect(self.model_class).all_orm_descriptors.items(): + if desc.extension_type == ASSOCIATION_PROXY: + fields.append(key) + + return fields def make_renderers(self): """ @@ -492,10 +410,7 @@ class Form(object): if len(prop.columns) == 1: column = prop.columns[0] if isinstance(column.type, sa.DateTime): - if self.assume_local_times: - renderers[prop.key] = self.render_datetime_local - else: - renderers[prop.key] = self.render_datetime + renderers[prop.key] = self.render_datetime elif isinstance(column.type, sa.Boolean): renderers[prop.key] = self.render_boolean @@ -515,9 +430,6 @@ class Form(object): def append(self, field): self.fields.append(field) - def insert(self, index, field): - self.fields.insert(index, field) - def insert_before(self, field, newfield): self.fields.insert_before(field, newfield) @@ -604,10 +516,7 @@ class Form(object): # apply any validators for key, validator in self.validators.items(): - if key is None: - # this one is form-wide - schema.validator = validator - elif key in schema: + if key in schema: schema[key].validator = validator # apply required flags @@ -630,9 +539,7 @@ class Form(object): self.schema[key].title = label def get_label(self, key): - config = self.request.rattail_config - app = config.get_app() - return self.labels.get(key, app.make_title(key)) + return self.labels.get(key, prettify(key)) def set_readonly(self, key, readonly=True): if readonly: @@ -649,39 +556,18 @@ class Form(object): node = colander.SchemaNode(nodeinfo, **kwargs) self.nodes[key] = node - # must explicitly replace node, if we already have a schema - if self.schema: - self.schema[key] = node - - def set_type(self, key, type_, **kwargs): - + def set_type(self, key, type_): if type_ == 'datetime': self.set_renderer(key, self.render_datetime) - - elif type_ == 'datetime_falafel': - self.set_renderer(key, self.render_datetime) - self.set_node(key, types.FalafelDateTime(request=self.request)) - if kwargs.get('helptext'): - app = self.request.rattail_config.get_app() - timezone = app.get_timezone() - self.set_helptext(key, f"NOTE: all times are local to {timezone}") - elif type_ == 'datetime_local': self.set_renderer(key, self.render_datetime_local) - elif type_ == 'date_plain': - self.set_widget(key, PlainDateWidget()) elif type_ == 'date_jquery': # TODO: is this safe / a good idea? # self.set_node(key, colander.Date()) self.set_widget(key, JQueryDateWidget()) - elif type_ == 'time_jquery': self.set_node(key, types.JQueryTime()) self.set_widget(key, JQueryTimeWidget()) - - elif type_ == 'time_falafel': - self.set_node(key, types.FalafelTime(request=self.request)) - elif type_ == 'duration': self.set_renderer(key, self.render_duration) elif type_ == 'boolean': @@ -691,8 +577,6 @@ class Form(object): self.set_renderer(key, self.render_currency) elif type_ == 'quantity': self.set_renderer(key, self.render_quantity) - elif type_ == 'percent': - self.set_renderer(key, self.render_percent) elif type_ == 'gpc': self.set_renderer(key, self.render_gpc) elif type_ == 'enum': @@ -701,47 +585,15 @@ class Form(object): self.set_renderer(key, self.render_codeblock) self.set_widget(key, dfwidget.TextAreaWidget(cols=80, rows=8)) elif type_ == 'text': - self.set_renderer(key, self.render_pre_sans_serif) - self.set_widget(key, dfwidget.TextAreaWidget(cols=80, rows=8)) - elif type_ == 'text_wrapped': - self.set_renderer(key, self.render_pre_sans_serif_wrapped) self.set_widget(key, dfwidget.TextAreaWidget(cols=80, rows=8)) elif type_ == 'file': tmpstore = SessionFileUploadTempStore(self.request) - kw = {'widget': FileUploadWidget(tmpstore, request=self.request), - 'title': self.get_label(key)} - if 'required' in kwargs and not kwargs['required']: - kw['missing'] = colander.null - self.set_node(key, colander.SchemaNode(deform.FileData(), **kw)) - elif type_ == 'multi_file': - tmpstore = SessionFileUploadTempStore(self.request) - file_node = colander.SchemaNode(deform.FileData(), - name='upload') - - kw = {'name': key, - 'title': self.get_label(key), - 'widget': MultiFileUploadWidget(tmpstore)} - # if 'required' in kwargs and not kwargs['required']: - # kw['missing'] = colander.null - if kwargs.get('validate_unique'): - kw['validator'] = self.validate_multiple_files_unique - files_node = colander.SequenceSchema(file_node, **kw) - self.set_node(key, files_node) + self.set_node(key, colander.SchemaNode(deform.FileData(), + widget=dfwidget.FileUploadWidget(tmpstore), + title=self.get_label(key))) else: raise ValueError("unknown type for '{}' field: {}".format(key, type_)) - def validate_multiple_files_unique(self, node, value): - - # get SHA256 hash for each file; error if duplicates encountered - hashes = {} - for fileinfo in value: - fp = fileinfo['fp'] - fp.seek(0) - filehash = hashlib.sha256(fp.read()).hexdigest() - if filehash in hashes: - node.raise_invalid(f"Duplicate file detected: {fileinfo['filename']}") - hashes[filehash] = fileinfo - def set_enum(self, key, enum, empty=None): if enum: self.enums[key] = enum @@ -756,12 +608,6 @@ class Form(object): def get_enum(self, key): return self.enums.get(key) - # TODO: i don't think this is actually being used anywhere..? - def set_enum_value(self, key, enum_key, enum_value): - enum = self.enums.get(key) - if enum: - enum[enum_key] = enum_value - def set_renderer(self, key, renderer): if renderer is None: if key in self.renderers: @@ -769,22 +615,6 @@ class Form(object): else: self.renderers[key] = renderer - def add_renderer_kwargs(self, key, kwargs): - self.renderer_kwargs.setdefault(key, {}).update(kwargs) - - def get_renderer_kwargs(self, key): - return self.renderer_kwargs.get(key, {}) - - def set_renderer_kwargs(self, key, kwargs): - self.renderer_kwargs[key] = kwargs - - def set_input_handler(self, key, value): - """ - Convenience method to assign "input handler" callback code for - the given field. - """ - self.add_renderer_kwargs(key, {'input_handler': value}) - def set_hidden(self, key, hidden=True): self.hidden[key] = hidden @@ -796,26 +626,8 @@ class Form(object): self.schema[key].widget = widget def set_validator(self, key, validator): - """ - Set the validator for the schema node represented by the given - key. - - :param key: Normally this the name of one of the fields - contained in the form. It can also be ``None`` in which - case the validator pertains to the form at large instead of - one of the fields. - - :param validator: Callable which accepts ``(node, value)`` - args. - """ self.validators[key] = validator - # we normally apply the validator when creating the schema, so - # if this form already has a schema, then go ahead and apply - # the validator to it - if self.schema and key in self.schema: - self.schema[key].validator = validator - def set_required(self, key, required=True): """ Set whether or not value is required for a given field. @@ -828,16 +640,11 @@ class Form(object): """ self.defaults[key] = value - def set_helptext(self, key, value, dynamic=False): + def set_helptext(self, key, value): """ Set the help text for a given field. """ - # nb. must avoid newlines, they cause some weird "blank page" error?! - self.helptext[key] = value.replace('\n', ' ') - if value and dynamic: - self.dynamic_helptext[key] = True - else: - self.dynamic_helptext.pop(key, None) + self.helptext[key] = value def has_helptext(self, key): """ @@ -850,22 +657,17 @@ class Form(object): """ Render the help text for the given field. """ - text = self.helptext[key] - text = text.replace('"', '"') - return HTML.literal(text) + return self.helptext[key] - def set_vuejs_field_converter(self, field, converter): - self.vuejs_field_converters[field] = converter - - def render(self, **kwargs): - warnings.warn("Form.render() is deprecated (for now?); " - "please use Form.render_deform() instead", - DeprecationWarning, stacklevel=2) - return self.render_deform(**kwargs) - - def get_deform(self): - """ """ - return self.make_deform_form() + def render(self, template=None, **kwargs): + if not template: + if self.readonly: + template = '/forms/form_readonly.mako' + else: + template = '/forms/form.mako' + context = kwargs + context['form'] = self + return render(template, context) def make_deform_form(self): if not hasattr(self, 'deform_form'): @@ -881,12 +683,7 @@ class Form(object): # get initial form values from model instance kwargs = {} - # TODO: ugh, this is necessary to avoid some logic - # which assumes a ColanderAlchemy schema i think? - if self.appstruct is not UNSPECIFIED: - if self.appstruct: - kwargs['appstruct'] = self.appstruct - elif self.model_instance: + if self.model_instance: if self.model_class: kwargs['appstruct'] = schema.dictify(self.model_instance) else: @@ -905,371 +702,36 @@ class Form(object): return self.deform_form - def render_vue_template(self, template='/forms/deform.mako', **context): - """ """ - output = self.render_deform(template=template, **context) - return HTML.literal(output) - - def render_deform(self, dform=None, template=None, **kwargs): - if not template: - template = '/forms/deform.mako' - + def render_deform(self, dform=None, template='/forms/deform.mako', **kwargs): if dform is None: dform = self.make_deform_form() # TODO: would perhaps be nice to leverage deform's default rendering # someday..? i.e. using Chameleon *.pt templates - # return dform.render() + # return form.render() context = kwargs context['form'] = self context['dform'] = dform - context.setdefault('can_edit_help', self.can_edit_help) - if context['can_edit_help']: - context.setdefault('edit_help_url', self.edit_help_url) - context['field_labels'] = self.get_field_labels() - context['field_markdowns'] = self.get_field_markdowns() context.setdefault('form_kwargs', {}) # TODO: deprecate / remove the latter option here if self.auto_disable_save or self.auto_disable: - context['form_kwargs'].setdefault('ref', self.vue_component) - context['form_kwargs']['@submit'] = 'submit{}'.format(self.vue_component) - if self.focus_spec: - context['form_kwargs']['data-focus'] = self.focus_spec + context['form_kwargs']['class_'] = 'autodisable' context['request'] = self.request context['readonly_fields'] = self.readonly_fields context['render_field_readonly'] = self.render_field_readonly - return render(template, context) - - def get_field_labels(self): - return dict([(field, self.get_label(field)) - for field in self]) - - def get_field_markdowns(self, session=None): - app = self.request.rattail_config.get_app() - model = app.model - session = session or Session() - - if not hasattr(self, 'field_markdowns'): - infos = session.query(model.TailboneFieldInfo)\ - .filter(model.TailboneFieldInfo.route_prefix == self.route_prefix)\ - .all() - self.field_markdowns = dict([(info.field_name, info.markdown_text) - for info in infos]) - - return self.field_markdowns - - def get_vue_field_value(self, key): - """ """ - if key not in self.fields: - return - - dform = self.get_deform() - if key not in dform: - return - - field = dform[key] - return make_json_safe(field.cstruct) - - def get_vuejs_model_value(self, field): - """ - This method must return "raw" JS which will be assigned as the initial - model value for the given field. This JS will be written as part of - the overall response, to be interpreted on the client side. - """ - if field.name in self.vuejs_field_converters: - convert = self.vuejs_field_converters[field.name] - value = convert(field.cstruct) - return json.dumps(value) - - if isinstance(field.schema.typ, colander.Set): - if field.cstruct is colander.null: - return '[]' - - try: - return self.jsonify_value(field.cstruct) - except Exception as error: - raise TailboneJSONFieldError(field.name, error) - - def jsonify_value(self, value): - """ - Take a Python value and convert to JSON - """ - if value is colander.null: - return 'null' - - if isinstance(value, dfwidget.filedict): - # TODO: we used to always/only return 'null' here but hopefully - # this also works, to show existing filename when present - if value and value['filename']: - return json.dumps({'name': value['filename']}) - return 'null' - - elif isinstance(value, list) and all([isinstance(f, dfwidget.filedict) - for f in value]): - return json.dumps([{'name': f['filename']} - for f in value]) - - app = self.request.rattail_config.get_app() - value = app.json_friendly(value) - return json.dumps(value) - - def get_error_messages(self, field): - if field.error: - return field.error.messages() - - error = self.make_deform_form().error - if error: - if isinstance(error, colander.Invalid): - if error.node.name == field.name: - return error.messages() - - def messages_json(self, messages): - dump = json.dumps(messages) - dump = dump.replace("'", ''') - return dump + return render('/forms/deform.mako', context) def field_visible(self, field): if self.hidden and self.hidden.get(field): return False return True - def set_vuejs_component_kwargs(self, **kwargs): - self.vuejs_component_kwargs.update(kwargs) - - def render_vue_tag(self, **kwargs): - """ """ - return self.render_vuejs_component(**kwargs) - - def render_vuejs_component(self, **kwargs): - """ - Render the Vue.js component HTML for the form. - - Most typically this is something like: - - .. code-block:: html - - - - """ - kw = dict(self.vuejs_component_kwargs) - kw.update(kwargs) - if self.can_edit_help: - kw.setdefault(':configure-fields-help', 'configureFieldsHelp') - return HTML.tag(self.vue_tagname, **kw) - - def set_json_data(self, key, value): - """ - Establish a data value for use in client-side JS. This value - will be JSON-encoded and made available to the - `` component within the client page. - """ - self.json_data[key] = value - - def include_template(self, template, context): - """ - Declare a JS template as required by the current form. This - template will then be included in the final page, so all - widgets behave correctly. - """ - self.included_templates[template] = context - - def render_included_templates(self): - templates = [] - for template, context in self.included_templates.items(): - context = dict(context) - context['form'] = self - templates.append(HTML.literal(render(template, context))) - return HTML.literal('\n').join(templates) - - def render_vue_field(self, fieldname, **kwargs): - """ """ - return self.render_field_complete(fieldname, **kwargs) - - def render_field_complete(self, fieldname, bfield_attrs={}, - session=None): - """ - Render the given field completely, i.e. with ```` - wrapper. Note that this is meant to render *editable* fields, - i.e. showing a widget, unless the field input is hidden. In - other words it's not for "readonly" fields. - """ - dform = self.make_deform_form() - field = dform[fieldname] if fieldname in dform else None - - include = bool(field) - if self.readonly or (not field and fieldname in self.readonly_fields): - include = True - if not include: - return - - if self.field_visible(fieldname): - label = self.get_label(fieldname) - markdowns = self.get_field_markdowns(session=session) - - # these attrs will be for the (*not* the widget) - attrs = { - ':horizontal': 'true', - } - - # add some magic for file input fields - if field and isinstance(field.schema.typ, deform.FileData): - attrs['class_'] = 'file' - - # next we will build array of messages to display..some - # fields always show a "helptext" msg, and some may have - # validation errors.. - field_type = None - messages = [] - - # show errors if present - error_messages = self.get_error_messages(field) if field else None - if error_messages: - field_type = 'is-danger' - messages.extend(error_messages) - - # show helptext if present - # TODO: older logic did this only if field was *not* - # readonly, perhaps should add that back.. - if self.has_helptext(fieldname): - messages.append(self.render_helptext(fieldname)) - - # ..okay now we can declare the field messages and type - if field_type: - attrs['type'] = field_type - if messages: - if len(messages) == 1: - msg = messages[0] - if msg.startswith('`') and msg.endswith('`'): - attrs[':message'] = msg - else: - attrs['message'] = msg - else: - # nb. must pass an array as JSON string - attrs[':message'] = '[{}]'.format(', '.join([ - "'{}'".format(msg.replace("'", r"\'")) - for msg in messages])) - - # merge anything caller provided - attrs.update(bfield_attrs) - - # render the field widget or whatever - if self.readonly or fieldname in self.readonly_fields: - html = self.render_field_value(fieldname) or HTML.tag('span') - if type(html) is str: - html = HTML.tag('span', c=[html]) - elif field: - html = field.serialize(**self.get_renderer_kwargs(fieldname)) - html = HTML.literal(html) - - # may need a complex label - label_contents = [label] - - # add 'help' icon/tooltip if defined - if markdowns.get(fieldname): - icon = HTML.tag('b-icon', size='is-small', pack='fas', - icon='question-circle') - tooltip = render_markdown(markdowns[fieldname]) - - # nb. must apply hack to get