Codementor Events

Shopyo: Enhance Your Flask by Exploring An Advanced Flask App

Published Mar 07, 2020

Shopyo is an Open Source Flask-based, Python-powered inventory solution and upcoming point of sales. It's aim is to help small business owners get a nice Python-based product with essential amenities for their businesses.

An empty Shopyo project at the very least makes a great Flask base.

The Tech Stack

A peek at Shopyo's requirements.txt gives

flask
flask_sqlalchemy
marshmallow_sqlalchemy
flask_marshmallow
flask_migrate
flask_script
flask_login
Flask-WTF
requests

flask_sqlalchemy is a wrapper around SQLAlchemy, a popular Python ORM. An ORM allows you to define your SQL tables without the need to write SQL codes. You just define models and the table is created for you. A typical model looks like this:

class Settings(db.Model): __tablename__ = 'settings' setting = db.Column(db.String(100), primary_key=True) value = db.Column(db.String(100))

flask_migrate is used to migrate your models. If you change your models by adding a new field, the change is not reflected in your database. That's why you need to apply migrations. It is a wrapper around Alembic, a popular migration package.

  • flask_marshmallow
  • marshmallow_sqlalchemy

Marshmallow is a project that allows you to create REST Apis. flask_marshmallow allows the easy integration of marshmallow with Flask and marshmallow_sqlalchemy is a required package that goes along.

Though deprecated by Flask's official command line utitlity, flask_script allows you to create scripts to manage your Flask app easily.

Flask login allows you to add authentication to your app.

A package that allows you to create forms. We use it to prevent CSRF (pronounced sea surf) attacks. It basically ensures that you don't tamper with a webpage then try to send it to someone else expecting it to work

A package to make web requests and pull in urls easily. You can view a tutorial about it here

Terms Explained

model

A model defines your table

class Settings(db.Model): __tablename__ = 'settings' setting = db.Column(db.String(100), primary_key=True) value = db.Column(db.String(100))

creates a table named settings with fields named settings and value

template

A template is an html file with spaces left for values. {% and {{ have special meaning. {{1 + 1}} will display 2 when rendered. Similarly {{x + 1}} will evaluate the expression before rendering.

{% extends "base/main_base.html" %} {% set active_page ='settings' %} {% block pagehead %}
<title>Settings</title>
<style> .hidden{ display: none; } .show{ display: inline-block; }
</style>
{% endblock %} {% block content %}
<script type="text/javascript">
$(function() { });
</script> <table class="table"> <thead> <tr> <th scope="col">Settings</th> <th scope="col">Value </th> <th scope="col"></th> </tr> </thead> <tbody>
{% for setting in settings %} <tr> <td>{{setting.setting}}</td> <td>{{setting.value}} </td> <td><a href="/settings/edit/{{setting.setting}}" class="btn btn-info" role="button"><i class="fas fa-pencil-alt"></i></a></td> </tr>
{%endfor%} </tbody>
</table>

{% endblock %}

let's take this snippet

{% extends "base/main_base.html" %}

Tells that we are inheriting from base.html

This eases our life by not copying whole <link> or <script> codes for example or not recopying the header/footer

{% block pagehead %}
...
{% endblock %}

{% block content %}
...
{% endblock %}

allows us to define our head and body respectively.

Views and blueprint

A view file has lots of routes and what happens when such a route is found. This is the settings view file for example

from flask import ( Blueprint, render_template, request, redirect, url_for, jsonify ) from addon import db
from views.settings.models import Settings
from flask_marshmallow import Marshmallow
from flask_login import login_required, current_user from project_api import base_context settings_blueprint = Blueprint('settings', __name__ , url_prefix='/settings') @settings_blueprint.route("/")
@login_required
def settings_main(): context = base_context() settings = Settings.query.all() context['settings'] = settings return render_template('settings/index.html', **context) ...

Here is a breaking down:

from flask import (
    Blueprint, render_template, request, redirect, jsonify
    )

Allows us to create blueprints. More on that further on

return render_template(filename) returns the rendered html

refers to the incoming web request by which we can check for GET or POST methods

redirects to another url

returns a string or dictionary as JSON response

addon contains the following:

from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_login import LoginManager db = SQLAlchemy()
ma = Marshmallow()
login_manager = LoginManager()
from views.settings.models import Settings

Tells us to go to views folder -> settings folder -> models file

...
from project_api import base_context

project_api.py contains base_context which returns just a dictionary

def base_context(): base_context = { 'APP_NAME': get_setting('APP_NAME'), 'SECTION_NAME': get_setting('SECTION_NAME'), 'SECTION_ITEMS': get_setting('SECTION_ITEMS') } return base_context.copy()

We copy so as not to let additions be global. Rener templates accepts the variables to be rendered as keywords. But those 3 variables are rendered everywhere so, instead of copy paste each time, we just add to this dictionary.

settings_blueprint = Blueprint('settings', __name__ , url_prefix='/settings')

This tells that whatever urls starts with /settings will be dealt with in this file

@settings_blueprint.route("/abc")

is actually for the url /settings/abc

@settings_blueprint.route("/")
@login_required
def settings_main(): context = base_context() settings = Settings.query.all() context['settings'] = settings return render_template('settings/index.html', **context)

Context is just a dictionary

context['settings'] = settings

passes the settings variable which the for loop in the template makes use of

{% for setting in settings %} <tr> <td>{{setting.setting}}</td> <td>{{setting.value}} </td> <td><a href="/settings/edit/{{setting.setting}}" class="btn btn-info" role="button"><i class="fas fa-pencil-alt"></i></a></td> </tr>
{%endfor%}

By the way,

context['settings'] = settings
return render_template('settings/index.html', **context)

is the same as

return render_template('settings/index.html', settings=settings, APP_NAME=get_setting('APP_NAME'), SECTION_NAME=get_setting('SECTION_NAME'), SECTION_ITEMS=get_setting('SECTION_ITEMS'))

Register blueprints

in app.py you will see

from views.settings.settings_modif import settings_blueprint app.register_blueprint(settings_blueprint)

which adds the views of the settings folder to the app.

Configuration management

in config.py we see

class Config: """Parent configuration class.""" DEBUG = False SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db' SQLALCHEMY_TRACK_MODIFICATIONS = False SECRET_KEY = 'qow32ijjdkc756osk5dmck' # Need a generator
 APP_NAME = 'Demo' SECTION_NAME = 'Manufacturer' SECTION_ITEMS = 'Products' HOMEPAGE_URL = '/manufac/' class DevelopmentConfig(Config): """Configurations for development""" ENV = 'development' DEBUG = True app_config = { 'development': DevelopmentConfig, 'production': Config,
}

then in app.py

def create_app(config_name): app = Flask( __name__ ) app.config.from_object(app_config[config_name]) ...

then further down

app = create_app('development')

so, if we put in 'production' it will load the production configs.

As info, creating apps this way is called the App Factory pattern

manage.py

manage.py

migrate = Migrate(app, db, compare_type=True)
manager = Manager(app) manager.add_command('db', MigrateCommand)

allows us to pass in normal Alembic migration commands

normal alembic commands run like:

alembic db init
alembic db migrate
alembic db upgrade

but using the above code, we automatically get

python manage.py db init
python manage.py db migrate
python manage.py db upgrate

similarly this:

@manager.command
def runserver(): app.run()

allows us to have

python manage.py runserver

Conclusion

This was written as part of the Shopyo docs but makes a nice Flask post by the way!

If you did not understand something, please ping me at

arj.python at gmail dot com
Discover and read more posts from Abdur-Rahmaan Janhangeer
get started
post commentsBe the first to share your opinion
Show more replies