Changeset - d46ae258fc4e
[Not reviewed]
0 3 0
Lance Edgar (lance) - 12 years ago 2012-04-10 22:25:32
lance@edbob.org
save point
3 files changed with 120 insertions and 63 deletions:
0 comments (0 inline, 0 general)
rattail/batches.py
Show inline comments
 
@@ -138,27 +138,32 @@ class RattailBatchTerminal(BatchTerminal):
 
    #     'F02'           : "Description",
 
    #     'F4001'         : "Description 2",
 
    #     }
 

	
 
    source_columns = {
 
        'ITEM_DCT': [
 
            'F01',
 
            'F02',
 
            ],
 
        }
 

	
 
    target_columns = {
 
        'DEPT_DCT': [
 
            'F03',
 
            'F238',
 
            ],
 
        'ITEM_DCT': [
 
            'F01',
 
            'F02',
 
            'F155',
 
            ],
 
        }
 

	
 
    # def import_main_item(self, session, elements=None, progress_factory=None):
 
    #     """
 
    #     Create a main item (ITEM_DCT) batch from current Rattail data.
 
    #     """
 

	
 
    #     elements = self.get_elements(elements, self.fieldname_map_main_item, 'F01')
 
    #     batch = make_batch(self.name, elements, session,
 
    #                        description="Main item data from Rattail")
 

	
 
@@ -169,40 +174,71 @@ class RattailBatchTerminal(BatchTerminal):
 
    #     for i, prod in enumerate(products, 1):
 
    #         fields = {}
 
    #         for name in elements:
 
    #             fields[name] = row[self.fieldname_map_main_item[name]]
 
    #         batch.append(**fields)
 
    #         if prog:
 
    #             prog.update(i)
 
    #     if prog:
 
    #         prog.destroy()
 

	
 
    #     return batch
 

	
 
    def execute_batch(self, batch):
 
        """
 
        Executes ``batch``, which should be a :class:`rattail.Batch` instance.
 
        """
 
    def add_departments(self, session, batch):
 
        for row in batch.provide_rows():
 
            dept = rattail.Department()
 
            dept.number = row.F03
 
            dept.name = row.F238
 
            session.add(dept)
 
            session.flush()
 

	
 
        assert batch.action_type == rattail.BATCH_ADD
 
        assert batch.dictionary.name == 'ITEM_DCT'
 
    def add_products(self, session, batch):
 
        q = session.query(rattail.Brand)
 
        brands = {}
 
        for brand in q:
 
            brands[brand.name] = brand
 

	
 
        session = object_session(batch)
 
        for row in batch.provide_rows():
 
            if row.F155 in brands:
 
                brand = brands[row.F155]
 
            else:
 
                brand = rattail.Brand(name=row.F155)
 
                session.add(brand)
 
                session.flush()
 
                brands[brand.name] = brand
 
            prod = rattail.Product()
 
            prod.upc = row.F01
 
            prod.description = row.F02
 
            prod.brand = brand
 
            session.add(prod)
 
            session.flush()
 

	
 
    def execute_batch(self, batch):
 
        """
 
        Executes ``batch``, which should be a :class:`rattail.Batch` instance.
 
        """
 

	
 
        session = object_session(batch)
 

	
 
        if batch.action_type == rattail.BATCH_ADD:
 

	
 
            if batch.dictionary.name == 'DEPT_DCT':
 
                self.add_departments(session, batch)
 
                return
 
            if batch.dictionary.name == 'ITEM_DCT':
 
                self.add_products(session, batch)
 
                return
 

	
 
        assert False, "FIXME"
 

	
 

	
 
# def make_batch(source, elements, session, batch_id=None, **kwargs):
 
#     """
 
#     Create and return a new SIL-based :class:`rattail.Batch` instance.
 
#     """
 

	
 
#     if not batch_id:
 
#         batch_id = next_batch_id(source, consume=True)
 

	
 
#     kwargs['source'] = source
 
#     kwargs['batch_id'] = batch_id
 
#     kwargs['target'] = target
rattail/db.py
Show inline comments
 
@@ -32,47 +32,54 @@ import rattail
 

	
 

	
 
def init_database(session):
 
    """
 
    Initialize an ``edbob`` database for use with Rattail.
 
    """
 

	
 
    activate_extension('rattail', session.bind)
 

	
 
    columns = [
 
        ('F01',         'UPC',                  'GPC(14)'),
 
        ('F02',         'Description',          'CHAR(20)'),
 
        ('F03',         'Department Number',    'NUMBER(4,0)'),
 
        ('F155',        'Brand',                'CHAR(30)'),
 
        ('F238',        'Department Name',      'CHAR(30)'),
 
        ]
 

	
 
    for name, disp, dtype in columns:
 
        session.add(rattail.SilColumn(
 
                sil_name=name, display=disp, data_type=dtype))
 
        session.flush()
 

	
 
    dictionaries = [
 
        # ('CLASS_GROUP', 'Scale Class / Group', []),
 
        # ('DEPT_DCT', 'Department', []),
 
        # ('FCOST_DCT', 'Future Cost', []),
 
        # ('FSPRICE_DCT', 'Future Sale Price', []),
 
        ('DEPT_DCT', 'Department', [
 
                ('F03', True),
 
                'F238',
 
                ]),
 
        ('ITEM_DCT', 'Product', [
 
                ('F01', True),
 
                'F02',
 
                'F155',
 
                ]),
 
        # ('NUTRITION', 'Scale Nutrition', []),
 
        # ('PRICE_DCT', 'Price', []),
 
        # ('FCOST_DCT', 'Future Cost', []),
 
        # ('FSPRICE_DCT', 'Future Sale Price', []),
 
        # ('CLASS_GROUP', 'Scale Class / Group', []),
 
        # ('NUTRITION', 'Scale Nutrition', []),
 
        # ('SCALE_TEXT', 'Scale Text', []),
 
        # ('VENDOR_DCT', 'Vendor', []),
 
        ]
 

	
 
    for name, desc, cols in dictionaries:
 
        bd = rattail.BatchDictionary(name=name, description=desc)
 
        for col in cols:
 
            key = False
 
            if not isinstance(col, basestring):
 
                col, key = col
 
            q = session.query(rattail.SilColumn)
 
            q = q.filter(rattail.SilColumn.sil_name == col)
 
            col = q.one()
 
            bd.columns.append(
 
                rattail.BatchDictionaryColumn(column=col, key=key))
 
                rattail.BatchDictionaryColumn(sil_column=col, key=key))
 
        session.add(bd)
 
        session.flush()
rattail/model.py
Show inline comments
 
@@ -20,36 +20,36 @@
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.db.extension.model`` -- Schema Definition
 
"""
 

	
 
import re
 

	
 
from sqlalchemy import (Column, String, Integer, Date, DateTime,
 
                        Boolean, Text, ForeignKey, BigInteger)
 
                        Boolean, Text, ForeignKey, BigInteger, Numeric)
 
from sqlalchemy.orm import relationship, object_session
 

	
 
import edbob
 
from edbob.db.model import Base, uuid_column
 

	
 

	
 
__all__ = ['SilColumn', 'BatchDictionaryColumn', 'BatchDictionary',
 
           'BatchTerminalSourceColumn', 'BatchTerminalTargetColumn',
 
           'BatchTerminal', 'BatchColumn', 'Batch', 'Brand', 'Product']
 
           'BatchTerminalColumn', 'BatchTerminal', 'BatchColumn',
 
           'Batch', 'Brand', 'Department', 'Product']
 

	
 
sil_type_pattern = re.compile(r'^(CHAR)\((\d+)\)$')
 
sil_type_pattern = re.compile(r'^(CHAR|NUMBER)\((\d+(?:\,\d+)?)\)$')
 

	
 

	
 
class SilColumn(Base):
 
    """
 
    Represents a SIL-compatible column available to the batch system.
 
    """
 

	
 
    __tablename__ = 'sil_columns'
 

	
 
    uuid = uuid_column()
 
    sil_name = Column(String(10))
 
    display = Column(String(20))
 
@@ -62,34 +62,34 @@ class SilColumn(Base):
 
        return str(self.sil_name or '')
 

	
 

	
 
class BatchDictionaryColumn(Base):
 
    """
 
    Represents a column within a :class:`BatchDictionary`.
 
    """
 

	
 
    __tablename__ = 'batch_dictionary_columns'
 

	
 
    uuid = uuid_column()
 
    dictionary_uuid = Column(String(32), ForeignKey('batch_dictionaries.uuid'))
 
    column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
    sil_column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
    key = Column(Boolean)
 

	
 
    column = relationship(SilColumn)
 
    sil_column = relationship(SilColumn)
 

	
 
    def __repr__(self):
 
        return "<BatchDictionaryColumn: %s>" % self.column
 
        return "<BatchDictionaryColumn: %s>" % self.sil_column
 

	
 
    def __str__(self):
 
        return str(self.column or '')
 
        return str(self.sil_column or '')
 

	
 

	
 
class BatchDictionary(Base):
 
    """
 
    Represents a SIL-based dictionary supported by one or more
 
    :class:`BatchTerminal` classes.
 
    """
 

	
 
    __tablename__ = 'batch_dictionaries'
 

	
 
    uuid = uuid_column()
 
    name = Column(String(20))
 
@@ -97,104 +97,84 @@ class BatchDictionary(Base):
 

	
 
    columns = relationship(
 
        BatchDictionaryColumn,
 
        backref='dictionary')
 

	
 
    def __repr__(self):
 
        return "<BatchDictionary: %s>" % self.name
 

	
 
    def __str__(self):
 
        return str(self.description or '')
 

	
 

	
 
class BatchTerminalSourceColumn(Base):
 
class BatchTerminalColumn(Base):
 
    """
 
    Represents a "source column" supported by a :class:`BatchTerminal`.
 
    Represents a column supported by a :class:`BatchTerminal`.
 
    """
 

	
 
    __tablename__ = 'batch_terminal_source_columns'
 
    __tablename__ = 'batch_terminal_columns'
 

	
 
    uuid = uuid_column()
 
    terminal_uuid = Column(String(32), ForeignKey('batch_terminals.uuid'))
 
    dictionary_uuid = Column(String(32), ForeignKey('batch_dictionaries.uuid'))
 
    column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
    ordinal = Column(Integer)
 

	
 
    dictionary = relationship(BatchDictionary)
 
    column = relationship(SilColumn)
 

	
 
    def __repr__(self):
 
        return "<BatchTerminalSourceColumn: %s, %s, %s>" % (
 
            self.terminal, self.dictionary, self.name)
 

	
 
    def __str__(self):
 
        return str(self.name or '')
 

	
 

	
 
class BatchTerminalTargetColumn(Base):
 
    """
 
    Represents a "target column" supported by a :class:`BatchTerminal`.
 
    """
 

	
 
    __tablename__ = 'batch_terminal_target_columns'
 

	
 
    uuid = uuid_column()
 
    terminal_uuid = Column(String(32), ForeignKey('batch_terminals.uuid'))
 
    dictionary_uuid = Column(String(32), ForeignKey('batch_dictionaries.uuid'))
 
    column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
    sil_column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
    ordinal = Column(Integer)
 
    source = Column(Boolean)
 
    target = Column(Boolean)
 

	
 
    dictionary = relationship(BatchDictionary)
 
    column = relationship(SilColumn)
 
    sil_column = relationship(SilColumn)
 

	
 
    def __repr__(self):
 
        return "<BatchTerminalTargetColumn: %s, %s, %s>" % (
 
            self.terminal, self.dictionary, self.name)
 
        return "<BatchTerminalColumn: %s, %s, %s>" % (
 
            self.terminal, self.dictionary, self.sil_column)
 

	
 
    def __str__(self):
 
        return str(self.name or '')
 
        return str(self.sil_column or '')
 

	
 

	
 
class BatchTerminal(Base):
 
    """
 
    Represents a terminal, or "junction" for batch data.
 
    """
 

	
 
    __tablename__ = 'batch_terminals'
 

	
 
    uuid = uuid_column()
 
    sil_id = Column(String(20), unique=True)
 
    description = Column(String(50))
 
    class_spec = Column(String(255))
 
    functional = Column(Boolean, default=False)
 
    source = Column(Boolean)
 
    target = Column(Boolean)
 
    source_kwargs = Column(Text)
 
    target_kwargs = Column(Text)
 

	
 
    source_columns = relationship(
 
        BatchTerminalSourceColumn,
 
        backref='terminal')
 
    target_columns = relationship(
 
        BatchTerminalTargetColumn,
 
    columns = relationship(
 
        BatchTerminalColumn,
 
        backref='terminal')
 

	
 
    _terminal = 'not_got_yet'
 

	
 
    def __repr__(self):
 
        return "<BatchTerminal: %s>" % self.sil_id
 

	
 
    def __str__(self):
 
        return str(self.description or '')
 

	
 
    def source_columns(self, dictionary):
 
        for col in self.columns:
 
            if col.dictionary is dictionary:
 
                yield col
 

	
 
    def get_terminal(self):
 
        """
 
        Returns the :class:`rattail.batches.BatchTerminal` instance which is
 
        associated with the database record via its ``python_spec`` field.
 
        """
 

	
 
        if self._terminal == 'not_got_yet':
 
            self._terminal = None
 
            if self.class_spec:
 
                term = edbob.load_spec(self.class_spec)
 
                if term:
 
                    self._terminal = term()
 
@@ -202,38 +182,38 @@ class BatchTerminal(Base):
 

	
 

	
 
class BatchColumn(Base):
 
    """
 
    Represents a :class:`SilColumn` associated with a :class:`Batch`.
 
    """
 

	
 
    __tablename__ = 'batch_columns'
 

	
 
    uuid = uuid_column()
 
    batch_uuid = Column(String(32), ForeignKey('batches.uuid'))
 
    ordinal = Column(Integer)
 
    column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
    sil_column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
    source_uuid = Column(String(32), ForeignKey('batch_terminals.uuid'))
 
    targeted = Column(Boolean)
 

	
 
    column = relationship(SilColumn)
 
    sil_column = relationship(SilColumn)
 

	
 
    source = relationship(
 
        BatchTerminal,
 
        primaryjoin=BatchTerminal.uuid == source_uuid,
 
        order_by=[BatchTerminal.description],
 
        )
 

	
 
    def __repr__(self):
 
        return "<BatchColumn: %s, %s>" % (self.batch, self.column)
 
        return "<BatchColumn: %s, %s>" % (self.batch, self.sil_column)
 

	
 

	
 
# def get_sil_column(name):
 
#     """
 
#     Returns a ``sqlalchemy.Column`` instance according to Rattail's notion of
 
#     what each SIL field ought to look like.
 
#     """
 

	
 
#     type_map = {
 

	
 
#         # The first list of columns is a subset of Level 1 SIL.
 

	
 
@@ -256,26 +236,36 @@ class BatchColumn(Base):
 
def get_sil_type(data_type):
 
    """
 
    Returns a SQLAlchemy-based data type according to the SIL-compliant type
 
    specifier found in ``data_type``.
 
    """
 

	
 
    if data_type == 'GPC(14)':
 
        return BigInteger
 

	
 
    m = sil_type_pattern.match(data_type)
 
    if m:
 
        data_type, precision = m.groups()
 
        if precision.isdigit():
 
            precision = int(precision)
 
            scale = 0
 
        else:
 
            precision, scale = precision.split(',')
 
            precision = int(precision)
 
            scale = int(scale)
 
        if data_type == 'CHAR':
 
            return String(int(precision))
 
            assert not scale, "FIXME"
 
            return String(precision)
 
        if data_type == 'NUMBER':
 
            return Numeric(precision, scale)
 

	
 
    assert False, "FIXME"
 
    
 

	
 
class Batch(Base):
 
    """
 
    Represents a batch of data, presumably in need of processing.
 
    """
 

	
 
    __tablename__ = 'batches'
 

	
 
    _rowclass = None
 
@@ -409,25 +399,25 @@ class Batch(Base):
 
    def rowclass(self):
 
        """
 
        Returns the SQLAlchemy-mapped class for the underlying data table.
 
        """
 

	
 
        assert self.uuid
 
        if self.uuid not in self._row_classes:
 
            kwargs = {
 
                '__tablename__':        'batch.%s' % self.name,
 
                'uuid':                 uuid_column(),
 
                }
 
            for col in self.columns:
 
                kwargs[col.column.sil_name] = Column(get_sil_type(col.column.data_type))
 
                kwargs[col.sil_column.sil_name] = Column(get_sil_type(col.sil_column.data_type))
 
            self._row_classes[self.uuid] = type('BatchRow', (Base,), kwargs)
 
        return self._row_classes[self.uuid]
 

	
 
    def create_table(self):
 
        """
 
        Creates the batch's data table within the database.
 
        """
 

	
 
        self.rowclass.__table__.create()
 

	
 
    # @property
 
    # def target_junction(self):
 
@@ -503,24 +493,48 @@ class Batch(Base):
 

	
 

	
 
class Brand(Base):
 
    """
 
    Represents a brand or similar product line.
 
    """
 

	
 
    __tablename__ = 'brands'
 

	
 
    uuid = uuid_column()
 
    name = Column(String(100))
 

	
 
    def __repr__(self):
 
        return "<Brand: %s>" % self.name
 

	
 
    def __str__(self):
 
        return str(self.name or '')
 

	
 

	
 
class Department(Base):
 
    """
 
    Represents an organizational department.
 
    """
 

	
 
    __tablename__ = 'departments'
 

	
 
    uuid = uuid_column()
 
    number = Column(Integer)
 
    name = Column(String(30))
 

	
 
    def __repr__(self):
 
        return "<Department: %s>" % self.name
 

	
 
    def __str__(self):
 
        return str(self.name)
 

	
 

	
 
class Product(Base):
 
    """
 
    Represents a product for sale and/or purchase.
 
    """
 

	
 
    __tablename__ = 'products'
 

	
 
    uuid = uuid_column()
 
    upc = Column(BigInteger)
 
    brand_uuid = Column(String(32), ForeignKey('brands.uuid'))
 
    description = Column(String(60))
0 comments (0 inline, 0 general)