diff --git a/data/projects.py b/data/projects.py index 409b29f..1c7c9bc 100644 --- a/data/projects.py +++ b/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) diff --git a/forms/project.py b/forms/project.py index 5945049..cce5563 100644 --- a/forms/project.py +++ b/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('Сохранить') diff --git a/forms/task.py b/forms/task.py index f47a566..7c2f195 100644 --- a/forms/task.py +++ b/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('Удалить') diff --git a/functions.py b/functions.py index 09fe82f..287e043 100644 --- a/functions.py +++ b/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() diff --git a/main.py b/main.py index 51361f5..0a80e6c 100644 --- a/main.py +++ b/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//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/') +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__': diff --git a/static/css/projects.css b/static/css/projects.css index f9f1896..c9807b4 100644 --- a/static/css/projects.css +++ b/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%; diff --git a/static/css/showcase.css b/static/css/showcase.css index 00c5268..c4cb230 100644 --- a/static/css/showcase.css +++ b/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; } \ No newline at end of file diff --git a/static/css/template_project.css b/static/css/template_project.css new file mode 100644 index 0000000..8ca90ae --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/static/js/project.js b/static/js/project.js index f0f198f..22f4e38 100644 --- a/static/js/project.js +++ b/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'; diff --git a/templates/new_project.html b/templates/new_project.html index 07f8307..58aaba9 100644 --- a/templates/new_project.html +++ b/templates/new_project.html @@ -51,6 +51,14 @@
{{ form.submit(type="submit", class="project_button") }} + {% if current_user.role != 3 %} +
+ {{ form.is_template(class="is_template")}} {{form.is_template.label }}
+ {% for error in form.is_template.errors %} + + {% endfor %} +
+ {% endif %}
diff --git a/templates/new_task.html b/templates/new_task.html index 6c81ef4..c83203e 100644 --- a/templates/new_task.html +++ b/templates/new_task.html @@ -26,7 +26,8 @@
-
+ {% if not porject.is_template %} +
{{ form.deadline_date(class="input_data deadline padding_data", type="date") }} {% for error in form.deadline_date.errors %} @@ -37,8 +38,9 @@ {% endfor %}
-
- {{ form.submit(type="submit", class="quest_button") }} + {% endif %} +
+ {{ form.submit(type="submit", class="quest_button", id="quest_button") }}
diff --git a/templates/project.html b/templates/project.html index 54663e4..b0ba458 100644 --- a/templates/project.html +++ b/templates/project.html @@ -65,6 +65,7 @@

{{ quest.name }}

+ {% if not project.is_template %}
{% if quest.overdue == 'yes' and quest.realized != 1 %} {% endif %} + {% endif %}
{% extends "base.html" %} {% block content %}
-
+
+

Витрина

+ Здесь вы можете находить макеты для своих проектов, а также подключатся к другим проектам +
+
+

Шаблоны проектов

+
+ {% for template in list_template %} +
+

+ {{ template.name }} +

+
+

{{ template.description }}

+
+ +
+ {% endfor %} +
+
+
-
+
{% endblock %} \ No newline at end of file diff --git a/templates/template_project.html b/templates/template_project.html new file mode 100644 index 0000000..e41a0a0 --- /dev/null +++ b/templates/template_project.html @@ -0,0 +1,107 @@ + +{% extends "base.html" %} {% block content %} +
+ +
+ +

{{ project.name }}

+
+
+
+

Задачи

+
+
+ {% for quest in quests %} +
+
+

+ +

+
+
+ {% if quest.realized == 0 %} +
+
+

Описание

+
+

{{ quest.description }}

+
+
+
+ {% else %} +
+
+

Описание

+
+

{{ quest.description }}

+
+
+ +
+ {% endif %} +
+
+
+
+ {% endfor %} +
+
+
+
+

Файлы

+
+
+ {% for item in file_tree %} + {% if item['type'] == 'file' %} +
+
+ +
+ +
+ {% endif %} + {% endfor %} +
+
+
+ +
+
+{% endblock %} \ No newline at end of file