Тестовая версия витрины

This commit is contained in:
Andrei 2023-03-08 00:17:50 +05:00
parent 62ad4907db
commit 3617afce1b
14 changed files with 651 additions and 15 deletions

View File

@ -17,3 +17,5 @@ class Projects(SqlAlchemyBase, UserMixin):
default=date.today())
creator = sqlalchemy.Column(sqlalchemy.Integer,
sqlalchemy.ForeignKey("users.id"), nullable=True, default=None)
is_open = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False)
is_template = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False)

View File

@ -1,6 +1,6 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed
from wtforms import StringField, SubmitField, TextAreaField, FileField, MultipleFileField
from wtforms import StringField, SubmitField, TextAreaField, FileField, MultipleFileField, BooleanField
from wtforms.validators import DataRequired
@ -8,6 +8,7 @@ class ProjectForm(FlaskForm):
name = StringField('Название', validators=[DataRequired()])
description = TextAreaField('Описание')
logo = FileField('Логотип', validators=[FileAllowed(['jpg', 'png', 'bmp', 'ico', 'jpeg'], 'Только изображения')])
is_template = BooleanField('Шаблон')
submit = SubmitField('Создать')
del_photo = SubmitField('Удалить фотографию')
save = SubmitField('Сохранить')

View File

@ -7,8 +7,8 @@ from wtforms.validators import DataRequired
class Task(FlaskForm):
name = StringField('Название', validators=[DataRequired()])
description = TextAreaField('Описание', validators=[DataRequired()])
deadline_date = DateField('Дедлайн', validators=[DataRequired()])
deadline_time = TimeField('', validators=[DataRequired()])
deadline_date = DateField('Дедлайн')
deadline_time = TimeField('')
submit = SubmitField('Создать')
save = SubmitField('Сохранить')
delete = SubmitField('Удалить')

View File

@ -56,7 +56,7 @@ def mail(msg, to, topic='Подтверждение почты'):
def init_db_default():
data_session = db_session.create_session()
roles = [['admin', 2], ['moderator', 1], ['user', 0]]
roles = [['admin', 90], ['moderator', 75], ['counselor', 45], ['user', 0]]
for i in roles:
role = Roles(
name=i[0],
@ -220,3 +220,42 @@ def delete_project_data(project, data_session):
shutil.rmtree(f'static/app_files/all_projects/{str(project.id)}')
data_session.delete(project)
data_session.commit()
def copy_file_from_template(file, new_project, data_session, current_user):
path = f'static/app_files/all_projects/{str(new_project.id)}/{str(file.path).split("/")[-1]}'
shutil.copy(file.path, path)
new_file = Files(
path=path,
user=current_user.id,
up_date=datetime.datetime.now()
)
data_session.add(new_file)
def copy_quests_from_template(quest, new_project, data_session, current_user):
new_quest = Quests(
project=new_project.id,
creator=current_user.id,
name=quest.name,
description=quest.description,
date_create=datetime.datetime.now(),
deadline=quest.deadline,
realized=False
)
data_session.add(new_quest)
def copy_template(template, new_project, data_session, current_user):
os.mkdir(f'static/app_files/all_projects/{str(new_project.id)}')
if 'none_project' not in template.photo:
filename = f'static/app_files/project_logo/{uuid.uuid4()}.png'
shutil.copy(template.photo, filename)
new_project.photo = filename
else:
new_project.photo = 'static/images/none_project.png'
list(map(lambda file: copy_file_from_template(file, new_project, data_session, current_user),
data_session.query(Files).filter(Files.path.contains(f'all_projects/{str(template.id)}/')).all()))
list(map(lambda quest: copy_quests_from_template(quest, new_project, data_session, current_user),
data_session.query(Quests).filter(Quests.project == template.id).all()))
data_session.commit()

59
main.py
View File

@ -1,6 +1,7 @@
import datetime
import os
import logging
import shutil
from flask import Flask, render_template, request, url_for
from flask_login import login_user, current_user, LoginManager, logout_user, login_required
@ -13,7 +14,8 @@ from sqlalchemy import or_
from json import loads
from functions import check_password, mail, init_db_default, get_projects_data, get_user_data, save_project_logo, \
overdue_quest_project, save_proof_quest, find_files_answer, file_tree, delete_project_data, delete_quest_data
overdue_quest_project, save_proof_quest, find_files_answer, file_tree, delete_project_data, delete_quest_data, \
copy_template
from forms.edit_profile import EditProfileForm
from forms.login import LoginForm
from forms.find_project import FindProjectForm
@ -39,6 +41,7 @@ with open('incepted.config', 'r', encoding='utf-8') as file:
file = loads(file)
key = file["encrypt_key"]
app.config['SECRET_KEY'] = key
app.debug = True
logging.basicConfig(level=logging.INFO, filename="logfiles/main.log", format="%(asctime)s %(levelname)s %(message)s",
encoding='utf-8')
csrf = CSRFProtect(app)
@ -55,10 +58,55 @@ def base():
return redirect('/projects')
@app.route('/template/<int:id_template>/create')
def create_by_template(id_template):
if current_user.is_authenticated:
data_session = db_session.create_session()
current_template = data_session.query(Projects).filter(Projects.id == id_template).first()
if current_template:
new_project = Projects(
name=current_template.name,
description=current_template.description,
date_create=datetime.datetime.now(),
creator=current_user.id,
is_open=False,
is_template=False
)
data_session.add(new_project)
data_session.flush()
data_session.refresh(new_project)
data_session.commit()
copy_template(current_template, new_project, data_session, current_user)
return redirect('/projects')
else:
abort(403)
else:
return redirect('/login')
@app.route('/template/<int:id_template>')
def template_project(id_template):
if current_user.is_authenticated:
data_session = db_session.create_session()
current_project = data_session.query(Projects).filter(Projects.id == id_template).first()
if current_project:
quests = data_session.query(Quests).filter(Quests.project == current_project.id).all()
files_list = file_tree(f'static/app_files/all_projects/{current_project.id}')
return render_template('template_project.html', title=f'Шаблон {current_project.name}',
project=current_project, quests=quests, file_tree=files_list)
else:
abort(404)
else:
return redirect('/login')
@app.route('/showcase', methods=['GET', 'POST'])
def showcase():
if current_user.is_authenticated:
return render_template('showcase.html', title='Витрина')
data_session = db_session.create_session()
list_template = list(map(lambda curr_project: get_projects_data(curr_project),
data_session.query(Projects).filter(Projects.is_template == 1).all()))
return render_template('showcase.html', title='Витрина', list_template=list_template)
else:
return redirect('/login')
@ -429,7 +477,8 @@ def new_project():
name=form.name.data,
description=form.description.data,
date_create=datetime.datetime.now(),
creator=current_user.id
creator=current_user.id,
is_template=form.is_template.data
)
current_project.photo = save_project_logo(
form.logo.data) if form.logo.data else 'static/images/none_project.png'
@ -595,7 +644,7 @@ def register():
activity=datetime.datetime.now(),
data_reg=datetime.date.today(),
photo='static/images/none_logo.png',
role=1
role=3
)
user.set_password(form.password.data)
data_session.add(user)
@ -655,7 +704,7 @@ def main():
db_session.global_init(db_path)
if not db:
init_db_default()
serve(app, host='0.0.0.0', port=5000, threads=10)
serve(app, host='0.0.0.0', port=5000, threads=100)
if __name__ == '__main__':

View File

@ -62,6 +62,14 @@
overflow-y: auto;
padding-top: 2vw;
}
.list_project_block::-webkit-scrollbar {
width: 0.8vw; /* ширина scrollbar */
}
.list_project_block::-webkit-scrollbar-thumb {
background-color: #d49d51; /* цвет плашки */
border-radius: 5vw; /* закругления плашки */
border: 0.25vw solid #ffffff;
}
.list_project {
width: 95%;
margin-left: 2.5%;
@ -133,6 +141,14 @@
border-radius: 2vw;
overflow-y: auto;
}
.collaborator_block::-webkit-scrollbar {
width: 0.8vw; /* ширина scrollbar */
}
.collaborator_block::-webkit-scrollbar-thumb {
background-color: #d49d51; /* цвет плашки */
border-radius: 5vw; /* закругления плашки */
border: 0.25vw solid #ffffff;
}
.description_block {
width: 48%;
height: 90%;
@ -145,12 +161,22 @@
font-size: 2vw;
}
.description_block_text {
overflow-y: auto;
overflow-x: hidden;
width: 90% !important;
height: 80% !important;
width: 50%;
background-color: #dcb495;
border-radius: 2vw;
}
.description_block_text::-webkit-scrollbar {
width: 0.8vw; /* ширина scrollbar */
}
.description_block_text::-webkit-scrollbar-thumb {
background-color: #d49d51; /* цвет плашки */
border-radius: 5vw; /* закругления плашки */
border: 0.25vw solid #ffffff;
}
.description_text {
width: 100% !important;
height: 100%;

View File

@ -1,4 +1,130 @@
.showscale_page {
height: 120vw;
background-color: #dcb495;
display: flex;
flex-direction: column;
align-items: center;
}
.header_block {
width: 100%;
height: 20vw;
background-position: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient( rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.85) ), url(../images/showcase.jpg);background-repeat: repeat; background-position: center;
}
.header_title {
color: #ffffff;
font-size: 7vw;
}
.header_title_2 {
color: #ffffff;
text-align: center;
font-size: 1.5vw;
width: 50vw;
}
.templates_block {
width: 95%;
margin-top: 5vw;
}
.templates_title {
display: flex;
justify-content: center;
font-size: 3.5vw;
}
.templates_list {
height: 30vw;
margin-top: 2vw;
border: 0.2vw solid #694a2d;
border-radius: 2vw;
overflow-x: auto;
overflow-y: hidden;
display: flex;
align-items: center;
justify-content: flex-start;
}
.template {
display: flex;
justify-content: start;
flex-direction: column;
align-items: center;
min-height: 28vw;
min-width: 25vw;
max-width: 25vw;
background-color: #9E795A;
margin: 1vw;
border-radius: 2vw;
}
.template_title {
margin-top: 1vw;
max-width: 90%;
text-align: center;
color: #ffffff;
font-size: 2vw;
font-weight: 500;
}
.description {
background-color: #EDCBB0;
max-width: 90;
height: auto;
height: 15vw;
min-width: 85%;
max-width: 85%;
border-radius: 0.5vw;
}
.description_text {
margin: 0.8vw;
overflow-wrap: normal; /* не поддерживает IE, Firefox; является копией word-wrap */
word-wrap: normal;
word-break: normal; /* не поддерживает Opera12.14, значение keep-all не поддерживается IE, Chrome */
line-break: auto; /* нет поддержки для русского языка */
hyphens: manual;
}
.description {
overflow-y: auto;
}
.description::-webkit-scrollbar {
width: 0.8vw; /* ширина scrollbar */
}
.description::-webkit-scrollbar-thumb {
background-color: #d49d51; /* цвет плашки */
border-radius: 5vw; /* закругления плашки */
border: 0.25vw solid #ffffff;
}
.open_button {
margin-top: 1vw;
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;
}

View File

@ -0,0 +1,249 @@
.template_page {
height: 120vw;
background-color: #dcb495;
display: flex;
flex-direction: column;
align-items: center;
}
.link_back_block {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
flex-wrap: nowrap;
}
.link_back {
background-color: #ffffff;
color: #000000;
width: 15vw;
height: 4.5vw;
vertical-align: middle;
border-radius: 5vw;
display: flex;
align-items: center;
justify-content: center;
}
.link_back:hover {
text-decoration: none;
color: #000000;
}
.link_back_text {
font-size: 1.5vw;
margin-top: 15px;
display: flex;
align-items: center;
justify-content: center;
}
.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;
color: #000000;
}
.name_project {
font-size: 3vw !important;
}
.header_task_block {
width: 60vw;
height: 30vw;
display: flex;
flex-direction: column;
align-items: center;
}
.task_block, .list_files_block {
background-color: #EDCBB0;
width: 95%;
height: 25vw;
border-radius: 2vw;
overflow-y: auto;
}
.task {
margin: 20px;
}
.body_block {
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
}
.head_task {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
width: 60vw;
justify-content: center;
align-items: flex-start;
}
.list_quests {
width: 95%;
margin-left: 2.5%;
margin-top: 0.5vw;
overflow-y: hidden;
overflow-x: hidden;
}
.quest_header_button {
height: 4.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;
}
.quest_button_block_one {
width: 50%;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.quest_title_block {
width: 90%;
height: 4vw;
display: flex;
align-items: center;
}
.quest_title {
overflow-y: hidden;
overflow-x: hidden;
max-height: 1.5vw;
font-size: 1.5vw;
display: flex;
align-items: center;
margin-top: 0.7vw;
margin-left: 1.8vw;
}
.quest_body_block {
background-color: #9E795A;
width: 100%;
height: 20vw;
border-radius: 2vw;
display: flex;
align-items: center;
justify-content: center;
}
.quest_body {
width: 94%;
height: 94%;
display: flex;
align-items: center;
justify-content: space-around;
}
.quest_description_block {
width: 100%;
height: 90%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.quest_description {
width: 100%;
height: 100%;
background-color: #dcb495;
border-radius: 2vw;
overflow-y: auto;
}
.quest_description::-webkit-scrollbar, .task_block::-webkit-scrollbar-thumb {
width: 0.8vw !important;
}
.quest_description::-webkit-scrollbar-thumb, .task_block::-webkit-scrollbar-thumb {
background-color: #d49d51 !important; /* цвет плашки */
border-radius: 5vw !important; /* закругления плашки */
border: 0.25vw solid #ffffff !important;
}
.quest_description_text {
margin: 20px;
}
.files_block {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 35vw;
}
.list_files {
margin: 2vw;
}
.files_title {
text-align: center;
color: #000000;
font-size: 4vw;
}
.file {
width: 98%;
display: flex;
background-color: #9E795A;
margin: 0.5vw;
align-items: center;
justify-content: space-between;
flex-direction: row;
height: 4.5vw;
border-radius: 2vw;
}
.file_head {
width: 30vw;
margin-left: 1vw;
height: 4vw;
background-color: #9E795A !important;
overflow-y: hidden;
overflow-x: auto;
}
.file_head_path, .file_path {
font-size: 1.5vw;
color: #ffffff !important;
font-weight: bold;
height: 3vw;
display: flex;
align-items: flex-start;
background-color: #9E795A !important;
}
.file_buttons {
margin-right: 2vw;
}
.file_delete, .file_download, .upload_button {
border-radius: 1vw !important;
margin: 1vw;
width: 8vw;
height: 3vw;
}
.file_delete {
background-color: hsla(0, 100%, 62%, 0.785) !important;
border-color: hsla(0, 100%, 62%, 0.785) !important;
}
.button_text {
font-size: 1.3vw;
}
.create_project_block {
width: 13vw;
height: 5vw;
background-color: #000000;
border: 2px solid #ffffff;
border-radius: 3vw;
margin-left: 2vw;
}
.create_link:hover {
text-decoration: none;
color: #000000;
}
.create_text {
width: 13vw;
height: 5vw;
text-align: center;
font-size: 1.5vw;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -1,7 +1,8 @@
var edit_button = document.getElementById("edit_button"),
new_task_link = document.getElementById("new_task_link"),
quest_solve_link = document.getElementById("quest_solve_link"),
quest_solve_link_id = document.getElementById("quest_solve_link_id");
quest_solve_link_id = document.getElementById("quest_solve_link_id"),
is_template = document.getElementById("is_template");
edit_button.href = String(window.location.href) + '/edit';
new_task_link.href = String(window.location.href) + '/quest/new';

View File

@ -51,6 +51,14 @@
</div>
<div class="form_data_button">
{{ form.submit(type="submit", class="project_button") }}
{% if current_user.role != 3 %}
<div class="box">
{{ form.is_template(class="is_template")}} {{form.is_template.label }}<br/>
{% for error in form.is_template.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -26,7 +26,8 @@
</div>
</div>
<div class="data_form_block">
<div class="form_data">
{% if not porject.is_template %}
<div class="form_data" id="form_data">
<label class="form_label">{{ form.deadline_date.label }}</label>
{{ form.deadline_date(class="input_data deadline padding_data", type="date") }}
{% for error in form.deadline_date.errors %}
@ -37,8 +38,9 @@
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
<div class="form_data_button">
{{ form.submit(type="submit", class="quest_button") }}
{% endif %}
<div class="form_data_button" {% if porject.is_template %} style="margin-left: -2vw;"{% endif %}>
{{ form.submit(type="submit", class="quest_button", id="quest_button") }}
</div>
</div>
</form>

View File

@ -65,6 +65,7 @@
<div class="quest_title_block">
<p class="quest_title">{{ quest.name }}</p>
</div>
{% if not project.is_template %}
</div>
{% if quest.overdue == 'yes' and quest.realized != 1 %}
<div class="deadline_block alert alert-danger" role="alert">
@ -87,6 +88,7 @@
Задача выполнена
</div>
{% endif %}
{% endif %}
</button>
</h2>
<div id="panelsStayOpen-collapse{{ quest.id }}"

View File

@ -1,8 +1,32 @@
<link rel="stylesheet" href="../static/css/showcase.css"/>
{% extends "base.html" %} {% block content %}
<div class="showscale_page">
<div class="header">
<div class="header_block">
<h2 class="header_title">Витрина</h2>
<strong class="header_title_2">Здесь вы можете находить макеты для своих проектов, а также подключатся к другим проектам</strong>
</div>
<div class="templates_block">
<h2 class="templates_title">Шаблоны проектов</h2>
<div class="templates_list">
{% for template in list_template %}
<div class="template">
<p class="template_title">
{{ template.name }}
</p>
<div class="description">
<p class="description_text">{{ template.description }}</p>
</div>
<div class="open_button">
<a class="open_button_link" href="/template/{{ template.id }}">
<p class="open_button_text">Открыть</p>
</a>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="open_projects_block">
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,107 @@
<link rel="stylesheet" href="../static/css/template_project.css"/>
{% extends "base.html" %} {% block content %}
<div class="template_page">
<div class="link_back_block">
<a class="link_back" href="../showcase">
<p class="link_back_text">К витрине</p>
</a>
</div>
<div class="brand_block">
<img class="project_logo" src="../{{project.photo}}"/>
<p class="name_project header_title">{{ project.name }}</p>
</div>
<div class="body_block">
<div class="header_task_block">
<h3 class="header_title_2">Задачи</h3>
<div class="task_block">
<div class="task">
{% for quest in quests %}
<div class="accordion list_quests" id="accordionPanelsStayOpen{{ quest.id }}">
<div class="accordion-item quest">
<h2 class="accordion-header quest_header" id="panelsStayOpen-heading{{ quest.id }}">
<button class="accordion-button quest_header_button" type="button"
data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapse{{ quest.id }}" aria-expanded="true"
aria-controls="panelsStayOpen-collapse{{ quest.id }}">
<div class="quest_button_block_one">
<div class="quest_title_block">
<p class="quest_title">{{ quest.name }}</p>
</div>
</div>
</button>
</h2>
<div id="panelsStayOpen-collapse{{ quest.id }}"
class="accordion-collapse collapse quest_body_block"
aria-labelledby="panelsStayOpen-heading{{ quest.id }}">
<div class="accordion-body quest_body">
{% if quest.realized == 0 %}
<div class="quest_body">
<div class="quest_description_block">
<p class="quest_description_title">Описание</p>
<div class="quest_description">
<p class="quest_description_text">{{ quest.description }}</p>
</div>
</div>
</div>
{% else %}
<div class="quest_body">
<div class="quest_description_block">
<p class="quest_description_title">Описание</p>
<div class="quest_description">
<p class="quest_description_text">{{ quest.description }}</p>
</div>
</div>
<div class="quest_solve_button">
<a class="quest_solve_link" href="{{ project.id }}/quest/{{ quest.id }}">
<p class="quest_solve_text">Посмотреть</p>
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="files_block">
<h2 class="files_title">Файлы</h2>
<div class="list_files_block">
<div class="list_files">
{% for item in file_tree %}
{% if item['type'] == 'file' %}
<div class="file">
<div class="file_head">
<nav class="file_head_group" style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb file_head_path">
{% for path in item['current_path'] %}
{% if loop.index != 1 %}
<li class="breadcrumb-item active file_path" aria-current="page">{{ path }}</li>
{% endif %}
{% endfor %}
</ol>
</nav>
</div>
<div class="file_buttons">
<div class="btn-group file_buttons_groud">
<a href="../../../{{ item['path'] }}" download="" class="btn btn-primary file_download">
<p class="button_text">Скачать</p>
</a>
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<div class="create_project_block">
<a class="create_link" href="/template/{{project.id}}/create">
<p class="create_text">Создать</p>
</a>
</div>
</div>
</div>
{% endblock %}