Why do you have this idea

Flask has been used for full stack development, but later found Fastapi asynchronous framework, feel really strong performance, used to do back-end interface services. In Flask, the page is mainly implemented by using Jinja2 template engine +BS style +JQuery, which is acceptable for a backend development, because if the front and back ends are separated, we have to learn Vue/React and other frameworks, and it always takes time for a person to develop dual ends.

After using Fastapi, I also tried a new way to realize the front-end page vertically. Some small things I do are always unable to do without background management. When USING Flask, I mainly use flask-Admin to achieve it. However, after using Fastapi, I found that there is no similar library, but I found a FastapI-Admin library developed by our people. However, this library is separated from the front and back ends, and the front end uses Vue, so I am still a little discouraged.

This was a bit of a headache until I discovered the AMIS front-end low code framework. After a bit of research, it feels like a good idea to combine AMIS with Fastapi.

Let’s take a look at the AMIS framework. Amis is a low-code front-end framework that uses JSON configuration to generate pages, reducing page development effort and increasing productivity.

To achieve the simplest way to generate most pages, Amis’s solution is configured based on JSON, which has the unique benefits of:

  • You don’t need to know the front end: Inside Baidu, most amis users have never written a front page before, nor will theyJavaScript, but can make a professional and complex background interface, which no other front-end UI library can do;
  • Not affected by front-end technology updates: Baidu’s oldest amis page was created more than 4 years ago and is still in use todayAngular/Vue/ReactVersions are now obsolete. They were popularGulpHave also beenWebpackInstead, these pages would now be expensive to maintain if they weren’t amis;
  • Enjoy the continuous upgrading of Amis: Amis has been improving the detailed interactive experience, such as freezing the first line of the table, no lag under the big data of the drop-down box, etc., and the previous JSON configuration does not need to be modified at all.
  • Pages can be created entirely using visual page editors: while typical front-end visual editors can only be used for static prototypes, amis visual editors produce pages that go live.

Amis can be used in two ways:

  • JS SDK, can be used in any page
  • React, can be used in the React project

I was pleasantly surprised to see the JS SDK available so I could embed JS into the project. Then AMIS visual design is used to generate front-end JSON, which is then put into the project page template, so that the page display can be realized quickly.

Take a look at the official example:

<! DOCTYPEhtml>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>amis demo</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1"
    />
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
    <link rel="stylesheet" href="sdk.css" />
    <! As of 1.1.0, sdK. CSS will not support IE11. If you want to support IE11, please reference this CSS and delete the previous one.
    <! -- <link rel="stylesheet" href="sdk-ie11.css" /> -->
    <! Amis has almost never tested IE 11, so there may be some details that don't work, please issue if found.
    <style>
      html.body..app-wrapper {
        position: relative;
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="root" class="app-wrapper"></div>
    <script src="sdk.js"></script>
    <script type="text/javascript">
      (function () {
        let amis = amisRequire('amis/embed');
        // Generate different pages by replacing the following configuration
        let amisJSON = {
          type: 'page'.title: 'Form page'.body: {
            type: 'form'.mode: 'horizontal'.api: '/saveForm'.controls: [{label: 'Name'.type: 'text'.name: 'name'
              },
              {
                label: 'Email'.type: 'email'.name: 'email'}}};let amisScoped = amis.embed('#root', amisJSON); }) ();</script>
  </body>
</html>
Copy the code

So it’s very simple, we just need to replace the JSON part and present our own page

Let amisJSON = {type: 'page', title: 'form page', body: {type: 'form', mode: 'horizontal', API: '/saveForm', controls: [ { label: 'Name', type: 'text', name: 'name' }, { label: 'Email', type: 'email', name: 'email' } ] } };Copy the code

Let me explain the implementation details.

Framework idea

A backend scaffolding based on fastAPI and amis frameworks. Based on this scaffold can easily and quickly start your own backend management services development work. (Python3.9)

  • Fastapi: Service support
  • Fastapi-login: implements login authentication
  • Loguru: Logs are printed
  • Jinja2: Implement the template mechanism
  • Amis – Js-SDK: Implement front-end pages
  • Sqlite: database

The directory structure

Very simple directory structure, two directories under app and three py files. The DB directory is empty, and the automatically generated SQLite database will be placed in this directory. Below the Templates directory are the three home pages and the Amis js-sdk directory, login. HTML is the login page, index.html is the user home page, and admin.html is the administrative home page. The three PY scripts in app are main.py main logic and startup entry file, database.py is database connection and table structure definition, and utils.py is utility method.

Key logic explanation

Let’s start with databases and table structures. This is mainly in the database.py script.

There is only one (users) user table with the following structure:

The field name The field type instructions
id int The id primary key is automatically added
email str The user’s email address serves as the login name
hashed_password str Ciphertext user password
is_active bool enabled

Use the SQLAlchemy library to manipulate the database and define the table structure

metadata = sqlalchemy.MetaData()

users = sqlalchemy.Table(
    "users",
    metadata,
    sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
    sqlalchemy.Column("email", sqlalchemy.String),
    sqlalchemy.Column("hashed_password", sqlalchemy.String),
    sqlalchemy.Column("is_active", sqlalchemy.Boolean)
)
Copy the code

The database and table structures are then created automatically

engine = sqlalchemy.create_engine(
    DATABASE_URL, connect_args={"check_same_thread": False}
)

metadata.create_all(engine)
Copy the code

Finally, it is the first time to initialize an administrator account (the administrator did not give the field to distinguish, this can be added by myself, I specified an account as the administrator account by default).

async def init_db() :
    await database.connect()
    query = users.select()
    user = await database.fetch_all(query)
    if len(user) == 0:
        query_insert = users.insert()
        values = {
            "email": "[email protected]"."hashed_password": get_password_hash('ykbjfree'),
            "is_active": True
        }
        await database.execute(query=query_insert, values=values)
    await database.disconnect()


loop = asyncio.get_event_loop()
result = loop.run_until_complete(init_db())
loop.close()
Copy the code

The encryption logic for the password is reversed in utils.py using passlib.context

from passlib.context import CryptContext


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


Verify password
def verify_password(plain_password, hashed_password) :
    return pwd_context.verify(plain_password, hashed_password)

# encrypt password
def get_password_hash(password) :
    return pwd_context.hash(password)
Copy the code

Then we’ll look at the main.py main script

Routes starting with /admin/ are used to manage users. For example:

  • /admin/user/list – Gets the list of all users
  • /admin/insert/user/ – New user
  • /admin/delete/user/{user_id} – Deletes a user
  • /admin/change/status/{user_id} – Changes the user status
  • /admin/update/ PWD / – Changes the user password
  • /admin – Displays the management page

This interface is all about basic user management functions, called in amis, as I’ll talk about later.

Other interfaces deal with login authentication, for example:

  • /auth/token – Login authentication
  • /current_user – Get user information from cookie (after successful login)
  • /cookie/clear – logout (clear cookie)
  • /login – Displays the login page
  • / – Displays the user home page

There are a few other ways, and I’ll show you:

@manager.user_loader async def load_user(email: str)

This is fastAPI-login to obtain user information implementation method.

@app.on_event(“startup”) async def startup(): await database.connect()

@app.on_event(“shutdown”) async def shutdown(): await database.disconnect()

These two methods are hook functions that automatically connect to and disconnect from the database after the application starts and ends.

Here’s how amis and Jinja2 come together.

The front-end page is in the Templates directory, and the SDK directory is pretty much a skip. I just added a vars.js file that has just one line:

var baseUrl = ‘http://localhost:8080’

The main thing is to unify the server address that amis JSON calls when defining the page in the HTML file into this global variable.

If you are not calling the local service, you need to change the baseUrl to your actual server address, otherwise you will not be able to call the back-end service.

Let’s see an example of how we can call the Amis page via jinja2.

@app.get(“/login”) async def login(request: Request): return templates.TemplateResponse(“login.html”, {“request”: request})

Templates /login. HTML. The json page is defined as follows:

           {
                "type": "form"."title": "Account"."controls": [{"label": "Email address"."type": "text"."name": "username"."hint": ""."addOn": null."required": true."validations": {
                            "isEmail": true
                        },
                        "validationErrors": {
                            "isEmail": "Incorrect Email format"}}, {"type": "password"."label": "Password"."name": "password"."required": true}]."submitText": "Login"."affixFooter": false."messages": {
                    "fetchFailed": "Initialization failed"."saveSuccess": "Saved successfully"."saveFailed": "Save failed"."validateFailed": ""
                },
                "initApi": ""."api": {
                    "method": "post"."url": baseUrl + "/auth/token"."dataType": "form"
                },
                "debug": false."autoFocus": true."redirect": baseUrl
            }
Copy the code

As you can see, the API field is the configuration of the backend interface that the page calls. In the URL, I used the baseUrl variable in vars.js, so you get the idea.

Then, in order for HTML CSS and JS files to be properly accessed under Jinja2, static file directories need to be defined in FastAPI.

app.mount(“/sdk”, StaticFiles(directory=“templates/sdk”), name=“static”) templates = Jinja2Templates(directory=“templates”)

Then note that our HTML needs to reference vars.js as well

So that’s pretty much it.

Finally is the user login verification piece, this part we first look at my code, more details, if you need, I will explain in detail. It can be used directly, so you don’t have to worry too much about the implementation details.

Code warehouse

Github.com/ykbj/fastap…

I hope you can give me a little star yo.