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

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 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.edit_profile import EditProfileForm
from forms.login import LoginForm from forms.login import LoginForm
from forms.find_project import FindProjectForm
from forms.register import RegisterForm from forms.register import RegisterForm
from forms.new_project import NewProjectForm from forms.new_project import NewProjectForm
from forms.recovery import RecoveryForm, NewPasswordForm
from data.users import User from data.users import User
from data.quests import Quests
from data.files import Files from data.files import Files
from data.projects import Projects from data.projects import Projects
from data.staff_projects import StaffProjects from data.staff_projects import StaffProjects
@ -40,6 +43,66 @@ def base():
return redirect('/projects') 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']) @app.route('/projects/delete/<int:id_project>', methods=['GET', 'POST'])
def delete_project(id_project): def delete_project(id_project):
if current_user.is_authenticated: if current_user.is_authenticated:
@ -70,11 +133,11 @@ def user_view(_login):
data_session = db_session.create_session() data_session = db_session.create_session()
user = data_session.query(User).filter(User.login == _login).first() user = data_session.query(User).filter(User.login == _login).first()
if user: 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( list(map(lambda x: x[0], data_session.query(
StaffProjects.project).filter( StaffProjects.project).filter(
StaffProjects.user == user.id).all()))))).all() 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, return render_template('user_view.html', title=user.name + ' ' + user.surname, user=user,
list_projects=resp) list_projects=resp)
else: else:
@ -91,21 +154,22 @@ def new_project():
list_users = list( list_users = list(
map(lambda x: get_user_data(x), data_session.query(User).filter(User.id != current_user.id).all())) map(lambda x: get_user_data(x), data_session.query(User).filter(User.id != current_user.id).all()))
if form.validate_on_submit(): if form.validate_on_submit():
project = Projects( currnet_project = Projects(
name=form.name.data, name=form.name.data,
description=form.description.data, description=form.description.data,
date_create=datetime.datetime.now(), date_create=datetime.datetime.now(),
creator=current_user.id creator=current_user.id
) )
project.photo = save_project_logo(form.logo.data) if form.logo.data else 'static/images/none_project.png' currnet_project.photo = save_project_logo(
data_session.add(project) form.logo.data) if form.logo.data else 'static/images/none_project.png'
data_session.add(currnet_project)
data_session.flush() data_session.flush()
data_session.refresh(project) data_session.refresh(currnet_project)
for i in list_users: for i in list_users:
if request.form.getlist(f"choose_{i['login']}") and i['id'] != current_user.id: if request.form.getlist(f"choose_{i['login']}") and i['id'] != current_user.id:
new_staffer = StaffProjects( new_staffer = StaffProjects(
user=i['id'], user=i['id'],
project=project.id, project=currnet_project.id,
role='user', role='user',
permission=3 permission=3
) )
@ -120,18 +184,29 @@ def new_project():
@app.route('/projects', methods=['GET', 'POST']) @app.route('/projects', methods=['GET', 'POST'])
def project(): def projects():
if current_user.is_authenticated: if current_user.is_authenticated:
find = False
form = FindProjectForm()
data_session = db_session.create_session() data_session = db_session.create_session()
resp = [] resp = []
if request.method == 'POST': current_projects = \
pass data_session.query(Projects).filter(or_(Projects.creator == current_user.id,
else: Projects.id.in_(
projects = data_session.query(Projects).filter(or_(Projects.creator == current_user.id, current_user.id in list(map(lambda x: x[0],
data_session.query(StaffProjects.project).filter( data_session.query(
StaffProjects.user == current_user.id).all())).all() StaffProjects.project).filter(
resp = list(map(lambda x: get_projects_data(x), projects)) StaffProjects.user
return render_template('projects.html', title='Проекты', list_projects=resp) == 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: else:
return redirect('/login') return redirect('/login')

View File

@ -93,3 +93,13 @@
.box { .box {
margin-left: 9vw; 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; overflow-x: auto;
color: #000000 !important; color: #000000 !important;
} }
.new_project_button { .new_project_button, .find_project_button {
width: 13vw; width: 13vw;
height: 5vw; height: 5vw;
background-color: #000000; background-color: #000000;
@ -242,7 +242,7 @@
border-radius: 3vw; border-radius: 3vw;
margin-left: 2vw; margin-left: 2vw;
} }
.new_project_button_text { .new_project_button_text, .find_project_button_text {
width: 13vw; width: 13vw;
height: 5vw; height: 5vw;
text-align: center; text-align: center;
@ -252,11 +252,11 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.new_project_button_link { .new_project_button_lin, find_project_button_linkk {
width: 13vw; width: 13vw;
height: 5vw; height: 5vw;
} }
.new_project_button_link:hover { .new_project_button_link:hover, .find_project_button_linkk:hover {
text-decoration: none; text-decoration: none;
color: #000000; 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"> <html lang="ru">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="stylesheet" href="../static/css/base.css" /> <link rel="stylesheet" href="../../static/css/base.css" />
<link <link
rel="stylesheet" rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous" 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> <title>{{title}}</title>
</head> </head>
<body> <body>
@ -38,7 +38,7 @@
<nav class="navbar" id="navbar"> <nav class="navbar" id="navbar">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="/"> <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>
<a class="auth_button" href="/login">Авторизация</a> <a class="auth_button" href="/login">Авторизация</a>
</div> </div>
@ -49,7 +49,7 @@
<footer class="footer"> <footer class="footer">
<div class="footer_block"> <div class="footer_block">
<a href="/#header_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> /></a>
<strong class="footer_rights">© All rights reserved</strong> <strong class="footer_rights">© All rights reserved</strong>
</div> </div>

View File

@ -26,6 +26,9 @@
<a class="input_button register_button" type="submit" href="/register"> <a class="input_button register_button" type="submit" href="/register">
<div class="register"><strong>Регистрация</strong></div> <div class="register"><strong>Регистрация</strong></div>
</a> </a>
<a class="recovery_button" type="submit" href="/recovery">
<div class="recovery"><strong>Забыли пароль?</strong></div>
</a>
</div> </div>
</div> </div>
<div class="box"> <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>
<div class="find_block"> <div class="find_block">
<form action="" method="post" class="form_project_block"> <form action="" method="post" class="form_project_block">
<input class="find_input_text" type="text" placeholder="Имя проекта" name="find_text"> {{ form.hidden_tag() }}
<button class="find_input_button">Поиск</button> {{ 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"> <div class="new_project_button">
<a class="new_project_button_link" href="/projects/new"> <a class="new_project_button_link" href="/projects/new">
<p class="new_project_button_text">Создать</p> <p class="new_project_button_text">Создать</p>
@ -35,7 +46,8 @@
</div> </div>
</button> </button>
</h2> </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 }}"> aria-labelledby="panelsStayOpen-heading{{ project.id }}">
<div class="accordion-body project_description"> <div class="accordion-body project_description">
<div class="collaborator_block"> <div class="collaborator_block">
@ -60,7 +72,7 @@
</div> </div>
<div class="open_project_block"> <div class="open_project_block">
<div class="open_button"> <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> <p class="open_button_text">Открыть</p>
</a> </a>
</div> </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 %}