From 839f6affe29447a635d571953c74d615f7f35f3c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 9 Jul 2019 22:14:12 -0500 Subject: [PATCH] Add basic "DB picker" support, for views which allow multiple engines i.e. whichever engine is "current" will determine where data comes from --- tailbone/templates/themes/falafel/base.mako | 22 +++++++++ tailbone/views/common.py | 20 ++++++++ tailbone/views/master.py | 53 +++++++++++++++++++-- 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/tailbone/templates/themes/falafel/base.mako b/tailbone/templates/themes/falafel/base.mako index 15aad18b..27308697 100644 --- a/tailbone/templates/themes/falafel/base.mako +++ b/tailbone/templates/themes/falafel/base.mako @@ -132,6 +132,21 @@ % endif + % if expose_db_picker is not Undefined and expose_db_picker: +
+

DB:

+
+
+ ${h.form(url('change_db_engine'))} + ${h.csrf_token(request)} + ${h.hidden('engine_type', value=master.engine_type_key)} +
+ ${h.select('dbkey', db_picker_selected, db_picker_options, id='db-picker')} +
+ ${h.end_form()} +
+ % endif +
@@ -303,6 +318,13 @@ var session_timeout = ${request.get_session_timeout() or 'null'}; var logout_url = '${request.route_url('logout')}'; var noop_url = '${request.route_url('noop')}'; + % if expose_db_picker is not Undefined and expose_db_picker: + $(function() { + $('#db-picker').change(function() { + $(this).parents('form:first').submit(); + }); + }); + % endif % if expose_theme_picker and request.has_perm('common.change_app_theme'): $(function() { $('#theme-picker').change(function() { diff --git a/tailbone/views/common.py b/tailbone/views/common.py index 8a5d560c..ba81babe 100644 --- a/tailbone/views/common.py +++ b/tailbone/views/common.py @@ -143,6 +143,20 @@ class CommonView(View): self.request.session.flash("App theme has been changed to: {}".format(theme)) return self.redirect(self.request.get_referrer()) + def change_db_engine(self): + """ + Simple view which can change user's "current" database engine, of a + given type, then redirect back to referring page. + """ + engine_type = self.request.POST.get('engine_type') + if engine_type: + dbkey = self.request.POST.get('dbkey') + if dbkey: + self.request.session['tailbone.engines.{}.current'.format(engine_type)] = dbkey + if self.rattail_config.getbool('tailbone', 'engines.flash_after_change', default=True): + self.request.session.flash("Switched '{}' database to: {}".format(engine_type, dbkey)) + return self.redirect(self.request.get_referrer()) + def feedback(self): """ Generic view to handle the user feedback form. @@ -212,6 +226,12 @@ class CommonView(View): config.add_route('mobile.about', '/mobile/about') config.add_view(cls, attr='about', route_name='mobile.about', renderer='/mobile/about.mako') + # change db engine + config.add_tailbone_permission('common', 'common.change_db_engine', + "Change which Database Engine is active (for user)") + config.add_route('change_db_engine', '/change-db-engine', request_method='POST') + config.add_view(cls, attr='change_db_engine', route_name='change_db_engine') + # change theme config.add_tailbone_permission('common', 'common.change_app_theme', "Change global App Template Theme") diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 0c0cc29d..dd43b44f 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -36,6 +36,7 @@ import sqlalchemy as sa from sqlalchemy import orm import sqlalchemy_continuum as continuum +from sqlalchemy_utils.functions import get_primary_keys from rattail.db import model, Session as RattailSession from rattail.db.continuum import model_transaction_query @@ -47,6 +48,7 @@ from rattail.csvutil import UnicodeDictWriter from rattail.files import temp_path from rattail.excel import ExcelWriter from rattail.gpc import GPC +from rattail.util import OrderedDict import colander import deform @@ -124,6 +126,11 @@ class MasterView(View): has_image = False has_thumbnail = False + # can set this to true, and set type key as needed, and implement some + # other things also, to get a DB picker in the header for all views + supports_multiple_engines = False + engine_type_key = 'rattail' + row_attrs = {} cell_attrs = {} @@ -2096,8 +2103,9 @@ class MasterView(View): """ if hasattr(cls, 'model_key'): return cls.model_key - mapper = orm.class_mapper(cls.get_model_class()) - return ','.join([k.key for k in mapper.primary_key]) + + pkeys = get_primary_keys(cls.get_model_class()) + return ','.join(pkeys) @classmethod def get_model_title(cls): @@ -2323,12 +2331,50 @@ class MasterView(View): return ['/mobile/master/{}.mako'.format(template)] return ['/master/{}.mako'.format(template)] + def get_current_engine_dbkey(self): + """ + Returns the "current" engine's dbkey, for the current user. + """ + return self.request.session.get('tailbone.engines.{}.current'.format(self.engine_type_key), + 'default') + def template_kwargs(self, **kwargs): """ Supplement the template context, for all views. """ + # whether or not to show the DB picker? + kwargs['expose_db_picker'] = False + if self.supports_multiple_engines: + + # view declares support for multiple engines, but we only want to + # show the picker if we have more than one engine configured + engines = self.get_db_engines() + if len(engines) > 1: + + # user session determines "current" db engine *of this type* + # (note that many master views may declare the same type, and + # would therefore share the "current" engine) + selected = self.get_current_engine_dbkey() + kwargs['expose_db_picker'] = True + kwargs['db_picker_options'] = [tags.Option(k) for k in engines] + kwargs['db_picker_selected'] = selected + return kwargs + def get_db_engines(self): + """ + Must return a dict (or even better, OrderedDict) which contains all + supported database engines for the master view. Used with the DB + picker feature. + """ + engines = OrderedDict() + if self.rattail_config.rattail_engine: + engines['default'] = self.rattail_config.rattail_engine + for dbkey in sorted(self.rattail_config.rattail_engines): + if dbkey != 'default': + engines[dbkey] = self.rattail_config.rattail_engines[dbkey] + return engines + ############################## # Grid Stuff ############################## @@ -2446,7 +2492,8 @@ class MasterView(View): except orm.exc.UnmappedInstanceError: return {self.model_key: row[self.model_key]} else: - keys = [k.key for k in mapper.primary_key] + pkeys = get_primary_keys(row) + keys = list(pkeys) values = [getattr(row, k) for k in keys] return dict(zip(keys, values))