def mark_products(product_id=None, mark=True): """Mark/unmark all products.""" assert isinstance(mark, bool) assert product_id is None or isinstance(product_id, (int, tuple, list)) row = pd.Row((('marked', pd.bval(mark)),)) if product_id is None: condition = pd.EQ('marked', pd.bval(not mark)) elif isinstance(product_id, (list, tuple)): condition = pd.OR(*[pd.EQ('product_id', pd.ival(x)) for x in product_id]) else: condition = pd.EQ('product_id', pd.ival(product_id)) import pytis.extensions return pytis.extensions.dbupdate_many('misc.Products', condition=condition, update_row=row)
class ItemsHelp(Specification): _ITEM_KIND = None public = True table = 'e_pytis_help_spec_items' def fields(self): return ( Field('item_id'), Field('kind'), Field('spec_name', not_null=True, codebook='help.Help', value_column='spec_name'), Field('identifier', _("Identifier"), width=30, editable=Editable.NEVER), Field('content', _("Description"), width=80, height=15, compact=True, text_format=pp.TextFormat.LCG, attachment_storage=self._attachment_storage), Field('label', _("Title"), width=30, virtual=True, editable=pp.Editable.NEVER, computer=computer(self._label)), Field('removed', _("Removed"), editable=Editable.NEVER), Field('changed', _("Changed"), editable=Editable.NEVER, computer=computer(lambda r, content: True)), ) def row_style(self, row): return not row['changed'].value() and pp.Style(background='#ffd') or None def _attachment_storage(self, record): return pp.DbAttachmentStorage('e_pytis_help_spec_attachments', 'spec_name', record['spec_name'].value()) def _label(self, record, spec_name, kind, identifier): if not kind or not identifier: return None resolver = pytis.util.resolver() try: view_spec = resolver.get(spec_name, 'view_spec') except pytis.util.ResolverError: return identifier kwargs = dict(unnest=True) if kind == 'action' else {} items = getattr(view_spec, kind + 's')(**kwargs) item = pytis.util.find(identifier, items, key=lambda x: x.id()) if item: if kind in ('field',): return item.label() elif kind in ('action', 'binding'): return item.title() else: return item.name() else: return identifier def condition(self): return pd.EQ('kind', pd.sval(self._ITEM_KIND)) columns = ('identifier', 'label', 'changed', 'removed') layout = ('identifier', 'label', 'content') profiles = pp.Profiles((pp.Profile('active', _("Active"), filter=pd.EQ('removed', pd.bval(False)), columns=('identifier', 'label', 'changed')),), default='active')
def _update_spec_help(self, spec_name): if self._done.get(spec_name): return self._done[spec_name] = True resolver = pytis.util.resolver() try: view_spec = resolver.get(spec_name, 'view_spec') except pytis.util.ResolverError as e: print(e) return description = view_spec.description() help_text = (view_spec.help() or '').strip() or None self._update(self._spec_help_data, dict(spec_name=spec_name), description=description, help=help_text) data = self._spec_help_items_data for kind, items in (('field', view_spec.fields()), ('profile', view_spec.profiles().unnest()), ('binding', view_spec.bindings()), ('action', view_spec.actions(unnest=True))): for item in items: self._update(data, dict(spec_name=spec_name, kind=kind, identifier=item.id()), content=item.descr()) # Items which were modified (content changed by hand) are kept with # the 'removed' flag set, since the texts may be reused for other # items in case of identifier change or other rearrangements. It # will also automatically resurrect texts for items which are # removed temporarily (eg. commented out) which is quite common # during development. Items which were not ever modified or have # no content may be safely deleted (they contain no hand-edited # data). conds = [pd.EQ('spec_name', pd.sval(spec_name)), pd.EQ('kind', pd.sval(kind)), pd.NOT(pd.ANY_OF('identifier', *[pd.sval(item.id()) for item in items])), ] data.delete_many(pd.AND(*(conds + [pd.OR(pd.EQ('changed', pd.bval(False)), pd.EQ('content', pd.sval(None)))]))) data.update_many(pd.AND(*(conds + [pd.EQ('removed', pd.bval(False))])), pd.Row(self._values(data, removed=True)))
def _update_spec_help(self, spec_name): if self._done.get(spec_name): return self._done[spec_name] = True resolver = pytis.util.resolver() try: view_spec = resolver.get(spec_name, 'view_spec') except pytis.util.ResolverError as e: print e return description = view_spec.description() help_text = (view_spec.help() or '').strip() or None self._update(self._spec_help_data, dict(spec_name=spec_name), description=description, help=help_text) data = self._spec_help_items_data for kind, items in (('field', view_spec.fields()), ('profile', view_spec.profiles().unnest()), ('binding', view_spec.bindings()), ('action', view_spec.actions(unnest=True))): for item in items: self._update(data, dict(spec_name=spec_name, kind=kind, identifier=item.id()), content=item.descr()) # Items which were modified (content changed by hand) are kept with # the 'removed' flag set, since the texts may be reused for other # items in case of identifier change or other rearrangements. It # will also automatically resurrect texts for items which are # removed temporarily (eg. commented out) which is quite common # during development. Items which were not ever modified or have # no content may be safely deleted (they contain no hand-edited # data). conds = [pd.EQ('spec_name', pd.sval(spec_name)), pd.EQ('kind', pd.sval(kind)), pd.NOT(pd.ANY_OF('identifier', *[pd.sval(item.id()) for item in items])), ] data.delete_many(pd.AND(*(conds + [pd.OR(pd.EQ('changed', pd.bval(False)), pd.EQ('content', pd.sval(None)))]))) data.update_many(pd.AND(*(conds + [pd.EQ('removed', pd.bval(False))])), pd.Row(self._values(data, removed=True)))
class Help(Specification): public = True table = 'ev_pytis_help' title = _("Help") def fields(self): return ( Field('help_id', # The computer is only used for help pages (, editable=pp.Editable.NEVER, editable=pp.Editable.NEVER, editable=pp.Editable.NEVER, editable=pp.Editable.NEVER, editable=Editable.NEVERwith page_id) so # we don't need to care about other kinds of help_id. New # record is always a new page. computer=computer(lambda r, page_id: 'page/%d' % page_id)), Field('fullname', _("Fullname"), width=50, editable=Editable.NEVER), Field('spec_name', _("Specification Name"), width=50, editable=Editable.NEVER), Field('page_id', default=nextval('e_pytis_help_pages_page_id_seq')), Field('position'), Field('position_nsub'), Field('title', _("Title"), width=20, editable=computer(self._is_page), type=_TreeOrderLTree(tree_column_id='position', subcount_column_id='position_nsub'), ), Field('description', _("Description"), width=70, editable=computer(self._is_page),), Field('content', _("Content"), width=80, height=20, compact=True, text_format=pp.TextFormat.LCG, attachment_storage=self._attachment_storage), Field('menu_help', _(u"Menu item description"), width=80, height=20, compact=True, text_format=pp.TextFormat.LCG, attachment_storage=self._attachment_storage), Field('spec_description', _("Brief form description"), width=80, height=3, compact=True), Field('spec_help', _("Detailed form help"), width=80, height=20, compact=True, text_format=pp.TextFormat.LCG, attachment_storage=self._attachment_storage), Field('parent', _("Parent item"), not_null=False, codebook='help.HelpParents', value_column='page_id', editable=computer(self._is_page), runtime_filter=computer(self._parent_filter), descr=_("Choose the directly superordinate item in menu hierarchy. Leave " "empty for pages at the top level menu.")), Field('ord', _("Ordering"), width=8, fixed=True, type=pd.Integer, maximum=999998, editable=computer(self._is_page), descr=_("Enter a number denoting the order of the item in menu between " "pages of the same hierarchy level. Leave empty to put the item " "automatically to bottom.")), Field('removed', _("Removed"), editable=Editable.NEVER), Field('changed', _("Changed"), editable=Editable.NEVER), ) def row_style(self, row): return not row['changed'].value() and pp.Style(background='#ffd') or None def _is_page(self, record, page_id): return record.new() or page_id is not None def _parent_filter(self, page_id): return pd.NE('page_id', pd.ival(None)) def _attachment_storage(self, record): if record['page_id'].value(): table, ref = ('e_pytis_help_pages_attachments', 'page_id') elif record['spec_name'].value(): table, ref = ('e_pytis_help_spec_attachments', 'spec_name') else: # The attachments are not allowed for some special pages, such as the menu root page. return None return pp.DbAttachmentStorage(table, ref, record[ref].value()) def redirect(self, record): if record['page_id'].value() is not None: return None elif record['spec_name'].value() is not None: return 'help.SpecHelp' elif record['fullname'].value() is not None: return 'help.MenuHelp' else: return 'help.NoHelp' def bindings(self): return ( Binding('content', _("Content"), uri=lambda r: 'help:'+r['help_id'].value()), Binding('fields', _("Fields"), 'help.FieldItemsHelp', 'spec_name'), Binding('profiles', _("Profiles"), 'help.ProfileItemsHelp', 'spec_name'), Binding('actions', _("Actions"), 'help.ActionItemsHelp', 'spec_name'), Binding('bindings', _("Side Forms"), 'help.BindingItemsHelp', 'spec_name'), ) layout = ('title', 'description', 'parent', 'ord', 'content') cb = CodebookSpec(display='title') columns = ('title', 'description', 'spec_name', 'changed', 'removed') sorting = ('position', pd.ASCENDENT), profiles = pp.Profiles((pp.Profile('active', _("Active"), filter=pd.EQ('removed', pd.bval(False)), columns=('title', 'description', 'spec_name', 'removed')),), default='active')
class Products(Specification): public = True table = dbdefs.Products title = _("Products") layout = TabGroup((_("Product"), ('product_id', 'product', 'count', 'price', 'marked')), (_("Notes"), ('since', 'notes'))) grouping_functions = ( ('f_date_year', _("Year"), pd.DateTime, pd.Integer()), ('f_date_month', _("Month"), pd.DateTime, pd.Integer()), ) profiles = ( pp.Profile('marked', _("Marked"), filter=pd.EQ('marked', pd.bval(True))), pp.Profile('unmarked', _("Unmarked"), filter=pd.EQ('marked', pd.bval(False))), ) def _customize_fields(self, fields): fields.set_property('width', product_id=3, product=30, count=12, price=12, notes=50) fields.modify('product_id', column_width=6, fixed=True, editable=Editable.ALWAYS) fields.modify('product', style=lambda r: (pp.Style(overstrike=True) if r['count'].value() == 0 else None)) fields.modify('count', style=lambda r: (pp.Style(foreground='#f00') if r['count'].value() <= 5 else None)) fields.modify('price', type=pd.Monetary(not_null=True)) fields.modify('since', descr=_("Date when the product was first included."), default=pd.DateTime.datetime) fields.modify('notes', descr=_("Arbitrary internal notes about given product."), height=3, text_format=pp.TextFormat.LCG) def prints(self): return ( PrintAction('product-page', _("Product Page"), 'Products.ProductPage', handler=self._print_handler), PrintAction('product-info', _("Product Info"), 'Products.ProductInfo'), ) def _print_handler(self, row): """Demonstration of using PrintAction handler. The output document is created by merging the output of two printout results. """ from PyPDF2 import PdfFileMerger, PdfFileReader merger = PdfFileMerger() for lang in ('cs', 'en'): output = io.BytesIO() pytis.form.printout( 'Products', 'Products.ProductPage', parameters=dict([(k, row[k].export()) for k in ('product_id', 'product', 'price')], language=lang), language=lang, output_file=output, ) merger.append(PdfFileReader(io.BytesIO(output.getvalue()))) with tempfile.NamedTemporaryFile(suffix='.pdf') as f: merger.write(f) f.flush() os.fsync(f) pytis.form.launch_file(f.name) time.sleep(1) def _content(self, record): par = os.path.pardir return pytis.util.lcg_node( ( lcg.fieldset(( (_("Price"), record['price'].export()), (_("Available since"), lcg.LocalizableDateTime(record['since'].value())), )), lcg.sec(_("Notes"), lcg.Parser().parse(record['notes'].value()), name='notes') if record['notes'].value() else lcg.Content(), ), title=record['product'].value(), resource_path=(os.path.normpath(os.path.join(__file__, par, par, par, par, 'css')),), resources=('pytis-demo-product.css',), ) def bindings(self): return ( Binding('webform', _("Product Info"), content=self._content, descr=_("Shows web page generated from LCG structured text.")), ) def actions(self): return (Action('toggle', _("Mark/unmark"), self._mark, hotkey='m'), Action('mark_all', _("Mark all"), self._mark, hotkey='Ctrl-a', mark_all=True), Action('unmark_all', _("Unmark all"), self._mark, hotkey='Ctrl-u', mark_all=True, mark=False), Action('mark_selected', _("Mark selected"), self._mark_selected, context=pp.ActionContext.SELECTION), Action('prices', _("Update prices"), self._update_prices), Action('print', _("Print price"), self._print), ) def row_style(self, row): if row['count'].value() <= row.form.query_fields.row['min_count'].value(): return pp.Style(background='#fdd') elif row['marked'].value(): return pp.Style(background='#ffd') else: return None query_fields = QueryFields( (Field('min_count', _("Highlight low count"), type=pytis.data.Integer(not_null=True), default=10),), autoinit=True, ) def _mark(self, row, mark_all=False, mark=True): if mark_all: product_id = None else: product_id = row["product_id"].value() mark = not row["marked"].value() count = pytis.form.run_procedure('misc', 'mark_products', product_id=product_id, mark=mark) app.echo(_("Marked %d rows") % count) def _mark_selected(self, rows): count = pytis.form.run_procedure('misc', 'mark_products', product_id=[r['product_id'].value() for r in rows], mark=True) app.echo(_("Marked %d rows") % count) def _update_prices(self, row): # This serves for testing user transactions. true_value = pd.Value(pd.Boolean(), True) condition = pd.EQ('marked', true_value) transaction = pd.transaction() try: def process(row): return row['product_id'] product_ids = row.data().select_map(process, condition=condition) for product_id in product_ids: if not pytis.form.run_form(pytis.form.PopupEditForm, 'misc.Products', select_row=product_id, transaction=transaction): app.error("Transaction aborted") transaction.rollback() return except Exception: transaction.rollback() raise transaction.commit() def _print(self, row): pytis.form.printout('misc.Products', 'misc.StandalonePrint', row)