[PYTHON] Codes pouvant être réutilisés dans Flask (inscription, connexion)

introduction

Je crée une application Web avec Flask et j'ai du code qui pourrait être réutilisé, alors je vais le partager. Nous n'avons pas mentionné quel code fournit quelle fonctionnalité, alors faites vos propres recherches.

supposition

Installation

Installez à l'avance l'extension à utiliser cette fois. Vous n'avez pas besoin de spécifier la version car toutes les dernières seront préparées.

requirements.txt


Flask
Flask-Login
Flask-Migrate
Flask-SQLAlchemy
Flask-WTF
SQLAlchemy
Werkzeug
WTForms
Jinja2
email-validator
pip install -r requirements.txt

organisation des fichiers

Flask utilise quelque chose appelé «Blueprint» pour diviser la fonctionnalité.

├───main.py # main app.py file to be called to start server for web app
├───requirements.txt # File of pip install statements for your app
├───migrations # folder created for migrations by calling
├───myproject # main project folder, sub-components will be in separate folders
│   │   data.sqlite
│   │   models.py
│   │   __init__.py
│   │
│   │───auth
│   │   │   forms.py
│   │   │   views.py
│   │   │
│   │   │───templates
│   │   │   └───auth
│   │   │           login.html
│   │   │           signup.html
│   │
│   ├───static # Where you store your CSS, JS, Images, Fonts, etc...
│   ├───templates
│          base.html
│          home.html

Dans cette configuration, __init __. Py contient config, mais vous pouvez le séparer en config.py.

Nous avons préparé une mise en page commune pour base.html et l'avons réutilisée comme {{% extend" base.html "%}} ʻen utilisant un moteur de modèle appelé jinja2 ( layout. Le nom hmtl` est également souvent utilisé).

J'utilise SQLite pour SQL, mais je ne peux pas déployer sur Heroku et ce n'est pas adapté à la mise à l'échelle, donc lorsque vous utilisez SQL, il est préférable d'utiliser réellement MySQL ou PostgreSQL. Je pense. Puisque nous utilisons ʻORM appelé SQLAlchemypour gérer la base de données, le même code peut être utilisé même si le SQL est différent (la configuration est différente).data.sqlite`` migrations` est un fichier / dossier créé par vous-même.

C'est une demande de Blueprint d'avoir une structure redondante comme myproject \ auth \ templates \ auth.

ʻAuth \ forms.py reçoit les formulaires d'inscription et de connexion, et ʻauth \ views.py reçoit les données saisies à partir des formulaires, les traite et les rend.

main.py et __ init __. py

Puisque main.py ne fait que des appels, cela ressemble à ceci:

main.py


from flask import render_template

from myproject import app

@app.route('/')
def index():
    return render_template('home.html')

if __name__ == '__main__':
    app.run(debug=True)

Ce fichier s'enregistre avec la configuration et le plan, et effectue toute l'initialisation.

__init__.py


import os
from flask import Flask, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager

login_manager = LoginManager()

app = Flask(__name__, static_folder='static', template_folder='templates')

basedir = os.path.abspath(os.path.dirname(__file__))

#Si vous ajoutez le module complémentaire Heroku Postgres, la variable d'environnement DATABASE_Vers l'URL
#Cette valeur est définie car l'URL de destination de connexion de la base de données PostgreSQL est définie.
#Une fois réglé(Lors de l'exécution sur Heroku)Utilise le,
#Lorsqu'il n'est pas défini(Débogage local, etc.)Le fait utiliser la base de données SQLite locale.

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 'data.sqlite')

app.config['SECRET_KEY'] = 'mysecretkey'
app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
Migrate(app, db)

#Assurez-vous d'importer ici après avoir terminé la définition de db
from myproject.auth.views import auth_blueprint

app.register_blueprint(auth_blueprint, url_prefix='/authenticate')

login_manager.init_app(app)
login_manager.login_view = "auth.login"

La dernière ligne, login_manager.login_view =" auth.login ", fait référence à l'écran de connexion qui est redirigé lorsque vous essayez d'y accéder, même s'il a @ login_required.

Par exemple, si vous essayez d'aller dans \ logout alors que vous n'êtes pas connecté, la fonction de connexion définie dans views.py sera appelée (puisque l'écran de connexion est rendu par le retour de la fonction, allez à cet écran. Masu).

Base de données des utilisateurs

La table est définie dans models.py. Je garde mon nom d'utilisateur, mon adresse e-mail et mon mot de passe haché.

En passant, la raison pour laquelle le bouton "Mot de passe oublié?" Ne vous indique pas le mot de passe est qu'il ne reste pas dans la base de données en premier lieu, vous ne pouvez donc pas le dire.

models.py



from werkzeug.security import generate_password_hash, check_password_hash
from flask_login  import UserMixin

from myproject import db, login_manager

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(user_id)

class User(db.Model, UserMixin):

    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key = True)
    username = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    def __init__(self, email, username, password):
        self.email = email
        self.username = username
        self.password_hash = generate_password_hash(password)

    def check_password(self,password):
        return check_password_hash(self.password_hash, password)

Processus d'inscription

Il est destiné à l'inscription des utilisateurs généraux. L'utilisateur

Entrer le.

Le premier est le formulaire d'inscription.

forms.py


from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired,Email,EqualTo

class SignupForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(),Email()])
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords Must Match!')])
    pass_confirm = PasswordField('Confirm password', validators=[DataRequired()])
    submit = SubmitField('Sign Up')

Vient ensuite le traitement interne. Le nom d'utilisateur et l'adresse e-mail ne doivent pas se chevaucher dans la base de données, donc s'ils correspondent dans la base de données, une erreur sera émise et vous serez invité à les saisir à nouveau.

Si vous vous inscrivez avec succès, vous serez redirigé vers le formulaire de connexion.

views.py


from flask import (Blueprint, render_template,
                     redirect, url_for, request,
                     flash, abort)
from flask_login import login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash

import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from myproject import db, app
from myproject.models import User
from myproject.auth.forms import LoginForm, SignupForm

auth_blueprint = Blueprint('auth',
                           __name__,
                           template_folder='templates/auth')

@auth_blueprint.route('/signup', methods=['GET', 'POST'])
def signup():
    form = SignupForm()

    if form.validate_on_submit():

        if User.query.filter_by(email=form.email.data).first():
            flash('Email has been registered already!')
            redirect(url_for('auth.signup'))


        elif User.query.filter_by(username=form.username.data).first():
            flash('Username has been registered already!')
            redirect(url_for('auth.signup'))

        else:
            user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)

            db.session.add(user)
            db.session.commit()
            flash('Now You can login!')
            return redirect(url_for('auth.login'))

    return  render_template('signup.html', form=form)

Processus de connexion

On suppose qu'un utilisateur général se connectera.

L'utilisateur entre une adresse e-mail et un mot de passe. Il est décrit dans le même fichier que le formulaire d'inscription.

forms.py


from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired,Email,EqualTo

class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Log In')


class SignupForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(),Email()])
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords Must Match!')])
    pass_confirm = PasswordField('Confirm password', validators=[DataRequired()])
    submit = SubmitField('Sign Up')

Vient ensuite le traitement interne. Identifiez l'utilisateur par adresse e-mail. Le mot de passe est comparé à la valeur de hachage du mot de passe de l'utilisateur à l'aide de la fonction check_password jouée dans models.py.

Si vous vous connectez avec succès, vous pouvez accéder à n'importe quel emplacement.

En passant, je quitterai également le processus de déconnexion.

views.py



from flask import (Blueprint, render_template,
                     redirect, url_for, request,
                     flash, abort)
from flask_login import login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash

import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from myproject import db, app
from myproject.models import User
from myproject.auth.forms import LoginForm, SignupForm

auth_blueprint = Blueprint('auth',
                           __name__,
                           template_folder='templates/auth')

@auth_blueprint.route('/logout')
@login_required
def logout():
    logout_user()
    flash('You logged out!')
    return redirect(url_for('index'))

@auth_blueprint.route('/login', methods=['GET', 'POST'])
def login():


    form = LoginForm()
    if form.validate_on_submit():

        user = User.query.filter_by(email=form.email.data).first()

        if user is None:
            flash('something wrong!')
            redirect(url_for('auth.login'))
        elif user.check_password(form.password.data):

            login_user(user)

            flash('Logged in successfully.')

            # login => somewhere
            return redirect(url_for('somewhere'))
        else:
            pass

    return   render_template('login.html', form=form)

@auth_blueprint.route('/signup', methods=['GET', 'POST'])
def signup():
    form = SignupForm()

    if form.validate_on_submit():

        if User.query.filter_by(email=form.email.data).first():
            flash('Email has been registered already!')
            redirect(url_for('auth.signup'))


        elif User.query.filter_by(username=form.username.data).first():
            flash('Username has been registered already!')
            redirect(url_for('auth.signup'))

        else:
            user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)

            db.session.add(user)
            db.session.commit()
            flash('Now You can login!')
            return redirect(url_for('auth.login'))

    return  render_template('signup.html', form=form)

Afficher le message flash ()

flash () est reçu comme suit.

example.html



{% with messages = get_flashed_messages() %}
  {% if messages %}
    {% for message in messages %}

      {{ message }}

    {% endfor %}
  {% endif %}
{% endwith %}

Changer l'affichage avant et après l'état de connexion

Vous souhaiterez peut-être modifier l'affichage avant et après l'état de connexion. Par exemple, lorsque vous vous connectez, un bouton de déconnexion s'affiche.

Dans ce cas, vous pouvez utiliser current_user.is_authenticated pour le branchement conditionnel.

example.html



{% if current_user.is_authenticated %}
    <a class="nav-item nav-link" href="#">{{ current_user.username }}</a>
    <a class="nav-item nav-link" href="{{ url_for('task.scheduletoday') }}">Horaire du jour</a>
    <a class="nav-item nav-link" href="{{ url_for('task.alltasks')}}">Liste de tâches</a>
    <a class="nav-item nav-link" href="{{ url_for('auth.logout') }}">Se déconnecter</a>
{% else %}
    <a class="nav-item nav-link" href="{{ url_for('auth.login') }}">S'identifier</a>
{% endif %}

Vous pouvez le faire comme ça.

Restrictions d'accès lorsque vous êtes connecté

Si vous essayez d'accéder à un emplacement qui nécessite une connexion sans vous connecter, vous pouvez le limiter tant que @ login_required est écrit, mais vous pourrez accéder à un emplacement qui ne nécessite pas de connexion lorsque vous êtes connecté. Si cela est possible, vous serez dans une situation incertaine où vous pourrez vous reconnecter même si vous êtes connecté. Dans ce cas

render_template('home.html')

ne pas


return  redirect(url_for('somewhere')) if current_user.is_authenticated else render_template('home.html')

Vous pouvez le faire comme ça.

Mise à jour de la base de données

Une fois que vous avez défini une nouvelle table, vous devez la mettre à jour pour qu'elle prenne effet.

---------------------------------------
MACOS/Linux
    $ export FLASK_APP=main.py
Windows
    $ set FLASK_APP=main.py

flask db init

#Seulement le premier jusqu'à présent
----------------------------------------
flask db migrate -m "message"
flask db upgrade

Vous pouvez le faire comme ça.

Essayez dans l'environnement local

python main.py

en conclusion

J'ai résumé le code que j'ai écrit jusqu'à présent et qui peut être réutilisé. Si vous avez une meilleure façon de l'écrire, faites-le nous savoir.

Recommended Posts

Codes pouvant être réutilisés dans Flask (inscription, connexion)
Un enregistrement que GAMEBOY n'a pas pu être fait avec Python. (PYBOY)
Tkinter n'a pas pu être importé en Python