Compare commits
No commits in common. "dd1518b69cd2d8ef5abb6a242da617dd756fd6ef" and "0cb92510b8a1d182a8fee93c847d9a5db5729bb6" have entirely different histories.
dd1518b69c
...
0cb92510b8
|
@ -1,21 +0,0 @@
|
|||
from flask import Blueprint, render_template, request
|
||||
|
||||
from app.models.collection import Collection
|
||||
from app.extensions import db
|
||||
|
||||
collections = Blueprint("collections", __name__, url_prefix="/collections", template_folder="templates")
|
||||
|
||||
@collections.route("/<int:id>", methods=["DELETE"])
|
||||
def delete_collection(id: int):
|
||||
|
||||
collection: Collection = Collection.query.get_or_404(id)
|
||||
|
||||
length = len(Collection.query.all()) - 1
|
||||
|
||||
db.session.delete(collection)
|
||||
db.session.commit()
|
||||
|
||||
if length <= 0:
|
||||
return "<small class=\"form-text\">Здесь ничего нет</small>", 200
|
||||
|
||||
return "", 204
|
|
@ -1,18 +0,0 @@
|
|||
<div data-collection-id="{{ collection.id }}" class="card">
|
||||
<div class="card-header"><h5>{{ collection.name }}</h5></div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">Кол-во пользрвателей: {{ collection.users|length }}</li>
|
||||
</ul>
|
||||
<form action="/api/collections/{{ collection.id }}" method="DELETE" class="card-footer px-2 pt-2">
|
||||
<button
|
||||
type="submit"
|
||||
hx-delete="/api/collections/{{ collection.id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-target='[data-collection-id="{{ collection.id }}"]'
|
||||
hx-confirm="Вы уверены, что хотите удалить коллекцию?"
|
||||
class="btn btn-outline-danger"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
<div id="cards-grid" class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4">
|
||||
{% for collection in collections %}
|
||||
<div class="col mb-3">
|
||||
{% include "collections/card.j2" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<small class="form-text">Здесь ничего нет.</small>
|
||||
{% endfor %}
|
||||
</div>
|
|
@ -3,11 +3,9 @@ from flask import Blueprint
|
|||
from .tasks.routes import tasks
|
||||
from .sessions.routes import sessions
|
||||
from .users.routes import users
|
||||
from .collections.routes import collections
|
||||
|
||||
api = Blueprint("api", __name__, url_prefix="/api")
|
||||
|
||||
api.register_blueprint(users)
|
||||
api.register_blueprint(tasks)
|
||||
api.register_blueprint(sessions)
|
||||
api.register_blueprint(collections)
|
|
@ -41,7 +41,7 @@ async def parse_users(session_id: int):
|
|||
|
||||
@users.route("/<int:session_id>", methods=["GET"])
|
||||
def get_users(session_id: int):
|
||||
users = [] #Session.query.get_or_404(session_id)
|
||||
users = Session.query.get_or_404(session_id).users
|
||||
|
||||
return render_template("user_cards.j2", users=users)
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ from flask import Blueprint, render_template
|
|||
from app.blueprints.api.sessions.routes import get_sessions
|
||||
from app.blueprints.api.users.routes import get_users
|
||||
from app.blueprints.api.tasks.routes import get_tasks
|
||||
from app.models.collection import Collection
|
||||
|
||||
frontend = Blueprint("frontend", __name__, url_prefix="/", template_folder="templates", static_folder="static", static_url_path="/static/frontend")
|
||||
|
||||
|
@ -22,8 +21,3 @@ def tasks():
|
|||
@frontend.route("/parse/<int:id>")
|
||||
def parse(id: int):
|
||||
return render_template("parse.j2", session_id=id, users_template=get_users(id))
|
||||
|
||||
@frontend.route("/collections")
|
||||
def collections():
|
||||
collections = Collection.query.all()
|
||||
return render_template("collections.j2", collections=collections)
|
|
@ -1,36 +0,0 @@
|
|||
const tabs_lists = document.querySelectorAll('.nav-tabs[role="tablist"]');
|
||||
let tabs = [];
|
||||
|
||||
tabs_lists.forEach((tabs_list) => {
|
||||
tabs = [...tabs, ...tabs_list.querySelectorAll('[role="tab"]')];
|
||||
})
|
||||
|
||||
function setInputsState(section, disabled = false) {
|
||||
section.querySelectorAll('input, textarea, button, select').forEach((el) => {
|
||||
// console.log(el);
|
||||
el.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
function tabSwitchHandler(event) {
|
||||
const id_content_hide = event.target.getAttribute('data-bs-target');
|
||||
const id_content_show = event.relatedTarget.getAttribute('data-bs-target');
|
||||
|
||||
// console.log('==== Hide ====');
|
||||
const content_hide = document.querySelector(id_content_hide);
|
||||
setInputsState(content_hide, true);
|
||||
|
||||
// console.log('==== Show ====');
|
||||
const content_show = document.querySelector(id_content_show);
|
||||
setInputsState(content_show, false);
|
||||
}
|
||||
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener('hide.bs.tab', tabSwitchHandler);
|
||||
|
||||
if (tab.getAttribute('aria-selected') == "false") {
|
||||
let id_content_hide = tab.getAttribute('data-bs-target');
|
||||
let content_hide = document.querySelector(id_content_hide);
|
||||
setInputsState(content_hide, true);
|
||||
}
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.j2" %}
|
||||
|
||||
{% block title %}
|
||||
Вход в аккаунт
|
||||
PaperParser: Вход в аккаунт
|
||||
{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>PaperParser: {% block title %}{% endblock %}</title>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
@ -14,22 +14,20 @@
|
|||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column vh-100">
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
<header>
|
||||
<!-- place navbar here -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-3">
|
||||
<nav class="navbar navbar-expand-sm navbar-light bg-light mb-3">
|
||||
<div class="container justify-content-beetwen">
|
||||
<a class="navbar-brand" href="/">PaperParser</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item"><a class="nav-link" href="/collections">Пользователи</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/tasks">Задачи</a></li>
|
||||
<li class="nav-item"><a class="btn btn-outline-success ms-2" href="/add" role="button">Войти</a></li>
|
||||
<ul class="nav">
|
||||
<li><a class="nav-link" href="/tasks">Задачи</a></li>
|
||||
<li><a class="btn btn-outline-success" href="/add" role="button">Войти</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="mb-3">
|
||||
<main>
|
||||
{% block main %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
{% extends "base.j2" %}
|
||||
|
||||
{% block title %}
|
||||
Базы пользователей
|
||||
{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="separator">
|
||||
<h2>Базы пользователей</h2>
|
||||
<hr class="divider">
|
||||
</div>
|
||||
{% include "collections/grid.j2" %}
|
||||
{% endblock main %}
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.j2" %}
|
||||
|
||||
{% block title %}
|
||||
Главная
|
||||
PaperParser: Главная
|
||||
{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -1,104 +1,101 @@
|
|||
{% extends "base.j2" %}
|
||||
|
||||
{% block title %}
|
||||
Парсинг
|
||||
PaperParser: Парсинг
|
||||
{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
<form method="post" action="/test" class="container">
|
||||
<div class="row">
|
||||
<div class="container">
|
||||
<!-- Actions -->
|
||||
<section class="col-lg mb-3">
|
||||
<section id="actions-section" class="mb-3">
|
||||
<div class="separator">
|
||||
<h2>Действия</h2>
|
||||
<hr class="divider">
|
||||
</div>
|
||||
<nav class="mb-3">
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
<button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#actions-parse-section" type="button" role="tab" aria-controls="actions-parse-section" aria-selected="true">Сбор</button>
|
||||
<button class="nav-link" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#actions-add-section" type="button" role="tab" aria-controls="actions-add-section" aria-selected="false">Добавление</button>
|
||||
<button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#actions-message-section" type="button" role="tab" aria-controls="actions-message-section" aria-selected="false">Рассылка</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
|
||||
<section class="tab-pane fade show active" id="actions-parse-section">
|
||||
<section id="actions-parse-add-section">
|
||||
<label for="" class="form-label">Парсинг пользователей и добавление в группу</label>
|
||||
<div class="row row-cols-1 row-cols-sm-2">
|
||||
<div class="col mb-3">
|
||||
<form hx-post="/api/users/parse/{{session_id}}" hx-swap="innerHTML" hx-target="#cards-grid" hx-indicator="#loading-spinner" class="input-group" id="group-from-form">
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" name="group" id="group-from-input" placeholder="Группа для парсинга">
|
||||
<input type="text" class="form-control" name="group" id="group-from-input"
|
||||
placeholder="Группа для парсинга">
|
||||
<label for="group-from-input">Группа для парсинга</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab-pane fade" id="actions-add-section">
|
||||
<button type="submit" class="btn btn-outline-primary">Спарсить</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col mb-3">
|
||||
<form hx-post="/api/tasks/session/{{session_id}}" hx-swap="none" hx-indicator="#loading-spinner" class="input-group" id="group-to-form">
|
||||
<div class="form-floating">
|
||||
<input type="text" name="url" class="form-control" id="group-to-input"
|
||||
placeholder="Группа для добавлнеия">
|
||||
placeholder="Группа, в которую нужно добавить">
|
||||
<label for="group-to-input">Группа для добавления</label>
|
||||
</div>
|
||||
<button type="submit" name="task" value="add" class="btn btn-outline-secondary">Добавить</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="tab-pane fade" id="actions-message-section">
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div>
|
||||
<section id="actions-message-section">
|
||||
<form hx-post="/api/tasks/session/{{session_id}}" hx-swap="none" hx-indicator="#loading-spinner" id="message-form">
|
||||
<label for="" class="form-label">Сообщение для пользователей</label>
|
||||
<div class="row row-cols-1 row-cols-sm-2">
|
||||
<div class="col">
|
||||
<div class="mb-3">
|
||||
<textarea class="form-control" name="message" rows="8" id="message-textarea"
|
||||
placeholder="Напишите здесь своё сообщение"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-3">
|
||||
<label for="formFile" class="form-label">Выберите изображение</label>
|
||||
<input class="form-control" name="file" type="file" id="picture-file-input">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" name="task" value="message" class="btn btn-outline-primary" style="width: 100%;">
|
||||
Отправить сообщение
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="col-lg mb-3">
|
||||
<div class="separator">
|
||||
<h2>Настройки</h2>
|
||||
<hr class="divider">
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-floating">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="collection"
|
||||
id="task-name-input"
|
||||
placeholder="Название задачи"
|
||||
>
|
||||
<label for="task-name-input">Название задачи</label>
|
||||
</div>
|
||||
|
||||
<section class="d-flex flex-column gap-3 mb-3">
|
||||
<div class="input-group">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="collection"
|
||||
id="collection-name-input"
|
||||
placeholder="Группа для парсинга"
|
||||
>
|
||||
<label for="collection-name-input">Название базы пользователей</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-primary">Добавить</button>
|
||||
</div>
|
||||
|
||||
<select class="form-select flex-fill" size="3">
|
||||
{% for collection in collections %}
|
||||
<option value="{{collection.id}}">{{ collection.name }}</option>
|
||||
{% else %}
|
||||
<option disabled>Добавьте новую базу пользователей</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</section>
|
||||
<button type="submit" class="w-100 btn btn-primary">Создать задачу</button>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock main %}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='frontend/tabs.js') }}"></script>
|
||||
{% endblock scripts %}
|
||||
<!-- Users -->
|
||||
<section id="users-section" class="mb-3">
|
||||
<div id="separator">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<h2 style="margin-bottom: 0;">Пользователи</h2>
|
||||
<div class="spinner-container htmx-indicator" id="loading-spinner">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-outline-primary disabled" onClick="exportToCSV()">Экспорт</button>
|
||||
<button
|
||||
class="btn btn-outline-danger"
|
||||
hx-delete="/api/users/{{session_id}}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#cards-grid"
|
||||
hx-confirm="Вы уверены, что хотите удалить пользователей для этой сессии?"
|
||||
hx-indicator="#loading-spinner"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<hr class="divider">
|
||||
</div>
|
||||
<div id="cards-grid" class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4">
|
||||
{{ users_template }}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock main %}
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.j2" %}
|
||||
|
||||
{% block title %}
|
||||
Заадчи
|
||||
PaperParser: Заадчи
|
||||
{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.extensions import db
|
||||
|
||||
class Collection(db.Model):
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(unique=True, nullable=False)
|
||||
|
||||
tasks: Mapped[List['Task']] = relationship("Task", back_populates="collection")
|
||||
users: Mapped[List['User']] = relationship("User", cascade="all, delete-orphan", back_populates="collection")
|
|
@ -2,6 +2,7 @@ from typing import List
|
|||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.models.task import Task
|
||||
from app.models.user import User
|
||||
from app.extensions import db
|
||||
|
||||
class Session(db.Model):
|
||||
|
@ -9,6 +10,7 @@ class Session(db.Model):
|
|||
name: Mapped[str] = mapped_column(unique=True)
|
||||
|
||||
authorized: Mapped[bool]
|
||||
users: Mapped[List[User]] = relationship("User", cascade="all, delete-orphan", back_populates="session")
|
||||
|
||||
tasks: Mapped[List[Task]] = relationship("Task", cascade="all, delete-orphan", back_populates="session")
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ from sqlalchemy import Integer, DateTime
|
|||
from sqlalchemy.schema import Column, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from app.extensions import db
|
||||
from app.models.collection import Collection
|
||||
|
||||
class Task(db.Model):
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
|
@ -14,10 +13,6 @@ class Task(db.Model):
|
|||
|
||||
session_id = Column(Integer, ForeignKey("session.id"))
|
||||
session: Mapped['Session'] = relationship("Session", back_populates="tasks")
|
||||
|
||||
collection_id = Column(Integer, ForeignKey("collection.id"))
|
||||
collection: Mapped['Collection'] = relationship('Collection', back_populates='tasks')
|
||||
|
||||
datetime: Mapped[DateTime] = Column(DateTime, default=datetime.datetime.utcnow())
|
||||
status: Mapped[str]
|
||||
status_message: Mapped[str]
|
||||
|
|
|
@ -15,5 +15,5 @@ class User(db.Model):
|
|||
phone: Mapped[str] = mapped_column(nullable=True, unique=True)
|
||||
username: Mapped[str] = mapped_column(nullable=True, unique=True)
|
||||
|
||||
collection_id = Column(Integer, ForeignKey("collection.id"))
|
||||
collection: Mapped["Collection"] = relationship("Collection", back_populates="users")
|
||||
session_id = Column(Integer, ForeignKey("session.id"))
|
||||
session: Mapped["Session"] = relationship("Session", back_populates="users")
|
|
@ -1,51 +0,0 @@
|
|||
"""user collections
|
||||
|
||||
Revision ID: 21750f5bbab1
|
||||
Revises: cdd471f48b0d
|
||||
Create Date: 2024-02-24 22:47:05.197801
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '21750f5bbab1'
|
||||
down_revision = 'cdd471f48b0d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('collection',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('task', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('collection_id', sa.Integer(), nullable=True))
|
||||
batch_op.create_foreign_key(None, 'collection', ['collection_id'], ['id'])
|
||||
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('collection_id', sa.Integer(), nullable=True))
|
||||
batch_op.drop_constraint('user_session_id_fkey', type_='foreignkey')
|
||||
batch_op.create_foreign_key(None, 'collection', ['collection_id'], ['id'])
|
||||
batch_op.drop_column('session_id')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('session_id', sa.INTEGER(), autoincrement=False, nullable=True))
|
||||
batch_op.drop_constraint(None, type_='foreignkey')
|
||||
batch_op.create_foreign_key('user_session_id_fkey', 'session', ['session_id'], ['id'])
|
||||
batch_op.drop_column('collection_id')
|
||||
|
||||
with op.batch_alter_table('task', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='foreignkey')
|
||||
batch_op.drop_column('collection_id')
|
||||
|
||||
op.drop_table('collection')
|
||||
# ### end Alembic commands ###
|
|
@ -1,34 +0,0 @@
|
|||
"""Added name field to collections
|
||||
|
||||
Revision ID: d573f6529ad5
|
||||
Revises: 21750f5bbab1
|
||||
Create Date: 2024-03-06 01:45:54.772732
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'd573f6529ad5'
|
||||
down_revision = '21750f5bbab1'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('collection', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('name', sa.String(), nullable=False))
|
||||
batch_op.create_unique_constraint(None, ['name'])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('collection', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
batch_op.drop_column('name')
|
||||
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in New Issue