Build A Note App In Flask As Fast As It Can Get (Using Shopyo)

Shopyo is a framework to get started with Flask really quick. It includes several libraries to get started by default. In this tutorial we will build a simple app to get started with. We used it for FlaskCon 2021 [site, codebase].

First steps

The first step is to install the library.

python3.9 -m venv venv # create virtual env
. venv/bin/activate # activate (linux version)
pip install shopyo==4.4.3

Then we create our folder

mkdir note_app

We enter into the folder

cd note_app

Then we create a new Shopyo project

shopyo new

The output looks like this:

creating project note_app...
#######################
[x] Project note_app created successfully!

A tree lookup gives

├── docs
│   ├── conf.py
│   ├── docs.rst
│   ├── index.rst
│   ├── Makefile
│   └── _static
│       └── custom.css
├── MANIFEST.in
├── note_app
├── pytest.ini
├── README.md
├── requirements.txt
├── setup.py
└── tox.ini

Since we are not interested in packaging our app for now, we can switch to the inner note_app/ folder

cd note_app

The inner note_app folder has this structure

├── note_app
│   ├── app.py
│   ├── app.txt
│   ├── autoapp.py
│   ├── CHANGELOG.md
│   ├── cli.py
│   ├── config_demo.json
│   ├── config.py
│   ├── conftest.py
│   ├── __init__.py
│   ├── init.py
│   ├── manage.py
│   ├── modules
│   ├── shopyo_admin.py
│   ├── static
│   ├── tests
│   │   ├── conftest.py
│   │   └── test_configs.py
│   └── wsgi.py

Creating modules

Lets create the notes app

shopyo startapp notes

A new folder will be created under modules/. The content of modules/note_app/ looks like this

modules/notes/
├── forms.py
├── global.py
├── info.json
├── models.py
├── static
├── templates
│   └── notes
│       ├── blocks
│       │   └── sidebar.html
│       └── dashboard.html
├── tests
│   ├── test_notes_functional.py
│   └── test_notes_models.py
└── view.py

These are files created by default.

Writing the first view

The info.json file lists some basics about the module, including it’s url namespace (module_name) and url prefix

{
    "author": {
        "mail": "",
        "name": "",
        "website": ""
    },
    "display_string": "Notes",
    "fa-icon": "fa fa-store",
    "module_name": "notes",
    "type": "show",
    "url_prefix": "/notes"
}

Let’s change url_prefix to "/"

{
    ...,
    "url_prefix": "/"
}

Our view.py looks like this, which is generated by default.

from shopyo.api.module import ModuleHelp

mhelp = ModuleHelp(__file__, __name__)
globals()[mhelp.blueprint_str] = mhelp.blueprint
module_blueprint = globals()[mhelp.blueprint_str]


@module_blueprint.route("/")
def index():
    return mhelp.info['display_string']

Let’s change the return string to String from notes app

@module_blueprint.route("/")
def index():
    return "String from notes app"

Running the app

Run shopyo rundebug to run the app in debug mode.

Going to “http://127.0.0.1:5000/” should return “String from notes app”

You can learn more about the run command here.

Creating models

Our notes will have a title and a content. In modules/notes/models.py write:

from init import db
from shopyo.api.models import PkModel



class Note(PkModel): 

    __tablename__ = 'notes'

    title = db.Column(db.String(80), nullable=False)
    content = db.Text()

The init import comes from the init.py file.
The PkModel is same as db.Model with by default id as primary key

Then we initialise the app. It uses Flask-Migrate under the hood. You can view more initialise options here

$ shopyo initialise
initializing...
Cleaning...
#######################
Auto importing models...
#######################

Creating db...
#######################

Migrating db...
#######################

Upgrading db...
#######################

Collecting static...
#######################

Uploading initial data to db...
#######################

All Done!

This worked as Shopyo adds a shopyo.db as basic sqlalchemy connection string.

Configuring Flask-Admin

Now let’s configure flask-admin to have a quick CRUD view. Fortunately, Shopyo already has some basics ongoing.

First, modify shopyo_admin.py to remove Flask-Login authentications

from flask import redirect
from flask import request
from flask import url_for
from flask_admin import AdminIndexView
from flask_admin import expose
from flask_admin.contrib import sqla as flask_admin_sqla
from flask_login import current_user


class DefaultModelView(flask_admin_sqla.ModelView):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    # def is_accessible(self):
    #     return current_user.is_authenticated and current_user.is_admin

    # def inaccessible_callback(self, name, **kwargs):
    #     # redirect to login page if user doesn't have access
    #     return redirect(url_for("auth.login", next=request.url))


class MyAdminIndexView(AdminIndexView):
    # def is_accessible(self):
    #     return current_user.is_authenticated and current_user.is_admin

    # def inaccessible_callback(self, name, **kwargs):
    #     # redirect to login page if user doesn't have access
    #     return redirect(url_for("auth.login", next=request.url))

    @expose("/")
    def index(self):
        # if not current_user.is_authenticated and current_user.is_admin:
        #     return redirect(url_for("auth.login"))
        return super().index()
# 
    @expose("/dashboard")
    def indexs(self):
        # if not current_user.is_authenticated and current_user.is_admin:
        #     return redirect(url_for("auth.login"))
        return super().index()

Then in app.py, don’t load Flask-Login by commenting it out.

def load_extensions(app):
    ...
    # login_manager.init_app(app)

Then in app.py import the Note model

from modules.notes.models import Note

And modify the setup_flask_admin function to look like this:

def setup_flask_admin(app):
    admin = Admin(
        app,
        name="My App",
        template_mode="bootstrap4",
        index_view=MyAdminIndexView(),
    )
    admin.add_view(ModelView(Note, db.session))

Now, navigating to /admin gives

Image description

Clicking on note allows you to edit the Note model. Let’s add few models!

Image description

Displaying templates

What remains is using displaying the note on our main page.

Under modules/notes/templates/notes create a file called index.html with content

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
</head>
<body>
    {% for note in notes %}
        {{note.title}}<br>
        {{note.content}}<hr>
    {% endfor %}
</body>
</html>

Modify the view.py to

from shopyo.api.module import ModuleHelp
from flask import render_template

from .models import Note 


mhelp = ModuleHelp(__file__, __name__)
globals()[mhelp.blueprint_str] = mhelp.blueprint
module_blueprint = globals()[mhelp.blueprint_str]


@module_blueprint.route("/")
def index():
    notes = Note.query.all()
    return render_template('notes/index.html', notes=notes)

Which results in:

Image description

Shopyo also provides some utils like

from shopyo.api.templates import yo_render

...
@module_blueprint.route("/")
def index():
    notes = Note.query.all()
    context = {
        'notes': notes
    }
    return yo_render('notes/index.html', context)

Trying out a demo app

If you just want to try a demo app, just run

mkdir project
cd project
shopyo new -m # -m adds default modules
cd project
shopyo initialise
shopyo rundebug

You can then see how an authed Flask-Admin looks like.

Hope you enjoy this post!