Изменена страница списка проектов, создан шаблон страницы создания нового проекта, изменены таблицы в базе данных
This commit is contained in:
parent
afca8cf50c
commit
709d2f971d
@ -11,7 +11,7 @@ class Projects(SqlAlchemyBase, UserMixin):
|
||||
id = sqlalchemy.Column(sqlalchemy.Integer,
|
||||
primary_key=True, autoincrement=True)
|
||||
name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
|
||||
about = sqlalchemy.Column(sqlalchemy.String, nullable=True)
|
||||
description = sqlalchemy.Column(sqlalchemy.String, nullable=True)
|
||||
photo = sqlalchemy.Column(sqlalchemy.Text)
|
||||
date_create = sqlalchemy.Column(sqlalchemy.Date,
|
||||
default=date.today())
|
||||
|
||||
@ -11,6 +11,8 @@ class StaffProjects(SqlAlchemyBase, UserMixin):
|
||||
primary_key=True, autoincrement=True)
|
||||
user = sqlalchemy.Column(sqlalchemy.Integer,
|
||||
sqlalchemy.ForeignKey("users.id"), nullable=True, default=None)
|
||||
project = sqlalchemy.Column(sqlalchemy.Integer,
|
||||
sqlalchemy.ForeignKey("projects.id"), nullable=True, default=None)
|
||||
role = sqlalchemy.Column(sqlalchemy.Text)
|
||||
permission = sqlalchemy.Column(sqlalchemy.Integer,
|
||||
sqlalchemy.ForeignKey("roles.id"), nullable=True, default=None)
|
||||
sqlalchemy.ForeignKey("roles.id"), nullable=True, default=None)
|
||||
|
||||
@ -7,7 +7,7 @@ from wtforms.validators import DataRequired
|
||||
class EditProfileForm(FlaskForm):
|
||||
email = EmailField('Почта', validators=[DataRequired()])
|
||||
name = StringField('Имя', validators=[DataRequired()])
|
||||
surname = StringField('Фамилия', )
|
||||
surname = StringField('Фамилия')
|
||||
about = TextAreaField('Расскажите о себе', default='')
|
||||
birthday = DateField('Дата рождения')
|
||||
photo = FileField('Фото', validators=[FileAllowed(['jpg', 'png', 'bmp'], 'Только фотографии!')])
|
||||
|
||||
10
forms/new_project.py
Normal file
10
forms/new_project.py
Normal file
@ -0,0 +1,10 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, TextAreaField, FileField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
|
||||
class NewProjectForm(FlaskForm):
|
||||
name = StringField('Название', validators=[DataRequired()])
|
||||
description = TextAreaField('Описание')
|
||||
logo = FileField('Логотип')
|
||||
submit = SubmitField('Регистрация')
|
||||
@ -1,5 +1,4 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileAllowed
|
||||
from wtforms import EmailField, StringField, PasswordField, SubmitField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
|
||||
27
functions.py
27
functions.py
@ -1,6 +1,8 @@
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
from data.roles import Roles
|
||||
from data.users import User
|
||||
from data.staff_projects import StaffProjects
|
||||
from data import db_session
|
||||
|
||||
|
||||
@ -50,3 +52,28 @@ def init_db_default():
|
||||
data_session.add(role)
|
||||
data_session.commit()
|
||||
data_session.close()
|
||||
|
||||
|
||||
def get_user_data(user):
|
||||
resp = {
|
||||
'name': user.name,
|
||||
'surname': user.surname,
|
||||
'login': user.login,
|
||||
'email': user.email,
|
||||
'photo': user.photo,
|
||||
'role': user.role
|
||||
}
|
||||
return resp
|
||||
|
||||
|
||||
def get_projects_data(project):
|
||||
data_session = db_session.create_session()
|
||||
resp = {
|
||||
'id': project.id,
|
||||
'name': project.name,
|
||||
'logo': project.photo,
|
||||
'description': project.description,
|
||||
'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()))
|
||||
}
|
||||
return resp
|
||||
|
||||
30
main.py
30
main.py
@ -1,18 +1,24 @@
|
||||
import datetime
|
||||
import os
|
||||
import pprint
|
||||
|
||||
from flask import Flask, render_template, request, url_for
|
||||
from flask_login import login_user, current_user, LoginManager, logout_user, login_required
|
||||
from werkzeug.datastructures import CombinedMultiDict
|
||||
from werkzeug.utils import redirect
|
||||
from itsdangerous import URLSafeTimedSerializer, SignatureExpired
|
||||
from sqlalchemy import or_
|
||||
|
||||
from functions import check_password, mail, init_db_default
|
||||
from functions import check_password, mail, init_db_default, get_projects_data
|
||||
from forms.edit_profile import EditProfileForm
|
||||
from forms.login import LoginForm
|
||||
from forms.register import RegisterForm
|
||||
from forms.new_project import NewProjectForm
|
||||
|
||||
from data.users import User
|
||||
from data.files import Files
|
||||
from data.projects import Projects
|
||||
from data.staff_projects import StaffProjects
|
||||
from waitress import serve
|
||||
from data import db_session
|
||||
|
||||
@ -32,12 +38,30 @@ def base():
|
||||
return redirect('/projects')
|
||||
|
||||
|
||||
@app.route('/projects/new', methods=['GET', 'POST'])
|
||||
def new_project():
|
||||
if current_user.is_authenticated:
|
||||
form = NewProjectForm()
|
||||
if form.validate_on_submit():
|
||||
pass
|
||||
return render_template('new_project.html', title='Новый проект', form=form)
|
||||
else:
|
||||
return redirect('/login')
|
||||
|
||||
|
||||
@app.route('/projects', methods=['GET', 'POST'])
|
||||
def project():
|
||||
if current_user.is_authenticated:
|
||||
data_session = db_session.create_session()
|
||||
resp = []
|
||||
if request.method == 'POST':
|
||||
print(request.form.to_dict())
|
||||
return render_template('projects.html', title='Проекты')
|
||||
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)
|
||||
else:
|
||||
return redirect('/login')
|
||||
|
||||
|
||||
38
static/css/new_project.css
Normal file
38
static/css/new_project.css
Normal file
@ -0,0 +1,38 @@
|
||||
.new_project_page {
|
||||
height: 120vw;
|
||||
background-color: #dcb495;
|
||||
}
|
||||
.form_data, .form_data_button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 2%;
|
||||
}
|
||||
.form_data_button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.input_data {
|
||||
color: #000000;
|
||||
border: 0.1vw solid #595008;
|
||||
height: 4.5vw;
|
||||
min-height: 4.5vw;
|
||||
width: 35vw;
|
||||
background-color: #dbc3af;
|
||||
border-radius: 4.5vw;
|
||||
font-size: 1.3vw;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.input_button {
|
||||
width: 10vw;
|
||||
height: 5vw;
|
||||
border-radius: 5vw;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.form_label {
|
||||
font-size: 1.3vw;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -51,7 +51,6 @@ form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 2%;
|
||||
margin-left: 2%;
|
||||
}
|
||||
.form_data_button {
|
||||
display: flex;
|
||||
|
||||
@ -64,4 +64,201 @@
|
||||
margin-left: 2.5%;
|
||||
height: 95%;
|
||||
margin-top: 2.5%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.project_header_button {
|
||||
height: 5.5vw;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-radius: 5vw;
|
||||
background-color: #9E795A;
|
||||
border-color: #9E795A;
|
||||
border-bottom-color: #9E795A;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.project_description_block {
|
||||
background-color: #9E795A;
|
||||
width: 100%;
|
||||
height: 20vw;
|
||||
border-radius: 2vw;
|
||||
}
|
||||
.project_logo_block {
|
||||
width: 4.5vw;
|
||||
height: 4.5vw;
|
||||
border:2px solid #ffffff;
|
||||
background-color: #ffffff;
|
||||
border-radius: 2vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.project_logo {
|
||||
width: 4vw;
|
||||
height: 4vw;
|
||||
border-radius: 5vw;
|
||||
}
|
||||
.project_title_block {
|
||||
width: 70%;
|
||||
height: 4vw;
|
||||
}
|
||||
.project_title {
|
||||
font-size: 3.5vw;
|
||||
}
|
||||
.project_button_block_one {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.project_description {
|
||||
width: 98%;
|
||||
height: 100%;
|
||||
margin-left: 1%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
.collaborator_block {
|
||||
width: 22%;
|
||||
height: 90%;
|
||||
background-color: #EDCBB0;
|
||||
border-radius: 2vw;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.description_block {
|
||||
width: 48%;
|
||||
height: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.description_header_text {
|
||||
font-size: 2vw;
|
||||
}
|
||||
.description_block_text {
|
||||
width: 90% !important;
|
||||
height: 80% !important;
|
||||
width: 50%;
|
||||
background-color: #dcb495;
|
||||
border-radius: 2vw;
|
||||
}
|
||||
.description_text {
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
font-size: 1.5vw;
|
||||
overflow-wrap: normal; /* не поддерживает IE, Firefox; является копией word-wrap */
|
||||
word-wrap: normal;
|
||||
word-break: normal; /* не поддерживает Opera12.14, значение keep-all не поддерживается IE, Chrome */
|
||||
line-break: auto; /* нет поддержки для русского языка */
|
||||
hyphens: manual; /* значение auto не поддерживается Chrome */
|
||||
margin: 2vw;
|
||||
}
|
||||
.open_project_block {
|
||||
width: 20%;
|
||||
height: 90%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.open_button {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
width: 15vw;
|
||||
height: 4.5vw;
|
||||
vertical-align: middle;
|
||||
border-radius: 5vw;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.open_button:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
}
|
||||
.open_button_text {
|
||||
font-size: 1.5vw;
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.open_button, .open_button_link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 15vw;
|
||||
height: 4.5vw;
|
||||
color: #000000;
|
||||
}
|
||||
.open_button_link:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
}
|
||||
.staff_block {
|
||||
margin: 20px;
|
||||
}
|
||||
.user {
|
||||
width: 16vw;
|
||||
height: 3.5vw;
|
||||
background-color: #ffffff;
|
||||
border: 2px solid #9E795A;
|
||||
border-radius: 3vw;
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: no-wrap;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.new_project_button {
|
||||
width: 13vw;
|
||||
height: 5vw;
|
||||
background-color: #000000;
|
||||
border: 2px solid #ffffff;
|
||||
border-radius: 3vw;
|
||||
margin-left: 2vw;
|
||||
}
|
||||
.new_project_button_text {
|
||||
width: 13vw;
|
||||
height: 5vw;
|
||||
text-align: center;
|
||||
font-size: 1.5vw;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.new_project_button_link {
|
||||
width: 13vw;
|
||||
height: 5vw;
|
||||
}
|
||||
.new_project_button_link:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
}
|
||||
.form_project_block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
BIN
static/images/none_project.png
Normal file
BIN
static/images/none_project.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
45
templates/new_project.html
Normal file
45
templates/new_project.html
Normal file
@ -0,0 +1,45 @@
|
||||
<link rel="stylesheet" href="../static/css/new_project.css"/>
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<div class="new_project_page">
|
||||
<div class="form_block">
|
||||
<form action="" method="post" class="register_form" enctype="multipart/form-data">
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="name_form_block">
|
||||
<div class="form_data">
|
||||
<label class="form_label">{{ form.name.label }}</label>
|
||||
{{ form.name(class="input_data", type="name", placeholder='your project name') }}
|
||||
{% for error in form.name.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="form_data">
|
||||
<label class="form_label">{{ form.description.label }}</label>
|
||||
{{ form.description(class="input_data", type="description", placeholder='your project description') }}
|
||||
{% for error in form.description.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="data_form_block">
|
||||
<div class="staff_form_block">
|
||||
<div class="staff_list">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons_form_block">
|
||||
<div class="form_data">
|
||||
<label class="form_label">{{ form.logo.label }}</label>
|
||||
{{ form.logo(class="input_data file_data", type="file") }}
|
||||
{% for error in form.logo.errors %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="form_data_button">
|
||||
{{ form.submit(type="submit", class="project_button") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -5,7 +5,8 @@
|
||||
<div class="open_button_content">
|
||||
<h2 class="open_button_title">Профиль</h2>
|
||||
<p class="open_button_article">Здесь можно поменять настройки учетной записи</p>
|
||||
<a class="open_button" data-bs-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">
|
||||
<a class="open_button" data-bs-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
<div class="open_button_text">
|
||||
Редикторовать
|
||||
</div>
|
||||
|
||||
@ -7,34 +7,67 @@
|
||||
добавлять участников в своей проект.</strong>
|
||||
</div>
|
||||
<div class="find_block">
|
||||
<form action="" method="post">
|
||||
<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>
|
||||
<div class="new_project_button">
|
||||
<a class="new_project_button_link" href="/projects/new">
|
||||
<p class="new_project_button_text">Создать</p>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="list_project_block">
|
||||
{% for project in list_projects %}
|
||||
<div class="accordion list_project" id="accordionPanelsStayOpenExample">
|
||||
<div class="accordion-item project">
|
||||
<h2 class="accordion-header project_header" id="panelsStayOpen-headingOne">
|
||||
<button class="accordion-button priject_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"
|
||||
aria-controls="panelsStayOpen-collapseOne">
|
||||
Project 1
|
||||
<div class="project_button_block_one">
|
||||
<div class="project_logo_block">
|
||||
<img src="{{ project.logo }}" class="project_logo">
|
||||
</div>
|
||||
<div class="project_title_block">
|
||||
<p class="project_title">{{ project.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse"
|
||||
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse project_description_block"
|
||||
aria-labelledby="panelsStayOpen-headingOne">
|
||||
<div class="accordion-body">
|
||||
<strong>This is the first item's accordion body.</strong> It is shown by default, until the
|
||||
collapse plugin adds the appropriate classes that we use to style each element. These classes
|
||||
control the overall appearance, as well as the showing and hiding via CSS transitions. You can
|
||||
modify any of this with custom CSS or overriding our default variables. It's also worth noting
|
||||
that just about any HTML can go within the <code>.accordion-body</code>, though the transition
|
||||
does limit overflow.
|
||||
<div class="accordion-body project_description">
|
||||
<div class="collaborator_block">
|
||||
<div class="staff_block">
|
||||
{% for user in project.staff %}
|
||||
<div class="user">
|
||||
<img class="user_logo" src="{{user.photo}}">
|
||||
<p class="user_names">{{user.name}}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="description_block">
|
||||
<div class="description_header_block">
|
||||
<p class="description_header_text">Описание</p>
|
||||
</div>
|
||||
<div class="description_block_text">
|
||||
<p class="description_text">{{ project.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="open_project_block">
|
||||
<div class="open_button">
|
||||
<a class="open_button_link" href="/projects/{{ project.id }}">
|
||||
<p class="open_button_text">Открыть</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user