angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Flask applicatie-instellingen die ter plekke door een beheerder worden gewijzigd

De toepassingsconfiguratie moet statisch zijn, de toepassingsinstellingen moeten dynamisch zijn.

26 juli 2019
In Flask
post main image
Original photo unsplash.com/@ssvveennn

Flask We hebben het configuratieobject dat gebruikt kan worden om databaseparameters, e-mailparameters, etc. te specificeren. Wanneer we een Flask programma uitvoeren, maakt het eerst de applicatie aan. Als de applicatie eenmaal is aangemaakt, worden de volgende verzoeken om de app te maken overgeslagen en doorgestuurd naar de blueprint weergaven.

Bij het Flask starten worden de configuratievariabelen geladen en gebruikt. Belangrijk om te begrijpen is dat iedere bezoeker zijn eigen instantie krijgt. Als de Flask applicatie eenmaal draait, kunnen we de configuratievariabelen veranderen, maar wat is het nut hiervan? Als we een configuratievariabele in één instantie veranderen, dan is dit niet zichtbaar in andere instanties.

Ik ben ook van mening dat we een onderscheid moeten maken tussen configuratievariabelen zoals database, e-mail, sessie-instellingsvariabelen en instellingenvariabelen die we on-the-fly willen wijzigen, zoals de tekst van de programmanaam die wordt weergegeven in sjablonen of een allow/blok-registraties boolean. We zouden in staat moeten zijn om de instellingsvariabelen te beheren met behulp van een beheerder en andere draaiende instanties te triggeren om ze te gebruiken zonder deze andere instanties opnieuw te laden.

Kortom, om de applicatie-instellingen te implementeren moeten we het volgende doen:

  • Definieer de toepassingsinstelklassen voor de applicatie-instellingen
  • Laad de huidige versie van de applicatie-instellingen bij het aanmaken van de Flask app
  • Maak een admin-functie om de applicatie-instellingen te bewerken en een nieuwe versie van de applicatie-instellingen te maken.
  • Maak een admin-functie aan om een nieuwe versie van de applicatie-instellingen te activeren.
  • Andere instanties triggeren om een nieuw geactiveerde versie van de applicatie-instellingen te gebruiken.
  • Maak de applicatie-instellingen toegankelijk in onze Jinja sjablonen

Definieer de toepassingsinstellingsklassen

Een vereiste is dat het mogelijk moet zijn om verschillende versies van de applicatie-instellingen te maken. Dit is meer werk, maar het maakt het mogelijk om zonder problemen over te schakelen naar een nieuwe versie en terug te schakelen naar een vorige versie. Ik gebruik een klasse-attribuut/databasetabelveld seqno om een versie te identificeren.

De applicatie-instellingen, Instellingen, zijn gegroepeerd in secties, SettingSections, en hebben een value_type dat aangeeft of het een geheel getal, string of booleaans is. Telkens wanneer de applicatie-instellingen worden opgeslagen, wordt een nieuwe versie van de applicatie-instellingen aangemaakt. Deze versie kan worden geïnspecteerd en als alles goed is kan deze worden geactiveerd. Ik heb ook een from_seqno variabele toegevoegd die aangeeft welke versie we hebben gewijzigd, ik gebruik deze om de wijzigingen tussen de versies te tonen.

Tenslotte is er de applicatie-instellingen seqno of versie, SettingSeqno, die het versienummer van de actieve versie van de applicatie-instellingen bevat. Merk op dat ik alle achtergrondbevolking weggelaten heb, ik denk niet dat het iets toevoegt, zie ook een vorige post.

class SettingSeqno(Base):

    __tablename__ = 'setting_seqno'

    id = Column(Integer, primary_key=True)
    ...
    # only one can be active at a time
    active_seqno = Column(Integer, server_default='0', index=True)


class Setting(Base):

    __tablename__ = 'setting'

    id = Column(Integer, primary_key=True)
    ...
    # seqno settings is the group of settings at a certain moment
    seqno = Column(Integer, server_default='0', index=True)
    name = Column(String(60), server_default='')
    # from_seqno is the seqno that was the base of this seqno
    from_seqno = Column(Integer, server_default='0', index=True)
    value = Column(String(60), server_default='')
    value_type = Column(Integer, server_default='0')
    setting_section_id = Column(Integer, ForeignKey('setting_section.id'))


class SettingSection(Base):

    __tablename__ = 'setting_section'

    id = Column(Integer, primary_key=True)
    ...
    # seqno settings is the group of settings at a certain moment
    seqno = Column(Integer, server_default='0', index=True)
    name = Column(String(60), server_default='')
    # from_seqno is the seqno that was the base of this seqno
    from_seqno = Column(Integer, server_default='0', index=True)

Laad de huidige versie van de applicatie-instellingen bij het aanmaken van de Flask app

Dit heb ik gedaan door een AppSettings-object te maken, een beetje zoals wanneer je een Flask extensie maakt. In de __init__ functie worden de applicatie-instellingen uit de database geladen. In de create_app functie heb ik net de regel toegevoegd:

app.app_settings = AppSettings(app)

Merk op dat ik app_instellingen voorvoegsel met 'app.' om app_instellingen globaal te maken. In de create_app functie kan ik er app.app_settings naar verwijzen en in de blueprint functies kan ik het current_app.app_settings noemen. Tot nu toe hebben we nu applicatie-instellingen die we kunnen gebruiken in onze applicatie, bijvoorbeeld door te bellen:

allow_registration = current_app.app_settings.get_value('SECTION_AUTH', 'ALLOW_REGISTRATION')

Maak een admin-functie om de applicatie-instellingen te bewerken en een nieuwe versie van de applicatie-instellingen te maken.

Dit gaat over het ontwerpen van sjablonen en codering. Ik heb een lijstpagina die een lijst met alle versies bevat en een bewerkingspagina die gebruikt wordt om de applicatie-instellingen te bewerken. Telkens wanneer wijzigingen worden opgeslagen, worden deze opgeslagen in een nieuwe versie van de applicatie-instellingen. Deze nieuwe versie wordt niet automatisch geactiveerd. De pagina met de versies toont ook de verschillen tussen de applicatie-instellingen met behulp van Python's difflib.

Maak een admin-functie aan om een nieuwe versie van de applicatie-instellingen te activeren.

De lijstpagina met de versies heeft ook een keuzerondgroep met een keuzerondje voor elke versie, de geactiveerde heeft het keuzerondje gecontroleerd. Om naar een nieuwe versie te gaan, selecteer je gewoon de versie en in de code update je de waarde van SettingSeqno.active_seqno en laad je de applicatie-instellingen opnieuw uit de database. Dit werkt prima, maar alleen in het geval van de beheerder, de andere instanties (websitebezoekers) zijn hiervan niet op de hoogte.

Andere instanties triggeren om een nieuw geactiveerde versie van de applicatie-instellingen te gebruiken.

Omdat we de Flask applicatie niet willen herladen voor websitebezoekers moeten we op zoek naar een andere manier. Wat ik heb gedaan is de Flask before_request handler gebruiken. Ik gebruik een sessievariabele 'app_settings_current_seqno' en een databasewaarde 'active_seqno' om te controleren of er een nieuwe versie van de applicatie-instellingen geladen moet worden.

Helaas moeten we in Flask's before_request handler een databaseverzoek toevoegen om de huidige versie van de applicatie-instellingen te krijgen. Dit zou vermeden kunnen worden door gebruik te maken van een gedeeld bestand, etc. maar omdat ik hier in de toekomst misschien ook andere waarden wil plaatsen heb ik besloten om een extra object/tabel BeforeRequestData toe te voegen die een kopie van de active_seqno variabele bevat. Om een lang verhaal kort te maken, als de sessievariabele afwijkt van de databasewaarde, worden de applicatie-instellingen opnieuw geladen vanuit de database. Daarna wordt de sessievariabele bijgewerkt naar de active_seqno waarde om te voorkomen dat dit bij volgende verzoeken gebeurt.

    @app.before_request
    def before_request():
        with app.app_context():
            ...
            # reload application settings if session var not equal to database var
            if session_app_settings_current_seqno != database_app_settings_current_seqno:
                app.app_settings.load_from_database(app)
                session['app_settings_current_seqno'] = database_app_settings_current_seqno

Maak de applicatie-instellingen toegankelijk in onze Jinja sjablonen

Bijna daar. Flask Configuratievariabelen worden standaard doorgegeven via een fles, waardoor je toegang hebt tot de configuratievariabelen in sjablonen. Om onze applicatie-instellingen door te geven aan de templates gebruiken we een context processor. Ik heb al een context processor die dingen injecteert, dus ik heb net de applicatie-instellingen toegevoegd.

Ik heb ook de huidige versie van de applicatie-instellingen toegevoegd zodat ik in de webpagina gemakkelijk kon zien welke versie van de applicatie-instellingen wordt gebruikt:

    @app.context_processor
    def inject_base_template_parameters():

        base_template_parameters = {}

        # app_settings
        base_template_parameters['app_settings'] = app.app_settings.get_setting_section_name2setting_name2settings()

        # app_settings seqno
        app_settings_current_seqno = ''
        if session.get('app_settings_current_seqno'):
            app_settings_current_seqno = str( session.get('app_settings_current_seqno') )
        base_template_parameters['app_settings_current_seqno'] = app_settings_current_seqno
        ...
        return base_template_parameters

En in het basissjabloon:

	<p class="copyright text-muted small">
		{{ app_settings['SECTION_GENERAL']['WEBSITE_COPYRIGHT']['value'] }} [{{ app_settings_current_seqno }}] [72ms]
	</p>

Samenvatting

Het was geen eenvoudige taak om dit te implementeren, maar het gaf meer inzicht in Flask. Het maken van een Flask app is anders dan het uitvoeren van volgende verzoeken. Het is verwarrend dat we tijdens de initialisatie het Flask app-object moeten gebruiken en daarna het Flask current_app object. Ik heb hier ergens over gelezen, zal er nog eens over moeten lezen, en nog eens, en nog eens, en nog eens en nog eens.

Links / credits

Configuration Handling
https://flask.palletsprojects.com/en/1.1.x/config/

Context Processors
https://flask.palletsprojects.com/en/1.1.x/templating/#context-processors

Dynaconf - Easy and Powerful Settings Configuration for Python
https://dynaconf.readthedocs.io/en/latest/index.html

How to reload a configuration file on each request for Flask?
https://stackoverflow.com/questions/39456672/how-to-reload-a-configuration-file-on-each-request-for-flask

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.