def __init__(self, host='0.0.0.0', port=8251, debug=False): ''' constructor Args: wikiId(str): id of the wiki to use as a CMS backend host(str): flask host port(int): the port to use for http connections debug(bool): True if debugging should be switched on ''' scriptdir = os.path.dirname(os.path.abspath(__file__)) template_folder=scriptdir + '/../templates' super().__init__(host=host,port=port,debug=debug,template_folder=template_folder) self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' db.init_app(self.app) self.db=db self.loginBluePrint=LoginBluePrint(self.app,'login') self.server = Server() self.server.load() self.enabledSites = ['admin'] # # setup global handlers # @self.app.before_first_request def before_first_request_func(): self.initDB() loginMenuList=self.adminMenuList("Login") self.loginBluePrint.setLoginArgs(menuList=loginMenuList) @self.app.route('/') def index(): return self.family() @self.app.route('/family') def family(): return wcw.family() @login_required @self.app.route('/wikis') def wikis(): return self.wikis() @login_required @self.app.route('/frontends') def frontends(): return wcw.frontends() @login_required @self.app.route('/generate/<string:siteName>', methods=['GET', 'POST']) def generate(siteName: str): ''' Handle wiki generator page request Args: siteName(str): wikiId of the wiki the generator should be returned for ''' return self.generate(siteName)
def __init__(self): ''' Constructor ''' scriptdir = os.path.dirname(os.path.abspath(__file__)) template_folder = scriptdir + '/templates' super().__init__(template_folder=template_folder) self.app.secret_key = 'dev' self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # set default button style and size, will be overwritten by macro parameters self.app.config['BOOTSTRAP_BTN_STYLE'] = 'primary' self.app.config['BOOTSTRAP_BTN_SIZE'] = 'sm' # app.config['BOOTSTRAP_BOOTSWATCH_THEME'] = 'lumen' # uncomment this line to test bootswatch theme db.init_app(self.app) self.db = db self.csrf = CSRFProtect(self.app) self.loginBluePrint = LoginBluePrint(self.app, 'login') self.loginBluePrint.hint = "'try user: scott, password: tiger2021'" self.sseBluePrint = SSE_BluePrint(self.app, 'sse', appWrap=self) self.icons = IconsBlueprint(self.app, "icons", appWrap=self) app = self.app link1 = Link("http://greyli.com/", title="Grey Li") link2 = Link("http://www.bitplan.com/Wolfgang_Fahl", title="Wolfgang Fahl") link = f"{link1}→{link2}" self.copyRight = Copyright(period="2018-2022", link=link) # # setup global handlers # @app.before_first_request def before_first_request_func(): self.initDB() # # setup the RESTFUL routes for this application # @app.route('/') def index(): return self.home() @app.route('/form', methods=['GET', 'POST']) def test_form(): return self.form() @app.route('/upload', methods=['GET', 'POST']) def test_upload(): return self.upload() @app.route('/nav', methods=['GET', 'POST']) def test_nav(): return self.nav() @app.route('/pagination', methods=['GET', 'POST']) def test_pagination(): return self.pagination() @app.route('/startsse1', methods=['POST']) def test_startSSE1(): return self.startSSE1() @app.route('/flash', methods=['GET', 'POST']) def test_flash(): return self.flash() @app.route('/datatable') def test_datatable(): return self.datatable() @app.route('/table') def test_table(): return self.table() @app.route('/table/<message_id>/view') def view_message(message_id): return self.message_view(message_id) @app.route('/table/<message_id>/edit') def edit_message(message_id): return self.message_edit(message_id) @app.route('/table/<message_id>/delete', methods=['POST']) def delete_message(message_id): return self.message_delete(message_id) @app.route('/icon') def test_icon(): return self.icon() @app.route('/widgets', methods=['GET', 'POST']) def test_widgets(): return self.widgets() @app.route('/bootstrapicons') def test_bootstrapIcons(): return self.bootstrapIcons() @app.route('/ping', methods=['GET', 'POST']) def test_ping(): return self.ping() @app.route('/events') def test_events(): return self.eventExample() @app.route('/eventfeed') def test_eventFeed(): return self.eventFeed() @app.route('/progressfeed') def test_progressFeed(): return self.progressFeed()
class ExampleApp(AppWrap): ''' flask app wrapped in class ''' def __init__(self): ''' Constructor ''' scriptdir = os.path.dirname(os.path.abspath(__file__)) template_folder = scriptdir + '/templates' super().__init__(template_folder=template_folder) self.app.secret_key = 'dev' self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # set default button style and size, will be overwritten by macro parameters self.app.config['BOOTSTRAP_BTN_STYLE'] = 'primary' self.app.config['BOOTSTRAP_BTN_SIZE'] = 'sm' # app.config['BOOTSTRAP_BOOTSWATCH_THEME'] = 'lumen' # uncomment this line to test bootswatch theme db.init_app(self.app) self.db = db self.csrf = CSRFProtect(self.app) self.loginBluePrint = LoginBluePrint(self.app, 'login') self.loginBluePrint.hint = "'try user: scott, password: tiger2021'" self.sseBluePrint = SSE_BluePrint(self.app, 'sse', appWrap=self) self.icons = IconsBlueprint(self.app, "icons", appWrap=self) app = self.app link1 = Link("http://greyli.com/", title="Grey Li") link2 = Link("http://www.bitplan.com/Wolfgang_Fahl", title="Wolfgang Fahl") link = f"{link1}→{link2}" self.copyRight = Copyright(period="2018-2022", link=link) # # setup global handlers # @app.before_first_request def before_first_request_func(): self.initDB() # # setup the RESTFUL routes for this application # @app.route('/') def index(): return self.home() @app.route('/form', methods=['GET', 'POST']) def test_form(): return self.form() @app.route('/upload', methods=['GET', 'POST']) def test_upload(): return self.upload() @app.route('/nav', methods=['GET', 'POST']) def test_nav(): return self.nav() @app.route('/pagination', methods=['GET', 'POST']) def test_pagination(): return self.pagination() @app.route('/startsse1', methods=['POST']) def test_startSSE1(): return self.startSSE1() @app.route('/flash', methods=['GET', 'POST']) def test_flash(): return self.flash() @app.route('/datatable') def test_datatable(): return self.datatable() @app.route('/table') def test_table(): return self.table() @app.route('/table/<message_id>/view') def view_message(message_id): return self.message_view(message_id) @app.route('/table/<message_id>/edit') def edit_message(message_id): return self.message_edit(message_id) @app.route('/table/<message_id>/delete', methods=['POST']) def delete_message(message_id): return self.message_delete(message_id) @app.route('/icon') def test_icon(): return self.icon() @app.route('/widgets', methods=['GET', 'POST']) def test_widgets(): return self.widgets() @app.route('/bootstrapicons') def test_bootstrapIcons(): return self.bootstrapIcons() @app.route('/ping', methods=['GET', 'POST']) def test_ping(): return self.ping() @app.route('/events') def test_events(): return self.eventExample() @app.route('/eventfeed') def test_eventFeed(): return self.eventFeed() @app.route('/progressfeed') def test_progressFeed(): return self.progressFeed() def initDB(self, limit=20): ''' initialize the database ''' self.db.drop_all() self.db.create_all() self.initUsers() self.initMessages(limit) self.initIcons() def initUsers(self): self.loginBluePrint.addUser(self.db, "scott", "tiger2021", userid=100) def initMessages(self, limit=20): ''' create an initial set of message with the given limit Args: limit(int): the number of messages to create ''' for i in range(limit): m = Message(text='Test message {}'.format(i + 1), author='Author {}'.format(i + 1), category='Category {}'.format(i + 1), create_time=4321 * (i + 1)) if i % 4: m.draft = True self.db.session.add(m) self.db.session.commit() def initIcons(self): ''' initialize the icons ''' iconNames = Icon.getBootstrapIconsNames() for index, iconName in enumerate(iconNames): bootstrapIcon = BootstrapIcon(id=iconName, index=index + 1) self.db.session.add(bootstrapIcon) self.db.session.commit() def getDisplayIcons(self, icons): displayIcons = [] for icon in icons: displayIcons.append("%04d%s%s" % (icon.index, icon.icon, icon.link)) return displayIcons def pagePing(self, host, path="/"): """ This function retrieves the status code of a website by requesting HEAD data from the host. This means that it only requests the headers. If the host cannot be reached or something else goes wrong, it returns False. see https://stackoverflow.com/a/1949507/1497139 """ startTime = time.time() try: conn = http.client.HTTPConnection(host) conn.request("HEAD", path) if re.match("^[23]\d\d$", str(conn.getresponse().status)): state = True except Exception: state = False elapsed = time.time() - startTime return state, elapsed def getTimeEvent(self): ''' get the next time stamp ''' time.sleep(1.0) s = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') return s def progressFeed(self): ''' feed progress info as json ''' self.pc = 0 def progress(): time.sleep(0.5) self.pc = (self.pc + 5) % 100 pcdict = {"progress": self.pc} return json.dumps(pcdict) sse = self.sseBluePrint return sse.streamFunc(progress) def eventFeed(self): ''' create a Server Sent Event Feed ''' sse = self.sseBluePrint # stream from the given function return sse.streamFunc(self.getTimeEvent) def startSSE1(self): ''' start a Server Sent Event Feed ''' if "channel" in request.form and "ssechannel" in request.form: channel = request.form["channel"] ssechannel = request.form["ssechannel"] #pubsub=PubSub.forChannel(ssechannel) sse = self.sseBluePrint now = datetime.now() limit = 15 self.debug = True # 0.5 secs per Job timePerJob = 1 for i in range(limit): run_date = now + timedelta(seconds=timePerJob * i) print("scheduling job %d for %s" % (i, run_date.isoformat())) sse.scheduler.add_job(sse.publish, 'date', run_date=run_date, kwargs={ "channel": ssechannel, "message": "message %d" % (i + 1), "debug": self.debug }) return "%s started" % channel else: abort(501) def eventExample(self): gen = ({"id": i, "data": str(uuid.uuid1())} for i in range(150)) generator = self.sseBluePrint.streamDictGenerator(gen, slowdown=1) return self.render_template("event.html", dictStreamdemo=generator) def render_template(self, templateName, **kwArgs): ''' render the given template with the default arguments ''' html = render_template(templateName, menu=self.getMenu(), copyright=self.copyRight, **kwArgs) return html def flash(self): ''' ''' flash('A simple default alert—check it out!') flash('A simple primary alert—check it out!', 'primary') flash('A simple secondary alert—check it out!', 'secondary') flash('A simple success alert—check it out!', 'success') flash('A simple danger alert—check it out!', 'danger') flash('A simple warning alert—check it out!', 'warning') flash('A simple info alert—check it out!', 'info') flash('A simple light alert—check it out!', 'light') flash('A simple dark alert—check it out!', 'dark') flash( Markup( 'A simple success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.' ), 'success') return self.render_template('flash.html') def form(self): ''' form handling ''' form = LoginForm() return self.render_template('form.html', form=form, telephone_form=TelephoneForm(), contact_form=ContactForm(), im_form=IMForm(), button_form=ButtonForm(), example_form=ExampleForm()) def uploadFiles(self, files, uploadDirectory): ''' upload the given Files ''' fileInfo = "" delim = "" for i, file in enumerate(files): targetFileName = secure_filename(file.filename) if targetFileName == "": targetFileName = f"Test{i}" uploadInfo = self.uploadFile(file, targetFileName, uploadDirectory) fileInfo = f"{fileInfo}{delim}{uploadInfo}" delim = ", " return fileInfo def uploadFile(self, file, targetFileName, uploadDirectory): ''' upload the given file ''' if not os.path.exists(uploadDirectory): os.makedirs(uploadDirectory) if targetFileName is None: targetFileName = secure_filename(file.filename) filePath = f'{uploadDirectory}/{targetFileName}' with open(filePath, 'wb') as f: f.write(file.read()) size = os.path.getsize(filePath) fileInfo = f"{file.filename}→{targetFileName}({size})" return fileInfo def upload(self): ''' handle the uploading ''' upload_form = UploadForm() dropzone_form = DropZoneWidgetForm() uploadDirectory = "/tmp/uploads" fileInfo = None if upload_form.submit.data and upload_form.validate_on_submit(): fileInfo = self.uploadFiles(upload_form.file.data, uploadDirectory=uploadDirectory) if dropzone_form.validate_on_submit(): if len(dropzone_form.dropzone.data) > 0: fileInfo = self.uploadFile( dropzone_form.dropzone.data[0], targetFileName=dropzone_form.fileName.data, uploadDirectory=uploadDirectory) if fileInfo: flash(f"uploaded {fileInfo}") return self.render_template('upload.html', upload_form=upload_form, dropzone_form=dropzone_form) def getMenu(self): ''' get the Menu ''' menu = Menu() for endpoint, title, mdiIcon, newTab in self.getMenuEntries(): menu.addItem( MenuItem(self.basedUrl(url_for(endpoint)), title=title, mdiIcon=mdiIcon, newTab=newTab)) menu.addItem( MenuItem("https://bootstrap-flask.readthedocs.io/", title="Documentation", mdiIcon="description", newTab=True)) menu.addItem( MenuItem("https://github.com/greyli/bootstrap-flask", title="greyli", newTab=True)) menu.addItem( MenuItem("https://github.com/WolfgangFahl/pyFlaskBootstrap4", title="github", newTab=True)) if current_user.is_anonymous: menu.addItem(MenuItem('/login', 'login', mdiIcon="login")) else: menu.addItem(MenuItem('/logout', 'logout', mdiIcon="logout")) return menu def getMenuEntries(self): entries = [ ('index', "Home", "home", False), ('test_form', "Form", "list_alt", False), ('test_upload', "Upload", "upload_file", False), ('test_nav', "Nav", "navigation", False), ('test_pagination', "Pagination", "swap_horizontal_circle", False), ('test_ping', "Ping", "sensors", False), ('test_events', "Events", "priority_high", False), ('test_flash', "Flash Messages", "flash_on", False), ('test_table', "Table", "table_chart", False), ('test_datatable', "DataTable", "table_rows", False), ('test_icon', "Icon", "insert_emoticon", False), ('test_widgets', "Widgets", "widgets", False), ('test_bootstrapIcons', "Bootstrap Icons", "web_asset", False) ] return entries def getMenuLinks(self): links = [] for endpoint, title, _mdiIcon, newTab in self.getMenuEntries(): links.append( Link(self.basedUrl(url_for(endpoint)), title=title, newTab=newTab)) return links def home(self): menuLinks = self.getMenuLinks() return self.render_template('index.html', menuLinks=menuLinks) def icon(self): return self.render_template('icon.html') def message_delete(self, message_id): message = Message.query.get(message_id) if message: db.session.delete(message) db.session.commit() return f'Message {message_id} has been deleted. Return to <a href="/table">table</a>.' return f'Message {message_id} did not exist and could therefore not be deleted. Return to <a href="/table">table</a>.' def message_edit(self, message_id): message = Message.query.get(message_id) if message: message.draft = not message.draft db.session.commit() return f'Message {message_id} has been edited by toggling draft status. Return to <a href="/table">table</a>.' return f'Message {message_id} did not exist and could therefore not be edited. Return to <a href="/table">table</a>.' def message_view(self, message_id): message = Message.query.get(message_id) if message: return f'Viewing {message_id} with text "{message.text}". Return to <a href="/table">table</a>.' return f'Could not view message {message_id} as it does not exist. Return to <a href="/table">table</a>.' def nav(self): return self.render_template('nav.html') def pagination(self): ''' pagination example Returns: rendered html for pagination ''' search_form = IconSearchForm() perPageChoice = search_form.perPage.data if perPageChoice is None: perPageChoice = "twenty" choices = dict(search_form.perPage.choices) perPageSelection = choices[perPageChoice] search_form.perPage.data = perPageChoice if perPageChoice == "all": per_page = 2000 else: per_page = int(perPageSelection) pagination = None icons = None if search_form.validate_on_submit() and search_form.search.data: search = "%{}%".format(search_form.search.data) print("searching %s: " % search) icons = BootstrapIcon.query.filter( BootstrapIcon.id.like(search)).all() if icons is None: page = request.args.get('page', 1, type=int) pagination = BootstrapIcon.query.paginate(page, per_page=per_page) icons = pagination.items displayIcons = self.getDisplayIcons(icons) return self.render_template('pagination.html', form=search_form, pagination=pagination, icons=displayIcons) def ping(self): ''' ping test ''' ping_form = PingForm() if ping_form.validate_on_submit(): choices = dict(ping_form.host.choices) host = choices[ping_form.host.data] state, pingTime = self.pagePing(host) pingState = "%s %5.0f ms" % ("✅" if state else "❌", pingTime * 1000) ping_form.pingState.data = pingState pass else: ping_form.pingState = "" return self.render_template('ping.html', ping_form=ping_form) def datatable(self): ''' test data table ''' icons = BootstrapIcon.query.all() dictList = [] for icon in icons: dictList.append(icon.asDict()) lodKeys = {d.keys() for d in dictList[:1]} return self.render_template('datatable.html', listOfDicts=dictList, lodKeys=lodKeys, tableHeaders=lodKeys) def table(self): ''' test table ''' page = request.args.get('page', 1, type=int) pagination = Message.query.paginate(page, per_page=10) messages = pagination.items titles = [('id', '#'), ('text', 'Message'), ('author', 'Author'), ('category', 'Category'), ('draft', 'Draft'), ('create_time', 'Create Time')] return self.render_template('table.html', messages=messages, titles=titles) def widgets(self): ''' test widgets ''' dropDownMenu = DropDownMenu("Links") dropDownMenu.addItem(Link("http://www.bitplan.com", "BITPlan Website")) dropDownMenu.addItem( Link("https://bootstrap-flask.readthedocs.io/", "Docs")) dropDownMenu.addItem( Link("https://github.com/WolfgangFahl/pyFlaskBootstrap4", "github")) dropDownMenu.addItem(Link("https://getbootstrap.com/", "bootstrap")) menu = Menu() menu.addItem(MenuItem("http://wiki.bitplan.com", "BITPlan Wiki", True)) menu.addItem( MenuItem("https://bootstrap-flask.readthedocs.io/", "Docs")) menu.addItem( MenuItem( "https://github.com/WolfgangFahl/pyFlaskBootstrap4", "github", )) menu.addItem(dropDownMenu) lodDataGenerator = lambda n: [ { 'text': f'Text message {i}', 'author': f"Author {i}", "Category": f"Category {i}", "create time": datetime.now() + timedelta(days=i) } for i in range(n) ] lodTable = LodTable(lodDataGenerator(5)) lodDataTable = LodTable(lodDataGenerator(500), isDatatable=True) widgetList = [ [ Link("https://github.com/WolfgangFahl/pyFlaskBootstrap4", "pyFlaskBootstrap4", "Extended Flask + Bootstrap4 Library", newTab=True), Link("http://wiki.bitplan.com/index.php/PyFlaskBootstrap4", "Wiki", "pyFlaskBootstrap4 wiki", newTab=True), Link("https://github.com/greyli/bootstrap-flask", "bootstrap-flask", "Flask + Bootstrap4 Library by Grey Li", newTab=True), Link("https://palletsprojects.com/p/flask/", "flask", "web application framework", newTab=True), Link("https://getbootstrap.com/", "bootstrap", "Open source web toolkit", newTab=True), Link("https://fonts.google.com/icons", "Google material icons", newTab=True) ], [ Image( "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Tux.svg/299px-Tux.svg.png", alt="Tux", height=150, title='Tux - the Linux kernel penguin mascot'), Image( "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Eiffel_Tower_Paris.jpg/180px-Eiffel_Tower_Paris.jpg", alt="Eiffel Tower", height=150, title='Eiffel Tower, Paris'), Image( "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Croce-Mozart-Detail.jpg/185px-Croce-Mozart-Detail.jpg", alt="Mozart", height=150, title='Wolfgang Amadeus Mozart'), Image( "https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/The_Blue_Marble.jpg/240px-The_Blue_Marble.jpg", alt="Earth", width=150, title='Earth as seen from Apollo 17 mission') ], [ Icon("award"), Icon("battery"), Icon("book"), Icon("heart"), Icon("calculator", size=48), Icon("person", size=48, color='red'), Icon("wifi", size=64), Icon("wrench", size=64) ], [menu], [dropDownMenu], [lodTable, lodDataTable] ] return self.render_template('widgets.html', widgetList=widgetList) def bootstrapIcons(self): """ returns index page of Bootstrap Icons displaying a table of all available icons """ return self.render_template('bootstrapIcons.html')
class WikiCMSWeb(AppWrap): ''' Wrapper for Flask Web Application ''' def __init__(self, host='0.0.0.0', port=8251, debug=False): ''' constructor Args: wikiId(str): id of the wiki to use as a CMS backend host(str): flask host port(int): the port to use for http connections debug(bool): True if debugging should be switched on ''' scriptdir = os.path.dirname(os.path.abspath(__file__)) template_folder=scriptdir + '/../templates' super().__init__(host=host,port=port,debug=debug,template_folder=template_folder) self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' db.init_app(self.app) self.db=db self.loginBluePrint=LoginBluePrint(self.app,'login') self.server = Server() self.server.load() self.enabledSites = ['admin'] # # setup global handlers # @self.app.before_first_request def before_first_request_func(): self.initDB() loginMenuList=self.adminMenuList("Login") self.loginBluePrint.setLoginArgs(menuList=loginMenuList) @self.app.route('/') def index(): return self.family() @self.app.route('/family') def family(): return wcw.family() @login_required @self.app.route('/wikis') def wikis(): return self.wikis() @login_required @self.app.route('/frontends') def frontends(): return wcw.frontends() @login_required @self.app.route('/generate/<string:siteName>', methods=['GET', 'POST']) def generate(siteName: str): ''' Handle wiki generator page request Args: siteName(str): wikiId of the wiki the generator should be returned for ''' return self.generate(siteName) def initDB(self): ''' initialize the database ''' self.db.drop_all() self.db.create_all() self.initUsers() def initUsers(self): if hasattr(self.server,"adminUser"): self.loginBluePrint.addUser(self.db,self.server.adminUser,self.server.adminPassword) else: self.loginBluePrint.hint="There is no adminUser configured yet" @staticmethod def splitPath(path): ''' split the given path Args: path(str): the path to split Returns: str,str: the site of the path an the actual path ''' # https://stackoverflow.com/questions/2136556/in-python-how-do-i-split-a-string-and-keep-the-separators parts = path.split(r"/") site = "" if len(parts) > 0: site = parts[0] path = "" if len(parts) > 1: for part in parts[1:]: path = path + "/%s" % (part) return site, path def enableSites(self, siteNames): ''' enable the sites given in the sites list Args: siteNames(list): a list of strings with wikiIds to be enabled ''' if siteNames is None: return for siteName in siteNames: self.server.enableFrontend(siteName,self) self.enabledSites.append(siteName) def adminMenuList(self,activeItem:str=None): ''' get the list of menu items for the admin menu Args: activeItem(str): the active menu item Return: list: the list of menu items ''' menuList=[ MenuItem('/','Home'), MenuItem('https://github.com/BITPlan/pyWikiCMS','github'), MenuItem('/generate/orth', 'Generator') # ToDo: Test Values ] if current_user.is_anonymous: menuList.append(MenuItem('/login','login')) else: menuList.append(MenuItem('/wikis','Wikis')), menuList.append(MenuItem('/frontends','Frontends')), menuList.append(MenuItem('/logout','logout')) if activeItem is not None: for menuItem in menuList: if menuItem.title==activeItem: menuItem.active=True if menuItem.url.startswith("/"): menuItem.url="%s%s" % (self.baseUrl,menuItem.url) return menuList def frontends(self) -> str: ''' render the frontends view Returns: str: the html for the admin view ''' menuList=self.adminMenuList("Frontends") html = render_template("tableview.html", title="Frontends", menuList=menuList,dictList=self.server.frontendConfigs) return html def wikis(self) -> str: ''' render the wikis table Returns: str: the html code for the table of wikis ''' wikiUsers = WikiUser.getWikiUsers() dictList = [] for wikiUser in wikiUsers.values(): url="%s%s/" % (wikiUser.url,wikiUser.scriptPath) wikiBackup=WikiBackup(wikiUser) dictList.append({ 'wikiId': Link(url,wikiUser.wikiId), 'url': Link(wikiUser.url,wikiUser.url), 'scriptPath':wikiUser.scriptPath, 'version':wikiUser.version, 'backup': "✅" if wikiBackup.exists() else "❌", 'git': Icon("github",32) if wikiBackup.hasGit() else "" }) menuList=self.adminMenuList("Wikis") html = render_template("tableview.html", menuList=menuList,title="Wikis", dictList=dictList) return html def logo(self, siteName:str) -> str: ''' render the Logo for the given siteName Args: siteName(str): the name of the site e.g. wiki.bitplan.com Returns: the rendered Logo for the given Site ''' wikiFamily = WikiFamily() if not siteName in wikiFamily.family: return self.error("Logo Error","invalid siteName %s" % siteName) wiki=wikiFamily.family[siteName] logoFile=wiki.getLogo() if logoFile is None: return "no logo for %s" %siteName else: return send_file(logoFile) def family(self) -> str: ''' show a html representation of the family of wikis on this server (if any) Returns: str: a html table of all wikis in the family ''' dictList = [] wikiFamily = WikiFamily() for siteName in wikiFamily.family: localWiki = wikiFamily.family[siteName] logoAccess="%s/family/%s/logo" % (self.baseUrl,siteName) apacheAvailable=self.server.checkApacheConfiguration(localWiki.siteId,'available') apacheEnabled=self.server.checkApacheConfiguration(localWiki.siteId,'enabled') dbName=localWiki.database dburl=self.server.sqlGetDatabaseUrl(localWiki.database, localWiki.dbUser, localWiki.dbPassword,hostname='localhost') dbState=self.server.sqlDatabaseExist(dburl) dbStateSymbol=self.server.stateSymbol(dbState) backupState=self.server.sqlBackupStateAsHtml(dbName) hereState=self.server.stateSymbol(localWiki.ip==self.server.ip) statusSymbol="❌" if localWiki.statusCode==200: statusSymbol="✅" elif localWiki.statusCode==404: statusSymbol="⚠️" siteDict={ 'site': "%s %s" % (Link(localWiki.url,localWiki.siteName),statusSymbol), 'logo': Image(logoAccess,height=70), } if not current_user.is_anonymous: adminDict={ 'database': "%s %s" % (localWiki.database,dbStateSymbol), 'SQL backup': backupState, 'ip': "%s%s" % (hereState,localWiki.ip), 'apache': "%s/%s" % (apacheAvailable,apacheEnabled) } siteDict={**siteDict,**adminDict} dictList.append(siteDict) menuList=self.adminMenuList("Family") html = render_template("welcome.html", server=self.server,menuList=menuList,title="Wiki Family", dictList=dictList) return html def wrap(self, siteName, path): ''' wrap the given path for the given site Args: siteName(str): the name of the site to wrap path(path): the path to wrap ''' if not siteName in self.enabledSites: error = "access to site '%s' is not enabled you might want to add it via the --sites command line option" % siteName content = None template = "index.html" title = "Error" return render_template(template, title=title, content=content, error=error) else: frontend = self.server.getFrontend(siteName) return frontend.render(path) def generate(self, siteName): ''' show generate page for given siteName (wikiId of wiki family member) Args: siteName(str): the name of the wiki Returns: the rendered generate page ''' actuator = GenerateTopicForm.getActuatorTopic(request.form.lists()) generator = Generator(siteName) menuList = self.adminMenuList("Generator") if actuator is not None: # Generate button was clicked generatedPages = generator.generatePages(actuator, dict(request.form.lists())) flash(f"Generated the following pages: {','.join(generatedPages)}", 'success') return generator.render(menuList)
def __init__(self, host=None, port=8334, debug=False): ''' constructor ''' scriptdir = os.path.dirname(os.path.abspath(__file__)) template_folder=scriptdir + '/../templates' if host is None: host=socket.gethostname() super().__init__(host=host,port=port,debug=debug,template_folder=template_folder,explainTemplateLoading=True) self.sseBluePrint=SSE_BluePrint(self.app,'sse') self.loginBluePrint=LoginBluePrint(self.app,'login',welcome="home") # server specific configurations link=Link("http://www.bitplan.com",title="BITPlan GmbH") self.copyRight=Copyright(period="2020-2022",link=link) self.scandir=DMSStorage.getScanDir() self.wikiUsers=WikiUser.getWikiUsers() self.sqlDB=DMSStorage.getSqlDB() self.am=ArchiveManager.getInstance() self.fm=FolderManager.getInstance() self.dm=DocumentManager.getInstance() self.archivesByName,_dup=self.am.getLookup("name") @self.app.route('/') def homeroute(): return self.home() @self.app.route('/files') @self.app.route('/files/<path:path>') def files(path='.'): return self.files(path) @self.app.route('/scandir') def showScanDirectory(): return self.watchDir() @self.app.route('/archives/getFoldersAndFiles/<name>') def getFoldersAndFiles(name:str): return self.getFoldersAndFiles(name) @self.app.route('/archives/refresh') def refreshArchives(): return self.refreshArchives() @self.app.route('/archives') def showArchives(): return self.showArchives() @self.app.route('/archive/<name>') def showArchive(name:str): return self.showArchive(name) @self.app.route('/folders') def showFolders(): return self.showFolders() @self.app.route('/folder/<archiveName>/<path:folderPath>/refresh') def refreshFolder(archiveName:str=None,folderPath=None): return self.refreshFolder(archiveName,f"/{folderPath}") @self.app.route('/folder/<archiveName>/<path:folderPath>') def showFolder(archiveName:str=None,folderPath=None): return self.showFolder(archiveName,f"/{folderPath}") @self.app.route('/documents') def showDocuments(): return self.showDocuments() @self.app.route('/delete/<path:path>') def delete(path=None): return self.delete(path) @self.app.route('/upload/<path:path>',methods=['GET', 'POST']) def upload(path=None): return self.upload(path)
def __init__(self): ''' Constructor ''' scriptdir = os.path.dirname(os.path.abspath(__file__)) template_folder=scriptdir + '/templates' super().__init__(template_folder=template_folder) self.app.secret_key = 'dev' self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # set default button style and size, will be overwritten by macro parameters self.app.config['BOOTSTRAP_BTN_STYLE'] = 'primary' self.app.config['BOOTSTRAP_BTN_SIZE'] = 'sm' # app.config['BOOTSTRAP_BOOTSWATCH_THEME'] = 'lumen' # uncomment this line to test bootswatch theme db.init_app(self.app) self.db=db self.csrf = CSRFProtect(self.app) self.loginBluePrint=LoginBluePrint(self.app,'login') self.loginBluePrint.hint="'try user: scott, password: tiger2021'" app=self.app # # setup global handlers # @app.before_first_request def before_first_request_func(): self.initDB() # # setup the RESTFUL routes for this application # @app.route('/') def index(): return self.home() @app.route('/form', methods=['GET', 'POST']) def test_form(): return self.form() @app.route('/nav', methods=['GET', 'POST']) def test_nav(): return self.nav() @app.route('/pagination', methods=['GET', 'POST']) def test_pagination(): return self.pagination() @app.route('/static', methods=['GET', 'POST']) def test_static(): return self.static() @app.route('/flash', methods=['GET', 'POST']) def test_flash(): return self.flash() @app.route('/datatable') def test_datatable(): return self.datatable() @app.route('/table') def test_table(): return self.table() @app.route('/table/<message_id>/view') def view_message(message_id): return self.message_view(message_id) @app.route('/table/<message_id>/edit') def edit_message(message_id): return self.message_edit(message_id) @app.route('/table/<message_id>/delete', methods=['POST']) def delete_message(message_id): return self.message_delete(message_id) @app.route('/icon') def test_icon(): return self.icon() @app.route('/widgets') def test_widgets(): return self.widgets() @app.route('/ping',methods=['GET', 'POST']) def test_ping(): return self.ping()
class ExampleApp(AppWrap): ''' flask app wrapped in class ''' def __init__(self): ''' Constructor ''' scriptdir = os.path.dirname(os.path.abspath(__file__)) template_folder=scriptdir + '/templates' super().__init__(template_folder=template_folder) self.app.secret_key = 'dev' self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # set default button style and size, will be overwritten by macro parameters self.app.config['BOOTSTRAP_BTN_STYLE'] = 'primary' self.app.config['BOOTSTRAP_BTN_SIZE'] = 'sm' # app.config['BOOTSTRAP_BOOTSWATCH_THEME'] = 'lumen' # uncomment this line to test bootswatch theme db.init_app(self.app) self.db=db self.csrf = CSRFProtect(self.app) self.loginBluePrint=LoginBluePrint(self.app,'login') self.loginBluePrint.hint="'try user: scott, password: tiger2021'" app=self.app # # setup global handlers # @app.before_first_request def before_first_request_func(): self.initDB() # # setup the RESTFUL routes for this application # @app.route('/') def index(): return self.home() @app.route('/form', methods=['GET', 'POST']) def test_form(): return self.form() @app.route('/nav', methods=['GET', 'POST']) def test_nav(): return self.nav() @app.route('/pagination', methods=['GET', 'POST']) def test_pagination(): return self.pagination() @app.route('/static', methods=['GET', 'POST']) def test_static(): return self.static() @app.route('/flash', methods=['GET', 'POST']) def test_flash(): return self.flash() @app.route('/datatable') def test_datatable(): return self.datatable() @app.route('/table') def test_table(): return self.table() @app.route('/table/<message_id>/view') def view_message(message_id): return self.message_view(message_id) @app.route('/table/<message_id>/edit') def edit_message(message_id): return self.message_edit(message_id) @app.route('/table/<message_id>/delete', methods=['POST']) def delete_message(message_id): return self.message_delete(message_id) @app.route('/icon') def test_icon(): return self.icon() @app.route('/widgets') def test_widgets(): return self.widgets() @app.route('/ping',methods=['GET', 'POST']) def test_ping(): return self.ping() def initDB(self,limit=20): ''' initialize the database ''' self.db.drop_all() self.db.create_all() self.initUsers() self.initMessages(limit) self.initIcons() def initUsers(self): self.loginBluePrint.addUser(self.db,"scott","tiger2021",userid=100) def initMessages(self,limit=20): ''' create an initial set of message with the given limit Args: limit(int): the number of messages to create ''' for i in range(limit): m = Message( text='Test message {}'.format(i+1), author='Author {}'.format(i+1), category='Category {}'.format(i+1), create_time=4321*(i+1) ) if i % 4: m.draft = True self.db.session.add(m) self.db.session.commit() def initIcons(self): ''' initialize the icons ''' iconNames=Icon.getBootstrapIconsNames() for index,iconName in enumerate(iconNames): bootstrapIcon=BootstrapIcon(id=iconName,index=index+1) self.db.session.add(bootstrapIcon) self.db.session.commit() def getDisplayIcons(self,icons): displayIcons=[] for icon in icons: displayIcons.append("%04d%s%s" % (icon.index,icon.icon,icon.link)) return displayIcons def pagePing(self,host, path="/"): """ This function retrieves the status code of a website by requesting HEAD data from the host. This means that it only requests the headers. If the host cannot be reached or something else goes wrong, it returns False. see https://stackoverflow.com/a/1949507/1497139 """ startTime=time.time() try: conn = http.client.HTTPConnection(host) conn.request("HEAD", path) if re.match("^[23]\d\d$", str(conn.getresponse().status)): state=True except Exception: state=False elapsed=time.time()-startTime return state,elapsed def flash(self): flash('A simple default alert—check it out!') flash('A simple primary alert—check it out!', 'primary') flash('A simple secondary alert—check it out!', 'secondary') flash('A simple success alert—check it out!', 'success') flash('A simple danger alert—check it out!', 'danger') flash('A simple warning alert—check it out!', 'warning') flash('A simple info alert—check it out!', 'info') flash('A simple light alert—check it out!', 'light') flash('A simple dark alert—check it out!', 'dark') flash(Markup('A simple success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.'), 'success') return render_template('flash.html') def form(self): form = LoginForm() return render_template('form.html', form=form, telephone_form=TelephoneForm(), contact_form=ContactForm(), im_form=IMForm(), button_form=ButtonForm(), example_form=ExampleForm()) def home(self): return render_template('index.html') def icon(self): return render_template('icon.html') def message_delete(self,message_id): message = Message.query.get(message_id) if message: db.session.delete(message) db.session.commit() return f'Message {message_id} has been deleted. Return to <a href="/table">table</a>.' return f'Message {message_id} did not exist and could therefore not be deleted. Return to <a href="/table">table</a>.' def message_edit(self,message_id): message = Message.query.get(message_id) if message: message.draft = not message.draft db.session.commit() return f'Message {message_id} has been edited by toggling draft status. Return to <a href="/table">table</a>.' return f'Message {message_id} did not exist and could therefore not be edited. Return to <a href="/table">table</a>.' def message_view(self,message_id): message = Message.query.get(message_id) if message: return f'Viewing {message_id} with text "{message.text}". Return to <a href="/table">table</a>.' return f'Could not view message {message_id} as it does not exist. Return to <a href="/table">table</a>.' def nav(self): return render_template('nav.html') def pagination(self): ''' pagination example Returns: rendered html for pagination ''' search_form=IconSearchForm() perPageChoice=search_form.perPage.data if perPageChoice is None: perPageChoice="twenty" choices=dict(search_form.perPage.choices) perPageSelection=choices[perPageChoice] search_form.perPage.data=perPageChoice if perPageChoice=="all": per_page=2000 else: per_page=int(perPageSelection) pagination=None icons=None if search_form.validate_on_submit() and search_form.search.data: search="%{}%".format(search_form.search.data) print("searching %s: " % search) icons = BootstrapIcon.query.filter(BootstrapIcon.id.like(search)).all() if icons is None: page = request.args.get('page', 1, type=int) pagination = BootstrapIcon.query.paginate(page, per_page=per_page) icons = pagination.items displayIcons=self.getDisplayIcons(icons) return render_template('pagination.html', form=search_form,pagination=pagination, icons=displayIcons) def ping(self): ''' ping test ''' ping_form=PingForm() if ping_form.validate_on_submit(): choices=dict(ping_form.host.choices) host=choices[ping_form.host.data] state,pingTime=self.pagePing(host) pingState="%s %5.0f ms" % ("✅" if state else "❌",pingTime*1000) ping_form.pingState.data=pingState pass else: ping_form.pingState="" return render_template('ping.html',ping_form=ping_form) def static(self): ''' test static content ''' return render_template('static.html') def datatable(self): ''' test data table ''' icons=BootstrapIcon.query.all() dictList=[] for icon in icons: dictList.append(icon.asDict()) return render_template('datatable.html',listOfDicts=dictList) def table(self): ''' test table ''' page = request.args.get('page', 1, type=int) pagination = Message.query.paginate(page, per_page=10) messages = pagination.items titles = [('id', '#'), ('text', 'Message'), ('author', 'Author'), ('category', 'Category'), ('draft', 'Draft'), ('create_time', 'Create Time')] return render_template('table.html', messages=messages, titles=titles) def widgets(self): ''' test widgets ''' widgetList=[ [ Link("https://github.com/WolfgangFahl/pyFlaskBootstrap4","pyFlaskBootstrap4","Extended Flask + Bootstrap4 Library"), Link("http://wiki.bitplan.com/index.php/PyFlaskBootstrap4","Wiki","pyFlaskBootstrap4 wiki"), Link("https://github.com/greyli/bootstrap-flask","bootstrap-flask","Flask + Bootstrap4 Library by Grey Li"), Link("https://palletsprojects.com/p/flask/","flask","web application framework"), Link("https://getbootstrap.com/","bootstrap","Open source web toolkit") ], [ Image("https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Tux.svg/299px-Tux.svg.png",alt="Tux",height=150,title='Tux - the Linux kernel penguin mascot'), Image("https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Eiffel_Tower_Paris.jpg/180px-Eiffel_Tower_Paris.jpg",alt="Eiffel Tower",height=150,title='Eiffel Tower, Paris'), Image("https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Croce-Mozart-Detail.jpg/185px-Croce-Mozart-Detail.jpg",alt="Mozart",height=150,title='Wolfgang Amadeus Mozart'), Image("https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/The_Blue_Marble.jpg/240px-The_Blue_Marble.jpg",alt="Earth",width=150,title='Earth as seen from Apollo 17 mission') ], [ Icon("award"),Icon("battery"),Icon("book"),Icon("heart"), Icon("calculator",size=48),Icon("person",size=48,color='red'), Icon("wifi",size=64),Icon("wrench",size=64) ] ] return render_template('widgets.html',widgetList=widgetList)