Реализован поиск проектов по имени, восстановление пароля по почте. Начал работать на "задачами" в проектах

This commit is contained in:
Andrei 2023-01-29 19:19:41 +05:00
parent 25e24462ff
commit b0837ce46f
13 changed files with 335 additions and 28 deletions

17
data/quests.py Normal file
View File

@ -0,0 +1,17 @@
import sqlalchemy
from flask_login import UserMixin
from .db_session import SqlAlchemyBase
class Quests(SqlAlchemyBase, UserMixin):
__tablename__ = 'quests'
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True)
project = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("projects.id"), nullable=True, default=None)
creator = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("users.id"), nullable=True, default=None)
name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
description = sqlalchemy.Column(sqlalchemy.String, nullable=True)
date_create = sqlalchemy.Column(sqlalchemy.DateTime, default=date.today())
deadline = sqlalchemy.Column(sqlalchemy.DateTime, default=date.today())
realized = sqlalchemy.Column(sqlalchemy.Boolean, default=False)

7
forms/find_project.py Normal file
View File

@ -0,0 +1,7 @@
from flask_wtf import FlaskForm
from wtforms import SubmitField, StringField
class FindProjectForm(FlaskForm):
project = StringField('', default='')
submit = SubmitField('Поиск')

14
forms/recovery.py Normal file
View File

@ -0,0 +1,14 @@
from flask_wtf import FlaskForm
from wtforms import EmailField, SubmitField, PasswordField
from wtforms.validators import DataRequired
class RecoveryForm(FlaskForm):
email = EmailField('Введите почту для восстановления', validators=[DataRequired()])
submit = SubmitField('Восстановить')
class NewPasswordForm(FlaskForm):
password = PasswordField('Пароль', validators=[DataRequired()])
repeat_password = PasswordField('Повторите пароль', validators=[DataRequired()])
submit = SubmitField('Восстановить')

107
main.py
View File

@ -13,10 +13,13 @@ from sqlalchemy import or_
from functions import check_password, mail, init_db_default, get_projects_data, get_user_data, save_project_logo
from forms.edit_profile import EditProfileForm
from forms.login import LoginForm
from forms.find_project import FindProjectForm
from forms.register import RegisterForm
from forms.new_project import NewProjectForm
from forms.recovery import RecoveryForm, NewPasswordForm
from data.users import User
from data.quests import Quests
from data.files import Files
from data.projects import Projects
from data.staff_projects import StaffProjects
@ -40,6 +43,66 @@ def base():
return redirect('/projects')
@app.route('/project/<int:id_project>')
def project(id_project):
if current_user.is_authenticated:
data_session = db_session.create_session()
current_project = data_session.query(Projects).filter(Projects.id == id_project).first()
if current_project:
staff = data_session.query(StaffProjects).filter(StaffProjects.project == current_project.id).all()
if current_user.id == current_project.creator or current_user.id in list(map(lambda x: x.user, staff)):
return render_template('project.html', project=current_project, title=current_project.name)
else:
abort(403)
else:
abort(404)
else:
return redirect('/login')
@app.route('/recovery/confirmation/<token>', methods=['GET', 'POST'])
def conf_recovery(token):
try:
user_email = s.loads(token, max_age=86400)
data_session = db_session.create_session()
user = data_session.query(User).filter(User.email == user_email).first()
if user:
form = NewPasswordForm()
if form.validate_on_submit():
if form.password.data != form.repeat_password.data:
return render_template('recovery.html', title='Восстановление', form=form, recovery=0,
message='Пароли не совпадают')
status_password = check_password(form.password.data)
if status_password != 'OK':
return render_template('recovery.html', title='Восстановление', form=form, recovery=0,
message=str(status_password))
user.set_password(form.password.data)
data_session.commit()
mail(f'Для аккаунта {user.login}, успешно был обновлен пароль', user.email,
'Изменение пароля')
return redirect('/login?message=Пароль обновлен')
return render_template('recovery.html', title='Восстановление', form=form, recovery=0, message='')
else:
return redirect('/login?message=Пользователь не найден&danger=True')
except SignatureExpired:
return redirect('/login?message=Срок действия ссылки истек&danger=True')
@app.route('/recovery', methods=['GET', 'POST'])
def recovery():
if not current_user.is_authenticated:
form = RecoveryForm()
if form.validate_on_submit():
token = s.dumps(form.email.data)
link_conf = url_for('conf_recovery', token=token, _external=True)
mail(f'Для сбросы пароля пройдите по ссылке: {link_conf}', form.email.data,
'Восстановление доступа')
return redirect('/login?message=Мы выслали ссылку для сброса вам на почту')
return render_template('recovery.html', title='Восстановление пароля', form=form, recovery=True, message='')
else:
return redirect('/')
@app.route('/projects/delete/<int:id_project>', methods=['GET', 'POST'])
def delete_project(id_project):
if current_user.is_authenticated:
@ -70,11 +133,11 @@ def user_view(_login):
data_session = db_session.create_session()
user = data_session.query(User).filter(User.login == _login).first()
if user:
projects = data_session.query(Projects).filter(or_(Projects.creator == user.id, Projects.id.in_(
current_projects = data_session.query(Projects).filter(or_(Projects.creator == user.id, Projects.id.in_(
list(map(lambda x: x[0], data_session.query(
StaffProjects.project).filter(
StaffProjects.user == user.id).all()))))).all()
resp = list(map(lambda x: get_projects_data(x), projects))
resp = list(map(lambda x: get_projects_data(x), current_projects))
return render_template('user_view.html', title=user.name + ' ' + user.surname, user=user,
list_projects=resp)
else:
@ -91,21 +154,22 @@ def new_project():
list_users = list(
map(lambda x: get_user_data(x), data_session.query(User).filter(User.id != current_user.id).all()))
if form.validate_on_submit():
project = Projects(
currnet_project = Projects(
name=form.name.data,
description=form.description.data,
date_create=datetime.datetime.now(),
creator=current_user.id
)
project.photo = save_project_logo(form.logo.data) if form.logo.data else 'static/images/none_project.png'
data_session.add(project)
currnet_project.photo = save_project_logo(
form.logo.data) if form.logo.data else 'static/images/none_project.png'
data_session.add(currnet_project)
data_session.flush()
data_session.refresh(project)
data_session.refresh(currnet_project)
for i in list_users:
if request.form.getlist(f"choose_{i['login']}") and i['id'] != current_user.id:
new_staffer = StaffProjects(
user=i['id'],
project=project.id,
project=currnet_project.id,
role='user',
permission=3
)
@ -120,18 +184,29 @@ def new_project():
@app.route('/projects', methods=['GET', 'POST'])
def project():
def projects():
if current_user.is_authenticated:
find = False
form = FindProjectForm()
data_session = db_session.create_session()
resp = []
if request.method == 'POST':
pass
else:
projects = data_session.query(Projects).filter(or_(Projects.creator == current_user.id, current_user.id in
data_session.query(StaffProjects.project).filter(
StaffProjects.user == current_user.id).all())).all()
resp = list(map(lambda x: get_projects_data(x), projects))
return render_template('projects.html', title='Проекты', list_projects=resp)
current_projects = \
data_session.query(Projects).filter(or_(Projects.creator == current_user.id,
Projects.id.in_(
list(map(lambda x: x[0],
data_session.query(
StaffProjects.project).filter(
StaffProjects.user
== current_user.id).all()))))).all()
if form.validate_on_submit():
new_resp = []
for i in range(len(current_projects)):
if str(form.project.data).lower().strip() in str(current_projects[i].name).lower().strip():
new_resp.append(current_projects[i])
current_projects = new_resp
find = True
resp = list(map(lambda x: get_projects_data(x), current_projects))
return render_template('projects.html', title='Проекты', list_projects=resp, form=form, find=find)
else:
return redirect('/login')

View File

@ -92,4 +92,14 @@
}
.box {
margin-left: 9vw;
}
.recovery_button {
color: #ffffff;
font-size: 1.5vw;
transition: color 0.5s ease-in, border-bottom 0.5s ease-in;
}
.recovery_button:hover {
color: #694a2d;
border-bottom: 3px solid #f3c79e;
text-decoration: none;
}

28
static/css/project.css Normal file
View File

@ -0,0 +1,28 @@
.projects_page {
height: 120vw;
background-color: #dcb495;
}
.project_header {
height: 25vw;
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
.project_logo {
margin-top: 30px;
width: 15vw;
height: 15vw;
border: 0.2vw solid #ffffff;
border-radius: 2vw;
}
.brand_block {
height: 25vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.name_project {
font-size: 3vw;
}

View File

@ -234,7 +234,7 @@
overflow-x: auto;
color: #000000 !important;
}
.new_project_button {
.new_project_button, .find_project_button {
width: 13vw;
height: 5vw;
background-color: #000000;
@ -242,7 +242,7 @@
border-radius: 3vw;
margin-left: 2vw;
}
.new_project_button_text {
.new_project_button_text, .find_project_button_text {
width: 13vw;
height: 5vw;
text-align: center;
@ -252,11 +252,11 @@
align-items: center;
justify-content: center;
}
.new_project_button_link {
.new_project_button_lin, find_project_button_linkk {
width: 13vw;
height: 5vw;
}
.new_project_button_link:hover {
.new_project_button_link:hover, .find_project_button_linkk:hover {
text-decoration: none;
color: #000000;
}

61
static/css/recovery.css Normal file
View File

@ -0,0 +1,61 @@
.recovery_page {
height: 60vw;
background-color: #dcb495;
}
.recovery {
width: 100%;
height: 60vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.header_title {
text-align: center;
color: #000000;
font-size: 3.5vw;
width: 100%;
}
.recovery_form {
margin-top: 55px;
width: 70%;
display: flex;
}
.data_block {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-items: flex-end;
flex-wrap: nowrap;
justify-content: space-evenly;
}
.form_data {
display: flex;
flex-direction: column;
margin-left: 2%;
margin-left: 2%;
height: 100%;
}
.form-label {
font-size: 1.3vw;
}
.input_data {
color: #000000;
border: 0.1vw solid #595008;
height: 4.7vw;
width: 30vw;
background-color: #dbc3af;
border-radius: 5vw;
font-size: 1.3vw;
}
.recovery_button {
background-color: #000000;
color: #ffffff;
min-width: 20vw !important;
height: 5vw;
border-radius: 5vw;
vertical-align: middle;
font-size: 1.5vw;
margin-left: 20px;
}

View File

@ -2,14 +2,14 @@
<html lang="ru">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="../static/css/base.css" />
<link rel="stylesheet" href="../../static/css/base.css" />
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
/>
<link rel="icon" href="../static/images/logo_b.ico" type="image/x-icon" />
<link rel="icon" href="../../static/images/logo_b.ico" type="image/x-icon" />
<title>{{title}}</title>
</head>
<body>
@ -38,7 +38,7 @@
<nav class="navbar" id="navbar">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="../static/images/logo_b.png" class="nav_logo" />
<img src="../../static/images/logo_b.png" class="nav_logo" />
</a>
<a class="auth_button" href="/login">Авторизация</a>
</div>
@ -49,7 +49,7 @@
<footer class="footer">
<div class="footer_block">
<a href="/#header_block"
><img class="footer_logo" src="../static/images/logo_w.png"
><img class="footer_logo" src="../../static/images/logo_w.png"
/></a>
<strong class="footer_rights">© All rights reserved</strong>
</div>

View File

@ -26,6 +26,9 @@
<a class="input_button register_button" type="submit" href="/register">
<div class="register"><strong>Регистрация</strong></div>
</a>
<a class="recovery_button" type="submit" href="/recovery">
<div class="recovery"><strong>Забыли пароль?</strong></div>
</a>
</div>
</div>
<div class="box">

28
templates/project.html Normal file
View File

@ -0,0 +1,28 @@
<link rel="stylesheet" href="../static/css/project.css"/>
{% extends "base.html" %} {% block content %}
<div class="projects_page">
<div class="project_header">
<div class="edit_block">
</div>
<div class="brand_block">
<img class="project_logo" src="../{{project.photo}}"/>
<p class="name_project">{{ project.name }}</p>
</div>
<div class="notification_block">
</div>
</div>
<div class="body_block">
<div class="staff_block">
</div>
<div class="task_block">
</div>
</div>
<div class="files_block">
</div>
</div>
{% endblock %}

View File

@ -8,8 +8,19 @@
</div>
<div class="find_block">
<form action="" method="post" class="form_project_block">
<input class="find_input_text" type="text" placeholder="Имя проекта" name="find_text">
<button class="find_input_button">Поиск</button>
{{ form.hidden_tag() }}
{{ form.project(class="find_input_text", type="text", placeholder='Имя проекта') }}
{% for error in form.project.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
{{ form.submit(type="submit", class="find_input_button") }}
{% if find == 1 %}
<div class="find_project_button">
<a class="find_project_button_link" href="/projects">
<p class="find_project_button_text">Сброс</p>
</a>
</div>
{% endif %}
<div class="new_project_button">
<a class="new_project_button_link" href="/projects/new">
<p class="new_project_button_text">Создать</p>
@ -35,7 +46,8 @@
</div>
</button>
</h2>
<div id="panelsStayOpen-collapse{{ project.id }}" class="accordion-collapse collapse project_description_block"
<div id="panelsStayOpen-collapse{{ project.id }}"
class="accordion-collapse collapse project_description_block"
aria-labelledby="panelsStayOpen-heading{{ project.id }}">
<div class="accordion-body project_description">
<div class="collaborator_block">
@ -60,7 +72,7 @@
</div>
<div class="open_project_block">
<div class="open_button">
<a class="open_button_link" href="/projects/{{ project.id }}">
<a class="open_button_link" href="/project/{{ project.id }}">
<p class="open_button_text">Открыть</p>
</a>
</div>

52
templates/recovery.html Normal file
View File

@ -0,0 +1,52 @@
<link rel="stylesheet" href="../../static/css/recovery.css"/>
{% extends "base.html" %} {% block content %}
<div class="recovery_page">
<div class="recovery">
{% if recovery == 1 %}
<h1 class="header_title">Восстановление пароля</h1>
<form action="" method="post" class="recovery_form">
{{ form.hidden_tag() }}
<div class="data_block">
<div class="form_data">
<label class="form-label">{{ form.email.label }}</label>
{{ form.email(class="input_data", type="email", placeholder='example@mail.ex') }}
{% for error in form.email.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
{{ form.submit(type="submit", class="recovery_button") }}
</div>
</form>
{% else %}
<h1 class="header_title">Восстановление пароля</h1>
<form action="" method="post" class="recovery_form">
{{ form.hidden_tag() }}
<div class="data_block">
<div class="form_data">
<label class="form-label">{{ form.password.label }}</label>
{{ form.password(class="input_data", type="password", placeholder='good_password') }}
{% for error in form.password.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
<div class="form_data">
<label class="form-label">{{ form.repeat_password.label }}</label>
{{ form.repeat_password(class="input_data", type="password", placeholder='good_password') }}
{% for error in form.repeat_password.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
{{ form.submit(type="submit", class="recovery_button") }}
</div>
</form>
{% endif %}
<div class="message_block">
{% if message != '' %}
<div class="alert alert-danger message" role="alert">
{{ message }}
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}