One place for hosting & domains

      Como adicionar uma autenticação ao seu aplicativo com o Flask-Login


      Introdução

      Permitir que usuários façam login em seu aplicativo é um dos recursos mais comuns a ser adicionado ao seu aplicativo Web. Este artigo irá abordar como adicionar uma autenticação ao seu aplicativo Flask com o pacote Flask-Login.

      Gif animado do aplicativo Flask e caixa de login

      Construiremos algumas páginas de inscrição e login que permitem que os usuários façam login e acessem páginas protegidas que usuários não autenticados são incapazes de visualizar. Coletaremos informações do modelo de usuário e as exibiremos em nossas páginas protegidas quando o usuário fizer login, de forma a simular como seria um perfil.

      Iremos abordar os seguintes assuntes neste artigo:

      • Como usar a biblioteca Flask-Login para o gerenciamento de sessão
      • Como usar o utilitário integrado do Flask para o hash de senhas
      • Como adicionar páginas protegidas ao nosso aplicativo acessíveis somente por usuários autenticados
      • Como usar o Flask-SQLAlchemy para criar um modelo de usuário
      • Como criar formulários de inscrição e login para que nossos usuários criem contas e façam login
      • Como exibir mensagens de erro aos usuários quando algo der errado
      • Como usar informações da conta do usuário para exibi-las na página do perfil

      O código fonte para este projeto está disponível no GitHub.

      Pré-requisitos

      Para concluir este tutorial, você precisará do seguinte:

      Nosso aplicativo irá utilizar os padrões de fábrica do aplicativo Flask com blueprints. Teremos um blueprint que processa tudo que está relacionado à autenticação e outro para nossas rotas regulares, que incluem o índice e a página de perfil protegida. Em um aplicativo real, é possível desmembrar as funcionalidades da maneira que achar melhor, mas a solução abordada aqui irá funcionar bem para este tutorial.

      Aqui está um diagrama para entendermos melhor como ficará a estrutura de arquivos do seu projeto após completar o tutorial:

      .
      └── flask_auth_app
          └── project
              ├── __init__.py       # setup our app
              ├── auth.py           # the auth routes for our app
              ├── db.sqlite         # our database
              ├── main.py           # the non-auth routes for our app
              ├── models.py         # our user model
              └── templates
                  ├── base.html     # contains common layout and links
                  ├── index.html    # show the home page
                  ├── login.html    # show the login form
                  ├── profile.html  # show the profile page
                  └── signup.html   # show the signup form
      

      À medida que avançarmos neste tutorial, iremos criar esses diretórios e arquivos.

      Passo 1 — Instalando os pacotes

      Existem três pacotes principais que precisamos para o nosso projeto:

      • Flask
      • Flask-Login: para processar as sessões de usuário após a autenticação
      • Flask-SQLAlchemy: para representar o modelo de usuário e interagir com nosso banco de dados

      Iremos utilizar o SQLite para evitar a instalação de dependências extras para o banco de dados.

      Primeiro, começaremos criando o diretório de projeto:

      Em seguida, precisamos navegar até o diretório de projeto:

      Crie um ambiente Python caso não tenha um. Dependendo da forma como o Python foi instalado em sua máquina, seus comandos serão semelhantes a:

      • python3 -m venv auth
      • source auth/bin/activate

      Nota: consulte o tutorial relevante ao seu ambiente de trabalho para configurar o venv.

      Execute os comandos a seguir a partir do seu ambiente virtual para instalar os pacotes necessários:

      • pip install flask flask-sqlalchemy flask-login

      Agora que você instalou os pacotes, tudo está pronto para a criação do arquivo principal do aplicativo.

      Passo 2 — Criando o arquivo principal do aplicativo

      Vamos começar criando um diretório project:

      O primeiro arquivo no qual trabalharemos será o arquivo __init__.py para o nosso projeto:

      Esse arquivo será responsável pela criação do nosso aplicativo, inicializando o banco de dados e registrando nossos blueprints. Neste momento, aparentemente nada irá mudar. No entanto, isso será necessário para o resto do nosso aplicativo. Precisamos inicializar o SQLAlachemy, definir alguns valores de configuração e registrar nossos blueprints aqui.

      project/__init__.py

      from flask import Flask
      from flask_sqlalchemy import SQLAlchemy
      
      # init SQLAlchemy so we can use it later in our models
      db = SQLAlchemy()
      
      def create_app():
          app = Flask(__name__)
      
          app.config['SECRET_KEY'] = 'secret-key-goes-here'
          app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
      
          db.init_app(app)
      
          # blueprint for auth routes in our app
          from .auth import auth as auth_blueprint
          app.register_blueprint(auth_blueprint)
      
          # blueprint for non-auth parts of app
          from .main import main as main_blueprint
          app.register_blueprint(main_blueprint)
      
          return app
      

      Agora que temos o arquivo principal do aplicativo, podemos começar a adicionar nossas rotas.

      Passo 3 — Adicionando rotas

      Para nossas rotas, iremos utilizar dois blueprints. Para o nosso blueprint principal, teremos uma página inicial (/) e uma página de perfil (/profile) para depois da sessão ser iniciada. Se o usuário tentar acessar a página de perfil sem estar autenticado, ele será enviado para a rota de login.

      Para o nosso blueprint de autenticação, teremos rotas para recuperar tanto a página de login (/login) quanto a página de inscrição (/sign-up). Também teremos rotas para lidar com as solicitações POST de ambas as rotas. Por fim, teremos uma rota de logoff (/logout) para efetuar o logoff de um usário ativo.

      Por enquanto, iremos definir login, signup e logout com devoluções simples. Iremos revisitá-los em um passo posterior e atualizá-los com a funcionalidade desejada.

      Primeiro, crie o main.py para o seu main_blueprint:

      project/main.py

      from flask import Blueprint
      from . import db
      
      main = Blueprint('main', __name__)
      
      @main.route('/')
      def index():
          return 'Index'
      
      @main.route('/profile')
      def profile():
          return 'Profile'
      

      Em seguida, crie o auth.py para o seu auth_blueprint:

      project/auth.py

      from flask import Blueprint
      from . import db
      
      auth = Blueprint('auth', __name__)
      
      @auth.route('/login')
      def login():
          return 'Login'
      
      @auth.route('/signup')
      def signup():
          return 'Signup'
      
      @auth.route('/logout')
      def logout():
          return 'Logout'
      

      Em um terminal, defina os valores FLASK_APP e FLASK_DEBUG:

      • export FLASK_APP=project
      • export FLASK_DEBUG=1

      A variável de ambiente FLASK_APP instrui o Flask sobre como carregar o aplicativo. Ela deve apontar para o local onde o create_app está localizado. Para o nosso caso, iremos apontar para o diretório project.

      A variável de ambiente FLASK_DEBUG é habilitada quando definida em 1. Isso irá habilitar um depurador que exibirá erros do aplicativo no navegador.

      Certifique-se de estar no diretório flask_auth_app e então execute o projeto:

      Agora, em um navegador, deve ser possível navegar até as cinco URLs possíveis e ver o texto retornado que foi definido em auth.py e main.py.

      Por exemplo, visitar o localhost:5000/profile exibe Profile:

      Captura de tela do projeto em localhost porta 5000 no navegador

      Agora que verificamos que nossas rotas estão se comportando conforme esperado, podemos seguir em frente para a criação de modelos.

      Passo 4 — Criando modelos

      Vamos continuar e criar os modelos usados em nosso aplicativo. Este é o primeiro passo antes de podermos implementar de fato a funcionalidade de login. Nosso aplicativo irá utilizar quatro modelos:

      • index.html
      • profile.html
      • login.html
      • signup.html

      Além disso, teremos um modelo base que terá partes de código comuns a cada uma das páginas. Neste caso, o modelo base terá links de navegação e o layout geral da página. Vamos criá-los agora.

      Primeiro, crie um diretório templates no seu diretório project:

      • mkdir -p project/templates

      Em seguida, crie o base.html:

      • nano project/templates/base.html

      Depois, adicione o código a seguir ao arquivo base.html:

      project/templates/base.html

      <!DOCTYPE html>
      <html>
      
      <head>
          <meta charset="utf-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Flask Auth Example</title>
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />
      </head>
      
      <body>
          <section class="hero is-primary is-fullheight">
      
              <div class="hero-head">
                  <nav class="navbar">
                      <div class="container">
      
                          <div id="navbarMenuHeroA" class="navbar-menu">
                              <div class="navbar-end">
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
                                      Home
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
                                      Profile
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
                                      Login
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
                                      Sign Up
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
                                      Logout
                                  </a>
                              </div>
                          </div>
                      </div>
                  </nav>
              </div>
      
              <div class="hero-body">
                  <div class="container has-text-centered">
                     {% block content %}
                     {% endblock %}
                  </div>
              </div>
          </section>
      </body>
      
      </html>
      

      Esse código irá criar uma série de links de menu para cada página do aplicativo e uma área onde o conteúdo irá aparecer.

      Nota: nos bastidores, estamos usando o Bulma para lidar com a estilização e o layout. Para entender mais sobre o Bulma, leia a documentação oficial do Bulma.

      Em seguida, crie o templates/index.html:

      • nano project/templates/index.html

      Adicione o código a seguir ao arquivo recém-criado para adicionar conteúdo à página:

      project/templates/index.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Flask Login Example
      </h1>
      <h2 class="subtitle">
        Easy authentication and authorization in Flask.
      </h2>
      {% endblock %}
      

      Esse código irá criar um uma página básica de índice com um título e um subtítulo.

      Em seguida, crie o templates/login.html:

      • nano project/templates/login.html

      Esse código gera uma página de login com campos para E-mail e Senha. Há também uma caixa de seleção para “lembrar” uma sessão iniciada.

      project/templates/login.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Login</h3>
          <div class="box">
              <form method="POST" action="/login">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Your Password">
                      </div>
                  </div>
                  <div class="field">
                      <label class="checkbox">
                          <input type="checkbox">
                          Remember me
                      </label>
                  </div>
                  <button class="button is-block is-info is-large is-fullwidth">Login</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Em seguida, crie o templates/signup.html:

      • nano project/templates/signup.html

      Adicione o código a seguir para criar uma página de inscrição com campos para e-mail, nome e senha:

      project/templates/signup.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Sign Up</h3>
          <div class="box">
              <form method="POST" action="/signup">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Password">
                      </div>
                  </div>
      
                  <button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Em seguida, crie o templates/profile.html:

      • nano project/templates/profile.html

      Adicione este código para criar uma página simples com um título embutido em código para dar boas vindas ao Anthony:

      project/templates/profile.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Welcome, Anthony!
      </h1>
      {% endblock %}
      

      Posteriormente, iremos adicionar um código para saudar dinamicamente qualquer usuário.

      Assim que os modelos forem adicionados, podemos atualizar as declarações de retorno em cada uma de nossas rotas para que retornem os modelos ao invés do texto.

      Em seguida, atualize o main.py alterando a linha de importação e as rotas para o index e profile:

      project/main.py

      from flask import Blueprint, render_template
      ...
      @main.route('/')
      def index():
          return render_template('index.html')
      
      @main.route('/profile')
      def profile():
          return render_template('profile.html')
      

      Agora, você irá atualizar o auth.py modificando a linha de importação e rotas para login e signup:

      project/auth.py

      from flask import Blueprint, render_template
      ...
      @auth.route('/login')
      def login():
          return render_template('login.html')
      
      @auth.route('/signup')
      def signup():
          return render_template('signup.html')
      

      Assim que as alterações forem feitas, a página de inscrição ficará desta forma, caso navegue até /sign-up:

      Página de inscrição em /signup

      Também deve ser possível ver as páginas para /, /login e /profile.

      Não iremos alterar /logout por enquanto, pois ela não irá exibir um modelo quando for finalizada.

      Passo 5 — Criando modelos de usuário

      Nosso modelo de usuário representa o que significa para o nosso aplicativo ter um usuário. Teremos campos para um endereço de e-mail, senha e nome. Em seu aplicativo, fique a vontade para decidir se quer que mais informações sejam armazenadas por usuário. É possível adicionar coisas como aniversário, imagem de perfil, localização ou qualquer preferência do usuário.

      Os modelos criados no Flask-SQLAlchemy são representados por classes que então se traduzem em tabelas em um banco de dados. Os atributos dessas classes transformam-se então em colunas para essas tabelas.

      Vamos continuar e criar o modelo de usuário:

      Este código cria um modelo de usuário com colunas para um id, email, password e name:

      project/models.py

      from . import db
      
      class User(db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Agora que um modelo de usuário foi criado, continue para a configuração do seu banco de dados.

      Passo 6 — Configurando o banco de dados

      Como mencionado nos pré-requisitos, iremos utilizar um banco de dados SQLite. Poderíamos criar um banco de dados SQLite por conta própria, mas vamos deixar o Flask-SQLAlchemy fazer isso por nós. Nós já temos o caminho do banco de dados especificado no arquivo __init__.py. Portanto, precisamos apenas dizer ao Flask-SQLAlchemy para criar o banco de dados no REPL do Python.

      Se você parar o aplicativo e abrir um REPL do Python, seremos capazes de criar o banco de dados usando o método create_all no objeto db. Certifique-se de que você ainda esteja no ambiente virtual e no diretório flask_auth_app.

      • from project import db, create_app
      • db.create_all(app=create_app()) # pass the create_app result so Flask-SQLAlchemy gets the configuration.

      Nota: caso usar o interpretador do Python seja algo novo para você, consulte a documentação oficial.

      Agora, você verá um arquivo db.sqlite em seu diretório de projeto. Esse banco de dados terá a nossa tabela de usuário em seu interior.

      Passo 7 — Configurando a função de autorização

      Para a nossa função de inscrição, enviaremos os dados que o usuário digita no formulário ao nosso banco de dados. Antes de adicioná-la, precisamos garantir que o usuário não exista no banco de dados. Se ele não existir, então precisamos nos certificar que a senha passará pelo hash antes de colocá-la no banco de dados, pois não queremos que nossas senhas sejam armazenadas em texto simples.

      Vamos começar adicionando uma segunda função para lidar com os dados do formulário POST. Nesta função, coletaremos dados passados pelo usuário primeiro.

      Crie a função e adicione um redirecionamento no final. Isso proporcionará uma experiência de usuário de uma inscrição bem-sucedida e direcionamento à Página de login.

      Atualize o auth.py alterando a linha de importação e implementando o signup_post:

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          # code to validate and add user to database goes here
          return redirect(url_for('auth.login'))
      

      Agora, vamos adicionar o resto do código necessário para a inscrição de um usuário.

      Para começar, será necessário usar o objeto de solicitação para obter os dados do formulário.

      Continue atualizando o auth.py adicionando as importações e implementando o signup_post:

      auth.py

      from flask import Blueprint, render_template, redirect, url_for, request
      from werkzeug.security import generate_password_hash, check_password_hash
      from .models import User
      from . import db
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          email = request.form.get('email')
          name = request.form.get('name')
          password = request.form.get('password')
      
          user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database
      
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              return redirect(url_for('auth.signup'))
      
          # create a new user with the form data. Hash the password so the plaintext version isn't saved.
          new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
      
          # add the new user to the database
          db.session.add(new_user)
          db.session.commit()
      
          return redirect(url_for('auth.login'))
      

      Nota: armazenar senhas em texto simples é considerada uma prática de segurança ruim. De maneira geral, é vantajoso utilizar um algoritmo de hash complexo e um valor de sal de senha para manter as senhas em segurança.

      Passo 8 — Testando o método de inscrição

      Agora que o método de inscrição está finalizado, devemos ser capazes de criar um novo usuário. Use o formulário para criar um usuário.

      Existem duas maneiras de verificar se a inscrição foi bem-sucedida: usando o visualizador do banco de dados para observar a linha que foi adicionada à sua tabela, ou tentando inscrever-se com o mesmo e-mail novamente e, caso receba um erro, saberá que o primeiro e-mail foi salvo corretamente. Portanto, vamos tomar esta segunda abordagem.

      Podemos adicionar um código para informar ao usuário que o e-mail já existe e pedir para que vá à página de login. Ao chamar a função flash, iremos enviar uma mensagem para a próxima solicitação, que, neste caso, é o redirecionamento. Dessa forma, a página em que chegarmos terá acesso a essa mensagem no modelo.

      Primeiro, adicionamos o flash antes de redirecionarmos o processo de volta à nossa página de inscrição.

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for, request, flash
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          ...
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              flash('Email address already exists')
              return redirect(url_for('auth.signup'))
      

      Para o recebimento da mensagem de flash no modelo, podemos adicionar este código acima do formulário. Isso fará com que a mensagem seja exibida diretamente acima do formulário.

      project/templates/signup.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}. Go to <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">login page</a>.
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/signup">
      

      Caixa de inscrição exibindo uma mensagem de que o

      Passo 9 — Adicionando o método de login

      O método de login é semelhante à função de inscrição, já que também iremos pegar informações do usuário para fazer algo com elas. Neste caso, iremos analisar o endereço de e-mail inserido para checar se ele está presente no banco de dados. Caso esteja, iremos testar a senha fornecida pelo usuário utilizando o hash na senha passada pelo usuário e comparando-a com a senha já com hash no banco de dados. Sabemos que o usuário digitou a senha correta quando ambas as senhas com hash correspondem.

      Assim que o usuário passar pela checagem de senha, sabemos que ele possui as credenciais corretas e podemos autenticá-los usando o Flask-Login. Ao chamar o login_user, o Flask-Login irá criar uma sessão para aquele usuário que irá persistir enquanto o usuário permanecer conectado. Isso permitirá que o usuário visualize páginas protegidas.

      Podemos começar com uma nova rota para o manuseio dos dados em POST. Redirecionaremos o usuário para a página de perfil quando o login for realizado com sucesso:

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          # login code goes here
          return redirect(url_for('main.profile'))
      

      Agora, é necessário verificar se o usuário possui as credenciais corretas:

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          email = request.form.get('email')
          password = request.form.get('password')
          remember = True if request.form.get('remember') else False
      
          user = User.query.filter_by(email=email).first()
      
          # check if the user actually exists
          # take the user-supplied password, hash it, and compare it to the hashed password in the database
          if not user or not check_password_hash(user.password, password):
              flash('Please check your login details and try again.')
              return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page
      
          # if the above check passes, then we know the user has the right credentials
          return redirect(url_for('main.profile'))
      

      Vamos adicionar o bloco de código no modelo para que o usuário possa ver a mensagem em flash. Assim como no formulário de inscrição, vamos adicionar a mensagem de erro em potencial diretamente acima do formulário:

      project/templates/login.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/login">
      

      Agora, somos capazes de dizer que um usuário foi conectado com sucesso, mas não há nada para fazer o login do usuário. É aqui onde trazemos o Flask-Login para o gerenciamento de sessões de usuário.

      Antes de iniciarmos, precisamos de algumas coisas para que o Flask-Login funcione. Comece adicionando o UserMixin ao seu Modelo de usuário. O UserMixin irá adicionar atributos do Flask-Login ao modelo para que o Flask-Login seja capaz de trabalhar com ele.

      models.py

      from flask_login import UserMixin
      from . import db
      
      class User(UserMixin, db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Em seguida, é necessário especificar nosso carregador de usuário. Um carregador de usuário informa ao Flask-Login como encontrar um usuário específico a partir do ID armazenado em seu cookie de sessão. Podemos adicionar isso em nossa função create_app, juntamente com o código init para o Flask-Login:

      project/__init__.py

      ...
      from flask_login import LoginManager
      ...
      def create_app():
          ...
          db.init_app(app)
      
          login_manager = LoginManager()
          login_manager.login_view = 'auth.login'
          login_manager.init_app(app)
      
          from .models import User
      
          @login_manager.user_loader
          def load_user(user_id):
              # since the user_id is just the primary key of our user table, use it in the query for the user
              return User.query.get(int(user_id))
      

      Por fim, adicionamos a função login_user um pouco antes de redirecionarmos o usuário para a página de perfil para criar a sessão:

      project/auth.py

      from flask_login import login_user
      from .models import User
      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          ...
          # if the above check passes, then we know the user has the right credentials
          login_user(user, remember=remember)
          return redirect(url_for('main.profile'))
      

      Com o Flask-Login configurado, podemos usar a rota /login. Quando tudo estiver no lugar, você verá a página de perfil.

      Página de perfil com

      Passo 10 — Protegendo as páginas

      Se o seu nome não é Anthony, então verá que a mensagem está errada. O que queremos é que o perfil exiba o nome presente no banco de dados. Portanto, primeiro, é necessário proteger a página e então acessar os dados do usuário para obter o seu nome.

      Para proteger uma página ao usar o Flask-Login, adicionamos o decorador @login_requried entre a rota e a função. Isso impede que um usuário que não esteja conectado veja a rota. Se o usuário não estiver conectado, ele será redirecionado para a página de login, conforme a configuração do Flask-Login.

      Com as rotas decoradas com o decorador @login_required, somos capazes de usar o objeto current_user dentro da função. O current_user representa o usuário do banco de dados e podemos acessar todos os atributos desse usuário com uma notação de ponto. Por exemplo, current_user.email, current_user.password, current_user.name e current_user.id irão retornar os valores reais armazenados no banco de dados para o usuário conectado.

      Vamos usar o nome do usuário atual e enviá-lo ao modelo. Em seguida, usaremos esse nome e exibiremos seu valor.

      project/main.py

      from flask_login import login_required, current_user
      ...
      @main.route('/profile')
      @login_required
      def profile():
          return render_template('profile.html', name=current_user.name)
      

      Depois disso, no arquivo profile.html, atualize a página para exibir o valor name:

      project/templates/profile.html

      ...
      <h1 class="title">
        Welcome, {{ name }}!
      </h1>
      

      Ao acessarmos nossa página de perfil, vemos agora que o nome do usuário aparece.

      Página de boas-vindas com o nome do usuário atualmente conectado

      A última coisa que podemos fazer é atualizar a visualização de logoff. Podemos chamar a função logout_user em uma rota para o logoff. Adicionamos o decorador @login_required porque não faz sentido fazer logoff de um usuário que não esteja conectado em primeiro lugar.

      project/auth.py

      from flask_login import login_user, logout_user, login_required
      ...
      @auth.route('/logout')
      @login_required
      def logout():
          logout_user()
          return redirect(url_for('main.index'))
      

      Depois de fazer o logoff, se tentarmos visualizar a página de perfil novamente, vemos que uma mensagem de erro aparece. Isso ocorre porque o Flask-Login exibe uma mensagem quando o usuário não é autorizado a acessar uma página.

      Página de login com uma mensagem mostrando que o usuário deve fazer login para acessar a página

      Uma última coisa que podemos fazer é colocar declarações if nos modelos para que apenas os links relevantes ao usuário sejam exibidos. Assim, antes do usuário fazer login, haverá a opção de fazer login ou se inscrever. Após ter feito o login, é possível ir ao perfil ou fazer logoff:

      templates/base.html

      ...
      <div class="navbar-end">
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
              Home
          </a>
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
              Profile
          </a>
          {% endif %}
          {% if not current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
              Login
          </a>
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
              Sign Up
          </a>
          {% endif %}
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
              Logout
          </a>
          {% endif %}
      </div>
      

      Página inicial com as opções Home, Login e Sign Up no topo da tela

      Com isso, você terminou de construir seu aplicativo de autenticação com sucesso.

      Conclusão

      Utilizamos neste tutorial o Flask-Login e o Flask-SQLAlchemy para construir um sistema de login para o nosso aplicativo e abordamos como autenticar um usuário. Primeiro, criamos um modelo de usuário e armazenamos suas informações. Em seguida, foi necessário verificar se a senha do usuário estava correta utilizando o hash na senha do formulário e comparando-a com a armazenada no banco de dados. Por fim, adicionamos a autorização ao nosso aplicativo usando o decorador @login_required em uma página de perfil para que apenas usuários conectados possam ver essa página.

      O que criamos neste tutorial será o suficiente para aplicativos menores, mas se você quiser ter mais funcionalidades logo no início, considere usar as bibliotecas Flask-User ou Flask-Security, que são desenvolvidas com base na biblioteca Flask-Login.



      Source link

      Como construir uma paginação personalizada com o React


      Introdução

      Muitas vezes, no desenvolvimento de aplicativos Web, precisamos buscar grandes conjuntos de registros de dados de um servidor remoto, API ou banco de dados. Se você estiver construindo um sistema de pagamento, por exemplo, ele poderia estar buscando milhares de transações. Se é um aplicativo de mídia social, poderia estar buscando muitos comentários de usuários, perfis ou atividades. Seja qual for o caso, existem diversas soluções para apresentar os dados de uma maneira que não sobrecarregue o usuário final que esteja interagindo com o aplicativo.

      Um dos métodos para lidar com grandes conjuntos de dados é usar a paginação. A paginação funciona de maneira eficaz quando o tamanho do conjunto de dados (o número total de registros no conjunto de dados) é conhecido previamente. Além disso, apenas a quantidade de dados necessária é carregada do conjunto total de dados com base na interação de usuários finais com o controle de paginação. Essa é a técnica usada na exibição de resultados na pesquisa do Google.

      Neste tutorial, veremos como criar um componente de paginação personalizada com o React para paginar grandes conjuntos de dados. Será construida uma visualização paginada dos países no mundo — um conjunto de dados com um tamanho conhecido.

      Aqui está uma demonstração daquilo que você construirá neste tutorial:

      Captura de tela do aplicativo de demonstração — exibindo os países no mundo

      Pré-requisitos

      Para completar este tutorial, você precisará de:

      • O Node instalado em sua máquina. Os passos para isso podem ser encontrados em Como instalar o Node.js e criar um ambiente de desenvolvimento local.
      • O pacote de linha de comando [create-react-app][`create-react-app] para criar um código boilerplate para seu aplicativo React. Caso esteja usando onpm < 5.2, pode ser necessário instalar ocreate-react-app` como uma dependência global.
      • Por fim, este tutorial assume que você já esteja familiarizado com o React. Se este não for o caso, verifique a série Como programar no React.js para aprender mais sobre o React.

      Este tutorial foi verificado com o Node v14.2.0, npm v6.14.4, react v16.13.1 e react-scripts v3.4.1.

      Passo 1 — Configurando o projeto

      Incie um novo aplicativo React usando o comando create-react-app. Sinta-se à vontade para dar o nome que preferir ao aplicativo, mas este tutorial irá chamá-lo de react-pagination:

      • npx create-react-app react-pagination

      Em seguida, você irá instalar as dependências necessárias para o seu aplicativo. Primeiro, use a janela do terminal para navegar até o diretório do projeto:

      Execute o seguinte comando para instalar as dependências necessárias:

      • npm install bootstrap@4.1.0 prop-types@15.6.1 react-flags@0.1.13 countries-api@2.0.1 node-sass@4.14.1

      Isso irá instalar o bootstrap, prop-types, react-flags, countries-api e o node-sass.

      O pacote bootstrap foi instalado como uma dependência para o seu aplicativo, pois você precisará de um estilo padrão. Além disso, você também irá utilizar estilos do componente pagination do Bootstrap.

      Para incluir o Bootstrap no aplicativo, edite o arquivo src/index.js:

      E adicione a linha a seguir antes das outras declarações import:

      src/index.js

      import "bootstrap/dist/css/bootstrap.min.css";
      

      Agora, o estilo do Bootstrap estará disponível em todo o seu aplicativo.

      Além disso, o react-flags foi instalado como uma dependência para o seu aplicativo. Para obter acesso aos ícones de bandeira a partir do seu aplicativo, será necessário copiar as imagens dos ícones para o diretório public do seu aplicativo.

      Crie um diretório img em seu diretório public:

      Copie os arquivos de imagem em flags para img:

      • cp -R node_modules/react-flags/vendor/flags public/img

      Isso gera uma cópia de todas as imagens do react-flag para o seu aplicativo.

      Agora que você incluiu algumas dependências, inicie o aplicativo executando o comando a seguir com o npm a partir do diretório de projeto react-pagination:

      Agora que o aplicativo foi iniciado, o desenvolvimento pode começar. Observe que uma guia do navegador foi aberta para você com a funcionalidade de carregamento dinâmico para manter tudo sincronizado à medida em que você avança no desenvolvimento.

      Neste ponto, a visualização do aplicativo deve se assemelhar à seguinte captura de tela:

      Visualização inicial – tela de boas-vindas ao React

      Agora, tudo está pronto para começarmos a criar os componentes.

      Passo 2 — Criando o componente ContryCard

      Neste passo, você irá criar o componente CountryCard. O componente CountryCard processa o nome, região e bandeira de um determinado país.

      Primeiro, vamos criar um diretório components dentro do diretório src:

      Em seguida, crie um novo arquivo CountryCard.js dentro do diretório src/components:

      • nano src/components/CountryCard.js

      E adicione o seguinte código a ele:

      src/components/CountryCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import Flag from 'react-flags';
      
      const CountryCard = props => {
        const {
          cca2: code2 = '', region = null, name = {}
        } = props.country || {};
      
        return (
          <div className="col-sm-6 col-md-4 country-card">
            <div className="country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light">
              <div className="h-100 position-relative border-gray border-right px-2 bg-white rounded-left">
                <Flag country={code2} format="png" pngSize={64} basePath="./img/flags" className="d-block h-100" />
              </div>
              <div className="px-3">
                <span className="country-name text-dark d-block font-weight-bold">{ name.common }</span>
                <span className="country-region text-secondary text-uppercase">{ region }</span>
              </div>
            </div>
          </div>
        )
      }
      
      CountryCard.propTypes = {
        country: PropTypes.shape({
          cca2: PropTypes.string.isRequired,
          region: PropTypes.string.isRequired,
          name: PropTypes.shape({
            common: PropTypes.string.isRequired
          }).isRequired
        }).isRequired
      };
      
      export default CountryCard;
      

      O componente CountryCard exige uma propriedade country que contém os dados sobre o país a ser processado. Como visto nas propTypes para o componente CountryCard, a propriedade country deve conter os seguintes dados:

      • cca2 – Código de 2 dígitos do país
      • region – a região do país (por exemplo, “África”)
      • name.common – o nome comum do país (por exemplo, “Nigéria”)

      Aqui está um objeto de país de amostra:

      {
        cca2: "NG",
        region: "Africa",
        name: {
          common: "Nigeria"
        }
      }
      

      Além disso, note como a bandeira do país é renderizada usando o pacote react-flags. Verifique a documentação do react-flags para aprender mais sobre as propriedades exigidas e como usar o pacote.

      Agora, você finalizou a criação de um componente CountryCard individual. No final das contas, você usará os CountryCards várias vezes para exibir diferentes bandeiras e informações dos países em seu aplicativo.

      Passo 3 — Criando o componente Pagination

      Neste passo, você irá criar o componente Pagination. O componente Pagination contém a lógica para a construção, renderização e troca de páginas no controle de paginação.

      Crie um novo arquivo Pagination.js dentro do diretório src/components:

      • nano src/components/Pagination.js

      E adicione o seguinte código a ele:

      src/components/Pagination.js

      import React, { Component, Fragment } from 'react';
      import PropTypes from 'prop-types';
      
      class Pagination extends Component {
        constructor(props) {
          super(props);
          const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props;
      
          this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 30;
          this.totalRecords = typeof totalRecords === 'number' ? totalRecords : 0;
      
          // pageNeighbours can be: 0, 1 or 2
          this.pageNeighbours = typeof pageNeighbours === 'number'
            ? Math.max(0, Math.min(pageNeighbours, 2))
            : 0;
      
          this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      
          this.state = { currentPage: 1 };
        }
      }
      
      Pagination.propTypes = {
        totalRecords: PropTypes.number.isRequired,
        pageLimit: PropTypes.number,
        pageNeighbours: PropTypes.number,
        onPageChanged: PropTypes.func
      };
      
      export default Pagination;
      

      O componente Pagination pode receber quatro propriedades especiais assim como especificado no objeto propTypes.

      • onPageChanged é uma função chamada com dados do estado atual de paginação apenas quando a página atual muda.
      • totalRecords indica o número total de registros a serem paginados. Ele é exigido.
      • pageLimit indica o número de registros a serem exibidos por página. Caso não seja especificado, seu valor padrão é 30 conforme definido no constructor().
      • pageNeighbours indica a quantidade de números de página adicionais a se exibir em cada lado da página atual. O valor mínimo é 0, e o valor máximo é 2. Caso não seja especificado, o valor padrão é 0 conforme definido no constructor().

      A imagem a seguir ilustra o efeito de diferentes valores para a propriedade pageNeighbours:

      Ilustração de pageNeighbours

      Na função constructor(), você computa o total de páginas da seguinte forma:

      this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      

      Observe que o Math.ceil() é usado aqui para garantir que seja obtido um valor inteiro para o número total de páginas. Isso também garante que os registros em excesso sejam capturados na última página, principalmente nos casos em que o número de registros em excesso seja inferior ao número de registros a serem exibidos por página.

      Por fim, você inicializou o estado com a propriedade currentPage definida em 1. Essa propriedade de estado é necessária para manter o controle interno da página ativa atual.

      Em seguida, você irá criar o método para gerar os números de página.

      Após os imports mas antes da classe Pagination, adicione as constantes a seguir e a função range:

      src/components/Pagination.js

      // ...
      
      const LEFT_PAGE = 'LEFT';
      const RIGHT_PAGE = 'RIGHT';
      
      /**
       * Helper method for creating a range of numbers
       * range(1, 5) => [1, 2, 3, 4, 5]
       */
      const range = (from, to, step = 1) => {
        let i = from;
        const range = [];
      
        while (i <= to) {
          range.push(i);
          i += step;
        }
      
        return range;
      }
      

      Na classe Pagination, após o constructor, adicione o seguinte método fetchPageNumbers:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        /**
         * Let's say we have 10 pages and we set pageNeighbours to 2
         * Given that the current page is 6
         * The pagination control will look like the following:
         *
         * (1) < {4 5} [6] {7 8} > (10)
         *
         * (x) => terminal pages: first and last page(always visible)
         * [x] => represents current page
         * {...x} => represents page neighbours
         */
        fetchPageNumbers = () => {
          const totalPages = this.totalPages;
          const currentPage = this.state.currentPage;
          const pageNeighbours = this.pageNeighbours;
      
          /**
           * totalNumbers: the total page numbers to show on the control
           * totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls
           */
          const totalNumbers = (this.pageNeighbours * 2) + 3;
          const totalBlocks = totalNumbers + 2;
      
          if (totalPages > totalBlocks) {
            const startPage = Math.max(2, currentPage - pageNeighbours);
            const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);
            let pages = range(startPage, endPage);
      
            /**
             * hasLeftSpill: has hidden pages to the left
             * hasRightSpill: has hidden pages to the right
             * spillOffset: number of hidden pages either to the left or to the right
             */
            const hasLeftSpill = startPage > 2;
            const hasRightSpill = (totalPages - endPage) > 1;
            const spillOffset = totalNumbers - (pages.length + 1);
      
            switch (true) {
              // handle: (1) < {5 6} [7] {8 9} (10)
              case (hasLeftSpill && !hasRightSpill): {
                const extraPages = range(startPage - spillOffset, startPage - 1);
                pages = [LEFT_PAGE, ...extraPages, ...pages];
                break;
              }
      
              // handle: (1) {2 3} [4] {5 6} > (10)
              case (!hasLeftSpill && hasRightSpill): {
                const extraPages = range(endPage + 1, endPage + spillOffset);
                pages = [...pages, ...extraPages, RIGHT_PAGE];
                break;
              }
      
              // handle: (1) < {4 5} [6] {7 8} > (10)
              case (hasLeftSpill && hasRightSpill):
              default: {
                pages = [LEFT_PAGE, ...pages, RIGHT_PAGE];
                break;
              }
            }
      
            return [1, ...pages, totalPages];
          }
      
          return range(1, totalPages);
        }
      }
      

      Aqui, você define duas constantes pela primeira vez: LEFT_PAGE e RIGHT_PAGE. Essas constantes serão usadas para indicar os pontos onde haverá controles de página para mover-se para a esquerda e direita, respectivamente.

      Você também definiu uma função auxiliar range() que pode ajudá-lo a gerar intervalos de números.

      Nota: se você usa uma biblioteca de utilidades como o Lodash em seu projeto, então pode usar a função _.range() disponibilizada pelo Lodash como alternativa. O código a seguir mostra a diferença entre a função range() que você acabou de definir e a do Lodash:

      range(1, 5); // returns [1, 2, 3, 4, 5]
      _.range(1, 5); // returns [1, 2, 3, 4]
      

      Em seguida, você definiu o método fetchPageNumbers() na classe Pagination. Esse método processa a lógica principal para a geração dos números de página a serem exibidos no controle de paginação. É desejável que a primeira página e a última sejam estejam visíveis.

      Primeiro, algumas variáveis foram definidas. totalNumbers representa os números totais de páginas que serão exibidos no controle. totalBlocks representa os números totais de página a serem exibidos com o acréscimo de dois blocos adicionais para os indicadores esquerdo e direito de página.

      Se o totalPages não for maior que totalBlocks, você retorna um intervalo de números de 1 até totalPages. Caso contrário, você retorna a matriz de números de página, com LEFT_PAGE e RIGHT_PAGE em pontos onde existem páginas sendo despejadas para a esquerda e direita, respectivamente.

      No entanto, observe que o controle de paginação garante que a primeira página e a última estejam sempre visíveis. Os controles de página da esquerda e direita aparecem na parte de dentro.

      Agora, você irá adicionar o método render() para habilitar a renderização do controle de paginação.

      Na classe Pagination, após os métodosconstructor e fetchPageNumbers, adicione o seguinte método render:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        render() {
          if (!this.totalRecords || this.totalPages === 1) return null;
      
          const { currentPage } = this.state;
          const pages = this.fetchPageNumbers();
      
          return (
            <Fragment>
              <nav aria-label="Countries Pagination">
                <ul className="pagination">
                  { pages.map((page, index) => {
      
                    if (page === LEFT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Previous" onClick={this.handleMoveLeft}>
                          <span aria-hidden="true">&laquo;</span>
                          <span className="sr-only">Previous</span>
                        </a>
                      </li>
                    );
      
                    if (page === RIGHT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Next" onClick={this.handleMoveRight}>
                          <span aria-hidden="true">&raquo;</span>
                          <span className="sr-only">Next</span>
                        </a>
                      </li>
                    );
      
                    return (
                      <li key={index} className={`page-item${ currentPage === page ? ' active' : ''}`}>
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" onClick={ this.handleClick(page) }>{ page }</a>
                      </li>
                    );
      
                  }) }
      
                </ul>
              </nav>
            </Fragment>
          );
        }
      }
      

      Aqui, você gera o array de números de página chamando o método fetchPageNumbers() que você criou anteriormente. Em seguida, você renderiza cada número de página usando o Array.prototype.map(). Observe que manipuladores de eventos de clique foram registrados em cada número de página renderizada para manipular os cliques.

      Além disso, note que o controle de paginação não será renderizado caso a propriedade totalRecords não tenha sido passada corretamente para o componente Pagination, ou nos casos em que haja apenas 1 página.

      Por fim, você irá definir os métodos do manipulador de eventos.

      Na classe Pagination, após o constructor e métodos fetchPageNumbers e render, adicione o seguinte:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        componentDidMount() {
          this.gotoPage(1);
        }
      
        gotoPage = page => {
          const { onPageChanged = f => f } = this.props;
          const currentPage = Math.max(0, Math.min(page, this.totalPages));
          const paginationData = {
            currentPage,
            totalPages: this.totalPages,
            pageLimit: this.pageLimit,
            totalRecords: this.totalRecords
          };
      
          this.setState({ currentPage }, () => onPageChanged(paginationData));
        }
      
        handleClick = page => evt => {
          evt.preventDefault();
          this.gotoPage(page);
        }
      
        handleMoveLeft = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage - (this.pageNeighbours * 2) - 1);
        }
      
        handleMoveRight = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage + (this.pageNeighbours * 2) + 1);
        }
      }
      

      Foi definido o método gotoPage() que modifica o estado e define o currentPage para a página especificada. Ele garante que o argumento page tenha um valor mínimo de 1 e um valor máximo do número total de páginas. Por fim, a função onPageChanged() que foi passada como uma propriedade é chamada, com dados indicando o novo estado de paginação.

      Quando o componente é montado, você vai até a primeira página chamando this.gotoPage(1) como mostrado no método de ciclo de vida componentDidMount().

      Observe como o (this.pageNeighbours * 2) foi usado em handleMoveLeft() e handleMoveRight() para deslizar os números de página para a esquerda e direita, respectivamente, com base no número atual de página.

      Aqui está uma demonstração da interação dos movimentos para a esquerde e direita.

      Interação da movimentação para a esquerda e direita

      Agora, você concluiu o componente Pagination. Os usuários serão capazes de interagir com os controles de navegação neste componente para exibir diferentes páginas de bandeiras.

      Passo 4 — Construindo o componente App

      Agora que você tem um componente CountryCard e um Pagination, use-os em seu componente App.

      Modifique o arquivo App.js no diretório src:

      Substitua o conteúdo de App.js pelas linhas de código a seguir:

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.css';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      
      class App extends Component {
        state = { allCountries: [], currentCountries: [], currentPage: null, totalPages: null }
      
        componentDidMount() {
          const { data: allCountries = [] } = Countries.findAll();
          this.setState({ allCountries });
        }
      
        onPageChanged = data => {
          const { allCountries } = this.state;
          const { currentPage, totalPages, pageLimit } = data;
          const offset = (currentPage - 1) * pageLimit;
          const currentCountries = allCountries.slice(offset, offset + pageLimit);
      
          this.setState({ currentPage, currentCountries, totalPages });
        }
      }
      
      export default App;
      

      Aqui, inicializa-se o estado do componente App com os seguintes atributos:

      • allCountries – é uma matriz de todos os países em seu aplicativo. Inicializada como uma matriz vazia ([]).
      • currentCountries – é uma matriz de todos os países a serem exibidos na página atualmente ativa. Inicializada como uma matriz vazia ([]).
      • currentPage – é o número da página que está atualmente ativa. Inicializada como null.
      • totalPages – é o número total de páginas para todos os registros de país. Inicializada como null.

      Em seguida, no método de ciclo de vida componentDidMount(), busca-se todos os países usando o pacote countries-api invocando o Countries.findAll(). Depois disso, atualiza-se o estado do aplicativo, definindo allCountries para conter todos os países do mundo. Veja a documentação do countries-apipara aprender mais sobre o pacote.

      Por fim, definiu-se o método onPageChanged(), que será chamado toda vez que o usuário navegar para uma nova página a partir do controle de paginação. Esse método será passado para a propriedade onPageChanged do componente Pagination.

      Há duas linhas que merecem atenção nesse método. A primeira é esta:

      const offset = (currentPage - 1) * pageLimit;
      

      O valor offset (deslocamento) indica o índice de partida para buscar os registros para a página atual. Usar (currentPage - 1) garante que o deslocamento é baseado em zero. Vamos supor que, por exemplo, estejam sendo exibidos 25 registros por página, e o usuário esteja visualizado atualmente a página 5. Sendo assim, o offset será ((5-1) * 25 = 100).

      Exemplificando, caso esteja buscando registros sob demanda de um banco de dados, esta é uma consulta SQL de amostra para mostrar como o offset pode ser usado:

      SELECT * FROM `countries` LIMIT 100, 25
      

      Como você não está coletando registros de um banco de dados ou qualquer fonte externa, uma maneira de extrair os registros a serem exibidos na página atual é necessária.

      A segunda é esta linha:

      const currentCountries = allCountries.slice(offset, offset + pageLimit);
      

      Aqui usa-se o método Array.prototype.slice() para extrair a parte dos registros requisitada de allCountries passando o offset como o índice de partida para a fatia e (offset + pageLimit) como o índice antes do qual a fatia deve terminar.

      Nota: neste tutorial, não foram coletados registros de nenhuma fonte externa. Em um aplicativo real, você provavelmente estaria buscando registros de um banco de dados ou uma API. A lógica para coletar os registros pode entrar no método onPageChanged() do componente App.

      Vamos supor que você tenha um ponto de extremidade de API fictício /api/countries?page={current_page}&limit={page_limit}. O código a seguir mostra como é possível buscar os países sob demanda a partir da API usando o pacote HTTP [axios][“axios]:

      onPageChanged = data => {
        const { currentPage, totalPages, pageLimit } = data;
      
        axios.get(`/api/countries?page=${currentPage}&limit=${pageLimit}`)
          .then(response => {
            const currentCountries = response.data.countries;
            this.setState({ currentPage, currentCountries, totalPages });
          });
      }
      

      Agora, finalize o componente App adicionando o método render().

      Na classe App, após o componentDidMount e onPageChanged, adicione o seguinde método render:

      src/App.js

      class App extends Component {
        // ... other methods here ...
      
        render() {
          const { allCountries, currentCountries, currentPage, totalPages } = this.state;
          const totalCountries = allCountries.length;
      
          if (totalCountries === 0) return null;
      
          const headerClass = ['text-dark py-2 pr-4 m-0', currentPage ? 'border-gray border-right' : ''].join(' ').trim();
      
          return (
            <div className="container mb-5">
              <div className="row d-flex flex-row py-5">
                <div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">
                  <div className="d-flex flex-row align-items-center">
                    <h2 className={headerClass}>
                      <strong className="text-secondary">{totalCountries}</strong> Countries
                    </h2>
                    { currentPage && (
                      <span className="current-page d-inline-block h-100 pl-4 text-secondary">
                        Page <span className="font-weight-bold">{ currentPage }</span> / <span className="font-weight-bold">{ totalPages }</span>
                      </span>
                    ) }
                  </div>
                  <div className="d-flex flex-row py-4 align-items-center">
                    <Pagination totalRecords={totalCountries} pageLimit={18} pageNeighbours={1} onPageChanged={this.onPageChanged} />
                  </div>
                </div>
                { currentCountries.map(country => <CountryCard key={country.cca3} country={country} />) }
              </div>
            </div>
          );
        }
      }
      

      No método render(), renderiza-se o número total de países, a página atual, o número total de páginas, o controle <Pagination> e o <CountryCard> para cada país na página atual.

      Observe que o método onPageChanged() definido anteriormente na propriedade onPageChanged do controle <Pagination> foi passado. Isso é muito importante para capturar alterações de página a partir do componente Pagination. Além disso, estão sendo exibidos 18 países por página.

      Neste ponto, o aplicativo estará parecido com o mostrado na captura de tela a seguir:

      Captura de tela do aplicativo com 248 países listados e números de página no topo para percorrer cada página

      Agora, você possui um componente App que exibe vários componentes CountryCard e um componente Pagination que divide o conteúdo em páginas separadas. Em seguida, você irá explorar a estilização do seu aplicativo.

      Passo 5 — Adicionando estilos personalizados

      Você pode ter notado que algumas classes personalizadas foram sendo adicionadas aos componentes criados anteriormente. Vamos definir algumas regras de estilo para essas classes no arquivo src/App.scss.

      O arquivo App.scss ficará parecido com o seguinte código:

      src/App.scss

      /* Declare some variables */
      $base-color: #ced4da;
      $light-background: lighten(desaturate($base-color, 50%), 12.5%);
      
      .current-page {
        font-size: 1.5rem;
        vertical-align: middle;
      }
      
      .country-card-container {
        height: 60px;
        cursor: pointer;
        position: relative;
        overflow: hidden;
      }
      
      .country-name {
        font-size: 0.9rem;
      }
      
      .country-region {
        font-size: 0.7rem;
      }
      
      .current-page,
      .country-name,
      .country-region {
        line-height: 1;
      }
      
      // Override some Bootstrap pagination styles
      ul.pagination {
        margin-top: 0;
        margin-bottom: 0;
        box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
      
        li.page-item.active {
          a.page-link {
            color: saturate(darken($base-color, 50%), 5%) !important;
            background-color: saturate(lighten($base-color, 7.5%), 2.5%) !important;
            border-color: $base-color !important;
          }
        }
      
        a.page-link {
          padding: 0.75rem 1rem;
          min-width: 3.5rem;
          text-align: center;
          box-shadow: none !important;
          border-color: $base-color !important;
          color: saturate(darken($base-color, 30%), 10%);
          font-weight: 900;
          font-size: 1rem;
      
          &:hover {
            background-color: $light-background;
          }
        }
      }
      

      Modifique seu arquivo App.js para fazer referência ao App.scss ao invés do App.css.

      Nota: para mais informações sobre isso, consulte a documentação do Create React App.

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.scss';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      

      Após adicionar os estilos, o aplicativo terá um visual semelhante ao da captura de tela a seguir:

      Captura de tela do aplicativo, páginas 1 a 14, com estilos

      Agora, você possui um aplicativo completo com uma estilização personalizada adicional. É possível utilizar estilos personalizados para modificar e aprimorar todos os estilos padrão disponibilizados por bibliotecas como o Bootstrap.

      Conclusão

      Neste tutorial, você criou um widget de paginação personalizada em seu aplicativo React. Embora não tenha feito chamadas para nenhuma API, nem interagido com qualquer backend de banco de dados neste tutorial, seu aplicativo pode exigir essas interações. Não sinta-se limitado à abordagem usada neste tutorial de maneira alguma — você pode estendê-la conforme desejar para que atenda aos requisitos do seu aplicativo.

      Para o código fonte completo deste tutorial, confira o repositório build-react-pagination-demo no GitHub. Se quiser, obtenha também uma demonstração em tempo real deste tutorial no Code Sandbox.

      Se quiser aprender mais sobre o React, dê uma olhada em nossa série Como programar no React.js, ou confira nossa página do tópico React para exercícios e projetos de programação.



      Source link

      Como implementar uma rolagem suave no React


      Introdução

      A rolagem suave é quando em vez de clicar em um botão e ser imediatamente levado a uma parte diferente da mesma página, o usuário é enviado para lá através de uma animação de rolagem. É um daqueles recursos sutis de IU em um site que fazem uma grande diferença estética.

      Neste artigo, você irá usar o pacote react-scroll no npm para implementar uma rolagem suave.

      Pré-requisitos

      Você precisará do seguinte para completar este tutorial:

      Este tutorial foi verificado com o Node v13.14.0, npm v6.14.5, react v16.13.1 e o react-scroll v.1.7.16.

      Você irá construir um aplicativo simples neste tutorial, mas caso deseje um resumo rápido de como o react-scroll funciona, sinta-se a vontade para consultar estes passos condensados:

      Instale o react-scroll:

      npm i -S react-scroll
      

      Importe o pacote react-scroll:

      import { Link, animateScroll as scroll } from "react-scroll";
      

      Adicione o componente de ligação. O componente <Link /> irá apontar para uma determinada área do seu aplicativo:

      <Link to="section1">
      

      Vamos nos aprofundar mais e construir um pequeno aplicativo React com rolagem suave.

      Passo 1 — Instalar e executar um aplicativo React

      Por questões de conveniência, este tutorial ira usar um projeto iniciador do React (usando o Create React App 2.0) que possui uma barra de navegação (ou navbar) no topo, juntamente com cinco seções distintas de conteúdo.

      Os links na barra de navegação são apenas marcadores de ancoragem neste ponto, mas logo serão atualizados para habilitar a rolagem suave.

      O projeto pode ser encontrado em React With Smooth Scrolling. Observe que esse link é para a ramificação de início. A ramificação mestre inclui todas as alterações finalizadas.

      Captura de tela do repositório GitHub

      Para clonar o projeto, use o comando a seguir:

      git clone https://github.com/do-community/React-With-Smooth-Scrolling.git
      

      Se você olhar no diretório src/Components, irá encontrar um arquivo Navbar.js que contém o <Navbar> com nav-items correspondentes a cinco diferentes <Section>s.

      src/Components/Navbar.js

      import React, { Component } from "react";
      import logo from "../logo.svg";
      
      export default class Navbar extends Component {
        render() {
          return (
            <nav className="nav" id="navbar">
              <div className="nav-content">
                <img
                  src={logo}
                  className="nav-logo"
                  alt="Logo."
                  onClick={this.scrollToTop}
                />
                <ul className="nav-items">
                  <li className="nav-item">Section 1</li>
                  <li className="nav-item">Section 2</li>
                  <li className="nav-item">Section 3</li>
                  <li className="nav-item">Section 4</li>
                  <li className="nav-item">Section 5</li>
                </ul>
              </div>
            </nav>
          );
        }
      }
      

      Em seguida, se abrir o arquivo App.js no diretório src, verá onde o <Navbar> está incluído juntamente com as cinco <Section>s.

      src/Components/App.js

      import React, { Component } from "react";
      import logo from "./logo.svg";
      import "./App.css";
      import Navbar from "./Components/Navbar";
      import Section from "./Components/Section";
      import dummyText from "./DummyText";
      class App extends Component {
        render() {
          return (
            <div className="App">
              <Navbar />
              <Section
                title="Section 1"
                subtitle={dummyText}
                dark={true}
                id="section1"
              />
              <Section
                title="Section 2"
                subtitle={dummyText}
                dark={false}
                id="section2"
              />
              <Section
                title="Section 3"
                subtitle={dummyText}
                dark={true}
                id="section3"
              />
              <Section
                title="Section 4"
                subtitle={dummyText}
                dark={false}
                id="section4"
              />
              <Section
                title="Section 5"
                subtitle={dummyText}
                dark={true}
                id="section5"
              />
            </div>
          );
        }
      }
      
      export default App;
      

      Cada componente <Section> assume um title (título) e subtitle (subtítulo).

      Como o projeto está usando textos fictícios nas diferentes seções, de forma a reduzir o código repetido, esse texto foi adicionado em um arquivo DummyText.js, importado e passado para cada componente <Section>.

      Para executar o aplicativo, utilize os comandos a seguir.

      • cd React-With-Smooth-Scrolling
      • npm install
      • npm start

      Isso irá iniciar o aplicativo no modo de desenvolvimento e atualizar automaticamente o aplicativo quando for salvar seus arquivos. É possível visualizá-lo no navegador em localhost:3000.

      Captura de tela do aplicativo no navegador

      Agora, é hora de instalar o pacote react-scroll e adicionar essa funcionalidade. Encontre mais informações para o pacote no npm.

      Pacote react-scroll no npm

      Para instalar o pacote, execute o seguinte comando:

      Em seguida, abra o arquivo Navbar.js novamente e adicione um import para duas importações chamadas Link e animateScroll.

      src/Components/Navbar.js

      import { Link, animateScroll as scroll } from "react-scroll";
      

      Observe que o nome animatedScroll foi alterado para scroll para facilitar o uso.

      Com todas as importações definidas, atualize agora seus nav-items para usar o componente <Link>. Esse componente recebe várias propriedades. Leia mais sobre todas elas na página de documentação.

      Por enquanto, preste bastante atenção no activeClass, to, spy, smooth, offset e duration.

      • activeClass – a classe aplicada quando o elemento é atingido.
      • to – o alvo para onde a rolagem é feita.
      • spy – para tornar o Link selecionado quando o scroll estiver na posição do seu alvo.
      • smooth – para animar a rolagem.
      • offset – para rolar pixels adicionais (como um preenchimento).
      • duration – o tempo que leva a animação de rolagem. Isso pode ser um número ou uma função.

      A propriedade to é a mais importante, já que informa ao componente para qual elemento deve ser feita a rolagem. Neste caso, isso será cada uma das suas <Section>s.

      Com a propriedade offset, é possível definir uma quantidade adicional de rolagem a ser feita de forma a chegar em cada <Section>.

      Aqui está um exemplo das propriedades que você usará para cada componente <Link>. A única diferença entre eles será a propriedade to, já que cada uma delas aponta para uma <Section> distinta:

      <Link
          activeClass="active"
          to="section1"
          spy={true}
          smooth={true}
          offset={-70}
          duration={500}
      >
      

      Será necessário atualizar cada um dos nav-items adequadamente. Com tudo isso adicionado, você deve ser capaz de voltar ao seu navegador (seu aplicativo já deve ter sido reiniciado automaticamente) e ver a rolagem suave em ação.

      A propriedade activeClass permite definir uma classe a ser aplicada no componente <Link> quando o seu elemento to estiver ativo. Um <Link> é considerado ativo se seu elemento to estiver em exibição próximo ao topo da página. Isso pode ser acionado clicando no <Link> em si ou rolando a página até a <Section> manualmente.

      Para provar isso, o Chrome DevTools foi aberto e o quinto <Link> foi inspecionado, assim como mostrado abaixo. Ao clicar nesse <Link> ou manualmente rolar a página até o final, vê-se que a classe ativa foi, de fato, aplicada.

      Visualização do navegador do aplicativo React

      Para aproveitar isso, crie uma classe ativa e adicione um sublinhado ao link. Adicione este pequeno código CSS no arquivo App.css no diretório src:

      src/App.css

      .nav-item > .active {
          border-bottom: 1px solid #333;
      }
      

      Agora, caso volte para o seu navegador e role um pouco na página, perceberá que o <Link> apropriado está sublinhado.

      Visualização do navegador atualizada do aplicativo React

      Passo 4 — Adicionando funções adicionais

      Para finalizar com mais um pouco de conteúdo, este pacote também fornece algumas funções que podem ser chamadas diretamente, como scrollToTop, scrollToBottom, etc., bem como diversos eventos que você pode manipular.

      Em referência a essas funções, tipicamente, o logotipo do aplicativo em uma barra de navegação irá levar o usuário à página inicial ou ao topo da página atual.

      Como um exemplo simples de como chamar uma dessas funções mencionadas, um manipulador de cliques foi adicionado ao nav-logo para trazer o usuário de volta ao topo da página, desta forma:

      src/Components/Navbar.js

      scrollToTop = () => {
          scroll.scrollToTop();
      };
      

      De volta no navegador, você deve ser capaz de rolar a página para baixo, clicar no logotipo na barra de navegação e ser levado de volta para o topo da página.

      Conclusão

      A rolagem suave é um daqueles recursos que pode adicionar um grande valor estético para seu aplicativo. O pacote react-scroll permite que você implemente esse recurso sem uma sobrecarga significativa.

      Neste tutorial, você adicionou uma rolagem suave a um aplicativo e experimentou diferentes configurações. Se tiver curiosidade, explore as outras funções e eventos que este pacote tem a oferecer.



      Source link