From b0837ce46fa40d146c5f287615fe5bb04baf1043 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 29 Jan 2023 19:19:41 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8,=20=D0=B2=D0=BE=D1=81=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8F=20=D0=BF=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=87=D1=82=D0=B5.=20=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C=20=D0=BD=D0=B0?= =?UTF-8?q?=20"=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B0=D0=BC=D0=B8"=20=D0=B2?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/quests.py | 17 +++++++ forms/find_project.py | 7 +++ forms/recovery.py | 14 ++++++ main.py | 107 ++++++++++++++++++++++++++++++++++------ static/css/login.css | 10 ++++ static/css/project.css | 28 +++++++++++ static/css/projects.css | 8 +-- static/css/recovery.css | 61 +++++++++++++++++++++++ templates/base.html | 8 +-- templates/login.html | 3 ++ templates/project.html | 28 +++++++++++ templates/projects.html | 20 ++++++-- templates/recovery.html | 52 +++++++++++++++++++ 13 files changed, 335 insertions(+), 28 deletions(-) create mode 100644 data/quests.py create mode 100644 forms/find_project.py create mode 100644 forms/recovery.py create mode 100644 static/css/project.css create mode 100644 static/css/recovery.css create mode 100644 templates/project.html create mode 100644 templates/recovery.html diff --git a/data/quests.py b/data/quests.py new file mode 100644 index 0000000..4d33818 --- /dev/null +++ b/data/quests.py @@ -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) diff --git a/forms/find_project.py b/forms/find_project.py new file mode 100644 index 0000000..a1f7ce7 --- /dev/null +++ b/forms/find_project.py @@ -0,0 +1,7 @@ +from flask_wtf import FlaskForm +from wtforms import SubmitField, StringField + + +class FindProjectForm(FlaskForm): + project = StringField('', default='') + submit = SubmitField('Поиск') diff --git a/forms/recovery.py b/forms/recovery.py new file mode 100644 index 0000000..dcc029e --- /dev/null +++ b/forms/recovery.py @@ -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('Восстановить') diff --git a/main.py b/main.py index 5c1b7fc..6b47e98 100644 --- a/main.py +++ b/main.py @@ -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/') +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/', 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/', 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') diff --git a/static/css/login.css b/static/css/login.css index 4af0234..29979d3 100644 --- a/static/css/login.css +++ b/static/css/login.css @@ -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; } \ No newline at end of file diff --git a/static/css/project.css b/static/css/project.css new file mode 100644 index 0000000..1c32441 --- /dev/null +++ b/static/css/project.css @@ -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; +} \ No newline at end of file diff --git a/static/css/projects.css b/static/css/projects.css index 385cee2..5780ec4 100644 --- a/static/css/projects.css +++ b/static/css/projects.css @@ -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; } diff --git a/static/css/recovery.css b/static/css/recovery.css new file mode 100644 index 0000000..2ab051c --- /dev/null +++ b/static/css/recovery.css @@ -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; +} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 224b790..e5deb27 100644 --- a/templates/base.html +++ b/templates/base.html @@ -2,14 +2,14 @@ - + - + {{title}} @@ -38,7 +38,7 @@