Сделал задачи в проектах, можно создавать задачу, устанавливать дедлайн, отвечать на задачу файлами или текстом

This commit is contained in:
Andrei 2023-02-18 20:04:06 +05:00
parent d18352d166
commit 88c2430ef4
13 changed files with 514 additions and 185 deletions

View File

@ -5,12 +5,11 @@ from datetime import datetime
from .db_session import SqlAlchemyBase from .db_session import SqlAlchemyBase
class Proofs(SqlAlchemyBase, UserMixin): class Answer(SqlAlchemyBase, UserMixin):
__tablename__ = 'proofs' __tablename__ = 'answer'
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True)
quest = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("quests.id"), nullable=True, default=None) quest = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("quests.id"), nullable=True, default=None)
file = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("files.id"), nullable=True, default=None)
text = sqlalchemy.Column(sqlalchemy.Text, nullable=True, default=None) text = sqlalchemy.Column(sqlalchemy.Text, nullable=True, default=None)
creator = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("users.id"), nullable=True, creator = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("users.id"), nullable=True,
default=None) default=None)

12
data/proof_file.py Normal file
View File

@ -0,0 +1,12 @@
import sqlalchemy
from flask_login import UserMixin
from datetime import datetime
from .db_session import SqlAlchemyBase
class FileProof(SqlAlchemyBase, UserMixin):
__tablename__ = 'file_proof'
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True)
answer = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("answer.id"), nullable=True, default=None)
file = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("files.id"), nullable=True, default=None)

View File

@ -1,5 +1,6 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField, DateField, TimeField, FileField from wtforms import StringField, SubmitField, TextAreaField, DateField, TimeField, MultipleFileField, \
BooleanField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired
@ -13,5 +14,8 @@ class NewTask(FlaskForm):
class AnswerTask(FlaskForm): class AnswerTask(FlaskForm):
text = TextAreaField('Письменный ответ') text = TextAreaField('Письменный ответ')
file = FileField('Файловый ответ') file = MultipleFileField('Файловый ответ')
submit = SubmitField('Ответить') realized = BooleanField('Задача решена')
deadline_date = DateField('Дедлайн', validators=[DataRequired()])
deadline_time = TimeField('', validators=[DataRequired()])
submit = SubmitField('Сохранить')

View File

@ -1,10 +1,13 @@
import datetime import datetime
import os
import smtplib import smtplib
from json import loads from json import loads
from email.message import EmailMessage from email.message import EmailMessage
from data.roles import Roles 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.answer import Answer
from data.files import Files
from data import db_session from data import db_session
import uuid import uuid
import pymorphy2 import pymorphy2
@ -95,12 +98,14 @@ def save_project_logo(photo):
def overdue_quest_project(quest): def overdue_quest_project(quest):
if str(quest.deadline.date()) == str(datetime.datetime.now().date()): if quest.deadline is None:
quest.overdue = ''
elif str(quest.deadline.date()) == str(datetime.datetime.now().date()):
quest.overdue = 'today' quest.overdue = 'today'
elif quest.deadline < datetime.datetime.now(): elif quest.deadline < datetime.datetime.now():
quest.overdue = 'yes' quest.overdue = 'yes'
quest.time_left = 'Просрочено на' + round_date(quest.deadline) quest.time_left = 'Просрочено на' + round_date(quest.deadline)
else: elif quest.deadline > datetime.datetime.now():
quest.overdue = 'no' quest.overdue = 'no'
quest.time_left = 'Еще есть: ' + round_date(quest.deadline) quest.time_left = 'Еще есть: ' + round_date(quest.deadline)
return quest return quest
@ -120,3 +125,31 @@ def round_date(date_time):
if difference: if difference:
resp += ', ' if resp else ' ' + f'{difference} {morph.parse("день")[0].make_agree_with_number(difference).word}' resp += ', ' if resp else ' ' + f'{difference} {morph.parse("день")[0].make_agree_with_number(difference).word}'
return f'{resp}' return f'{resp}'
def save_proof_quest(project, file, user_id):
data_session = db_session.create_session()
path = f'static/app_files/all_projects/{str(project.id)}/{str(file.filename)}'
file_check = data_session.query(Files).filter(Files.path == path).first()
file.save(path)
if file_check:
return file_check.id
file = Files(
path=path,
user=user_id,
up_date=datetime.datetime.now()
)
data_session.add(file)
data_session.flush()
data_session.refresh(file)
file_id = file.id
data_session.commit()
data_session.close()
return file_id
def find_files_answer(file_id):
data_session = db_session.create_session()
file = data_session.query(Files).filter(Files.id == file_id).first()
return {'id': file.id, 'path': file.path, 'user': file.user, 'up_date': file.up_date,
'current_path': file.path[str(file.path).find('all_projects') + 13:].split('/')}

106
main.py
View File

@ -12,7 +12,7 @@ from sqlalchemy import or_
from json import loads from json import loads
from functions import check_password, mail, init_db_default, get_projects_data, get_user_data, save_project_logo, \ from functions import check_password, mail, init_db_default, get_projects_data, get_user_data, save_project_logo, \
overdue_quest_project overdue_quest_project, save_proof_quest, find_files_answer
from forms.edit_profile import EditProfileForm from forms.edit_profile import EditProfileForm
from forms.login import LoginForm from forms.login import LoginForm
from forms.find_project import FindProjectForm from forms.find_project import FindProjectForm
@ -24,6 +24,8 @@ from forms.task import NewTask, AnswerTask
from data.users import User from data.users import User
from data.quests import Quests from data.quests import Quests
from data.answer import Answer
from data.proof_file import FileProof
from data.files import Files from data.files import Files
from data.projects import Projects from data.projects import Projects
from data.staff_projects import StaffProjects from data.staff_projects import StaffProjects
@ -50,7 +52,35 @@ def base():
return redirect('/projects') return redirect('/projects')
@app.route('/project/<int:id_project>/quest/<int:id_task>') @app.route('/project/<int:id_project>/file/<int:id_file>/delete')
def delete_file(id_project, id_file):
if current_user.is_authenticated:
data_session = db_session.create_session()
current_project = data_session.query(Projects).filter(Projects.id == id_project).first()
current_file = data_session.query(Files).filter(Files.id == id_file).first()
if current_project and current_file:
if current_user.id in map(lambda x: x[0], data_session.query(StaffProjects.user).filter(
StaffProjects.project == current_project.id).all()) or current_user.id == current_project.creator:
current_proof = data_session.query(FileProof).filter(FileProof.file == id_file).all()
os.remove(current_file.path)
data_session.delete(current_file)
if current_proof:
quest = data_session.query(Answer.quest).filter(Answer.id == current_proof[0].answer).first()
for i in current_proof:
data_session.delete(i)
data_session.commit()
return redirect(f'/project/{current_project.id}/quest/{quest[0]}')
data_session.commit()
return redirect(f'/project/{current_project.id}')
else:
abort(403)
else:
abort(404)
else:
return redirect('/login')
@app.route('/project/<int:id_project>/quest/<int:id_task>', methods=['GET', 'POST'])
def task_project(id_project, id_task): def task_project(id_project, id_task):
if current_user.is_authenticated: if current_user.is_authenticated:
data_session = db_session.create_session() data_session = db_session.create_session()
@ -58,15 +88,75 @@ def task_project(id_project, id_task):
current_task = data_session.query(Quests).filter(Quests.id == id_task).first() current_task = data_session.query(Quests).filter(Quests.id == id_task).first()
if current_project and current_task and current_task.project == current_project.id: if current_project and current_task and current_task.project == current_project.id:
form = AnswerTask() form = AnswerTask()
return render_template('decision.html', title='Решение', project=current_project, task=current_task, current_answer = data_session.query(Answer).filter(Answer.quest == current_task.id).first()
form=form) list_files = None
if form.validate_on_submit():
if form.deadline_date.data and form.deadline_time.data:
deadline = datetime.datetime.combine(form.deadline_date.data, form.deadline_time.data)
else:
deadline = None
current_task.deadline = deadline
if current_answer:
current_answer.text = form.text.data if form.text.data else None
current_answer.date_edit = datetime.datetime.now()
current_task.realized = form.realized.data
data_session.commit()
if form.file.data[0].filename:
files = list(
map(lambda x: save_proof_quest(current_project, x, current_user.id), form.file.data))
for i in files:
if not data_session.query(FileProof).filter(FileProof.answer == current_answer.id,
FileProof.file == i).first():
proof_file = FileProof(
answer=current_answer.id,
file=i
)
data_session.add(proof_file)
data_session.commit()
else:
if form.file.data[0].filename:
files = list(
map(lambda x: save_proof_quest(current_project, x, current_user.id), form.file.data))
else:
files = False
current_task.realized = form.realized.data
current_answer = Answer(
quest=current_task.id,
text=form.text.data if form.text.data else None,
creator=current_user.id,
date_create=datetime.datetime.now(),
date_edit=datetime.datetime.now()
)
data_session.add(current_answer)
data_session.flush()
data_session.refresh(current_answer)
if files:
for i in files:
proof_file = FileProof(
proof=current_answer.id,
file=i
)
data_session.add(proof_file)
data_session.commit()
return redirect(f'/project/{current_project.id}')
if current_answer:
form.text.data = current_answer.text
form.realized.data = current_task.realized
files = data_session.query(FileProof).filter(FileProof.answer == current_answer.id).all()
if files:
list_files = list(map(lambda x: find_files_answer(x.file), files))
if current_task.deadline and current_task.deadline:
form.deadline_date.data = current_task.deadline.date()
form.deadline_time.data = current_task.deadline.time()
return render_template('answer.html', title='Решение', project=current_project, task=current_task,
form=form, list_files=list_files)
else: else:
abort(404) abort(404)
else: else:
return redirect('/login') return redirect('/login')
@app.route('/project/<int:id_project>/task/new', methods=['GET', 'POST']) @app.route('/project/<int:id_project>/quest/new', methods=['GET', 'POST'])
def new_task_project(id_project): def new_task_project(id_project):
if current_user.is_authenticated: if current_user.is_authenticated:
data_session = db_session.create_session() data_session = db_session.create_session()
@ -166,7 +256,11 @@ def project(id_project):
User.id.in_(list(map(lambda x: x.user, staff)))).all())) if staff else [] User.id.in_(list(map(lambda x: x.user, staff)))).all())) if staff else []
quests = data_session.query(Quests).filter(Quests.project == current_project.id).all() quests = data_session.query(Quests).filter(Quests.project == current_project.id).all()
if quests: if quests:
quests.sort(key=lambda x: (x.realized, x.deadline)) quests_sort = sorted(list(filter(lambda x: x.deadline is not None, quests)),
key=lambda x: (x.realized, x.deadline))
quests = list(filter(lambda x: x.realized == 0, quests_sort)) + list(
filter(lambda x: x.deadline is None, quests)) + list(
filter(lambda x: x.realized == 1, quests_sort))
quests = list(map(lambda x: overdue_quest_project(x), quests)) quests = list(map(lambda x: overdue_quest_project(x), quests))
return render_template('project.html', return render_template('project.html',
project=current_project, project=current_project,

243
static/css/answer.css Normal file
View File

@ -0,0 +1,243 @@
body {
background-color: #dcb495 !important;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.decision_page {
background-color: #dcb495;
min-height: 100vw;
height: auto;
display: flex;
flex-direction: column;
align-items: center;
margin: 3vw;
margin-bottom: 20vw;
}
.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;
}
.name_block {
margin-top: 3vw;
width: 90%;
height: auto;
display: flex;
flex-direction: column;
align-items: center;
}
.title_block {
width: 90%;
display: flex;
justify-content: center;
}
.title_task, .files_title {
text-align: center;
color: #000000;
font-size: 4vw;
}
.description_task {
width: 80%;
background-color: #EDCBB0;
height: auto;
max-height: 15vw;
border-radius: 2vw;
display: flex;
overflow-y: auto;
}
.description_task::-webkit-scrollbar {
width: 0.8vw !important;
height: auto;
}
.description_task::-webkit-scrollbar-thumb {
background-color: #d49d51 !important; /* цвет плашки */
border-radius: 5vw !important; /* закругления плашки */
border: 0.25vw solid #ffffff !important;
}
.description {
margin: 15px;
}
.description_text {
font-size: 1.5vw;
text-align: justify;
}
.data_block {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: center
}
.bottom_data {
margin: 2vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form_label {
margin-top: 10px;
font-size: 1.3vw;
color: #000000;
font-weight: bold;
}
.input_data {
color: #000000;
border: 0.1vw solid #595008;
height: 4.5vw;
min-height: 4.5vw;
width: 30vw;
background-color: #dbc3af;
border-radius: 5vw;
font-size: 1.3vw;
display: inline-flex;
align-items: center;
}
.input_button {
width: 10vw;
height: 5vw;
border-radius: 5vw;
vertical-align: middle;
}
.form_data {
display: flex;
flex-direction: column;
margin-left: 2%;
}
.decision_block {
margin-top: 3vw;
width: 90%;
height: 25vw;
display: flex;
flex-direction: column;
align-items: center;
}
.padding_data {
padding: 1vw;
}
.quest_button {
color: #ffffff;
width: 13vw;
height: 5vw;
background-color: #000000;
border: 2px solid #ffffff;
border-radius: 3vw;
margin-left: 2vw;
}
form {
display: flex;
flex-direction: column;
align-items: center;
}
.form_data_button {
margin-top: 20px;
display: flex;
width: 30vw;
align-items: center;
justify-content: space-between;
}
.deadline {
margin-top: 5px;
}
.text_data {
width: 80%;
border-radius: 2vw !important;
min-height: 10vw;
max-height: 20vw;
}
.form_text_one {
width: 100%;
}
.files_block {
width: 100%;
margin: 2vw;
background-color: #dbc3af;
display: flex;
flex-direction: column;
align-items: stretch;
border-radius: 2vw;
min-height: 25vw;
}
.files_list {
margin: 2vw;
height: auto;
overflow-y: auto;
overflow-x: hidden;
}
.files {
width: 80%;
margin: 2vw;
display: flex;
flex-direction: column;
align-items: center;
min-height: 25vw;
max-height: 30vw;
}
.file {
width: 98%;
display: flex;
background-color: #694a2d;
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: #694a2d !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: #694a2d !important;
}
.file_buttons {
margin-right: 2vw;
}
.file_delete, .file_download {
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;
}

View File

@ -1,124 +0,0 @@
.decision_page {
height: 90vw;
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;
}
.name_block {
margin-top: 3vw;
width: 90%;
height: 25vw;
display: flex;
flex-direction: column;
align-items: center;
}
.title_block {
width: 90%;
display: flex;
justify-content: center;
}
.title_task {
text-align: center;
color: #000000;
font-size: 4vw;
}
.description_task {
width: 80%;
background-color: #EDCBB0;
height: auto;
max-height: 15vw;
border-radius: 2vw;
display: flex;
overflow-y: auto;
}
.description {
margin: 15px;
}
.description_text {
font-size: 1.5vw;
text-align: justify;
}
.data_block {
width: 90%;
display: flex;
align-items: flex-start;
justify-content: center
}
.bottom_data {
margin: 2vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form_label {
margin-top: 10px;
font-size: 1.3vw;
color: #000000;
font-weight: bold;
}
.input_data {
color: #000000;
border: 0.1vw solid #595008;
height: 4.5vw;
min-height: 4.5vw;
width: 30vw;
background-color: #dbc3af;
border-radius: 5vw;
font-size: 1.3vw;
display: inline-flex;
align-items: center;
}
.input_button {
width: 10vw;
height: 5vw;
border-radius: 5vw;
vertical-align: middle;
}
.text_data {
border-radius: 2vw !important;
width: 35vw;
}
.form_data {
display: flex;
flex-direction: column;
margin-left: 2%;
}
.decision_block {
margin-top: 3vw;
width: 90%;
height: 25vw;
display: flex;
flex-direction: column;
align-items: center;
}

View File

@ -2,6 +2,7 @@ var edit_button = document.getElementById("edit_button"),
new_task_link = document.getElementById("new_task_link"), new_task_link = document.getElementById("new_task_link"),
quest_solve_link = document.getElementById("quest_solve_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");
edit_button.href = String(window.location.href) + '/edit'; edit_button.href = String(window.location.href) + '/edit';
new_task_link.href = String(window.location.href) + '/task/new'; new_task_link.href = String(window.location.href) + '/quest/new';
quest_solve_link.href = String(window.location.href) + '/quest/' + quest_solve_link_id.className; quest_solve_link.href = String(window.location.href) + '/quest/' + quest_solve_link_id.className;

94
templates/answer.html Normal file
View File

@ -0,0 +1,94 @@
<link rel="stylesheet" href="../../../static/css/answer.css"/>
{% extends "base.html" %} {% block content %}
<div class="decision_page">
<div class="link_back_block">
<a class="link_back" href="../../../project/{{ project.id }}">
<p class="link_back_text">К проекту</p>
</a>
</div>
<div class="name_block">
<div class="title_block">
<h3 class="title_task">{{ task.name }}</h3>
</div>
<div class="description_task">
<div class="description">
<p class="description_text">{{ task.description }}</p>
</div>
</div>
{% if list_files %}
<div class="files">
<h2 class="files_title">Файлы</h2>
<div class="files_block">
<div class="files_list">
{% for file in list_files %}
<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 file['current_path'] %}
<li class="breadcrumb-item active file_path" aria-current="page">{{ path }}</li>
{% endfor %}
</ol>
</nav>
</div>
<div class="file_buttons">
<div class="btn-group file_buttons_groud">
{% if current_user.id == project.creator or task.creator == current_user.id or file.user == current_user.id %}
<a href="../file/{{ file.id }}/delete" class="btn btn-primary file_delete"><p class="button_text">Удалить</p></a>
{% endif %}
<a href="../../../{{ file['path'] }}" download="" class="btn btn-primary file_download"><p class="button_text">Скачать</p></a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<div class="form_data bottom_data form_text_one">
<label class="form_label">{{ form.text.label }}</label>
{{ form.text(class="input_data text_data", type="text", id="text_data", placeholder='your answer') }}
{% for error in form.text.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
</div>
<div class="decision_block">
<form action="" method="post" class="answer_form" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="data_block">
<div class="form_data bottom_data">
<label class="form_label">{{ form.file.label }}</label>
{{ form.file(class="input_data padding_data", type="file") }}
{% for error in form.file.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
</div>
{% if current_user.id == project.creator %}
<div class="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 %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
{{ form.deadline_time(class="input_data deadline padding_data", type="time") }}
{% for error in form.deadline_time.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
{% endif %}
<div class="form_data_button">
{{ form.submit(type="submit", class="quest_button") }}
<div class="box">
{{ form.realized(class="realized")}}
{{form.realized.label }}<br/>
{% for error in form.realized.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@ -1,41 +0,0 @@
<link rel="stylesheet" href="../../../static/css/decision.css"/>
{% extends "base.html" %} {% block content %}
<div class="decision_page">
<div class="link_back_block">
<a class="link_back" href="../../../project/{{ project.id }}">
<p class="link_back_text">К проекту</p>
</a>
</div>
<div class="name_block">
<div class="title_block">
<h3 class="title_task">{{ task.name }}</h3>
</div>
<div class="description_task">
<div class="description">
<p class="description_text">{{ task.description }}</p>
</div>
</div>
</div>
<div class="decision_block">
<form action="" method="post" class="answer_form" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="data_block">
<div class="form_data bottom_data">
<label class="form_label">{{ form.text.label }}</label>
{{ form.text(class="input_data label_data text_data", type="text", placeholder='your answer') }}
{% for error in form.text.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
<div class="form_data bottom_data">
<label class="form_label">{{ form.file.label }}</label>
{{ form.file(class="input_data padding_data", type="file") }}
{% for error in form.file.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@ -32,8 +32,7 @@
</div> </div>
</div> </div>
<div class="box"> <div class="box">
{{ form.remember_me(class="remember")}} {{ {{ form.remember_me(class="remember")}} {{form.remember_me.label }}<br/>
form.remember_me.label }}<br/>
{% for error in form.remember_me.errors %} {% for error in form.remember_me.errors %}
<div class="alert alert-danger" role="alert">{{ error }}</div> <div class="alert alert-danger" role="alert">{{ error }}</div>
{% endfor %} {% endfor %}

View File

@ -72,6 +72,10 @@
<div class="deadline_block alert alert-success" role="alert"> <div class="deadline_block alert alert-success" role="alert">
{{ quest.time_left }} {{ quest.time_left }}
</div> </div>
{% elif quest.overdue == '' and quest.realized != 1 %}
<div class="deadline_block alert alert-warning" role="alert">
Дедлайна нет
</div>
{% else %} {% else %}
<div class="deadline_block alert alert-success" role="alert"> <div class="deadline_block alert alert-success" role="alert">
Задача выполнена Задача выполнена
@ -92,14 +96,25 @@
</div> </div>
</div> </div>
<div class="quest_solve_button"> <div class="quest_solve_button">
<a class="quest_solve_link" id="quest_solve_link"> <a class="quest_solve_link" href="{{ project.id }}/quest/{{ quest.id }}">
<p id="quest_solve_link_id" class="{{ quest.id }}"></p>
<p class="quest_solve_text">Решить</p> <p class="quest_solve_text">Решить</p>
</a> </a>
</div> </div>
</div> </div>
{% else %} {% 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 %} {% endif %}
</div> </div>
</div> </div>