Рубрики
Без рубрики

Помимо оснований (часть V): Formik, D3 и многое другое!

После того, как вы закончите этот пост, у вас будет шаблон для легко создания форм, используя Formik, как … с меткой JavaScript, React, Python.

После того, как вы закончите этот пост, у вас будет шаблон для легко создания форм, используя Formik, а также опыт визуализации D3!

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

В последнем посте мы реализовали аутентификацию пользователей как в колбе, так и в реагировании. Теперь, когда базовая структура на месте, мы собираемся реализовать полный «ломтик» приложения – мы создадим страницу прокси, где пользователи могут добавлять и удалять прокси Crawler. Я называю это ломтиком, потому что мы создадим каждую часть функциональности в этом посте, от модели данных до пользовательского интерфейса.

Вы можете найти полный код на GitHub.

Оглавление

  • Часть I: Создание скребка поиска Google
    • Настройка кукловода на экземпляре AWS
    • Создание простого запроса поиска в Google
    • Использование прокси -сети для запросов скребков
    • Сбор результатов поиска
    • Обработка ошибок скрещин
  • Часть II: Развертывание готового производства с Nginx, Flask и Postgres
    • Настройка Docker и Docker Compose
    • Развертывание версии разработки
    • Понимание того, как nginx и колба работают вместе
    • Тестирование конфигурации Nginx и Flask
    • Конфигурация Postgres
    • Настройка SSL с помощью Let’s Encrypt
    • Развертывание производственной версии
  • Часть III: колба, SQLalchemy и Postgres
    • Настройка sqlalchemy и postgres
    • SQLalchemy Performance Performation
    • Настройка нашего первого обработчика маршрута API
  • Часть IV: аутентификация пользователя с помощью колбы и реагировать
    • Защита API REST Flask Rest с помощью JSON Web Tokens
    • Обработка регистрации пользователя в колбе
    • Проверка электронной почты и активация учетной записи
    • Создание пользователя и отправка электронной почты активации
    • Защита страниц в приложении React
    • Добавление Google oauth в качестве опции регистрации

Создание модели данных прокси -подключения

Прокси -модель будет содержать все детали, необходимые для марионета, чтобы заплести Google, используя это соединение, такие как URL, имя пользователя и пароль. Мы также следим за некоторыми статистиками, такими как счетчик того, сколько раз заблокирован прокси, который пригодится позже, когда мы хотим визуализировать производительность прокси с D3.

class ProxyConnection(db.Model):
    __tablename__ = "proxyconn"

    id = db.Column(db.Integer, primary_key=True)

    user_id = db.Column(
        db.Integer,
        db.ForeignKey("user.id", ondelete="CASCADE"),
        index=True,
        nullable=False,
    )

    proxy_url = db.Column(db.String, nullable=False)
    username = db.Column(db.String, nullable=False)
    password = db.Column(db.String, nullable=False)

    # Can this proxy support multiple parallel requests?
    allow_parallel = db.Column(
        db.Boolean, default=False, server_default="f", nullable=False
    )

    success_count = db.Column(db.Integer, default=0, server_default="0")
    block_count = db.Column(db.Integer, default=0, server_default="0")
    no_result_count = db.Column(db.Integer, default=0, server_default="0")
    consecutive_fails = db.Column(db.Integer, default=0, server_default="0")

    # Proxy is currently in use (only applicable when allow_parallel = 'f').
    engaged = db.Column(db.Boolean, default=False, server_default="f")

    # Must wait at least this long before allowing another request.
    min_wait_time = db.Column(db.Integer, default=0, server_default="0", nullable=False)

    # Use random delay when proxying with a static IP to avoid blocks.
    random_delay = db.Column(db.Integer, default=0, server_default="0", nullable=False)

    last_used = db.Column(db.DateTime, index=True, nullable=True)

    user = db.relationship("User")

Я также определю схему зефира как часть модели данных. Это облегчит принятие заявок формы в формате JSON, а также возвращает данные из API.

from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field
from app.models.proxyconn import ProxyConnection


class ProxySchema(SQLAlchemyAutoSchema):

    class Meta:
        model = ProxyConnection
        load_instance = True

    # Set password to load_only so that it is accepted during form
    # submissions, but never dumped back into JSON format.
    password = auto_field(load_only=True)

SQLALCHEMYAUTOSCHEMA Класс – это отличное удобство, потому что он автоматически отображает класс модели на поля зефира. Когда нам нужно относиться к определенному полю по -другому, например, пароль здесь, достаточно легко переопределить функциональность.

Всякий раз, когда в проекте создаются новые модели, нам нужны эти модели, чтобы существовать в виде реальных таблиц в Postgres. Позже мы проведем выполнение миграции базы данных, но для целей разработки легко создать новые таблицы в Postgres, используя скрипт Manager Flask.

docker exec -it openranktracker_app_1 python manage.py shell
>> db.create_all()

Создание и удаление прокси -подключений

Нам понадобится получить, публиковать и удалить методы для прокси -модели. К счастью, это довольно просто, особенно потому, что мы будем использовать зефир для обработки проверки и сериализации.

ProxiesView Обработка создает новые прокси, а также возвращает все прокси, принадлежащие конкретному пользователю.

from flask import request, g, abort
from marshmallow import ValidationError

from app.api.auth import AuthenticatedView
from app.models.proxyconn import ProxyConnection
from app.serde.proxy import ProxySchema
from app import db


class ProxiesView(AuthenticatedView):
    def get(self):
        return (
            ProxySchema().dump(
                ProxyConnection.query.filter_by(user_id=g.user.id)
                .order_by(ProxyConnection.id)
                .all(),
                many=True,
            ),
            200,
        )

    def post(self):
        try:
            proxy = ProxySchema().load(request.get_json(), session=db.session)
            proxy.user = g.user
        except ValidationError:
            abort(400)

        db.session.add(proxy)
        db.session.commit()

        return ProxySchema().dump(proxy), 201

Мы используем контекст глобальной колбы для фильтрации прокси по пользователю и назначать владельца новым прокси. Метод POST просто возвращает 400 плохой запрос, если проверка зефира не удается. Однако этого не должно произойти, потому что передняя форма будет иметь свои собственные проверки, чтобы предотвратить плохие представления. Иногда иногда необходимы более сложные проверки, которые могут быть выполнены только на заднем плане, но в этом случае мы обеспокоены тем, представлены ли необходимые поля.

ProxyView будет обрабатывать удаление прокси -соединений.

from flask import g, abort

from app.api.auth import AuthenticatedView
from app.models.proxyconn import ProxyConnection
from app import db


class ProxyView(AuthenticatedView):
    def delete(self, proxy_id):
        proxy = ProxyConnection.query.get(proxy_id)

        if proxy.user_id != g.user.id:
            abort(403)

        db.session.delete(proxy)
        db.session.commit()

        return "", 200

Довольно просто, правда! Если вы не пытаетесь удалить прокси, которые вам не принадлежат. В этом случае мы прерваем с 403.

Наконец, мы быстро останавливаемся в app/api/__ init__.py связывать новые обработчики с маршрутами API.

api.add_resource(ProxyView, "/proxies//")
api.add_resource(ProxiesView, "/proxies/")

Создание новой прокси -формы

Теперь, когда модели базы данных и маршруты API на месте, нам нужна форма для отправки новых прокси. Это не будет первой формой в приложении – в конце концов, у нас уже есть формы регистрации и входа в систему. На этот раз, однако, мы собираемся получить немного более причудливого и использовать библиотеку Formik.

Формы логина и регистрации были очень простыми. Прокси, однако, имеет пять полей и дополнительные проверки, помимо того, требуется ли что -то или нет. Обработка всего этого с помощью Formik должна сократить количество кода, которое нам нужно написать.

Первым шагом в построении формы будет определение значений по умолчанию, а также проверки, которые мы должны выполнить. Давайте посмотрим на первую часть ProxyPopup.js модуль, чтобы увидеть, как это делается.

import { Formik, Form, Field } from "formik";
import * as Yup from "yup";

const defaultProxy = {
    proxy_url: "",
    username: "",
    password: "",
    min_wait_time: 60,
    random_delay: 10
};  

const proxySchema = Yup.object().shape({
    proxy_url: Yup.string().required(),
    username: Yup.string().required(),
    password: Yup.string().required(),
    min_wait_time: Yup.number()
        .positive()
        .integer()
        .required(),
    random_delay: Yup.number()
        .positive()
        .integer()
        .required()
});

Библиотека YUP легко интегрируется с Formik и позволяет легко создавать различные комбинации валидаторов.

Формик сам обеспечивает базу Formik компонент, который ожидает функции в качестве ребенка. Мы определим нашу форму внутри этой функции, и Formik будет передавать аргументы, которые включают объект значений, а также касаются и ошибки объектов.

Мы можем использовать эти объекты, чтобы управлять стилем формы, как вы можете видеть ниже.

Форма опирается на тронут и Ошибки Объекты, чтобы пометить поле имени пользователя как ошибку. Ввод пароля не помечен, даже если он требуется, потому что тронут Объект указывает, что он еще не испытал размытого события. Ошибки Объект обновляется автоматически в зависимости от схемы YUP, которую мы предоставили. Formik упрощает отслеживание всей этой информации о состоянии.

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


    {({
        values,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit,
        isValid
    }) => (
        
)}

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

Пользовательские элементы и кнопки можно найти в util/controls.js модуль.

import styled from "styled-components";
import { BORDER_RADIUS, COLORS, PAD_XS, PAD_SM } from "./constants";

export const Input = styled.input`
    color: ${COLORS.fg1};
    background-color: ${COLORS.bg4};
    box-sizing: border-box;
    padding: ${PAD_XS} ${PAD_SM};
    outline: none;
    border-radius: ${BORDER_RADIUS};
    border: ${props => props.border || "none"};
`;

export const Button = styled.button`
    background: none;
    border: none;
    border-radius: ${BORDER_RADIUS};
    outline: none;
    cursor: pointer;

    &:disabled {
        filter: brightness(50%);
        cursor: default;
    }
`;

Создание прокси -панели с помощью Flexbox

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

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

Сначала мы посмотрим на JSX, который производит панель панели.

Add Proxy Server
{proxies.map(proxy => (
))}

Div Buttonrow – это контейнер с гибкой, в котором находится кнопка Add Proxy, которая отображается в правой стороне страницы. Вместо использования Float: справа Здесь можно использовать Маржа-лето: Auto Чтобы достичь того же результата. Конечно, класс Proxylist также является гибким контейнером, но с flex-wrap свойство добавлено.

nowrap По умолчанию Flex-WRAP означает, что элементы проливаются за пределами своего контейнера, когда места не хватает. Переодевшись на сворачивать детям вместо этого разрешено прорваться на следующую строку.

Это соответствующий CSS, который делает все это.

.container {
    padding: var(--pad-md);
    padding-top: var(--pad-sm);
    box-sizing: border-box;
}

.buttonRow {
    display: flex;
    margin-bottom: var(--margin-md);
}

.proxyList {
    display: flex;
    flex-wrap: wrap;
}

.proxyContainer {
    margin-right: var(--margin-sm);
    margin-bottom: var(--margin-sm);
}

Внешний класс контейнеров применяет некоторую прокладку, так что панель панели не нажимает на края страницы. Используя Распределение коробки: пограничная коробка Предотвращает, что добавленная прокладка от создания полос прокрутки.

Добавление диаграммы пончиков с помощью D3

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

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

Мы создадим Donutchart компонент, который работает с любыми данными, имеющими до 3 категорий. Компонент ожидает категории, которая имеет положительные, нейтральные и отрицательные ключи, которые отображают значения целочисленных целых числа.

В отличие от подавляющего большинства приложения, Donutchart является классовым компонентом. Это необходимо, потому что D3 работает напрямую с DOM. В результате мы не можем полагаться на нормальный цикл рендеринга. Вместо этого нам придется вручную следить за изменениями опоры, чтобы определить, когда необходимо повторное рендеринг.

К счастью, для компонентов на основе классов мы можем использовать ComponentDidupdate Чтобы определить, требуется ли повторный рендерин.

componentDidUpdate(prevProps) {
    if (prevProps.category != this.props.category) {
        this.drawChart();
    }
}

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

DrawChart Метод содержит фактическую логику рендеринга D3.

drawChart() {
    const svg = d3.select(this.svgRef.current).select("g");

    const radius = Math.min(this.width, this.height) / 2;
    const donutWidth = 10;

    const arc = d3
        .arc()
        .padAngle(0.05)
        .innerRadius(radius - donutWidth)
        .outerRadius(radius)
        .cornerRadius(15);

    const data = [
        this.props.category.POSITIVE,
        this.props.category.NEGATIVE,
        this.props.category.NEUTRAL
    ];

    const pie = d3
        .pie()
        .value(d => d)
        .sort(null);

    // Select all existing SVG path elements and associate them with
    // the positive, neutral, and negative sections of the donut
    // chart.
    const path = svg.selectAll("path").data(pie(data));

    // The enter() and append() methods take into account any existing
    // SVG paths (i.e. drawChart was already called) and appends
    // additional path elements if necessary.
    path.enter()
        .append("path")
        .merge(path)
        .attr("d", arc)
        .attr("fill", (d, i) => {
            return [COLORS.success, COLORS.warning, COLORS.caution][i];
        })
        .attr("transform", "translate(0, 0)");

    // The exit() method defines what should happen if there are more
    // SVG path elements than data elements.  In this case, we simply
    // remove the extra path elements, but we can do more here, such
    // as adding transition effects.
    path.exit().remove();
}

Помните, что Весь код находится на GitHub Если вы хотите использовать этот проект в качестве шаблона для настройки собственных визуализаций!

Что дальше?

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

Оригинал: “https://dev.to/zchtodd_79/beyond-the-basics-part-v-formik-d3-and-more-3p98”