lab 3
169
labs/lab-3/app/app.py
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
import re
|
||||||
|
import random
|
||||||
|
from flask import Flask, render_template, request, session, redirect, url_for, flash
|
||||||
|
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
fake = Faker('ru_RU')
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
application = app
|
||||||
|
app.secret_key = '1234567890secret'
|
||||||
|
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.init_app(app)
|
||||||
|
login_manager.login_view = 'auth'
|
||||||
|
login_manager.login_message = 'Для доступа к данной странице необходимо пройти процедуру аутентификации.'
|
||||||
|
login_manager.login_message_category = 'warning'
|
||||||
|
|
||||||
|
|
||||||
|
class User(UserMixin):
|
||||||
|
def __init__(self, id, login, password):
|
||||||
|
self.id = id
|
||||||
|
self.login = login
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
|
||||||
|
users = [User(1, 'user', 'qwerty'), User(2, 'egor', '12345')]
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
for user in users:
|
||||||
|
if user.id == int(user_id):
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
images_ids = ['Pic1', 'Pic2', 'Pic3', 'Pic4', 'Pic5']
|
||||||
|
|
||||||
|
def generate_comments(replies=True):
|
||||||
|
comments = []
|
||||||
|
for i in range(random.randint(1, 3)):
|
||||||
|
comment = { 'author': fake.name(), 'text': fake.text() }
|
||||||
|
if replies:
|
||||||
|
comment['replies'] = generate_comments(replies=False)
|
||||||
|
comments.append(comment)
|
||||||
|
return comments
|
||||||
|
|
||||||
|
def generate_post(i):
|
||||||
|
return {
|
||||||
|
'title': fake.company(),
|
||||||
|
'text': fake.paragraph(nb_sentences=100),
|
||||||
|
'author': fake.name(),
|
||||||
|
'date': fake.date_time_between(start_date='-2y', end_date='now'),
|
||||||
|
'image_id': f'{images_ids[i]}.jpg',
|
||||||
|
'comments': generate_comments()
|
||||||
|
}
|
||||||
|
|
||||||
|
posts_list = sorted([generate_post(i) for i in range(5)], key=lambda p: p['date'], reverse=True)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/posts')
|
||||||
|
def posts():
|
||||||
|
return render_template('posts.html', title='Посты', posts=posts_list)
|
||||||
|
|
||||||
|
@app.route('/posts/<int:index>')
|
||||||
|
def post(index):
|
||||||
|
p = posts_list[index]
|
||||||
|
return render_template('post.html', title=p['title'], post=p)
|
||||||
|
|
||||||
|
@app.route('/about')
|
||||||
|
def about():
|
||||||
|
return render_template('about.html', title='Об авторе')
|
||||||
|
|
||||||
|
@app.route('/visits')
|
||||||
|
def visits():
|
||||||
|
if 'visits' in session:
|
||||||
|
session['visits'] = session.get('visits') + 1
|
||||||
|
else:
|
||||||
|
session['visits'] = 1
|
||||||
|
return render_template('visits.html', title='Счётчик посещений', visits=session['visits'])
|
||||||
|
|
||||||
|
@app.route('/auth', methods=['GET', 'POST'])
|
||||||
|
def auth():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
if request.method == 'POST':
|
||||||
|
login_input = request.form.get('login', '')
|
||||||
|
password_input = request.form.get('password', '')
|
||||||
|
remember = request.form.get('remember') == 'on'
|
||||||
|
for user in users:
|
||||||
|
if user.login == login_input and user.password == password_input:
|
||||||
|
login_user(user, remember=remember)
|
||||||
|
flash('Вы успешно вошли в систему!', 'success')
|
||||||
|
next_page = request.args.get('next')
|
||||||
|
return redirect(next_page or url_for('index'))
|
||||||
|
flash('Неверный логин или пароль.', 'error')
|
||||||
|
return render_template('login.html', title='Вход')
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route('/secret')
|
||||||
|
@login_required
|
||||||
|
def secret():
|
||||||
|
return render_template('secret.html', title='Секретная страница')
|
||||||
|
|
||||||
|
@app.route('/request-data', methods=['GET', 'POST'])
|
||||||
|
def request_data():
|
||||||
|
form_data = None
|
||||||
|
if request.method == 'POST':
|
||||||
|
form_data = {
|
||||||
|
'login': request.form.get('login', ''),
|
||||||
|
'password': request.form.get('password', '')
|
||||||
|
}
|
||||||
|
return render_template(
|
||||||
|
'request_data.html',
|
||||||
|
title='Данные запроса',
|
||||||
|
url_params=request.args,
|
||||||
|
headers=request.headers,
|
||||||
|
cookies=request.cookies,
|
||||||
|
form_data=form_data
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_phone(phone):
|
||||||
|
allowed = set('0123456789 ()-.+')
|
||||||
|
for ch in phone:
|
||||||
|
if ch not in allowed:
|
||||||
|
return None, 'Недопустимый ввод. В номере телефона встречаются недопустимые символы.'
|
||||||
|
|
||||||
|
digits = re.sub(r'\D', '', phone)
|
||||||
|
|
||||||
|
stripped = phone.strip()
|
||||||
|
if stripped.startswith('+7') or stripped.startswith('8'):
|
||||||
|
if len(digits) != 11:
|
||||||
|
return None, 'Недопустимый ввод. Неверное количество цифр.'
|
||||||
|
else:
|
||||||
|
if len(digits) != 10:
|
||||||
|
return None, 'Недопустимый ввод. Неверное количество цифр.'
|
||||||
|
|
||||||
|
if len(digits) == 11:
|
||||||
|
digits = digits[1:]
|
||||||
|
|
||||||
|
formatted = f'8-{digits[0:3]}-{digits[3:6]}-{digits[6:8]}-{digits[8:10]}'
|
||||||
|
return formatted, None
|
||||||
|
|
||||||
|
@app.route('/phone', methods=['GET', 'POST'])
|
||||||
|
def phone():
|
||||||
|
error = None
|
||||||
|
formatted_phone = None
|
||||||
|
phone_value = ''
|
||||||
|
if request.method == 'POST':
|
||||||
|
phone_value = request.form.get('phone', '')
|
||||||
|
formatted_phone, error = validate_phone(phone_value)
|
||||||
|
return render_template(
|
||||||
|
'phone.html',
|
||||||
|
title='Проверка номера телефона',
|
||||||
|
error=error,
|
||||||
|
formatted_phone=formatted_phone,
|
||||||
|
phone_value=phone_value
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
BIN
labs/lab-3/app/static/images/Pic1.jpg
Normal file
|
After Width: | Height: | Size: 373 KiB |
BIN
labs/lab-3/app/static/images/Pic2.jpg
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
labs/lab-3/app/static/images/Pic3.jpg
Normal file
|
After Width: | Height: | Size: 385 KiB |
BIN
labs/lab-3/app/static/images/Pic4.jpg
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
labs/lab-3/app/static/images/Pic5.jpg
Normal file
|
After Width: | Height: | Size: 583 KiB |
BIN
labs/lab-3/app/static/images/avatar.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
labs/lab-3/app/static/images/example.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
1069
labs/lab-3/app/static/styles.css
Normal file
19
labs/lab-3/app/templates/about.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mt-5 text-center">Об авторе</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<img class="avatar" src="{{ url_for('static', filename='images/avatar.jpg') }}" alt="Author">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 text-justify d-flex align-items-center">
|
||||||
|
<p>
|
||||||
|
Деев Егор Викторович — backend-разработчик и Python-специалист из Москвы, специализирующийся на создании backend-решений, парсинге данных, автоматизации процессов и интеграции AI-компонентов.
|
||||||
|
<br><br>
|
||||||
|
Обучается в Московском Политехническом университете (МосПолитех), владеет широким технологическим стеком, включающим Python (Django, FastAPI, Flask), работу с базами данных (PostgreSQL, SQLite), DevOps-инструменты (Docker, Nginx), а также frontend-технологии (HTML5, CSS3, JavaScript).
|
||||||
|
<br><br>
|
||||||
|
Контакты для связи: email egor@deev.space, Telegram @Egor_Deev, GitHub @EDeev, личный сайт deev.space с блогом и портфолио проектов.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
135
labs/lab-3/app/templates/base.html
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" href="https://deev.space/media/favicon.ico" type="image/x-icon">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
|
|
||||||
|
<title>
|
||||||
|
{% if title %}
|
||||||
|
{{ title }}
|
||||||
|
{% else %}
|
||||||
|
Лабораторная работа №3
|
||||||
|
{% endif %}
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('index') }}">Лабораторная работа № 3</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('index') }}"><i class="fas fa-home"></i> Задание</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navDropdownContent" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-newspaper"></i> Лаб. 1
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="navDropdownContent">
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('posts') }}"><i class="fas fa-newspaper"></i> Посты</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('about') }}"><i class="fas fa-user"></i> Об авторе</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navDropdownLab2" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-flask"></i> Лаб. 2
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="navDropdownLab2">
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('request_data') }}"><i class="fas fa-database"></i> Данные запроса</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('phone') }}"><i class="fas fa-phone"></i> Телефон</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navDropdownLab3" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-shield-alt"></i> Лаб. 3
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="navDropdownLab3">
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('visits') }}"><i class="fas fa-eye"></i> Счётчик</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('secret') }}"><i class="fas fa-lock"></i> Секретная</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('visits') }}"><i class="fas fa-eye"></i> Счётчик</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('logout') }}"><i class="fas fa-sign-out-alt"></i> Выйти</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('auth') }}"><i class="fas fa-sign-in-alt"></i> Войти</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="my-1">
|
||||||
|
<div class="container">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="flash-messages mt-3">
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="flash-message flash-{{ category }}">
|
||||||
|
<div class="flash-icon">
|
||||||
|
{% if category == 'success' %}
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
{% elif category == 'error' %}
|
||||||
|
<i class="fas fa-exclamation-circle"></i>
|
||||||
|
{% elif category == 'warning' %}
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<span>{{ message }}</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Содержимое по умолчанию</h1>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer-content">
|
||||||
|
<div>
|
||||||
|
<div class="footer-name">Деев Егор Викторович, ст. гр. 241-327</div>
|
||||||
|
<div class="footer-group">Московский Политехнический университет</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href="mailto:egor@deev.space">egor@deev.space</a>
|
||||||
|
<a href="https://deev.space" target="_blank">My Site</a>
|
||||||
|
<a href="https://t.me/Egor_Deev" target="_blank">Telegram</a>
|
||||||
|
<a href="https://github.com/EDeev" target="_blank">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
labs/lab-3/app/templates/index.html
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h1 class="my-5">Задание к лабораторной работе №3</h1>
|
||||||
|
|
||||||
|
<p class="task-description">Разработайте веб-приложение с использованием фреймворка Flask. Приложение должно предоставлять следующий функционал.</p>
|
||||||
|
|
||||||
|
<p class="fw-bold task-description mt-4">
|
||||||
|
1) Страница "Счётчик посещений"
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="task-description">
|
||||||
|
На данной странице пользователю должно отображаться сообщение, содержащее информацию о количестве посещений им данной страницы. Реализуйте этот функционал с помощью глобального объекта session.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="fw-bold task-description mt-4">
|
||||||
|
2) Аутентификация пользователей
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="task-description">
|
||||||
|
Реализуйте механизм аутентификации пользователей с использованием библиотеки Flask-Login. Добавьте в приложение страницу с формой для ввода логина и пароля. Также на форме должен присутствовать чекбокс "Запомнить меня", реализующий функционал сохранения данных сессии после закрытия браузера. Добавьте в приложение пользователя с логином "user" и паролем "qwerty". После удачной аутентификации пользователь должен быть перенаправлен на главную страницу, где ему должно быть отображено сообщение об успешном входе. В случае некорректного ввода пользователь должен остаться на странице с формой, где ему должно быть отображено сообщение о неверно введённых данных.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="fw-bold task-description mt-4">
|
||||||
|
3) "Секретная страница"
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="task-description">
|
||||||
|
Добавьте в приложение страницу, к которой имеют доступ только аутентифицированные пользователи. Добавьте в навбар ссылку на данную страницу. Ссылка должна отображаться только для аутентифицированных пользователей. В случае, если неаутентифицированный пользователь попробует получить доступ к данной странице, он должен быть перенаправлен на страницу входа с сообщением о том, что для доступа к запрашиваемой странице необходимо пройти процедуру аутентификации. После прохождения аутентификации пользователь автоматически должен быть перенаправлен на запрашиваемую ранее страницу.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
44
labs/lab-3/app/templates/login.html
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center my-5">
|
||||||
|
<div class="col-lg-5 col-md-7">
|
||||||
|
<div class="auth-card">
|
||||||
|
<div class="auth-header">
|
||||||
|
<div class="auth-icon">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
</div>
|
||||||
|
<h1>Вход в систему</h1>
|
||||||
|
<p class="auth-subtitle">Введите свои учётные данные</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('auth', next=request.args.get('next', '')) }}">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="login" class="form-label">Логин</label>
|
||||||
|
<div class="input-icon-wrapper">
|
||||||
|
<i class="fas fa-user input-icon"></i>
|
||||||
|
<input type="text" class="form-control form-control-icon" id="login" name="login" placeholder="Введите логин" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Пароль</label>
|
||||||
|
<div class="input-icon-wrapper">
|
||||||
|
<i class="fas fa-lock input-icon"></i>
|
||||||
|
<input type="password" class="form-control form-control-icon" id="password" name="password" placeholder="Введите пароль" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="checkbox-wrapper">
|
||||||
|
<input type="checkbox" name="remember" class="custom-checkbox">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="checkbox-label">Запомнить меня</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
|
<i class="fas fa-sign-in-alt"></i> Войти
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
46
labs/lab-3/app/templates/phone.html
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center my-5">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h1 class="mb-4">Проверка номера телефона</h1>
|
||||||
|
|
||||||
|
<div class="data-card">
|
||||||
|
<form method="POST" action="{{ url_for('phone') }}">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="phone" class="form-label">Номер телефона</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control {% if error %}is-invalid{% endif %}"
|
||||||
|
id="phone"
|
||||||
|
name="phone"
|
||||||
|
placeholder="Например: +7 (123) 456-75-90"
|
||||||
|
value="{{ phone_value }}">
|
||||||
|
{% if error %}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Проверить</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if formatted_phone %}
|
||||||
|
<div class="result-card mt-4">
|
||||||
|
<h4>Результат</h4>
|
||||||
|
<p class="formatted-phone">{{ formatted_phone }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-card mt-4">
|
||||||
|
<h4>Допустимые форматы ввода</h4>
|
||||||
|
<div class="code-block">
|
||||||
|
<code>+7 (123) 456-75-90</code><br>
|
||||||
|
<code>8(123)4567590</code><br>
|
||||||
|
<code>123.456.75.90</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
56
labs/lab-3/app/templates/post.html
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center my-5">
|
||||||
|
<div class="col-lg-10">
|
||||||
|
<div class="post-header">
|
||||||
|
<h1 class="post-title">{{ post.title }}</h1>
|
||||||
|
<div class="post-meta">
|
||||||
|
<span>{{ post.author }}</span>
|
||||||
|
<span>{{ post.date.strftime('%d.%m.%Y') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img class="post-image" src="{{ url_for('static', filename='images/' + post.image_id) }}" alt="{{ post.title }}">
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
{{ post.text }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-form-section">
|
||||||
|
<h3>Оставьте комментарий</h3>
|
||||||
|
<form method="POST">
|
||||||
|
<textarea name="comment" placeholder="Напишите ваш комментарий..." rows="4"></textarea>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Отправить</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comments-section">
|
||||||
|
<h3>Комментарии ({{ post.comments | length }})</h3>
|
||||||
|
{% for comment in post.comments %}
|
||||||
|
<div class="comment">
|
||||||
|
<div class="comment-avatar">{{ comment.author[0] }}</div>
|
||||||
|
<div class="comment-body">
|
||||||
|
<div class="comment-author">{{ comment.author }}</div>
|
||||||
|
<div class="comment-text">{{ comment.text }}</div>
|
||||||
|
|
||||||
|
{% if comment.replies %}
|
||||||
|
<div class="replies">
|
||||||
|
{% for reply in comment.replies %}
|
||||||
|
<div class="reply">
|
||||||
|
<div class="reply-avatar">{{ reply.author[0] }}</div>
|
||||||
|
<div class="comment-body">
|
||||||
|
<div class="comment-author">{{ reply.author }}</div>
|
||||||
|
<div class="comment-text">{{ reply.text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
25
labs/lab-3/app/templates/posts.html
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="my-5">Последние посты</h1>
|
||||||
|
<div class="row mb-3">
|
||||||
|
{% for post in posts %}
|
||||||
|
<div class="col-md-6 d-flex">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<img class="card-img-top" src="{{ url_for('static', filename='images/' + post.image_id) }}" alt="Card image cap">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{ post.title }}</h2>
|
||||||
|
<p class="card-text">
|
||||||
|
{{ post.text | truncate(100) }}
|
||||||
|
</p>
|
||||||
|
<a href="{{ url_for('post', index=loop.index0) }}" class="btn btn-primary">Читать дальше →</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-muted">
|
||||||
|
Опубликовано {{ post.date.strftime('%d.%m.%Y') }}.
|
||||||
|
Автор: {{ post.author }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
116
labs/lab-3/app/templates/request_data.html
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center my-5">
|
||||||
|
<div class="col-lg-10">
|
||||||
|
<h1 class="mb-4">Данные запроса</h1>
|
||||||
|
|
||||||
|
<div class="data-card mb-4">
|
||||||
|
<h3>Форма авторизации</h3>
|
||||||
|
<form method="POST" action="{{ url_for('request_data') }}" class="login-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="login" class="form-label">Логин</label>
|
||||||
|
<input type="text" class="form-control" id="login" name="login" placeholder="Введите логин">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Пароль</label>
|
||||||
|
<input type="password" class="form-control" id="password" name="password" placeholder="Введите пароль">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Войти</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if form_data %}
|
||||||
|
<div class="mt-4">
|
||||||
|
<h4>Отправленные данные формы</h4>
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Параметр</th>
|
||||||
|
<th>Значение</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>login</td>
|
||||||
|
<td>{{ form_data.login }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>password</td>
|
||||||
|
<td>{{ form_data.password }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-card mb-4">
|
||||||
|
<h3>Параметры URL (request.args)</h3>
|
||||||
|
{% if url_params %}
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Параметр</th>
|
||||||
|
<th>Значение</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, value in url_params.items() %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ key }}</td>
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted-custom">Нет параметров URL. Попробуйте добавить параметры в адресную строку, например: <code>?fname=Egor&lname=Deev&age=21</code></p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-card mb-4">
|
||||||
|
<h3>Заголовки запроса (request.headers)</h3>
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Заголовок</th>
|
||||||
|
<th>Значение</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, value in headers %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ key }}</td>
|
||||||
|
<td class="header-value">{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-card mb-4">
|
||||||
|
<h3>Cookie (request.cookies)</h3>
|
||||||
|
{% if cookies %}
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Имя</th>
|
||||||
|
<th>Значение</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, value in cookies.items() %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ key }}</td>
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted-custom">Нет cookie в текущем запросе.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
34
labs/lab-3/app/templates/secret.html
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center my-5">
|
||||||
|
<div class="col-lg-8 col-md-10">
|
||||||
|
<div class="secret-card">
|
||||||
|
<div class="secret-icon">
|
||||||
|
<i class="fas fa-user-secret"></i>
|
||||||
|
</div>
|
||||||
|
<h1>Секретная страница</h1>
|
||||||
|
<p class="secret-welcome">Добро пожаловать, <span class="secret-username">{{ current_user.login }}</span>!</p>
|
||||||
|
<p class="secret-description">
|
||||||
|
Эта страница доступна только аутентифицированным пользователям. Если вы видите эту страницу, значит вы успешно прошли процедуру аутентификации с использованием библиотеки Flask-Login.
|
||||||
|
</p>
|
||||||
|
<div class="secret-info">
|
||||||
|
<div class="secret-info-item">
|
||||||
|
<i class="fas fa-id-badge"></i>
|
||||||
|
<div>
|
||||||
|
<span class="info-label">ID пользователя</span>
|
||||||
|
<span class="info-value">{{ current_user.id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="secret-info-item">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
<div>
|
||||||
|
<span class="info-label">Логин</span>
|
||||||
|
<span class="info-value">{{ current_user.login }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
19
labs/lab-3/app/templates/visits.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center my-5">
|
||||||
|
<div class="col-lg-6 col-md-8">
|
||||||
|
<div class="visits-card">
|
||||||
|
<div class="visits-icon">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</div>
|
||||||
|
<h1>Счётчик посещений</h1>
|
||||||
|
<p class="visits-description">Эта страница отслеживает количество ваших посещений с помощью сессии Flask.</p>
|
||||||
|
<div class="visits-counter">
|
||||||
|
<span class="visits-number">{{ visits }}</span>
|
||||||
|
<span class="visits-label">{{ visits }} {{ 'посещение' if visits == 1 else ('посещения' if visits < 5 else 'посещений') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
10
labs/lab-3/requirements.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
click==8.0.4
|
||||||
|
Faker==13.3.2
|
||||||
|
Flask==2.0.3
|
||||||
|
itsdangerous==2.1.1
|
||||||
|
Jinja2==3.0.3
|
||||||
|
MarkupSafe==2.1.1
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
six==1.16.0
|
||||||
|
Werkzeug==2.0.3
|
||||||
|
Flask-Login
|
||||||