Esempio n. 1
0
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)
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')
Esempio n. 3
0
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)