Секреты создания Q&A сайта, где ИИ знает, что вам нужно, даже если вы не знаете сами

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Автор hackeryaroslav
Источник xss.is


Создание платформы вопросов и ответов для сообщества разработчиков, подобной Stack Overflow, с интеграцией ИИ — увлекательно, все как мы любим. В данном проекте мы реализуем функционал, который позволяет пользователям задавать вопросы, получать ответы от ИИ, автоматически подбирать теги, а также анализировать и суммировать вопросы. В дополнение, система включает админ-панель и панель модератора, профили пользователей, и обладает современным дизайном. Все это будет реализовано с использованием Flask, снова.

Начнем с разбора основного кода, который отвечает за обработку вопросов и ответов. Эти модели являются ключевыми компонентами платформы, так как именно они представляют основную сущность любого Q&A ресурса. За картинку сразу извиняюсь, игрался с ИИ😂 ( надо ник бы поменять:(, я не хакер и имя даже не мое😁 )


MU-PanyzSWG9rgSgWNbfYg.png



Демо



Разбор файла answer.py

Файл answer.py содержит модель Answer, которая отвечает за хранение и обработку данных, связанных с ответами пользователей на вопросы. Рассмотрим каждую часть этого файла более подробно.

Python: Скопировать в буфер обмена
Код:
from app import db
from datetime import datetime

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

Python: Скопировать в буфер обмена
Код:
class Answer(db.Model):
__tablename__ = "answers"
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
question_id = db.Column(db.Integer, db.ForeignKey("questions.id"))
votes = db.Column(db.Integer, default=0)
is_accepted = db.Column(db.Boolean, default=False)
reported = db.Column(db.Boolean, default=False)

Модель Answer описывает структуру таблицы answers в базе данных. Каждый ответ имеет уникальный идентификатор id, текстовое поле body, временную метку создания timestamp, а также связи с таблицами пользователей (user_id) и вопросов (question_id). Поле votes хранит количество голосов за ответ, is_accepted указывает, был ли ответ принят как лучший, а reported позволяет отметить ответ как неподобающий или спам.

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

Разбор файла question.py

Модель Question определяет структуру таблицы questions и описывает основные свойства и связи, присущие каждому вопросу на платформе.

Python: Скопировать в буфер обмена
Код:
from app import db
from datetime import datetime

question_tags = db.Table(
"question_tags",
db.Column(
"question_id", db.Integer, db.ForeignKey("questions.id"), primary_key=True
),
db.Column("tag_id", db.Integer, db.ForeignKey("tags.id"), primary_key=True),
)

Здесь мы создаем вспомогательную таблицу question_tags для реализации связи многие-ко-многим между вопросами и тегами. Эта таблица поможет нам хранить информацию о том, какие теги привязаны к каждому вопросу.

Python: Скопировать в буфер обмена
Код:
class Question(db.Model):
__tablename__ = "questions"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
body = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
answers = db.relationship("Answer", backref="question", lazy="dynamic")
tags = db.relationship(
"Tag", secondary=question_tags, backref=db.backref("questions", lazy="dynamic")
)
views = db.Column(db.Integer, default=0)
votes = db.Column(db.Integer, default=0)
reported = db.Column(db.Boolean, default=False)
summary = db.Column(db.Text)

Модель Question описывает основную сущность вопроса. Здесь присутствуют поля для хранения идентификатора id, заголовка title, содержания body, временной метки создания timestamp, а также идентификатора пользователя user_id, который задал вопрос. Поле answers устанавливает связь один-ко-многим с моделью Answer, что позволяет привязывать к вопросу множество ответов. Поле tags связано с моделью Tag через таблицу question_tags, что позволяет назначать теги к каждому вопросу.

Python: Скопировать в буфер обмена
Код:
user = db.relationship("User", foreign_keys=[user_id], back_populates="questions")
reported_by_id = db.Column(db.Integer, db.ForeignKey("users.id"))
reported_by_user = db.relationship(
"User", foreign_keys=[reported_by_id], back_populates="reported_questions"
)

Далее, в модели Question, мы добавляем связь с моделью User для хранения информации о том, кто задал вопрос, а также кто его пожаловался.

Разбор файла tag.py

Файл tag.py описывает модель Tag, которая представляет теги, используемые для классификации вопросов.

Python: Скопировать в буфер обмена
Код:
from app import db

class Tag(db.Model):
__tablename__ = "tags"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True, nullable=False)

Здесь модель Tag содержит два поля: id, который уникально идентифицирует каждый тег, и name, который хранит название тега.

Разбор файла user.py

Файл user.py включает в себя модель User, которая описывает пользователей платформы, а также их аутентификацию и авторизацию.

Python: Скопировать в буфер обмена
Код:
from app import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

Мы импортируем db и login_manager из нашего приложения, а также используем UserMixin из Flask-Login для упрощения работы с пользователями и модули из Werkzeug для управления паролями.

Python: Скопировать в буфер обмена
Код:
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(120), unique=True, index=True)
password_hash = db.Column(db.String(128))
reputation = db.Column(db.Integer, default=0)
questions = db.relationship(
"Question",
foreign_keys="Question.user_id",
back_populates="user",
lazy="dynamic",
)
reported_questions = db.relationship(
"Question",
foreign_keys="Question.reported_by_id",
back_populates="reported_by_user",
lazy="dynamic",
)
answers = db.relationship("Answer", backref="author", lazy="dynamic")
is_admin = db.Column(db.Boolean, default=False)
is_moderator = db.Column(db.Boolean, default=False)

Модель User включает основные атрибуты пользователя, такие как username, email, password_hash, и reputation. Она также определяет связи с вопросами и ответами, позволяя хранить информацию о том, какие вопросы и ответы связаны с конкретным пользователем. Поля is_admin и is_moderator определяют, является ли пользователь администратором или модератором.

Python: Скопировать в буфер обмена
Код:
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)

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

Python: Скопировать в буфер обмена
Код:
@login_manager.user_loader
def load_user(id):
return User.query.get(int(id))

Эта функция используется Flask-Login для загрузки пользователя по его идентификатору.

Разбор файла vote.py

Последний файл в этом наборе модел — vote.py, который отвечает за голосование за вопросы и ответы.

Python: Скопировать в буфер обмена
Код:
from app import db

class Vote(db.Model):
__tablename__ = "votes"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
question_id = db.Column(db.Integer, db.ForeignKey("questions.id"))
answer_id = db.Column(db.Integer, db.ForeignKey("answers.id"))
vote_type = db.Column(db.String(4), nullable=False)

Модель Vote содержит поля user_id, question_id, и answer_id, что позволяет связывать голос с конкретным пользователем, вопросом или ответом. Поле vote_type хранит тип голоса, который может быть положительным (up) или отрицательным (down).

Python: Скопировать в буфер обмена
Код:
__table_args__ = (
db.CheckConstraint("(question_id IS NULL) != (answer_id IS NULL)"),
)

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

Файл admin.py

Файл admin.py отвечает за управление пользователями и контентом через административную панель. Этот файл содержит маршруты для редактирования и удаления пользователей, вопросов и тегов, а также предоставляет интерфейс для выполнения этих задач.

Первоначально, давайте взглянем на необходимые импорты и инициализацию:

Python: Скопировать в буфер обмена
Код:
from flask import Blueprint, render_template, request, redirect, url_for, flash
from app import db
from app.models.user import User
from app.models.tag import Tag
from app.models.question import Question

Здесь импортируются модули Flask, которые позволяют управлять маршрутизацией и взаимодействовать с шаблонами. Blueprint используется для группировки маршрутов под префиксом /admin, что упрощает организацию кода. Также мы импортируем объекты моделей для работы с пользователями, тегами и вопросами, что позволит нам выполнять операции с этими сущностями в базе данных.

Редактирование пользователя

Маршрут для редактирования пользователя выглядит следующим образом:

Python: Скопировать в буфер обмена
Код:
@bp.route("/edit_user/<int:user_id>", methods=["GET", "POST"])
def edit_user(user_id):
user = User.query.get_or_404(user_id)

if request.method == "POST":
user.username = request.form["username"]
user.email = request.form["email"]
user.role = request.form["role"]

try:
db.session.commit()
flash("User details updated successfully!", "success")
return redirect(url_for("admin.admin_dashboard"))
except Exception as e:
db.session.rollback()
flash("An error occurred while updating the user.", "error")

return render_template("edit_user.html", user=user)

Этот маршрут позволяет администратору редактировать информацию о пользователе. Сначала происходит поиск пользователя по user_id. Если запрос является POST, данные из формы передаются в соответствующие поля пользователя: имя, email и роль. После этого мы пытаемся сохранить изменения в базе данных с помощью db.session.commit(). Если возникает ошибка, транзакция откатывается, и пользователю показывается сообщение об ошибке.

Удаление пользователя

Следующий маршрут отвечает за удаление пользователя:

Python: Скопировать в буфер обмена
Код:
@bp.route("/delete_user/<int:user_id>", methods=["POST"])
def delete_user(user_id):
user = User.query.get_or_404(user_id)

try:
db.session.delete(user)
db.session.commit()
flash("User deleted successfully!", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while deleting the user.", "error")

return redirect(url_for("main.admin_dashboard"))

Здесь мы также используем user_id для поиска пользователя. Если пользователь найден, он удаляется из базы данных с помощью db.session.delete(user). В случае успешного удаления происходит перенаправление на административную панель с соответствующим сообщением.

Удаление вопросов и тегов

Аналогично пользователям, маршруты для удаления вопросов и тегов работают по тому же принципу:

Python: Скопировать в буфер обмена
Код:
@bp.route("/delete_question/<int:question_id>", methods=["POST"])
def delete_question(question_id):
question = Question.query.get_or_404(question_id)

try:
db.session.delete(question)
db.session.commit()
flash("Question deleted successfully!", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while deleting the question.", "error")

return redirect(url_for("main.admin_dashboard"))

@bp.route("/delete_tag/<int:tag_id>", methods=["POST"])
def delete_tag(tag_id):
tag = Tag.query.get_or_404(tag_id)

try:
db.session.delete(tag)
db.session.commit()
flash("Tag deleted successfully!", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while deleting the tag.", "error")

return redirect(url_for("auth.admin_dashboard"))

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

Файл answers.py

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

Сначала рассмотрим импорты и инициализацию:

Python: Скопировать в буфер обмена
Код:
from flask import Blueprint, redirect, url_for, request, flash
from flask_login import login_required, current_user
from app import db
from app.models.question import Question
from app.models.answer import Answer
from app.models.vote import Vote
from app.utils.ai_helper import get_ai_answer_review

В этом файле используется Blueprint для создания группы маршрутов, связанных с ответами. login_required декоратор из flask_login гарантирует, что только авторизованные пользователи могут добавлять и голосовать за ответы. Модели Question, Answer, и Vote импортируются для работы с соответствующими данными в базе, а функция get_ai_answer_review из ai_helper используется для получения анализа ответа с помощью ИИ.

Добавление ответа

Маршрут для добавления ответа на вопрос выглядит следующим образом:

Python: Скопировать в буфер обмена
Код:
@bp.route("/question/<int:question_id>/answer", methods=["POST"])
@login_required
def post_answer(question_id):
question = Question.query.get_or_404(question_id)
answer_body = request.form["answer_body"]

answer = Answer(body=answer_body, author=current_user, question=question)
db.session.add(answer)
db.session.commit()

ai_review = get_ai_answer_review(question.body, answer_body)
flash(f"Your answer has been posted! AI Review: {ai_review}")

return redirect(url_for("questions.question_detail", question_id=question_id))

Этот маршрут позволяет пользователю добавлять ответы на вопросы. Сначала выполняется поиск вопроса по question_id, а затем из формы извлекается текст ответа. Создается объект Answer, который привязывается к текущему пользователю (author=current_user) и вопросу. После сохранения ответа в базе данных с помощью db.session.commit(), вызывается функция get_ai_answer_review, которая анализирует ответ с помощью ИИ. Результат анализа отображается пользователю через flash-сообщение, после чего происходит перенаправление обратно на страницу вопроса.

Голосование за ответ

Следующий маршрут отвечает за голосование за ответ:

Python: Скопировать в буфер обмена
Код:
@bp.route("/answer/<int:answer_id>/vote", methods=["POST"])
@login_required
def vote_answer(answer_id):
answer = Answer.query.get_or_404(answer_id)
vote_type = request.form["vote_type"]

existing_vote = Vote.query.filter_by(
user_id=current_user.id, answer_id=answer_id
).first()

if existing_vote:
if existing_vote.vote_type != vote_type:
answer.votes += 2 if vote_type == "up" else -2
existing_vote.vote_type = vote_type
else:
answer.votes += -1 if vote_type == "up" else 1
db.session.delete(existing_vote)
else:
new_vote = Vote(
user_id=current_user.id, answer_id=answer_id, vote_type=vote_type
)
db.session.add(new_vote)
answer.votes += 1 if vote_type == "up" else -1

db.session.commit()
return redirect(
url_for("questions.question_detail", question_id=answer.question_id)
)

Этот маршрут позволяет пользователям голосовать за ответы, улучшая качество контента на платформе. После нахождения ответа по answer_id, проверяется, существует ли уже голос от текущего пользователя за данный ответ. Если голос существует и его тип отличается от нового голоса, то счетчик голосов ответа корректируется, и тип голоса обновляется. Если голос уже совпадает с новым, то голос удаляется, уменьшая счетчик голосов. Если голос отсутствует, создается новый объект Vote, который добавляется в базу данных. После всех изменений, результат сохраняется в базе данных, и пользователь перенаправляется обратно на страницу вопроса. Не лучшее решение, ведь это занимает какое-то время чтобы сохранится.

Принятие ответа

Последний маршрут в этом файле позволяет автору вопроса принять ответ:

Python: Скопировать в буфер обмена
Код:
@bp.route("/answer/<int:answer_id>/accept", methods=["POST"])
@login_required
def accept_answer(answer_id):
answer = Answer.query.get_or_404(answer_id)
question = answer.question

if current_user != question.author:
flash("You can only accept answers to your own questions.")
return redirect(url_for("questions.question_detail", question_id=question.id))

for a in question.answers:
a.is_accepted = False

answer.is_accepted = True
db.session.commit()

flash("Answer has been accepted.")
return redirect(url_for("questions.question_detail", question_id=question.id))

Этот маршрут позволяет автору вопроса принять один из предложенных ответов. После нахождения ответа по answer_id, проверяется, является ли текущий пользователь автором вопроса. Если нет, пользователю показывается сообщение об ошибке. Если да, то все ответы на вопрос помечаются как непринятые, а выбранный ответ отмечается как принятый. Изменения сохраняются в базе данных, и пользователь перенаправляется обратно на страницу вопроса. Функцию можно в дальнейшем применить на стороне клиента (но пока это решает админ).

Файл auth.py

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

Импорт библиотек и инициализация

Python: Скопировать в буфер обмена
Код:
import os
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from werkzeug.security import check_password_hash, generate_password_hash
from app.models.user import User
from app import db
from app.models.question import Question
from app.models.tag import Tag
from app.utils.decorators import admin_required, moderator_required

Здесь импортируются необходимые модули и библиотеки. Blueprint используется для создания группы маршрутов, связанных с аутентификацией. login_user, logout_user, и login_required из flask_login управляют процессом аутентификации и авторизации. check_password_hash и generate_password_hash из werkzeug.security предназначены для проверки и создания хэшей паролей. Импортируются модели User, Question, и Tag, а также декораторы admin_required и moderator_required для контроля доступа.

Регистрация пользователя

Маршрут для регистрации нового пользователя:

Python: Скопировать в буфер обмена
Код:
@bp.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form["username"]
email = request.form["email"]
password = request.form["password"]

user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()

flash("Registration successful. Please log in.")
return redirect(url_for("auth.login"))

return render_template("register.html")

Этот маршрут обрабатывает регистрацию нового пользователя. При отправке POST-запроса из формы регистрации извлекаются username, email, и password. Создается новый объект User, и пароль хэшируется с помощью метода set_password. После добавления пользователя в базу данных и сохранения изменений, пользователю показывается сообщение о успешной регистрации и происходит перенаправление на страницу входа.

Вход в систему

Маршрут для входа в систему:

Python: Скопировать в буфер обмена
Код:
@bp.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
login_type = request.form["login_type"]
user = User.query.filter_by(username=username).first()

if user and user.check_password(password):
login_user(user)

if login_type == "admin":
if user.is_admin:
return redirect(url_for("auth.admin_dashboard"))
else:
flash("You do not have admin access.")
elif login_type == "moderator":
if user.is_moderator:
return redirect(url_for("auth.moderate"))
else:
flash("You do not have moderator access.")
else:
return redirect(url_for("main.index"))
else:
flash("Invalid username or password.")

return render_template("login.html")

Этот маршрут обрабатывает вход пользователя в систему. При отправке POST-запроса из формы извлекаются username, password, и login_type. Проверяется, существует ли пользователь с таким именем и правильность пароля. Если проверка проходит успешно, пользователь аутентифицируется с помощью login_user. В зависимости от типа входа (admin или moderator), происходит перенаправление на соответствующую панель управления. Если пользователь не имеет соответствующих прав, отображается сообщение о недостатке доступа. Если данные неверны, выводится сообщение об ошибке.

Выход из системы

Маршрут для выхода из системы:

Python: Скопировать в буфер обмена
Код:
@bp.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for("main.index"))

Этот маршрут позволяет пользователю выйти из системы. После выхода из системы происходит перенаправление на главную страницу платформы.

Административная панель

Маршрут для доступа к административной панели:

Python: Скопировать в буфер обмена
Код:
@bp.route("/dashboard")
@login_required
@admin_required
def admin_dashboard():
users = User.query.all()
questions = Question.query.all()
tags = Tag.query.all()
return render_template(
"admin_dashboard.html", users=users, questions=questions, tags=tags
)

Этот маршрут доступен только администраторам, и предоставляет им доступ к информации о всех пользователях, вопросах и тегах на платформе. Данные извлекаются из базы данных и передаются в шаблон admin_dashboard.html, который отображает административную панель.

Панель модератора

Маршрут для доступа к панели модератора:

Python: Скопировать в буфер обмена
Код:
@bp.route("/moderate")
@login_required
@moderator_required
def moderate():
reported_questions = Question.query.filter(Question.reported == True).all()
return render_template(
"moderate.html",
reported_questions=reported_questions,
)

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

Сообщение о нарушении

Маршрут для сообщения о нарушении:

Python: Скопировать в буфер обмена
Код:
@bp.route("/report_question/<int:question_id>", methods=["POST"])
@login_required
def report_question(question_id):
question = Question.query.get_or_404(question_id)
if not question.reported:
question.reported = True
question.reported_by_id = current_user.id
try:
db.session.commit()
flash("The question has been reported to the moderators.", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while reporting the question.", "error")
else:
flash("This question has already been reported.", "warning")

return redirect(url_for("main.index"))

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

Файл moderator.py

Файл moderator.py реализует функциональность, необходимую модераторам для управления вопросами, которые были отмечены пользователями. Он предоставляет маршруты для просмотра, одобрения и удаления вопросов. Рассмотрим подробнее, как организована эта часть системы.

Импорт библиотек и инициализация


Python: Скопировать в буфер обмена
Код:
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user
from app import db
from app.models.question import Question
from app.models.answer import Answer
from app.utils.decorators import moderator_required

Здесь импортируются необходимые библиотеки и модули. Blueprint используется для создания маршрутов модератора. Модули из flask_login управляют доступом к маршрутам только для вошедших в систему пользователей. Модели Question и Answer из app.models позволяют взаимодействовать с данными о вопросах и ответах в базе данных. Декоратор moderator_required используется для ограничения доступа к определенным маршрутам только для модераторов.

Просмотр вопросов, требующих модерации


Python: Скопировать в буфер обмена
Код:
@bp.route("/moderate")
@login_required
@moderator_required
def moderate():
reported_questions = Question.query.filter(Question.reported == True).all()
return render_template(
"moderate.html",
reported_questions=reported_questions,
)

Этот маршрут позволяет модераторам просматривать все вопросы, которые были помечены пользователями как нарушающие правила. Используется фильтрация по полю reported, чтобы получить список таких вопросов. Данные передаются в шаблон moderate.html, где модераторы могут просматривать и обрабатывать эти вопросы.

Одобрение вопроса


Python: Скопировать в буфер обмена
Код:
@bp.route("/approve_question/<int:question_id>", methods=["POST"])
@moderator_required
def approve_question(question_id):
question = Question.query.get_or_404(question_id)
question.reported = False
question.reported_by = None
try:
db.session.commit()
flash("Question approved successfully!", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while approving the question.", "error")
return redirect(url_for("moderator.moderate"))

Этот маршрут позволяет модераторам одобрить вопрос. При отправке POST-запроса из формы, вопрос, который был помечен как нарушающий правила, обновляется: флаг reported сбрасывается, и поле reported_by очищается. Изменения сохраняются в базе данных. Если процесс проходит успешно, пользователю показывается сообщение об успешном одобрении. В случае ошибки транзакция откатывается, и отображается сообщение об ошибке. После обработки вопроса происходит перенаправление на страницу модерации.

Удаление вопроса


Python: Скопировать в буфер обмена
Код:
@bp.route("/delete_question/<int:question_id>", methods=["POST"])
@moderator_required
def delete_question(question_id):
question = Question.query.get_or_404(question_id)
try:
db.session.delete(question)
db.session.commit()
flash("Question deleted successfully!", "success")
except Exception as e:
db.session.rollback()
flash("An error occurred while deleting the question.", "error")
return redirect(url_for("moderator.moderate"))

Этот маршрут позволяет модераторам удалить вопрос из системы. При отправке POST-запроса осуществляется поиск вопроса по question_id. Если вопрос найден, он удаляется из базы данных. Если операция проходит успешно, пользователю показывается сообщение об успешном удалении. В случае ошибки транзакция откатывается, и выводится сообщение об ошибке. После удаления вопроса происходит перенаправление на страницу модерации.

Файл questions.py

Файл questions.py реализует маршруты для работы с вопросами на платформе. Этот файл охватывает функционал для работы и отображения деталей вопроса. Давайте разберем его более подробно.

Импорт библиотек и инициализация

Python: Скопировать в буфер обмена
Код:
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_required, current_user
from app import db
from app.models.question import Question
from app.models.tag import Tag
from app.models.vote import Vote
from app.utils.ai_helper import (
get_ai_insights,
get_ai_suggested_tags,
get_ai_question_summary,
get_ai_answer_ranking,
get_ai_similar_questions,
)

Здесь импортируются необходимые модули и функции. Blueprint используется для создания маршрутов для работы с вопросами. flask_login управляет доступом к маршрутам. Модели Question, Tag, и Vote из app.models используются для работы с данными вопросов, тегов и голосов. Функции из app.utils.ai_helper предоставляют ИИ-функционал, такой как генерация тегов и ранжирование ответов.

Создание нового вопроса

Python: Скопировать в буфер обмена
Код:
@bp.route("/ask", methods=["GET", "POST"])
@login_required
def ask_question():
if request.method == "POST":
title = request.form["title"]
body = request.form["body"]

suggested_tags = get_ai_suggested_tags(title, body)

summary = get_ai_question_summary(title, body)
if summary is None:
summary = "Unable to generate summary at this time."

question = Question(
title=title, body=body, user_id=current_user.id, summary=summary
)

tags = (
request.form["tags"].split(",") if request.form["tags"] else suggested_tags
)

for tag_name in tags:
tag = Tag.query.filter_by(name=tag_name.strip()).first()
if not tag:
tag = Tag(name=tag_name.strip())
question.tags.append(tag)

db.session.add(question)
db.session.commit()

flash("Your question has been posted!")
return redirect(url_for("questions.question_detail", question_id=question.id))

return render_template("ask_question.html")

Этот маршрут позволяет пользователям создавать новые вопросы. При отправке формы POST данные извлекаются из полей title и body. AI-сервисы генерируют предложенные теги и краткое содержание вопроса. Вопрос добавляется в базу данных, а теги либо предоставляются пользователем, либо сгенерированы AI. После сохранения пользователю показывается сообщение об успешной публикации вопроса.

Голосование за вопрос

Python: Скопировать в буфер обмена
Код:
@bp.route("/question/<int:question_id>/vote", methods=["POST"])
@login_required
def vote_question(question_id):
question = Question.query.get_or_404(question_id)
vote_type = request.form["vote_type"]

existing_vote = Vote.query.filter_by(
user_id=current_user.id, question_id=question_id
).first()

if existing_vote:
if existing_vote.vote_type != vote_type:
question.votes += 2 if vote_type == "up" else -2
existing_vote.vote_type = vote_type
else:
question.votes += -1 if vote_type == "up" else 1
db.session.delete(existing_vote)
else:
new_vote = Vote(
user_id=current_user.id, question_id=question_id, vote_type=vote_type
)
db.session.add(new_vote)
question.votes += 1 if vote_type == "up" else -1

db.session.commit()
return redirect(url_for("questions.question_detail", question_id=question.id))

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

Просмотр деталей вопроса

Python: Скопировать в буфер обмена
Код:
@bp.route("/question/<int:question_id>")
def question_detail(question_id):
question = Question.query.get_or_404(question_id)
question.views += 1
db.session.commit()

ai_insights = get_ai_insights(question.title, question.body)
if ai_insights is None:
ai_insights = "Unable to generate AI insights at this time."

if question.summary is None:
question.summary = get_ai_question_summary(question.title, question.body)
if question.summary is None:
question.summary = "Unable to generate summary at this time."
db.session.commit()

answers_list = list(question.answers)

ranking = get_ai_answer_ranking(question.body, answers_list)

ranked_answers = [
answers_list[i - 1] for i in ranking if 1 <= i <= len(answers_list)
]

similar_question_ids = get_ai_similar_questions(
question.title, question.body, Question.query.all()
)
similar_questions = Question.query.filter(
Question.id.in_(similar_question_ids)
).all()

return render_template(
"question_detail.html",
question=question,
ai_insights=ai_insights,
ranked_answers=ranked_answers,
similar_questions=similar_questions,
)

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

Файл main.js

Файл main.js включает в себя функциональные возможности, такие как плавная прокрутка, динамическое отображение кнопки "Назад вверх", анимации на прокрутку, валидацию форм и управление навигационным меню. Также реализована функциональность для предложения тегов и выделения лучших ответов. Рассмотрим подробно каждую часть этого файла.

Плавная прокрутка

Первый блок кода добавляет плавную прокрутку к якорным ссылкам. После загрузки документа (DOMContentLoaded), скрипт находит все ссылки, начинающиеся с #, и добавляет к ним обработчик клика. При клике на такую ссылку, прокрутка страницы к указанному элементу происходит плавно.

JavaScript: Скопировать в буфер обмена
Код:
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
document.querySelector(this.getAttribute("href")).scrollIntoView({
behavior: "smooth",
});
});
});
});

Кнопка "Назад вверх"

Следующий функционал добавляет кнопку "Назад вверх", которая появляется при прокрутке страницы вниз и исчезает, когда пользователь возвращается вверх. Кнопка создается и добавляется в документ, а также настроен обработчик событий для управления её видимостью на основе положения прокрутки страницы. При клике на кнопку происходит плавный скролл к верху страницы.

JavaScript: Скопировать в буфер обмена
Код:
const backToTopButton = document.createElement("button");
backToTopButton.textContent = "↑";
backToTopButton.classList.add("back-to-top");
backToTopButton.style.display = "none";
document.body.appendChild(backToTopButton);

window.addEventListener("scroll", () => {
if (window.pageYOffset > 100) {
backToTopButton.style.display = "block";
} else {
backToTopButton.style.display = "none";
}
});

backToTopButton.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
});

Анимации при прокрутке

Код также включает в себя анимации элементов при прокрутке страницы. Функция animateOnScroll проверяет видимость элементов с классами .ai-insights. Если элемент виден на экране, к нему добавляется класс animate-in, который управляет анимацией. Эта функция вызывается при прокрутке страницы и при загрузке страницы, чтобы обеспечить анимацию уже видимых элементов.

JavaScript: Скопировать в буфер обмена
Код:
const animateOnScroll = () => {
const elements = document.querySelectorAll(" .ai-insights");
elements.forEach((element) => {
const elementTop = element.getBoundingClientRect().top;
const elementBottom = element.getBoundingClientRect().bottom;
if (elementTop < window.innerHeight && elementBottom > 0) {
element.classList.add("animate-in");
}
});
};

window.addEventListener("scroll", animateOnScroll);
animateOnScroll();

Валидация форм

Код также включает валидацию форм. При отправке формы скрипт проверяет все обязательные поля и добавляет класс error к тем полям, которые не заполнены. Это позволяет пользователю легко увидеть, какие поля требуют заполнения.

JavaScript: Скопировать в буфер обмена
Код:
const forms = document.querySelectorAll("form");
forms.forEach((form) => {
form.addEventListener("submit", (e) => {
const requiredFields = form.querySelectorAll("[required]");
requiredFields.forEach((field) => {
if (!field.value) {
e.preventDefault();
field.classList.add("error");
} else {
field.classList.remove("error");
}
});
});
});

Управление навигационным меню

Для управления навигационным меню добавлен переключатель меню. Кнопка создается динамически и добавляется в DOM перед элементом списка навигации. При клике на эту кнопку меню переключается между показанным и скрытым состоянием. Срабатывает когда девайс мобильный телефон.

JavaScript: Скопировать в буфер обмена
Код:
const menuToggle = document.createElement("button");
menuToggle.textContent = "☰";
menuToggle.classList.add("menu-toggle");
const nav = document.querySelector("nav ul");
nav.parentNode.insertBefore(menuToggle, nav);

menuToggle.addEventListener("click", () => {
nav.classList.toggle("show");
});

Предложение тегов

В конце файла добавляется функциональность для предложения тегов. К кнопке "Suggest Tags" привязан обработчик событий, который при нажатии отправляет запрос на сервер с заголовком и текстом вопроса. Полученные теги отображаются в поле ввода тегов.

JavaScript: Скопировать в буфер обмена
Код:
const tagInput = document.getElementById("tags");
const suggestTagsButton = document.createElement("button");
suggestTagsButton.textContent = "Suggest Tags";
suggestTagsButton.classList.add("btn", "btn-secondary", "mt-2");
tagInput.parentNode.insertBefore(suggestTagsButton, tagInput.nextSibling);

suggestTagsButton.addEventListener("click", async (e) => {
e.preventDefault();
const title = document.getElementById("title").value;
const body = document.getElementById("body").value;

try {
const response = await fetch("/api/suggest_tags", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title, body }),
});
const data = await response.json();
tagInput.value = data.tags.join(", ");
} catch (error) {
console.error("Error suggesting tags:", error);
}
});

Выделение лучших ответов

Последний блок кода выделяет лучший ответ на вопрос, если он присутствует. Он ищет ответ с атрибутом data-rank="1" (декоративный) и добавляет к нему класс top-answer, который может использоваться для особого оформления.

JavaScript: Скопировать в буфер обмена
Код:
const topAnswer = document.querySelector('.answer[data-rank="1"]');
if (topAnswer) {
topAnswer.classList.add("top-answer");
}

Файл base.html

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

Основные элементы шаблона

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

HTML: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}Dev Q&A Platform{% endblock %}</title>

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

Подключение стилей

В разделе <head> подключаются CSS-файлы, которые определяют внешний вид различных компонентов и макета сайта. Используются как общие стили, так и более специализированные для различных элементов интерфейса.

HTML: Скопировать в буфер обмена
Код:
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/base.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/components/_buttons.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/components/_forms.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/components/_navigation.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/components/_tags.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/components/_typography.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/components/_utilities.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/layout/_footer.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/layout/_header.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/layout/_main.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/utilities/_animations.css') }}"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/utilities/_breakpoints.css') }}"
/>
</head>

Заголовок и навигация

В разделе <body> определены элементы заголовка и навигации. В <header> содержится навигационное меню и форма поиска.

HTML: Скопировать в буфер обмена
Код:
<header>
<nav>
<ul>
<li><a href="{{ url_for('main.index') }}">Home</a></li>
{% if current_user.is_authenticated %}
<li>
<a href="{{ url_for('questions.ask_question') }}">Ask Question</a>
</li>
<li>
<a
href="{{ url_for('main.user_profile', username=current_user.username) }}"
>Profile</a
>
</li>
<li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
{% else %}
<li><a href="{{ url_for('auth.login') }}">Login</a></li>
<li><a href="{{ url_for('auth.register') }}">Register</a></li>
{% endif %}
</ul>
</nav>
<form
action="{{ url_for('main.search') }}"
method="get"
class="search-form"
>
<input type="text" name="q" placeholder="Search questions..." />
<button type="submit">Search</button>
</form>
</header>

Меню навигации меняется в зависимости от статуса аутентификации пользователя. Если пользователь вошел в систему, отображаются ссылки на страницы "Задать вопрос", "Профиль" и "Выход". Если нет, отображаются ссылки на "Вход" и "Регистрацию". Форма поиска позволяет пользователям искать вопросы по ключевым словам.

Основное содержимое

Основное содержимое страницы определяется в блоке {% block content %}, который будет переопределен в других шаблонах, расширяющих этот базовый шаблон.

HTML: Скопировать в буфер обмена
Код:
<main class="container">
{% with messages = get_flashed_messages() %} {% if messages %}
<div class="flashes">
{% for message in messages %}
<div class="flash">{{ message }}</div>
{% endfor %}
</div>
{% endif %} {% endwith %} {% block content %}{% endblock %}
</main>

Функция get_flashed_messages() используется для отображения временных сообщений, таких как сообщения об успешных действиях или ошибках. Можно также украсить стилями, я решил оставить это как есть.

Подвал сайта

В подвале (<footer>) отображается навигационное меню с дополнительными ссылками и иконками социальных сетей.

HTML: Скопировать в буфер обмена
Код:
<footer>
<div class="container">
<div class="footer-nav">
<ul>
<li><a href="{{ url_for('main.index') }}">Home</a></li>
<li><a href="#">About Us</a></li>
<li><a href="#">Contact</a></li>
<li><a href="#">FAQ</a></li>
</ul>
</div>
<div class="social-icons">
<a href="#"><i class="fab fa-facebook-f"></i></a>
<a href="#"><i class="fab fa-twitter"></i></a>
<a href="#"><i class="fab fa-linkedin-in"></i></a>
</div>
<p>&copy; 2024 Dev Q&A Platform. All rights reserved.</p>
</div>
</footer>

Этот раздел предоставляет пользователям дополнительную информацию о сайте и ссылки на страницы, такие как "О нас", "Контакт" и "Часто задаваемые вопросы". Иконки социальных сетей позволяют пользователям быстро перейти на профили компании в социальных сетях.

Подключение скриптов

Наконец, перед закрывающим тегом </body> подключается JavaScript-файл main.js, который содержит клиентский код для взаимодействия с пользовательским интерфейсом. Разобрали его выше.

HTML: Скопировать в буфер обмена
Код:
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>

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

Файл admin_dashboard.html

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

Расширение базового шаблона

Шаблон начинается с расширения базового шаблона base.html, который содержит общую структуру и стили для всего сайта. В блоке content определяется основное содержимое страницы административной панели.

HTML: Скопировать в буфер обмена
Код:
{% extends "base.html" %}
{% block content %}

Управление пользователями

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

HTML: Скопировать в буфер обмена
Код:
<h2>Users</h2>
<table class="table">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.role }}</td>
<td>
<a
href="{{ url_for('admin.edit_user', user_id=user.id) }}"
class="btn btn-secondary"
>Edit</a
>
|
<form
action="{{ url_for('admin.delete_user', user_id=user.id) }}"
method="post"
style="display: inline"
>
<button
type="submit"
class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this user?')"
>
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>

Каждая строка таблицы представляет одного пользователя. Кнопка "Edit" перенаправляет на страницу редактирования пользователя, а форма с кнопкой "Delete" отправляет запрос на удаление пользователя после подтверждения действия.

Управление вопросами

Следующий раздел посвящен управлению вопросами. В этой таблице отображаются заголовки вопросов и авторы. Также предусмотрена форма для удаления вопросов.

HTML: Скопировать в буфер обмена
Код:
<h2>Questions</h2>
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for question in questions %}
<tr>
<td>{{ question.title }}</td>
<td>{{ question.user.username }}</td>
<td>
<form
action="{{ url_for('admin.delete_question', question_id=question.id) }}"
method="post"
style="display: inline"
>
<button
type="submit"
class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this question?')"
>
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>

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

Управление тегами

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

HTML: Скопировать в буфер обмена
Код:
<h2>Tags</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for tag in tags %}
<tr>
<td>{{ tag.name }}</td>
<td>
<form
action="{{ url_for('admin.delete_tag', tag_id=tag.id) }}"
method="POST"
style="display: inline"
>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button
type="submit"
class="btn btn-danger"
onclick="return confirm('Are you sure?')"
>
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>

Таблица тегов позволяет администратору удалять теги с помощью кнопки "Delete", также требующей подтверждения.

Файл edit_user.html

Файл edit_user.html используется для редактирования информации о пользователе. В блоке content отображается форма для обновления данных пользователя.

HTML: Скопировать в буфер обмена
Код:
<!-- edit_user.html -->

{% extends "base.html" %}

{% block content %}
<h1>Edit User</h1>
<div class="form-container">
    <form method="POST">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        <div class="form-group">
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" value="{{ user.username }}" required>
        </div>
        
        <div class="form-group">
            <label for="email">Email:</label>
            <input type="email" id="email" name="email" value="{{ user.email }}" required>
        </div>
        
        <div class="form-group">
            <label for="role">Role:</label>
            <select id="role" name="role">
                <option value="user" {% if user.role == 'user' %}selected{% endif %}>User</option>
                <option value="moderator" {% if user.role == 'moderator' %}selected{% endif %}>Moderator</option>
                <option value="admin" {% if user.role == 'admin' %}selected{% endif %}>Admin</option>
            </select>
        </div>
        
        <button type="submit" class="btn">Update User</button>
    </form>   
    <a href="{{ url_for('auth.admin_dashboard') }}" class="btn btn-secondary">Back to Dashboard</a>
</div>
{% endblock %}

Опять же, форма включает скрытое поле для CSRF-токена, что защищает от атак межсайтовой подделки запросов. Поля формы включают ввод для имени пользователя, электронной почты и выбора роли (пользователь, модератор или администратор). Эти данные будут обновлены в базе данных при отправке формы. Форма завершается кнопкой "Update User" для отправки данных и ссылкой для возврата на панель управления администратора.

Файл index.html

Файл index.html представляет собой главную страницу приложения, где пользователи могут увидеть последние вопросы.

HTML: Скопировать в буфер обмена
Код:
<!-- index.html -->

{% extends "base.html" %} {% block content %}
<section class="hero">
  <div class="container">
    <h1>Find Solutions, Share Expertise</h1>
    <p>
      Connect with fellow developers, get answers, and level up your coding
      skills.
    </p>
    <a href="{{ url_for('questions.ask_question') }}" class="btn"
      >Ask a Question</a
    >
  </div>
</section>

<div class="container">
  <h1>Latest Questions</h1>
  <div class="question-list">
    {% for question in questions %}
    <div class="question-summary">
      <h2>
        <a
          href="{{ url_for('questions.question_detail', question_id=question.id) }}"
        >
          {{ question.title }}
        </a>
      </h2>
      <p>
        {{ question.body[:200] }}{% if question.body|length > 200 %}...{% endif
        %}
      </p>
      <div class="question-meta">
        <span>Asked by {{ question.user.username }}</span>
        <span>{{ question.timestamp.strftime('%Y-%m-%d %H:%M') }}</span>
        <span>{{ question.views }} views</span>
        <span>{{ question.votes }} votes</span>
      </div>
      <div class="question-tags">
        {% for tag in question.tags %}
        <span class="tag">{{ tag.name }}</span>
        {% endfor %}
      </div>
    </div>
    {% endfor %}
  </div>
</div>
{% endblock %}

На главной странице представлена секция "hero" с приветственным сообщением и кнопкой для создания нового вопроса. Ниже идет список последних вопросов, каждый из которых отображается в виде краткого резюме. Включены ссылки на детали каждого вопроса, информация о пользователе, который задал вопрос, метка времени, количество просмотров и голосов. Теги, связанные с каждым вопросом, также отображаются, что помогает пользователям быстро понять тему вопроса и найти вопросы по интересующим их темам.

Файл login.html

Шаблон login.html предназначен для входа в систему. Он содержит форму, в которую пользователи вводят свои учетные данные — имя пользователя и пароль. В форме также есть выпадающий список для выбора типа входа (пользователь, администратор или модератор), что позволяет системе идентифицировать и аутентифицировать пользователя.

HTML: Скопировать в буфер обмена
Код:
<!-- login.html -->

{% extends "base.html" %} {% block content %}
<div class="container">
  <div class="form-container">
    <h1>Login</h1>
    <form method="POST" action="{{ url_for('auth.login') }}">
      <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
      <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required />
      </div>
      <div class="form-group">
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required />
      </div>
      <div class="form-group">
        <label for="login_type">Login as:</label>
        <select id="login_type" name="login_type" required>
          <option value="user">User</option>
          <option value="admin">Admin</option>
          <option value="moderator">Moderator</option>
        </select>
      </div>
      <button type="submit">Login</button>
    </form>
    <p class="text-center">
      Don't have an account?
      <a href="{{ url_for('auth.register') }}">Register here</a>
    </p>
  </div>
</div>
{% endblock %}

И снова, форма включает CSRF-токен для безопасности и кнопку "Login" для отправки данных. Внизу страницы размещена ссылка для регистрации, если у пользователя еще нет аккаунта. Маленький трюк, который помогает новым пользователям быстро найти способ создания аккаунта, или просто улучшает UX.

Файл register.html

Файл register.html предназначен для регистрации новых пользователей. Этот шаблон содержит форму для ввода имени пользователя, электронной почты, пароля и подтверждения пароля. Также включен CSRF-токен для защиты от атак. После успешной регистрации пользователь сможет войти в систему, используя свои учетные данные. Внизу страницы предоставлена ссылка для перехода на страницу входа, если у пользователя уже есть аккаунт.

HTML: Скопировать в буфер обмена
Код:
<!-- register.html -->

{% extends "base.html" %} {% block content %}
<div class="container">
  <div class="form-container">
    <h1>Register</h1>
    <form method="POST" action="{{ url_for('auth.register') }}">
      <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
      <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required />
      </div>
      <div class="form-group">
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required />
      </div>
      <div class="form-group">
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required />
      </div>
      <div class="form-group">
        <label for="confirm_password">Confirm Password:</label>
        <input
          type="password"
          id="confirm_password"
          name="confirm_password"
          required
        />
      </div>
      <button type="submit">Register</button>
    </form>
    <p class="text-center">
      Already have an account?
      <a href="{{ url_for('auth.login') }}">Login here</a>
    </p>
  </div>
</div>
{% endblock %}

Файл moderate.html

Файл moderate.html предназначен для панели модерации, где модераторы могут просматривать и управлять сообщениями, которые были пожалованы пользователями.

HTML: Скопировать в буфер обмена
Код:
<!-- moderate.html -->

{% extends "base.html" %} {% block content %}
<h1>Moderation Panel</h1>

<h2>Reported Questions</h2>
<table class="table">
  <thead>
    <tr>
      <th>Title</th>
      <th>Author</th>
      <th>Reported By</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    {% for question in reported_questions %}
    <tr>
      <td>{{ question.title }}</td>
      <td>{{ question.user.username }}</td>
      <td>
        {% if question.reported_by_user %} {{ question.reported_by_user.username
        }} {% else %} Not reported {% endif %}
      </td>
      <td>
        <form
          action="{{ url_for('moderator.approve_question', question_id=question.id) }}"
          method="post"
          style="display: inline"
        >
          <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
          <button type="submit" class="btn btn-success">Approve</button>
        </form>
        |
        <form
          action="{{ url_for('moderator.delete_question', question_id=question.id) }}"
          method="post"
          style="display: inline"
        >
          <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
          <button
            type="submit"
            class="btn btn-danger"
            onclick="return confirm('Are you sure you want to delete this question?')"
          >
            Delete
          </button>
        </form>
      </td>
    </tr>
    {% endfor %}
  </tbody>
</table>
{% endblock %}

В содержимом шаблона сначала отображается заголовок "Moderation Panel" и таблица с сообщениями, которые были пожалованы. Таблица содержит такие колонки, как заголовок вопроса, автор, кто пожаловался и действия. В каждой строке таблицы указана информация о вопросе и пользователе, который его пожаловался.

Кнопки для действий включают "Approve" и "Delete". Эти кнопки представлены в виде форм, отправляющих POST-запросы к соответствующим маршрутам для утверждения или удаления вопроса.

Файл question_detail.html

Шаблон question_detail.html предназначен для отображения подробностей конкретного вопроса. Здесь представлены кнопки для голосования за вопрос, которые отправляют POST-запросы для увеличения или уменьшения рейтинга вопроса. Также отображается информация о вопросе, такая как заголовок, тело, теги и метаданные, включая автора и дату.

HTML: Скопировать в буфер обмена
Код:
<!-- question_detail.html -->

{% extends "base.html" %} {% block content %}
<div class="container">
  <div class="question-detail">
    <div class="content-wrapper">
      <div class="vote-buttons">
        <form
          action="{{ url_for('questions.vote_question', question_id=question.id) }}"
          method="post"
        >
          <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
          <button type="submit" name="vote_type" value="up">+</button>
        </form>
        <span>{{ question.votes }}</span>
        <form
          action="{{ url_for('questions.vote_question', question_id=question.id) }}"
          method="post"
        >
          <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
          <button type="submit" name="vote_type" value="down">-</button>
        </form>
      </div>

      <div class="question-content">
        <div class="question-header">
          <h1>{{ question.title }}</h1>
          <div class="question-meta">
            <span>Asked by {{ question.user.username }}</span>
            <span>{{ question.timestamp.strftime('%Y-%m-%d %H:%M') }}</span>
          </div>
        </div>

        <div class="question-body">{{ question.body }}</div>

        <div class="question-tags">
          {% for tag in question.tags %}
          <span class="tag">{{ tag.name }}</span>
          {% endfor %}
        </div>

        <form
          action="{{ url_for('auth.report_question', question_id=question.id) }}"
          method="post"
        >
          <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
          <button type="submit" class="btn btn-warning">Report Question</button>
        </form>
      </div>
    </div>

    <div class="ai-insights">
      <h2 class="section-title">AI Insights</h2>
      <div class="insights-content">
        {{ ai_insights|default('No AI insights available at the moment.', true)
        }}
      </div>
    </div>

    <div class="ai-summary">
      <h2 class="section-title">AI-Generated Summary</h2>
      <div class="summary-content">
        {% if question.summary %} {{ question.summary }} {% else %} No
        AI-generated summary available at the moment. {% endif %}
      </div>
    </div>

    <div class="similar-questions">
      <h2 class="section-title">Similar Questions</h2>
      {% if similar_questions %}
      <ul>
        {% for similar_question in similar_questions %}
        <li>
          <a
            href="{{ url_for('questions.question_detail', question_id=similar_question.id) }}"
            >{{ similar_question.title }}</a
          >
        </li>
        {% endfor %}
      </ul>
      {% else %}
      <p>No similar questions found.</p>
      {% endif %}
    </div>

    <div class="answers">
      <h2 class="section-title">{{ question.answers.count() }} Answers</h2>
      {% for answer in ranked_answers %}
      <div class="answer">
        <div class="vote-buttons">
          <form
            action="{{ url_for('answers.vote_answer', answer_id=answer.id) }}"
            method="post"
          >
            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
            <button type="submit" name="vote_type" value="up">+</button>
          </form>
          <span>{{ answer.votes }}</span>
          <form
            action="{{ url_for('answers.vote_answer', answer_id=answer.id) }}"
            method="post"
          >
            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
            <button type="submit" name="vote_type" value="down">-</button>
          </form>
        </div>
        <div class="answer-body">{{ answer.body }}</div>
        <div class="answer-meta">
          {% if current_user == question.author and not answer.is_accepted %}
          <form
            action="{{ url_for('answers.accept_answer', answer_id=answer.id) }}"
            method="post"
          >
            <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
            <button type="submit" class="btn btn-success">Accept Answer</button>
          </form>
          {% endif %} {% if answer.is_accepted %}
          <span class="accepted">Accepted Answer</span>
          {% endif %}
        </div>
      </div>
      {% endfor %}
    </div>

    <div class="post-answer">
      <h2 class="section-title">Your Answer</h2>
      <form
        action="{{ url_for('answers.post_answer', question_id=question.id) }}"
        method="post"
      >
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
        <textarea
          name="answer_body"
          rows="6"
          placeholder="Write your answer here..."
          required
        ></textarea>
        <button type="submit" class="btn btn-primary">Post Your Answer</button>
      </form>
    </div>
  </div>
</div>
{% endblock %}

Особое внимание уделяется разделу "AI Insights", который отображает результаты анализа вопроса с использованием ИИ. Этот раздел может содержать полезные замечания или рекомендации, предоставляемые ИИ на основе анализа вопроса.

Раздел "AI-Generated Summary" показывает автоматически сгенерированное резюме вопроса, если таковое имеется. В блоке "Similar Questions" представлены вопросы, похожие на текущий, что позволяет пользователям находить связанные темы и расширяет возможности поиска.

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

Файл search_results.html

Файл search_results.html отображает результаты поиска по запросу. Он показывает заголовки вопросов, соответствующих запросу, вместе с кратким описанием и метаданными, такими как количество просмотров и голосов.

HTML: Скопировать в буфер обмена
Код:
<!-- search_results.html -->

{% extends "base.html" %} {% block content %}
<h1>Search Results for "{{ query }}"</h1>
<div class="search-results">
  {% if questions %} {% for question in questions %}
  <div class="question-summary">
    <h2>
      <a
        href="{{ url_for('questions.question_detail', question_id=question.id) }}"
        >{{ question.title }}</a
      >
    </h2>
    <p>
      {{ question.body[:200] }}{% if question.body|length > 200 %}...{% endif %}
    </p>
    <div class="question-meta">
      <span>Asked by {{ question.user.username }}</span>
      <span>{{ question.timestamp.strftime('%Y-%m-%d %H:%M') }}</span>
      <span>{{ question.views }} views</span>
      <span>{{ question.votes }} votes</span>
    </div>
    <div class="question-tags">
      {% for tag in question.tags %}
      <span class="tag">{{ tag.name }}</span>
      {% endfor %}
    </div>
  </div>
  {% endfor %} {% else %}
  <p>No results found for "{{ query }}".</p>
  {% endif %}
</div>
{% endblock %}

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

Файл user_profile.html

Шаблон user_profile.html предназначен для отображения профиля пользователя. Он включает аватар пользователя, основную информацию, такую как имя пользователя, email и репутацию.

HTML: Скопировать в буфер обмена
Код:
<!-- user_profile.html -->

{% extends "base.html" %} {% block content %}
<div class="container">
  <div class="profile-header">
    <img
      src="{{ url_for('static', filename='images/default-avatar.jpg') }}"
      alt="{{ user.username }}'s Avatar"
      class="avatar"
    />
    <div class="user-info">
      <h1>{{ user.username }}</h1>
      <p>Email: {{ user.email }}</p>
      <p class="reputation">Reputation: {{ user.reputation }}</p>
    </div>
  </div>

  <div class="user-questions">
    <h2 class="section-title">Questions ({{ questions.items|length }})</h2>
    {% if questions.items %} {% for question in questions.items %}
    <div class="question-summary">
      <h3>
        <a
          href="{{ url_for('questions.question_detail', question_id=question.id) }}"
          >{{ question.title }}</a
        >
      </h3>
      <p>
        {{ question.body[:200] }}{% if question.body|length > 200 %}...{% endif
        %}
      </p>
      <div class="question-meta">
        <span>{{ question.timestamp.strftime('%Y-%m-%d %H:%M') }}</span>
        <span>{{ question.views }} views</span>
        <span>{{ question.votes }} votes</span>
      </div>
    </div>
    {% endfor %} {% else %}
    <p class="no-contributions">This user hasn't asked any questions yet.</p>
    {% endif %}
  </div>

  <div class="user-answers">
    <h2 class="section-title">Answers ({{ answers.items|length }})</h2>
    {% if answers.items %} {% for answer in answers.items %}
    <div class="answer-summary">
      <h3>
        <a
          href="{{ url_for('questions.question_detail', question_id=answer.question.id) }}"
          >{{ answer.question.title }}</a
        >
      </h3>
      <p>
        {{ answer.body[:200] }}{% if answer.body|length > 200 %}...{% endif %}
      </p>
      <div class="answer-meta">
        <span>{{ answer.timestamp.strftime('%Y-%m-%d %H:%M') }}</span>
        <span>{{ answer.votes }} votes</span>
        {% if answer.is_accepted %}
        <span class="accepted">Accepted Answer</span>
        {% endif %}
      </div>
    </div>
    {% endfor %} {% else %}
    <p class="no-contributions">This user hasn't posted any answers yet.</p>
    {% endif %}
  </div>
</div>
{% endblock %}

Далее представлены разделы с вопросами и ответами пользователя. Каждый вопрос и ответ отображаются в виде краткого описания с ссылкой на полное содержание. В блоке "user-questions" показываются вопросы, заданные пользователем, а в блоке "user-answers" — ответы, которые он оставил. Если у пользователя нет вопросов или ответов, отображаются соответствующие сообщения.

Заключение
Друзья, огромное спасибо всем, кто дочитал статью до конца! Тема ИИ и веб-разработки действительно интересна, но написание проектов и, тем более, качественно оформленных статей требует ещё больше времени. Не забудьте также создать вашу дб и таблицы, перед тем как запускать код. Самые внимательные могли заметить, что не каждая функция реализована на клиенте, я лишь создал простой и приятный шаблон, вы сами можете "закончить" добавив это. Буду благодарен за фидбек. Если обнаружите баги или недочёты, пожалуйста, сообщите, и я постараюсь их исправить. Всем добра!

никак не могу наиграться с ИИ))
 
Сверху Снизу