При создании веб-сайта нам часто может потребоваться создать способ входа пользователей в систему. При этом мы, вероятно, также хотим избежать дублирования имен пользователей. Мы можем даже захотеть ограничить то, что могут быть имена пользователей (длиннее символов «x» и т. Д.).

Мы в основном хотим взять это:

И превратите это в это:

Ниже приведены несколько способов обработки этого сценария с использованием клиентского JavaScript, серверного узла, а также серверного Python / Flask.

JavaScript

Во-первых, вот один из возможных способов справиться с этим, используя только клиентский JavaScript. Самое замечательное в использовании JavaScript - это то, что мы можем написать функцию, которая запускается при вводе, а не при отправке, что означает, что она будет обновляться по мере ввода пользователем. Например, пользователь намеревается ввести имя пользователя «Сэмми», но имя пользователя «Сэм» уже есть в базе данных. Как только пользователь набирает «Сэм», мы можем создать ошибку, которая уведомит пользователя о том, что «Сэм» уже занят. Конечно, они могут продолжать вводить оставшуюся часть имени пользователя, но было бы неплохо получить это предупреждение перед отправкой имени пользователя, а затем с ошибкой. Конечно, это не всегда лучший вариант, особенно если у вас миллионы пользователей. Функция должна будет проверять миллионы пользователей при каждом нажатии клавиши. Но с меньшими базами данных, похоже, все должно работать нормально. Вот пример того, как может выглядеть этот код:

Прежде чем мы начнем нашу функцию «при вводе», мне нужно будет заранее создать несколько переменных, чтобы я мог работать с ними на протяжении всей «функции ввода». Во-первых, мне нужно получить все текущие имена пользователей, чтобы я мог сравнить текущий ввод с ними. Во-вторых, моя подписка будет иметь имя пользователя, пароль и адрес электронной почты. Здесь я не буду вдаваться в подробности работы с электронной почтой и паролем, но я создаю переменную для каждого из них следующим образом:

        let userVal = false;
        let passVal = false;
        let emailVal = false;

Я устанавливаю для каждого значение false. Если пользователь вводит имя пользователя, которое соответствует всем установленным мною критериям, я устанавливаю для userVal значение true. Затем я сделаю то же самое с passVal и emailVal. Но я не позволю пользователю отправить заявку, если все три не верны. Таким образом, даже если имя пользователя соответствует надлежащим критериям, они все равно не смогут зарегистрироваться, если не будут выполнены другие критерии для пароля и области электронной почты.

Начнем с выборки, это может выглядеть примерно так:

fetch('url_for_usernames_in_database').then(function(response) {
      if (response.status != 200) {
        window.alert("oopsie daisy");
        return;
      }
response.json().then(function(data) {
        let api = data;

Затем мы выберем поле имени пользователя с помощью jQuery и создадим функцию при вводе:

$('#username').on('input', function() {
///---Do stuff here----///
});

У нас также будет кнопка отправки, поэтому в разделе - делайте вещи здесь - я хочу отключить кнопку отправки и высветить сообщение, если текущий ввод не соответствует нашим критериям. Для начала я возьму текущее значение пользовательского ввода и сохраню его как переменную под названием «значение».

$('#username').on('input', function() {
let value2 = document.getElementById('username');
      let value = username.value;
});

Затем я проверю значение на соответствие нужным мне критериям. В этом случае я хочу, чтобы имена пользователей были длиной не менее 6 символов.

$('#username').on('input', function() {
let value2 = document.getElementById('username');
      let value = username.value;
if(value.length < 6){
        document.getElementById('userMsg').innerHTML = "username must be longer than 6 characters";
        let btn = document.getElementById("userBtn");
        btn.disabled = true;
        btn.className = 'noDisplay';
        userVal = false;
      }
});

В приведенном выше примере, если входное значение меньше 6 символов, я отправляю пользователю сообщение в блоке div под полем ввода, которое позволяет им узнать о проблеме. Я также беру кнопку отправки, отключаю ее и устанавливаю для ее класса значение «noDisplay», которое представляет собой созданный мной CSS-класс display: none. Таким образом, пользователь не сможет отправить или даже не увидит кнопку отправки. Это также состояние кнопки по умолчанию, но я устанавливаю его здесь снова, чтобы быть уверенным. Затем я выбираю ту переменную userVal, о которой упоминал ранее, и оставляю ее как false.

Затем я создам еще несколько сценариев, которые также приведут к ложному результату, не позволяющему пользователю отправить информацию. В следующем сценарии имя пользователя состоит из 6 символов, а пароль пуст:

if(value.length >= 6 && password == ""){
        document.getElementById('userMsg').innerHTML = "please enter a valid password";
        let btn = document.getElementById("userBtn");
        btn.disabled = true;
        btn.className = 'noDisplay';
        userVal = false;
      }

Затем мы создадим сценарий, в котором пользователь вводит уже существующее имя пользователя. Я проверю это, посмотрев, есть ли уже в базе данных индекс переменной ‘value’:

if(usernames.indexOf(value)>-1){
        document.getElementById('userMsg').innerHTML = "Sorry, that    username is already taken!";
        let btn = document.getElementById("userBtn");
        btn.disabled = true;
        btn.className = 'noDisplay';
        userVal = false;
      }

Наконец, у нас может быть сценарий, в котором соблюдаются все критерии для каждого входа:

if (userVal == true && passVal == true && emailVal == true){
        document.getElementById('eMsg').innerHTML = "";
        document.getElementById('userMsg').innerHTML = "";
        document.getElementById('passMsg').innerHTML = "";
        let btn = document.getElementById("userBtn");
        btn.disabled = false;
        btn.className = 'btn90';
      }

Мы устанавливаем поле сообщения пустым, включаем кнопку отправки и меняем класс на тот, который является видимым.

Поскольку нам не обязательно заботиться о серверной части, если мы обрабатываем сообщения об ошибках на стороне клиента, единственными файлами, которые нам действительно нужны, будут HTML и JavaScript, которые могут быть даже одним файлом. Обработка ошибок и логинов пользователя на стороне сервера немного сложнее, поэтому, прежде чем мы начнем с этого, я хочу показать, как может выглядеть типичное файловое дерево, поскольку нам, вероятно, понадобится несколько файлов, чтобы наши данные были разделены. и организовано. Вот более или менее способ организации деревьев файлов Node и Python.

Функциональность будет очень похожа как на Python, так и на Node, в основном с синтаксическими различиями.

Node.js

В Node, если мы хотим создать проверку пользователя, нам сначала нужно создать соответствующий маршрут и модель данных. Начнем сначала с модели. Обычно мне нравится хранить свои модели в отдельном файле, чтобы импортировать их в основной файл app.js. Как видите, я также использую пакет mongoose для создания своих моделей. Вы также можете использовать мангуста, выполнив команду «npm install mongoose» в командной строке, если вы предпочитаете использовать его для создания своих моделей. В мангусте мы создаем схему, которая представляет наши данные. Здесь я указываю имя пользователя и пароль.

const mongoose = require('mongoose');
let Schema = mongoose.Schema;
const userSchema = new Schema({
  username: {
    type: String,
    required: true,
  },
  password: {
    type: String,
  },
})
const User = mongoose.model('User', userSchema);
module.exports = User;

Теперь, когда мы экспортировали этот модуль как «Пользователь», нам нужно импортировать его в наш файл app.js.

const User = require('./models/user.js');

В моем дереве файлов у меня будет отдельная папка для всех моих моделей. В моем основном файле app.js я могу импортировать каждую из моих моделей таким образом, если есть несколько моделей данных.

Затем мы создадим маршрут для нашей страницы регистрации:

app.get('/signup', function(req, res) {
  res.render('signup')
});

Затем мы создадим POST, который создаст нового пользователя:

app.post('/signup', function(req, res) {
  User.create({
    username: req.body.username,
    password: req.body.password,
  }).then(function(user) {
    req.username = user.username;
    req.session.authenticated = true;
}).then(user => {
  res.redirect('/index')
});
});

В нашем запросе POST мы используем req.body.username и req.body.password. Они определяют имена входных данных в теле HTML. Таким образом, наши имена для ввода HTML должны быть «имя пользователя» и «пароль», чтобы они соответствовали нашему маршруту здесь. Вот как может выглядеть наш HTML, чтобы убедиться, что он соответствует нашим маршрутам в Node:

<body>
    <div class="bodywrapper">
<div class="loginContainer">
      <div>
        <div class="login">
          <div class="title2b">Username: </div>
          <form class="login_input" action="/signup" method="post">
            <input class="input2" required type="text" name="username" placeholder="enter username">
        </div>
<div class="login">
          <div class="title2b">Password: </div>
          <input class="input2" required type="password" name="password" placeholder="enter password">
        </div>
      </div>
      <div>
        <input class="login__submit-button" type="submit">
      </div>
      </form>
</div>
<div class="logintext">signup</div>
</div>
  </body>

Теперь, до этого момента в узле, мы просто создали регистрацию пользователя, но не создали ни одной проверки пользователя, которую мы выполняли с помощью JavaScript. Но лучше сначала убедиться, что все это работает, чтобы наши проблемы было легче диагностировать на каждом этапе. Наконец, мы вернемся к нашей модели для нашего пользователя и добавим туда некоторую проверку, которая предотвратит регистрацию пользователя с тем же именем пользователя. Наша обновленная модель пользователя теперь может выглядеть так:

const mongoose = require('mongoose');
let Schema = mongoose.Schema;
const userSchema = new Schema({
  username: {
    type: String,
    required: true,
    validate: {
      isAsync: true,
      validator: function(value, isValid) {
        const self = this;
        return self.constructor.findOne({ username: value}).exec(function(err, user){
          if(err){
            throw err;
          }
          else if(user) {
            if(self.id === user.id) {
              return isValid(true);
            }
            return isValid(false);
          }
          else{
            return isValid(true);
          }
        })
      },
      message: 'The Username is already taken!'
    },
  },
  password: {
    type: String,
  },
})
const User = mongoose.model('User', userSchema);
module.exports = User;

используя метод findOne, мы в основном используем ту же идею, что и в JavaScript, но ищем дубликаты в нашей базе данных пользователей. Если есть дубликаты, мы получим сообщение на стороне сервера о том, что имя пользователя уже занято.

Python / Flask

Сначала мы импортируем несколько пакетов из flask, которые упростят создание необходимых форм, а затем создадим формы на серверной части с помощью Python. Я хочу установить пакеты flask_wtf, с помощью которых я могу получить доступ к некоторым валидаторам, которые сделают за нас большую часть тяжелой работы.

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

Затем я создам новый класс с именем RegistrationFrom из FlaskForm.

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
    
    password = PasswordField('Password', validators=[DataRequired()])
    password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')])
    
    submit = SubmitField('Register')

В этих формах мы можем добавить проверку имени пользователя в функции. когда мы добавляем какие-либо методы, соответствующие шаблону validate_ ‹field_name›, WTForms принимает их как пользовательские валидаторы, которые вызывают их в дополнение к стандартным валидаторам. В этом случае мы хотим убедиться, что введенное имя пользователя еще не находится в базе данных, поэтому этот метод выдает запрос к базе данных, ожидая, что результатов не будет. В случае наличия результата ошибка проверки запускается путем повышения ValidationError. Сообщение, включенное в качестве аргумента в исключение, будет сообщением, которое будет отображаться рядом с полем ввода для просмотра пользователем. Эту функцию мы можем просто включить в наш класс следующим образом:

def validate_username(self, username):
        user = User.query.filter_by(username=username.data).first()
        if user is not None:
            raise ValidationError('Please use a different username.')

Поскольку это дает тот же результат, что и другие наши примеры, теперь нам нужно вернуться и создать нашу форму в HTML и создать маршрут для формы в нашем файле routes.py.

Так же, как мы сделали некоторый импорт в наших формах Python, нам также необходимо импортировать flask здесь.

from flask import render_template, flash, redirect
from flask import request, url_for
from werkzeug.urls import url_parse
from app import app
from app.forms import LoginForm
from flask_login import current_user, login_user
from app.models import User
from flask_login import logout_user
from flask_login import login_required
from app import db
from app.forms import RegistrationForm

Затем мы создадим наш маршрут так же, как мы это делали с помощью узла. Здесь нам нужно убедиться, что пользователь, который запускает этот маршрут, не вошел в систему.

@app.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('Congratulations, you are now a registered user!')
        return redirect(url_for('login'))
    return render_template('register.html', title='Register', form=form)

Логика, которая выполняется внутри условного выражения if validate_on_submit(), создает нового пользователя с указанным именем пользователя, адресом электронной почты и паролем, записывает его в базу данных, а затем перенаправляет на приглашение входа, чтобы пользователь мог войти в систему.

Очевидно, здесь у нас также будут маршруты для нашей индексной страницы, а также для нашей страницы входа, но пока мы озабочены только проблемой регистрации новых пользователей.

Затем мы создадим модель пользователя в models.py, как мы это делали ранее в наших моделях мангуста. Синтаксис здесь будет немного другим, но идея почти такая же:

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
def __repr__(self):
        return '<User {}>'.format(self.username)
def set_password(self, password):
        self.password_hash = generate_password_hash(password)
def check_password(self, password):
        return check_password_hash(self.password_hash, password)

Я включил здесь некоторую логику для создания хэша пароля, чего мы не делали в нашей модели узла. Теперь последний шаг - создание HTML. Одна из замечательных особенностей Flask заключается в том, что мы можем создать базовый шаблон, а затем расширить его. Например, если каждая HTML-страница будет иметь навигацию, мы можем добавить панель навигации как часть базового HTML-шаблона. Затем наши индексные страницы, страницы входа или основные страницы просто расширят нашу базовую HTML-страницу. Эта функция может показаться очень похожей на то, как это обрабатывается в react, если вы знакомы с этим синтаксисом. Вот один из способов настроить наш базовый шаблон:

<html>
    <head>
     
    </head>
    <body>
      <div>
        <a href="{{ url_for('index') }}">Home</a>
        {% if current_user.is_anonymous %}
        <a href="{{ url_for('login') }}">Login</a>
        {% else %}
        <a href="{{ url_for('logout') }}">Logout</a>
        {% endif %}
    </div>
     
        {% block content %}{% endblock %}
    </body>
</html>

Он будет содержаться в нашей папке шаблонов, и мы могли бы назвать файл base.html. Затем во втором HTML-файле мы могли бы сделать что-то вроде этого:

{% extends "base.html" %}
{% block content %}
    <h1>Register</h1>
    <form action="" method="post">
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.email.label }}<br>
            {{ form.email(size=64) }}<br>
            {% for error in form.email.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.password2.label }}<br>
            {{ form.password2(size=32) }}<br>
            {% for error in form.password2.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

Здесь операция, которая преобразует шаблон в полную HTML-страницу, называется рендерингом. Для рендеринга шаблона нам нужно импортировать функцию render_template (), входящую в состав фреймворка Flask, которую вы, возможно, заметили при импорте, который мы вызвали ранее. Эта функция принимает имя файла шаблона и список переменных аргументов шаблона и возвращает тот же шаблон, но со всеми заполнителями в нем, замененными фактическими значениями. Функция render_template () вызывает механизм шаблонов Jinja2, который поставляется вместе с фреймворком Flask. Jinja2 заменяет блоки {{…}} их значениями, заданными аргументами.

На этом мы в основном закончили с этой частью нашего сайта. Очевидно, что из этих трех методов JavaScript намного быстрее и может выполняться более или менее «на лету». Однако мы можем столкнуться с множеством проблем с этим методом, и, вероятно, он обычно не рекомендуется. Однако мне нравится идея запретить пользователю даже отправлять сообщения до тех пор, пока не будут выполнены необходимые нам условия. Когда я начал программировать, я использовал в основном node и JavaScript, но недавно начал изучать Python и Flask. Мое мнение, с самого начала, заключается в том, что Flask и Python предлагают множество пакетов, которые, кажется, упрощают процесс создания логина пользователя намного больше, чем это делает Node. Однако логика в обоих случаях во многом одинакова.