Source code for ciowarehouse.models.dbmetafield

"""SQLAlchemy-powered model definitions for metadata fields."""

from json import dumps, loads

from sqlalchemy import Column, ForeignKey, String, Text, Enum, Integer
from sqlalchemy.orm import relationship
from lxml import etree

from chrysalio.lib.i18n import translate_field, record_format_i18n
from chrysalio.lib.utils import make_id
from chrysalio.lib.xml import XML_NS, i18n_xml_text, db2xml_i18n_labels
from chrysalio.models import DBDeclarativeClass, ID_LEN
from chrysalio.models.dbbase import DBBaseClass
from ..relaxng import RELAXNG_CIOWAREHOUSE
from ..lib.i18n import _


METAFIELD_TYPES = {
    'string': _('String'), 'text': _('Text'), 'integer': _('Integer'),
    'decimal': _('Decimal'), 'boolean': _('Boolean'),
    'date': _('Date'), 'datetime': _('Date and hour'), 'list': _('List'),
    'palette': _('Color palette')}
METAFIELD_DEFAULTS = ('title', 'annotation')


# =============================================================================
[docs]class DBMetafield(DBDeclarativeClass, DBBaseClass): """SQLAlchemy-powered metadata field class.""" suffix = 'ciomta' __tablename__ = 'wrh_metafields' __table_args__ = {'mysql_engine': 'InnoDB'} metafield_id = Column(String(ID_LEN), primary_key=True) i18n_label = Column(Text(), nullable=False) field_type = Column( Enum(*METAFIELD_TYPES.keys(), name='metafield_type'), nullable=False) position = Column(Integer(), default=0) choices = relationship('DBMetafieldChoice', cascade='all, delete') # -------------------------------------------------------------------------
[docs] @classmethod def xml2db(cls, dbsession, metafield_elt, error_if_exists=True, kwargs=None): """Load a metadata field from a XML element. :type dbsession: sqlalchemy.orm.session.Session :param dbsession: SQLAlchemy session. :type metafield_elt: lxml.etree.Element :param metafield_elt: Metadata field XML element. :param bool error_if_exists: (default=True) It returns an error if metadata field already exists. :param dict kwargs: (optional) Dictionary of keyword arguments. :rtype: :class:`pyramid.i18n.TranslationString` or ``None`` :return: Error message or ``None``. """ # pylint: disable = unused-argument # Check if already exists metafield_id = make_id(metafield_elt.get('id'), 'token', ID_LEN) dbmetafield = dbsession.query(cls).filter_by( metafield_id=metafield_id).first() if dbmetafield is not None: if error_if_exists: return _('Metadata field "${d}" already exists.', {'d': metafield_id}) return None # Create metafield record = cls.record_from_xml(metafield_id, metafield_elt) error = cls.record_format(record) if error: return error dbmetafield = cls(**record) dbsession.add(dbmetafield) # Add choices dbsession.flush() namespace = RELAXNG_CIOWAREHOUSE['namespace'] for elt in metafield_elt.xpath( 'ns0:item|ns0:color', namespaces={'ns0': namespace}): dbmetafield.choices.append(DBMetafieldChoice( value=elt.get('value'), i18n_label=dumps(i18n_xml_text( elt, 'ns0:label', {'ns0': namespace})))) return None
# -------------------------------------------------------------------------
[docs] @classmethod def record_from_xml(cls, metafield_id, metafield_elt): """Convert a metafield XML element into a dictionary. :param str metafield_id: Metafield ID. :type metafield_elt: lxml.etree.Element :param metafield_elt: Metafield XML element. :rtype: dict """ namespace = RELAXNG_CIOWAREHOUSE['namespace'] return { 'metafield_id': metafield_id, 'i18n_label': dumps(i18n_xml_text( metafield_elt, 'ns0:label', {'ns0': namespace})), 'field_type': metafield_elt.get('type'), 'position': int( metafield_elt.xpath('count(preceding-sibling::*)'))}
# -------------------------------------------------------------------------
[docs] @classmethod def record_format(cls, record): """Check and possibly correct a record before inserting it in the database. :param dict record: Dictionary of values to check. :rtype: ``None`` or :class:`pyramid.i18n.TranslationString` :return: ``None`` or error message. """ for k in [i for i in record if record[i] is None]: del record[k] # Meta field ID if not record.get('metafield_id'): return _('Metadata field without ID.') record['metafield_id'] = make_id( record['metafield_id'], 'token', ID_LEN) # Labels if not record_format_i18n(record): return _('Metadata field without label.') # Field type if not record.get('field_type'): return _('Metadata field without type.') return None
# -------------------------------------------------------------------------
[docs] def db2xml(self, dbsession=None): """Serialize a metadata field to a XML representation. :type dbsession: sqlalchemy.orm.session.Session :param dbsession: (optional) SQLAlchemy session. :rtype: lxml.etree.Element """ # pylint: disable = unused-argument metafield_elt = etree.Element('metafield') metafield_elt.set('id', self.metafield_id) metafield_elt.set('type', self.field_type) db2xml_i18n_labels(self, metafield_elt, 6) if self.field_type in ('list', 'palette'): for choice in self.choices: choice_elt = etree.SubElement( metafield_elt, 'item' if self.field_type == 'list' else 'color', value=choice.value) i18n = loads(choice.i18n_label) for lang in sorted(i18n): elt = etree.SubElement(choice_elt, 'label') elt.set('{0}lang'.format(XML_NS), lang) elt.text = i18n[lang] return metafield_elt
# =============================================================================
[docs]class DBMetafieldChoice(DBDeclarativeClass): """SQLAlchemy-powered metadata field choices class (one-to-many).""" __tablename__ = 'wrh_metafields_choices' __table_args__ = {'mysql_engine': 'InnoDB'} metafield_id = Column( String(ID_LEN), ForeignKey( 'wrh_metafields.metafield_id', ondelete='CASCADE'), primary_key=True) value = Column(String(ID_LEN), primary_key=True, nullable=False) i18n_label = Column(Text(), nullable=False) # -------------------------------------------------------------------------
[docs] def label(self, request): """Return a translated label. :type request: pyramid.request.Request :param request: Current request. :rtype: str """ return translate_field(request, loads(self.i18n_label))