فهرست منبع

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

Andrei 3 سال پیش
والد
کامیت
b0837ce46f
13فایلهای تغییر یافته به همراه335 افزوده شده و 28 حذف شده
  1. 17 0
      data/quests.py
  2. 7 0
      forms/find_project.py
  3. 14 0
      forms/recovery.py
  4. 91 16
      main.py
  5. 10 0
      static/css/login.css
  6. 28 0
      static/css/project.css
  7. 4 4
      static/css/projects.css
  8. 61 0
      static/css/recovery.css
  9. 4 4
      templates/base.html
  10. 3 0
      templates/login.html
  11. 28 0
      templates/project.html
  12. 16 4
      templates/projects.html
  13. 52 0
      templates/recovery.html

+ 17 - 0
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)

+ 7 - 0
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('Поиск')

+ 14 - 0
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('Восстановить')

+ 91 - 16
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/<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')
 

+ 10 - 0
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;
 }

+ 28 - 0
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;
+}

+ 4 - 4
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;
 }

+ 61 - 0
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;
+}

+ 4 - 4
templates/base.html

@@ -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>

+ 3 - 0
templates/login.html

@@ -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 - 0
templates/project.html

@@ -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 %}

+ 16 - 4
templates/projects.html

@@ -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 - 0
templates/recovery.html

@@ -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 %}