Jelajahi Sumber

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

Andrei 2 tahun lalu
induk
melakukan
3617afce1b

+ 2 - 0
data/projects.py

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

+ 2 - 1
forms/project.py

@@ -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('Сохранить')

+ 2 - 2
forms/task.py

@@ -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('Удалить')

+ 40 - 1
functions.py

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

+ 54 - 5
main.py

@@ -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__':

+ 26 - 0
static/css/projects.css

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

+ 126 - 0
static/css/showcase.css

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

+ 249 - 0
static/css/template_project.css

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

+ 2 - 1
static/js/project.js

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

+ 8 - 0
templates/new_project.html

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

+ 5 - 3
templates/new_task.html

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

+ 2 - 0
templates/project.html

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

+ 26 - 2
templates/showcase.html

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

+ 107 - 0
templates/template_project.html

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