Add initial Shopfoo pattern feature
data models, import/export, web views etc.
This commit is contained in:
parent
a7656928f5
commit
852f9d4902
46
rattail_demo/commands.py
Normal file
46
rattail_demo/commands.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Rattail Demo Commands
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from rattail import commands
|
||||
from rattail_demo import __version__
|
||||
|
||||
|
||||
def main(*args):
|
||||
"""
|
||||
Main entry point for Rattail Demo command system
|
||||
"""
|
||||
args = list(args or sys.argv[1:])
|
||||
cmd = Command()
|
||||
cmd.run(*args)
|
||||
|
||||
|
||||
class Command(commands.Command):
|
||||
"""
|
||||
Main command for Rattail Demo
|
||||
"""
|
||||
name = 'rattail-demo'
|
||||
version = __version__
|
||||
description = "Rattail Demo (custom Rattail system)"
|
||||
long_description = ""
|
||||
|
||||
|
||||
class ExportShopfoo(commands.ExportFileSubcommand):
|
||||
"""
|
||||
Export data to the Shopfoo system
|
||||
"""
|
||||
name = 'export-shopfoo'
|
||||
description = __doc__.strip()
|
||||
handler_spec = 'rattail_demo.shopfoo.importing.rattail:FromRattailToShopfoo'
|
||||
|
||||
|
||||
class ImportSelf(commands.ImportSubcommand):
|
||||
"""
|
||||
Update "cascading" Rattail data based on "core" Rattail data
|
||||
"""
|
||||
name = 'import-self'
|
||||
description = __doc__.strip()
|
||||
handler_spec = 'rattail_demo.importing.local:FromRattailDemoToSelf'
|
|
@ -20,4 +20,4 @@ class DemoConfigExtension(ConfigExtension):
|
|||
config.setdefault('tailbone', 'menus', 'rattail_demo.web.menus')
|
||||
|
||||
# default import handlers
|
||||
config.setdefault('rattail.importing', 'versions.handler', 'rattail_corepos.importing.versions:FromRattailToRattailVersions')
|
||||
config.setdefault('rattail.importing', 'versions.handler', 'rattail_demo.importing.versions:FromRattailDemoToRattailDemoVersions')
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""initial tables
|
||||
|
||||
Revision ID: 2108f9efa758
|
||||
Revises: efb7cd318947
|
||||
Create Date: 2020-08-19 20:02:15.501843
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2108f9efa758'
|
||||
down_revision = None
|
||||
branch_labels = ('rattail_demo',)
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import rattail.db.types
|
||||
|
||||
|
||||
|
||||
def upgrade():
|
||||
|
||||
# demo_shopfoo_product
|
||||
op.create_table('demo_shopfoo_product',
|
||||
sa.Column('uuid', sa.String(length=32), nullable=False),
|
||||
sa.Column('product_uuid', sa.String(length=32), nullable=True),
|
||||
sa.Column('upc', sa.String(length=14), nullable=True),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('price', sa.Numeric(precision=13, scale=2), nullable=True),
|
||||
sa.Column('enabled', sa.Boolean(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['product_uuid'], ['product.uuid'], name='demo_shopfoo_product_fk_product'),
|
||||
sa.PrimaryKeyConstraint('uuid')
|
||||
)
|
||||
op.create_table('demo_shopfoo_product_version',
|
||||
sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False),
|
||||
sa.Column('product_uuid', sa.String(length=32), autoincrement=False, nullable=True),
|
||||
sa.Column('upc', sa.String(length=14), autoincrement=False, nullable=True),
|
||||
sa.Column('description', sa.String(length=255), autoincrement=False, nullable=True),
|
||||
sa.Column('price', sa.Numeric(precision=13, scale=2), autoincrement=False, nullable=True),
|
||||
sa.Column('enabled', sa.Boolean(), autoincrement=False, nullable=True),
|
||||
sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False),
|
||||
sa.Column('end_transaction_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('operation_type', sa.SmallInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('uuid', 'transaction_id')
|
||||
)
|
||||
op.create_index(op.f('ix_demo_shopfoo_product_version_end_transaction_id'), 'demo_shopfoo_product_version', ['end_transaction_id'], unique=False)
|
||||
op.create_index(op.f('ix_demo_shopfoo_product_version_operation_type'), 'demo_shopfoo_product_version', ['operation_type'], unique=False)
|
||||
op.create_index(op.f('ix_demo_shopfoo_product_version_transaction_id'), 'demo_shopfoo_product_version', ['transaction_id'], unique=False)
|
||||
|
||||
# demo_shopfoo_product_export
|
||||
op.create_table('demo_shopfoo_product_export',
|
||||
sa.Column('uuid', sa.String(length=32), nullable=False),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created', sa.DateTime(), nullable=False),
|
||||
sa.Column('created_by_uuid', sa.String(length=32), nullable=False),
|
||||
sa.Column('record_count', sa.Integer(), nullable=True),
|
||||
sa.Column('filename', sa.String(length=255), nullable=True),
|
||||
sa.Column('uploaded', sa.Boolean(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['created_by_uuid'], ['user.uuid'], name='demo_shopfoo_product_export_fk_created_by'),
|
||||
sa.PrimaryKeyConstraint('uuid')
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
# demo_shopfoo_product_export
|
||||
op.drop_table('demo_shopfoo_product_export')
|
||||
|
||||
# demo_shopfoo_product
|
||||
op.drop_index(op.f('ix_demo_shopfoo_product_version_transaction_id'), table_name='demo_shopfoo_product_version')
|
||||
op.drop_index(op.f('ix_demo_shopfoo_product_version_operation_type'), table_name='demo_shopfoo_product_version')
|
||||
op.drop_index(op.f('ix_demo_shopfoo_product_version_end_transaction_id'), table_name='demo_shopfoo_product_version')
|
||||
op.drop_table('demo_shopfoo_product_version')
|
||||
op.drop_table('demo_shopfoo_product')
|
|
@ -8,3 +8,6 @@ from rattail.db.model import *
|
|||
|
||||
# also bring in CORE integration models
|
||||
from rattail_corepos.db.model import *
|
||||
|
||||
# now bring in Demo-specific models
|
||||
from .shopfoo import ShopfooProduct, ShopfooProductExport
|
37
rattail_demo/db/model/shopfoo.py
Normal file
37
rattail_demo/db/model/shopfoo.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Database schema extensions for Shopfoo integration
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from rattail.db import model
|
||||
from rattail.db.model.shopfoo import ShopfooProductBase, ShopfooProductExportBase
|
||||
|
||||
|
||||
class ShopfooProduct(ShopfooProductBase, model.Base):
|
||||
"""
|
||||
Shopfoo-specific product cache table. Each record in this table *should*
|
||||
match exactly, what is in the actual "Shopfoo" system (even though that's
|
||||
made-up in this case).
|
||||
"""
|
||||
__tablename__ = 'demo_shopfoo_product'
|
||||
__versioned__ = {}
|
||||
|
||||
upc = sa.Column(sa.String(length=14), nullable=True)
|
||||
|
||||
description = sa.Column(sa.String(length=255), nullable=True)
|
||||
|
||||
price = sa.Column(sa.Numeric(precision=13, scale=2), nullable=True)
|
||||
|
||||
enabled = sa.Column(sa.Boolean(), nullable=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.description or self.upc or ""
|
||||
|
||||
|
||||
class ShopfooProductExport(ShopfooProductExportBase, model.Base):
|
||||
"""
|
||||
Shopfoo product exports
|
||||
"""
|
||||
__tablename__ = 'demo_shopfoo_product_export'
|
6
rattail_demo/importing/__init__.py
Normal file
6
rattail_demo/importing/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Importing into Rattail Demo
|
||||
"""
|
||||
|
||||
from . import model
|
60
rattail_demo/importing/local.py
Normal file
60
rattail_demo/importing/local.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Rattail Demo -> Rattail Demo "self" data import
|
||||
"""
|
||||
|
||||
from rattail.importing.local import FromRattailLocalToRattail, FromRattailLocal
|
||||
from rattail.importing.shopfoo import ShopfooProductImporterMixin
|
||||
from rattail.util import OrderedDict
|
||||
from rattail_demo import importing as rattail_demo_importing
|
||||
|
||||
|
||||
class FromRattailDemoToSelf(FromRattailLocalToRattail):
|
||||
"""
|
||||
Handler for Rattail Demo -> Rattail Demo ("self") imports
|
||||
"""
|
||||
|
||||
def get_importers(self):
|
||||
importers = OrderedDict()
|
||||
importers['ShopfooProduct'] = ShopfooProductImporter
|
||||
return importers
|
||||
|
||||
|
||||
class ShopfooProductImporter(ShopfooProductImporterMixin, FromRattailLocal, rattail_demo_importing.model.ShopfooProductImporter):
|
||||
"""
|
||||
Product -> ShopfooProduct
|
||||
"""
|
||||
supported_fields = [
|
||||
'uuid',
|
||||
'product_uuid',
|
||||
'upc',
|
||||
'description',
|
||||
'price',
|
||||
'enabled',
|
||||
]
|
||||
|
||||
def normalize_base_product_data(self, product):
|
||||
|
||||
price = None
|
||||
if product.regular_price:
|
||||
price = product.regular_price.price
|
||||
|
||||
return {
|
||||
'product_uuid': product.uuid,
|
||||
'upc': str(product.upc or '') or None,
|
||||
'description': product.full_description,
|
||||
'price': price,
|
||||
'enabled': True, # will maybe unset this in mark_unwanted()
|
||||
}
|
||||
|
||||
def product_is_unwanted(self, product, data):
|
||||
if super(ShopfooProductImporter, self).product_is_unwanted(product, data):
|
||||
return True
|
||||
if not data['price']: # let's say this is a required field for Shopfoo
|
||||
return True
|
||||
return False
|
||||
|
||||
def mark_unwanted(self, product, data):
|
||||
data = super(ShopfooProductImporter, self).mark_unwanted(product, data)
|
||||
data['enabled'] = False
|
||||
return data
|
18
rattail_demo/importing/model.py
Normal file
18
rattail_demo/importing/model.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Rattail Demo model importers
|
||||
"""
|
||||
|
||||
from rattail.importing.model import ToRattail
|
||||
from rattail_demo.db import model
|
||||
|
||||
|
||||
##############################
|
||||
# custom models
|
||||
##############################
|
||||
|
||||
class ShopfooProductImporter(ToRattail):
|
||||
"""
|
||||
Importer for ShopfooProduct data
|
||||
"""
|
||||
model_class = model.ShopfooProduct
|
25
rattail_demo/importing/versions.py
Normal file
25
rattail_demo/importing/versions.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Rattail Demo -> Rattail Demo "versions" data import
|
||||
"""
|
||||
|
||||
from rattail_demo.db import model
|
||||
from rattail.importing import versions as base
|
||||
from rattail_corepos.importing.versions import CoreposVersionMixin
|
||||
|
||||
|
||||
class FromRattailDemoToRattailDemoVersions(base.FromRattailToRattailVersions,
|
||||
CoreposVersionMixin):
|
||||
"""
|
||||
Handler for Rattail Demo -> Rattail Demo "versions" data import
|
||||
"""
|
||||
|
||||
def get_importers(self):
|
||||
importers = super(FromRattailDemoToRattailDemoVersions, self).get_importers()
|
||||
importers = self.add_corepos_importers(importers)
|
||||
importers['ShopfooProduct'] = ShopfooProductImporter
|
||||
return importers
|
||||
|
||||
|
||||
class ShopfooProductImporter(base.VersionImporter):
|
||||
host_model_class = model.ShopfooProduct
|
0
rattail_demo/shopfoo/__init__.py
Normal file
0
rattail_demo/shopfoo/__init__.py
Normal file
6
rattail_demo/shopfoo/importing/__init__.py
Normal file
6
rattail_demo/shopfoo/importing/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Importing into Shopfoo
|
||||
"""
|
||||
|
||||
from . import model
|
28
rattail_demo/shopfoo/importing/model.py
Normal file
28
rattail_demo/shopfoo/importing/model.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Shopfoo model importers
|
||||
"""
|
||||
|
||||
from rattail_demo.db import model
|
||||
from rattail.importing.exporters import ToCSV
|
||||
from rattail.shopfoo.importing.model import ProductImporterMixin
|
||||
|
||||
|
||||
class ToShopfoo(ToCSV):
|
||||
pass
|
||||
|
||||
|
||||
class ProductImporter(ProductImporterMixin, ToShopfoo):
|
||||
"""
|
||||
Shopfoo product data importer
|
||||
"""
|
||||
key = 'uuid'
|
||||
simple_fields = [
|
||||
'uuid',
|
||||
'product_uuid',
|
||||
'upc',
|
||||
'description',
|
||||
'price',
|
||||
'enabled',
|
||||
]
|
||||
export_model_class = model.ShopfooProductExport
|
62
rattail_demo/shopfoo/importing/rattail.py
Normal file
62
rattail_demo/shopfoo/importing/rattail.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Rattail -> Shopfoo importing
|
||||
"""
|
||||
|
||||
from rattail import importing
|
||||
from rattail.util import OrderedDict
|
||||
|
||||
from rattail_demo.db import model
|
||||
from rattail_demo.shopfoo import importing as shopfoo_importing
|
||||
from rattail.shopfoo.importing.rattail import ProductImporterMixin
|
||||
|
||||
|
||||
class FromRattailToShopfoo(importing.FromRattailHandler):
|
||||
"""
|
||||
Rattail -> Shopfoo import handler
|
||||
"""
|
||||
host_title = "Rattail"
|
||||
local_title = "Shopfoo"
|
||||
direction = 'export'
|
||||
|
||||
def get_importers(self):
|
||||
importers = OrderedDict()
|
||||
importers['Product'] = ProductImporter
|
||||
return importers
|
||||
|
||||
|
||||
class FromRattail(importing.FromSQLAlchemy):
|
||||
"""
|
||||
Base class for Shopfoo -> Rattail importers
|
||||
"""
|
||||
|
||||
|
||||
class ProductImporter(ProductImporterMixin, FromRattail, shopfoo_importing.model.ProductImporter):
|
||||
"""
|
||||
Product data importer
|
||||
"""
|
||||
host_model_class = model.ShopfooProduct
|
||||
supported_fields = [
|
||||
'uuid',
|
||||
'product_uuid',
|
||||
'upc',
|
||||
'description',
|
||||
'price',
|
||||
'enabled',
|
||||
]
|
||||
|
||||
def query(self):
|
||||
return self.host_session.query(model.ShopfooProduct)\
|
||||
.order_by(model.ShopfooProduct.upc)
|
||||
|
||||
def normalize_host_object(self, product):
|
||||
|
||||
# copy all values "as-is" from our cache record
|
||||
data = dict([(field, getattr(product, field))
|
||||
for field in self.fields])
|
||||
|
||||
# TODO: is it ever a good idea to set this flag? doing so will mean
|
||||
# the record is *not* included in CSV output file
|
||||
# data['_deleted_'] = product.deleted_from_shopfoo
|
||||
|
||||
return data
|
|
@ -126,6 +126,22 @@ def simple_menus(request):
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Shopfoo",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{
|
||||
'title': "Products",
|
||||
'url': url('shopfoo.products'),
|
||||
'perm': 'shopfoo.products.list',
|
||||
},
|
||||
{
|
||||
'title': "Product Exports",
|
||||
'url': url('shopfoo.product_exports'),
|
||||
'perm': 'shopfoo.product_exports.list',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Batches",
|
||||
'type': 'menu',
|
||||
|
|
|
@ -37,6 +37,9 @@ def includeme(config):
|
|||
# core-pos views
|
||||
config.include('tailbone_corepos.views.corepos')
|
||||
|
||||
# shopfoo views
|
||||
config.include('rattail_demo.web.views.shopfoo')
|
||||
|
||||
# batch views
|
||||
config.include('tailbone.views.handheld')
|
||||
config.include('tailbone.views.batch.inventory')
|
||||
|
|
9
rattail_demo/web/views/shopfoo/__init__.py
Normal file
9
rattail_demo/web/views/shopfoo/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Shopfoo views
|
||||
"""
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('rattail_demo.web.views.shopfoo.products')
|
||||
config.include('rattail_demo.web.views.shopfoo.exports')
|
42
rattail_demo/web/views/shopfoo/exports.py
Normal file
42
rattail_demo/web/views/shopfoo/exports.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Views for Shopfoo product exports
|
||||
"""
|
||||
|
||||
from rattail_demo.db import model
|
||||
|
||||
from tailbone.views.exports import ExportMasterView
|
||||
|
||||
|
||||
class ShopfooProductExportView(ExportMasterView):
|
||||
"""
|
||||
Master view for Shopfoo product exports.
|
||||
"""
|
||||
model_class = model.ShopfooProductExport
|
||||
route_prefix = 'shopfoo.product_exports'
|
||||
url_prefix = '/shopfoo/exports/product'
|
||||
downloadable = True
|
||||
editable = True
|
||||
delete_export_files = True
|
||||
|
||||
grid_columns = [
|
||||
'id',
|
||||
'created',
|
||||
'created_by',
|
||||
'filename',
|
||||
'record_count',
|
||||
'uploaded',
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
'id',
|
||||
'created',
|
||||
'created_by',
|
||||
'record_count',
|
||||
'filename',
|
||||
'uploaded',
|
||||
]
|
||||
|
||||
|
||||
def includeme(config):
|
||||
ShopfooProductExportView.defaults(config)
|
70
rattail_demo/web/views/shopfoo/products.py
Normal file
70
rattail_demo/web/views/shopfoo/products.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Shopfoo product views
|
||||
"""
|
||||
|
||||
from rattail_demo.db import model
|
||||
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
class ShopfooProductView(MasterView):
|
||||
"""
|
||||
Shopfoo Product views
|
||||
"""
|
||||
model_class = model.ShopfooProduct
|
||||
url_prefix = '/shopfoo/products'
|
||||
route_prefix = 'shopfoo.products'
|
||||
creatable = False
|
||||
editable = False
|
||||
bulk_deletable = True
|
||||
has_versions = True
|
||||
|
||||
labels = {
|
||||
'upc': "UPC",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
'upc',
|
||||
'description',
|
||||
'price',
|
||||
'enabled',
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
'product',
|
||||
'upc',
|
||||
'description',
|
||||
'price',
|
||||
'enabled',
|
||||
]
|
||||
|
||||
def configure_grid(self, g):
|
||||
super(ShopfooProductView, self).configure_grid(g)
|
||||
|
||||
g.filters['upc'].default_active = True
|
||||
g.filters['upc'].default_verb = 'equal'
|
||||
|
||||
g.filters['description'].default_active = True
|
||||
g.filters['description'].default_verb = 'contains'
|
||||
|
||||
g.set_sort_defaults('upc')
|
||||
|
||||
g.set_type('price', 'currency')
|
||||
|
||||
g.set_link('upc')
|
||||
g.set_link('description')
|
||||
|
||||
def grid_extra_class(self, product, i):
|
||||
if not product.enabled:
|
||||
return 'warning'
|
||||
|
||||
def configure_form(self, f):
|
||||
super(ShopfooProductView, self).configure_form(f)
|
||||
|
||||
f.set_renderer('product', self.render_product)
|
||||
f.set_type('price', 'currency')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
ShopfooProductView.defaults(config)
|
8
setup.py
8
setup.py
|
@ -89,5 +89,13 @@ setup(
|
|||
'rattail.config.extensions': [
|
||||
'rattail-demo = rattail_demo.config:DemoConfigExtension',
|
||||
],
|
||||
'console_scripts': [
|
||||
'rattail-demo = rattail_demo.commands:main',
|
||||
],
|
||||
|
||||
'rattail_demo.commands': [
|
||||
'export-shopfoo = rattail_demo.commands:ExportShopfoo',
|
||||
'import-self = rattail_demo.commands:ImportSelf',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue