Source code for xmm.models.cms.block

import flask
from bson import ObjectId
from flask import render_template_string
from flask_babel import lazy_gettext as _

from xmm.util.mongo import KeyValidator

from .base import BaseCMSType, CMSFieldsMixin
from .. import fields, SignalizedMetaclass
from ..dynamicfields import DynamicFieldsMixin
from ..embedded import BaseEmbeddedDocument


[docs]class BlockType(BaseCMSType, metaclass=SignalizedMetaclass): """ A single block inside a page type template. A block type defines the expected content defined by the implementing block. BlockTypes specify fields with a defined type that will be defined by the implementing Block. ex: BlockType Product defines a sub-template that displays a product's details, contains only one field to select a product, which will be chosen on the Block instance. (This could also make for dynamic blocks, that fill the given blocktype's fields from, say, a URL parameter for automatic browsing of models maybe). """ _elastic_index = 'system' _elastic_doc_type = 'blocktype' meta = { 'collection': 'system_blocktype', 'verbose_name': _('Blocktyp'), 'verbose_name_plural': _('Blocktypen'), 'indexes': [ { 'name': 'key', 'fields': ['key'], 'unique': True }, { 'name': 'attribute_key', 'fields': ['key', 'attributes.key'], 'unique': True }, ], } key = fields.StringField(label=_('Schlüssel'), required=True, validation=KeyValidator(), isSortable=True) label = fields.MultilingualField(fields.StringField(label=_('Bezeichnung'), isSortable=True)) label_plural = fields.MultilingualField(fields.StringField()) description = fields.MultilingualField(fields.TextField(label=_('Beschreibung'), width=400, flexGrow=1)) identifier = fields.TextField(label=_('Block Preview Template')) valid_blocktypes = fields.ListField(fields.ReferenceField('BlockType')) default_blocktype = fields.ReferenceField('BlockType') dict_fields = [ 'key', 'label', 'description', ] columns_definitions = dict_fields def __str__(self): return self.label() or self.key @classmethod def get_impl_class(cls): from .page import Page return Page @classmethod def post_save(cls, sender, document, **kwargs): super().post_save(sender, document, **kwargs) from .page import Page Page.elastic_put_mapping() @classmethod def verify_deletions(cls, objects): from .page import Page for block_type in objects: if Page.objects(blocks__blocktype=block_type).count(): flask.flash(_('Blocktyp kann nicht gelöscht werden, da er von Blöcken verwendet wird.'), 'warning') return False return True def impl_objects(self, **kwargs): """ Return a filtered cursor that queries only this block type. .. warning:: This method returns a cursor on :class:`Page` objects! :param kwargs: Passed to the ``blocks__`` filter query. :returns: A query on pages! """ from .page import Page kwargs['blocktype'] = self query = { 'blocks__{}'.format(key): value for key, value in kwargs.items() } return Page.objects(**query) def identify_block(self, block, depth=0): """ Get the preview text for a block. :param Block block: The block to describe :param depth: Depth of current identification call. """ if self.identifier: context = { 'block': block, 'blocktype': self, 'request': flask.request or None, } return render_template_string(self.identifier, **context) # No identifier template on block! return block.id def update_from_json(self, json, compare_timestamps=True): if 'valid_blocktypes' in json: self.valid_blocktypes = list(map(BlockType.from_id, json.pop('valid_blocktypes') or [])) return super().update_from_json(json, compare_timestamps)
[docs]class Block(CMSFieldsMixin, DynamicFieldsMixin, BaseEmbeddedDocument): """ A block that implements a single blocktype's expected contents. Will show a field to be filled out for every defined property in the block's type. This can be anything from freetext, free HTML, a number or a number of models from a defined list. Could also possibly be dynamically calculated by the current page's parameters and status, such as URL and user login status. """ meta = { 'verbose_name': _('Block'), 'verbose_name_plural': _('Blöcke'), } id = fields.ObjectIdField(required=True, default=ObjectId) type_field = 'blocktype' blocktype = fields.ReferenceField('BlockType', required=True) area = fields.StringField(help_text='Page area key') pos = fields.IntField(default=0) blocks = fields.EmbeddedDocumentListField('Block') def __str__(self): return str(self.id) def __repr__(self): return '<{}>'.format(self.__class__.__name__) @property def key(self): return self.id @classmethod def create_from_json(cls, json): blocktype = BlockType.objects.get(id=json.pop('blocktypeId')) return cls._create_from_json(blocktype, json) def identify(self, depth=0): """Get a preview of this block's contents.""" if self.blocktype is not None: return self.blocktype.identify_block(self, depth=0) return _('Blocktyp zu diesem Block existiert nicht mehr!') def update_from_json(self, json): self.set_validity_fields(json) if 'blocktypeId' in json: self.blocktype = BlockType.objects.get_or_404(id=ObjectId(json.pop('blocktypeId'))) # to move a block around, simply change these two values if 'area' in json: self.area = json.pop('area') if 'pos' in json: self.pos = json.pop('pos') return super().update_from_json(json) def sort_subblocks(self): for i, subblock in enumerate(sorted(self.blocks, key=lambda block: block.pos if block.pos else float('inf'))): subblock.pos = i + 1 subblock.sort_subblocks() def remove_id_recursively(self): """Set id to None so id will be autogenerated.""" self.id = None for block in self.blocks: block.remove_id_recursively()