Создана страница добавления проектов, проекты отображаются в списке проектов, добавлена функция удаления проекта

This commit is contained in:
Andrei 2023-01-26 22:41:23 +05:00
parent 709d2f971d
commit 22da6ee3ef
14 changed files with 235 additions and 58 deletions

View File

@ -13,7 +13,7 @@ class Projects(SqlAlchemyBase, UserMixin):
name = sqlalchemy.Column(sqlalchemy.String, nullable=False) name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
description = sqlalchemy.Column(sqlalchemy.String, nullable=True) description = sqlalchemy.Column(sqlalchemy.String, nullable=True)
photo = sqlalchemy.Column(sqlalchemy.Text) photo = sqlalchemy.Column(sqlalchemy.Text)
date_create = sqlalchemy.Column(sqlalchemy.Date, date_create = sqlalchemy.Column(sqlalchemy.DateTime,
default=date.today()) default=date.today())
creator = sqlalchemy.Column(sqlalchemy.Integer, creator = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey("users.id"), nullable=True, default=None) sqlalchemy.ForeignKey("users.id"), nullable=True, default=None)

View File

@ -7,4 +7,4 @@ class NewProjectForm(FlaskForm):
name = StringField('Название', validators=[DataRequired()]) name = StringField('Название', validators=[DataRequired()])
description = TextAreaField('Описание') description = TextAreaField('Описание')
logo = FileField('Логотип') logo = FileField('Логотип')
submit = SubmitField('Регистрация') submit = SubmitField('Создать')

View File

@ -4,6 +4,7 @@ from data.roles import Roles
from data.users import User from data.users import User
from data.staff_projects import StaffProjects from data.staff_projects import StaffProjects
from data import db_session from data import db_session
import uuid
def check_password(password=''): def check_password(password=''):
@ -56,6 +57,7 @@ def init_db_default():
def get_user_data(user): def get_user_data(user):
resp = { resp = {
'id': user.id,
'name': user.name, 'name': user.name,
'surname': user.surname, 'surname': user.surname,
'login': user.login, 'login': user.login,
@ -68,12 +70,21 @@ def get_user_data(user):
def get_projects_data(project): def get_projects_data(project):
data_session = db_session.create_session() data_session = db_session.create_session()
staff = data_session.query(StaffProjects.user).filter(StaffProjects.project == project.id).all()
resp = { resp = {
'id': project.id, 'id': project.id,
'name': project.name, 'name': project.name,
'logo': project.photo, 'logo': project.photo,
'description': project.description, 'description': project.description,
'staff': list(map(lambda x: get_user_data(x), data_session.query(User).filter( 'staff': list(map(lambda x: get_user_data(x), data_session.query(User).filter(
User.id.in_(*data_session.query(StaffProjects.user).filter(StaffProjects.id == project.id).all())).all())) User.id.in_(list(map(lambda x: x[0], staff)))).all())) if staff else []
} }
resp['staff'].insert(0, get_user_data(data_session.query(User).filter(User.id == project.creator).first()))
return resp return resp
def save_project_logo(photo):
filename = f'static/app_files/project_logo/{uuid.uuid4()}.png'
with open(filename, 'wb') as f:
photo.save(f)
return filename

66
main.py
View File

@ -1,15 +1,16 @@
import datetime import datetime
import os import os
import pprint
from flask import Flask, render_template, request, url_for from flask import Flask, render_template, request, url_for
from flask_login import login_user, current_user, LoginManager, logout_user, login_required from flask_login import login_user, current_user, LoginManager, logout_user, login_required
from flask_wtf import CSRFProtect
from flask_restful import abort
from werkzeug.datastructures import CombinedMultiDict from werkzeug.datastructures import CombinedMultiDict
from werkzeug.utils import redirect from werkzeug.utils import redirect
from itsdangerous import URLSafeTimedSerializer, SignatureExpired from itsdangerous import URLSafeTimedSerializer, SignatureExpired
from sqlalchemy import or_ from sqlalchemy import or_
from functions import check_password, mail, init_db_default, get_projects_data 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.register import RegisterForm from forms.register import RegisterForm
@ -25,6 +26,7 @@ from data import db_session
app = Flask(__name__) app = Flask(__name__)
key = 'test_secret_key' key = 'test_secret_key'
app.config['SECRET_KEY'] = key app.config['SECRET_KEY'] = key
csrf = CSRFProtect(app)
s = URLSafeTimedSerializer(key) s = URLSafeTimedSerializer(key)
login_manager = LoginManager() login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
@ -38,13 +40,62 @@ def base():
return redirect('/projects') return redirect('/projects')
@app.route('/projects/delete/<int:id_project>', methods=['GET', 'POST'])
def delete_project(id_project):
if current_user.is_authenticated:
data_session = db_session.create_session()
project_del = data_session.query(Projects).filter(Projects.id == id_project).first()
if project_del:
if project_del.creator == current_user.id:
staff = data_session.query(StaffProjects).filter(StaffProjects.project == id_project).all()
for i in staff:
data_session.delete(i)
if 'none_project' not in project_del.photo:
os.remove(project_del.photo)
data_session.delete(project_del)
data_session.commit()
data_session.close()
return redirect('/projects')
else:
abort(403)
else:
abort(404)
else:
return redirect('/login')
@app.route('/projects/new', methods=['GET', 'POST']) @app.route('/projects/new', methods=['GET', 'POST'])
def new_project(): def new_project():
if current_user.is_authenticated: if current_user.is_authenticated:
form = NewProjectForm() form = NewProjectForm()
data_session = db_session.create_session()
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(): if form.validate_on_submit():
pass project = Projects(
return render_template('new_project.html', title='Новый проект', form=form) 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)
data_session.flush()
data_session.refresh(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,
role='user',
permission=3
)
data_session.add(new_staffer)
data_session.commit()
data_session.close()
return redirect('/projects')
data_session.close()
return render_template('new_project.html', title='Новый проект', form=form, list_users=list_users)
else: else:
return redirect('/login') return redirect('/login')
@ -218,7 +269,12 @@ def confirmation(token):
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(error): def page_not_found(error):
return render_template('page404.html', title='Страница не найдена') return render_template('page_error.html', title='Страница не найдена', error='404', message='Страница не найдена')
@app.errorhandler(403)
def page_not_found(error):
return render_template('page_error.html', title='Ошибка доступа', error='403', message='Доступ сюда запрещен')
def main(): def main():

View File

@ -26,13 +26,108 @@
align-items: center; align-items: center;
} }
.input_button { .input_button {
width: 10vw; width: 35vw;
height: 5vw; height: 5vw;
border-radius: 5vw; border-radius: 5vw;
vertical-align: middle; vertical-align: middle;
} }
.form_label { .form_label {
margin-top: 10px;
font-size: 1.3vw; font-size: 1.3vw;
color: #ffffff; color: #ffffff;
font-weight: bold; font-weight: bold;
}
.description {
border-radius: 2vw !important;
width: 50vw;
}
.padding_data {
padding-top: 1vw;
padding-left: 1vw;
}
.label_data {
padding-left: 0.8vw;
width: 50vw;
}
.project_button {
margin-top: 15px;
width: 35vw;
height: 5vw;
background-color: #000000;
color: #ffffff;
border-radius: 5vw;
vertical-align: middle;
font-size: 1.5vw;
}
.collaborator_block {
width: 30%;
height: 20vw;
background-color: #EDCBB0;
border-radius: 2vw;
overflow-y: auto;
}
.user {
width: 30vw;
height: 3.5vw;
background-color: #ffffff;
border: 2px solid #9E795A;
border-radius: 3vw;
margin-top: 5px;
display: inline-flex;
justify-content: space-between;
}
.user_logo {
margin-left: 3px;
width: 3vw;
height: 3vw;
border-radius: 5vw;
background-color: #000000;
}
.user_names {
margin-left: 9px;
margin-top: 10px;
overflow-x: auto;
}
.name_form_block {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.data_form_block {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
}
.buttons_form_block {
width: 45%;
display: flex;
flex-direction: column;
align-items: center;
}
.staff_form_block, .collaborator_block {
margin-top: 10px;
width: 60%;
height: 20vw;
display: flex;
align-items: center;
justify-content: center;
}
.staff_block {
margin: 5%;
width: 90%;
height: 20vw;
}
.choose_user {
align-self: flex-end;
margin-bottom: 1.1vw;
width: 10%;
}
.user_data {
display: inline-flex;
align-items: center;
justify-content: flex-start;
flex-direction: row;
} }

View File

@ -1,7 +1,7 @@
.navbar { .navbar {
display: none !important; display: none !important;
} }
.page_404 { .page_error {
height: 55vw; height: 55vw;
background-color: #dcb495; background-color: #dcb495;
display: flex; display: flex;

View File

@ -150,7 +150,7 @@ form {
width: 20vw; width: 20vw;
height: 5vw; height: 5vw;
vertical-align: middle; vertical-align: middle;
border-radius: 30px; border-radius: 5vw;
} }
.open_button:hover { .open_button:hover {
text-decoration: none; text-decoration: none;
@ -162,4 +162,7 @@ form {
text-align: center; text-align: center;
font-size: 1.5vw; font-size: 1.5vw;
margin-top: 5%; margin-top: 5%;
}
.about {
border-radius: 2vw !important;
} }

View File

@ -39,8 +39,9 @@
width: 45vw; width: 45vw;
height: 5vw; height: 5vw;
color: #776658; color: #776658;
border-radius: 30px; border-radius: 5vw;
vertical-align: middle; vertical-align: middle;
font-size: 1.5vw;
} }
.find_input_button { .find_input_button {
margin-left: 12px; margin-left: 12px;
@ -49,22 +50,23 @@
width: 10vw; width: 10vw;
height: 5vw; height: 5vw;
color: #ffffff; color: #ffffff;
border-radius: 30px; border-radius: 5vw;
vertical-align: middle; vertical-align: middle;
font-size: 1.5vw;
} }
.list_project_block { .list_project_block {
margin-left: 3%; margin-left: 3%;
border: 2px solid #694a2d; border: 0.2vw solid #694a2d;
border-radius: 25px; border-radius: 4.5vw;
width: 94%; width: 94%;
height: 45vw; height: 45vw;
overflow-y: auto;
} }
.list_project { .list_project {
width: 95%; width: 95%;
margin-left: 2.5%; margin-left: 2.5%;
height: 95%; margin-top: 2vw;
margin-top: 2.5%; overflow-y: hidden;
overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
.project_header_button { .project_header_button {
@ -88,7 +90,7 @@
.project_logo_block { .project_logo_block {
width: 4.5vw; width: 4.5vw;
height: 4.5vw; height: 4.5vw;
border:2px solid #ffffff; border: 0.3vw solid #ffffff;
background-color: #ffffff; background-color: #ffffff;
border-radius: 2vw; border-radius: 2vw;
display: flex; display: flex;

View File

@ -7,14 +7,14 @@
<div class="name_form_block"> <div class="name_form_block">
<div class="form_data"> <div class="form_data">
<label class="form_label">{{ form.name.label }}</label> <label class="form_label">{{ form.name.label }}</label>
{{ form.name(class="input_data", type="name", placeholder='your project name') }} {{ form.name(class="input_data label_data", type="name", placeholder='your project name') }}
{% for error in form.name.errors %} {% for error in form.name.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div> <div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="form_data"> <div class="form_data">
<label class="form_label">{{ form.description.label }}</label> <label class="form_label">{{ form.description.label }}</label>
{{ form.description(class="input_data", type="description", placeholder='your project description') }} {{ form.description(class="input_data description padding_data", type="description", placeholder='your project description') }}
{% for error in form.description.errors %} {% for error in form.description.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div> <div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %} {% endfor %}
@ -22,14 +22,24 @@
</div> </div>
<div class="data_form_block"> <div class="data_form_block">
<div class="staff_form_block"> <div class="staff_form_block">
<div class="staff_list"> <div class="collaborator_block">
<div class="staff_block">
{% for user in list_users %}
<div class="user">
<div class="user_data">
<img class="user_logo" src="../{{user.photo}}">
<p class="user_names">{{user.name}}</p>
</div>
<input class="choose_user" name="choose_{{user.login}}" type="checkbox" value="y">
</div>
{% endfor %}
</div>
</div> </div>
</div> </div>
<div class="buttons_form_block"> <div class="buttons_form_block">
<div class="form_data"> <div class="form_data">
<label class="form_label">{{ form.logo.label }}</label> <label class="form_label">{{ form.logo.label }}</label>
{{ form.logo(class="input_data file_data", type="file") }} {{ form.logo(class="input_data padding_data", type="file") }}
{% for error in form.logo.errors %} {% for error in form.logo.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div> <div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %} {% endfor %}

View File

@ -1,27 +0,0 @@
<link rel="stylesheet" href="../static/css/page404.css"/>
{% extends "base.html" %} {% block content %}
<div class="page_404">
<div class="header_block">
<div class="header">
<hr class="line_top">
<div class="header_rect">
<strong class="header_rect_text">Ошибка 404</strong>
</div>
<hr class="line_top">
</div>
<div class="header">
<h2 class="header_title">Страница не найдена</h2>
</div>
<div class="header">
<hr class="line_bottom">
</div>
</div>
<div class="link_block">
<div class="block_to_home">
<a class="link_to_home" href="/#header_block">
<img class="link_image" src="../static/images/logo_w.png">
</a>
</div>
</div>
</div>
{% endblock %}

27
templates/page_error.html Normal file
View File

@ -0,0 +1,27 @@
<link rel="stylesheet" href="../../../../static/css/page_error.css"/>
{% extends "base.html" %} {% block content %}
<div class="page_error">
<div class="header_block">
<div class="header">
<hr class="line_top">
<div class="header_rect">
<strong class="header_rect_text">{{ error }}</strong>
</div>
<hr class="line_top">
</div>
<div class="header">
<h2 class="header_title">{{ message }}</h2>
</div>
<div class="header">
<hr class="line_bottom">
</div>
</div>
<div class="link_block">
<div class="block_to_home">
<a class="link_to_home" href="/#header_block">
<img class="link_image" src="../../../../static/images/logo_w.png">
</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -64,7 +64,7 @@
</div> </div>
<div class="form_data"> <div class="form_data">
<label class="form-label">{{ form.about.label }}</label> <label class="form-label">{{ form.about.label }}</label>
{{ form.about(class="input_data dop_data", type="name", {{ form.about(class="input_data dop_data about", type="name",
placeholder='about') }} {% for error in form.about.errors %} placeholder='about') }} {% for error in form.about.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div> <div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %} {% endfor %}

View File

@ -19,12 +19,12 @@
</div> </div>
<div class="list_project_block"> <div class="list_project_block">
{% for project in list_projects %} {% for project in list_projects %}
<div class="accordion list_project" id="accordionPanelsStayOpenExample"> <div class="accordion list_project" id="accordionPanelsStayOpen{{ project.id }}">
<div class="accordion-item project"> <div class="accordion-item project">
<h2 class="accordion-header project_header" id="panelsStayOpen-headingOne"> <h2 class="accordion-header project_header" id="panelsStayOpen-heading{{ project.id }}">
<button class="accordion-button project_header_button" type="button" data-bs-toggle="collapse" <button class="accordion-button project_header_button" type="button" data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseOne" aria-expanded="true" data-bs-target="#panelsStayOpen-collapse{{ project.id }}" aria-expanded="true"
aria-controls="panelsStayOpen-collapseOne"> aria-controls="panelsStayOpen-collapse{{ project.id }}">
<div class="project_button_block_one"> <div class="project_button_block_one">
<div class="project_logo_block"> <div class="project_logo_block">
<img src="{{ project.logo }}" class="project_logo"> <img src="{{ project.logo }}" class="project_logo">
@ -35,8 +35,8 @@
</div> </div>
</button> </button>
</h2> </h2>
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse project_description_block" <div id="panelsStayOpen-collapse{{ project.id }}" class="accordion-collapse collapse project_description_block"
aria-labelledby="panelsStayOpen-headingOne"> 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">
<div class="staff_block"> <div class="staff_block">