main.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. import datetime
  2. import os
  3. from flask import Flask, render_template, request, url_for
  4. from flask_login import login_user, current_user, LoginManager, logout_user, login_required
  5. from flask_wtf import CSRFProtect
  6. from flask_restful import abort
  7. from werkzeug.datastructures import CombinedMultiDict
  8. from werkzeug.utils import redirect
  9. from itsdangerous import URLSafeTimedSerializer, SignatureExpired
  10. from sqlalchemy import or_
  11. from json import loads
  12. from functions import check_password, mail, init_db_default, get_projects_data, get_user_data, save_project_logo, \
  13. overdue_quest_project
  14. from forms.edit_profile import EditProfileForm
  15. from forms.login import LoginForm
  16. from forms.find_project import FindProjectForm
  17. from forms.register import RegisterForm
  18. from forms.project import ProjectForm
  19. from forms.recovery import RecoveryForm, NewPasswordForm
  20. from forms.conf_delete_project import DeleteProjectForm
  21. from forms.task import NewTask, AnswerTask
  22. from data.users import User
  23. from data.quests import Quests
  24. from data.files import Files
  25. from data.projects import Projects
  26. from data.staff_projects import StaffProjects
  27. from waitress import serve
  28. from data import db_session
  29. app = Flask(__name__)
  30. with open('incepted.config', 'r', encoding='utf-8') as file:
  31. file = file.read()
  32. file = loads(file)
  33. key = file["encrypt_key"]
  34. app.config['SECRET_KEY'] = key
  35. csrf = CSRFProtect(app)
  36. s = URLSafeTimedSerializer(key)
  37. login_manager = LoginManager()
  38. login_manager.init_app(app)
  39. @app.route('/')
  40. def base():
  41. if not current_user.is_authenticated:
  42. return render_template('main.html', title='Главная')
  43. else:
  44. return redirect('/projects')
  45. @app.route('/project/<int:id_project>/quest/<int:id_task>')
  46. def task_project(id_project, id_task):
  47. if current_user.is_authenticated:
  48. data_session = db_session.create_session()
  49. current_project = data_session.query(Projects).filter(Projects.id == id_project).first()
  50. current_task = data_session.query(Quests).filter(Quests.id == id_task).first()
  51. if current_project and current_task and current_task.project == current_project.id:
  52. form = AnswerTask()
  53. return render_template('decision.html', title='Решение', project=current_project, task=current_task,
  54. form=form)
  55. else:
  56. abort(404)
  57. else:
  58. return redirect('/login')
  59. @app.route('/project/<int:id_project>/task/new', methods=['GET', 'POST'])
  60. def new_task_project(id_project):
  61. if current_user.is_authenticated:
  62. data_session = db_session.create_session()
  63. current_project = data_session.query(Projects).filter(Projects.id == id_project).first()
  64. if current_project:
  65. form = NewTask()
  66. if form.validate_on_submit():
  67. if form.deadline_date.data and form.deadline_time.data:
  68. deadline = datetime.datetime.combine(form.deadline_date.data, form.deadline_time.data)
  69. else:
  70. deadline = None
  71. quest = Quests(
  72. project=current_project.id,
  73. creator=current_user.id,
  74. name=form.name.data if form.name.data else None,
  75. description=form.description.data if form.description.data else None,
  76. date_create=datetime.datetime.now(),
  77. deadline=deadline,
  78. realized=False
  79. )
  80. data_session.add(quest)
  81. data_session.commit()
  82. return redirect(f'/project/{str(current_project.id)}')
  83. return render_template('new_task.html', title='Новая задача', form=form, porject=current_project)
  84. else:
  85. abort(404)
  86. else:
  87. return redirect('/login')
  88. @app.route('/project/<int:id_project>/edit', methods=['GET', 'POST'])
  89. def edit_project(id_project):
  90. if current_user.is_authenticated:
  91. data_session = db_session.create_session()
  92. current_project = data_session.query(Projects).filter(Projects.id == id_project).first()
  93. if current_project:
  94. staff = data_session.query(StaffProjects).filter(StaffProjects.project == current_project.id).all()
  95. if current_user.id == current_project.creator or current_user.id in list(map(lambda x: x.user, staff)):
  96. list_users = list(
  97. map(lambda x: get_user_data(x), data_session.query(User).filter(User.id != current_user.id).all()))
  98. staff = list(map(lambda x: get_user_data(x), data_session.query(User).filter(
  99. User.id.in_(list(map(lambda x: x.user, staff)))).all())) if staff else []
  100. form = ProjectForm()
  101. if form.save.data:
  102. new_staff = []
  103. for i in list_users:
  104. if request.form.getlist(f"choose_{i['login']}") and i['id'] != current_user.id:
  105. new_staff.append(i)
  106. if i not in staff:
  107. new_staffer = StaffProjects(
  108. user=i['id'],
  109. project=current_project.id,
  110. role='user',
  111. permission=3
  112. )
  113. data_session.add(new_staffer)
  114. data_session.commit()
  115. if sorted(new_staff, key=lambda x: x['id']) != sorted(staff, key=lambda x: x['id']):
  116. for i in staff:
  117. if i not in new_staff:
  118. data_session.delete(data_session.query(StaffProjects).filter(
  119. StaffProjects.user == i['id'], StaffProjects.project == current_project.id).first())
  120. data_session.commit()
  121. if form.logo.data:
  122. current_project.photo = save_project_logo(form.logo.data)
  123. data_session.commit()
  124. current_project.name = form.name.data
  125. current_project.description = form.description.data
  126. data_session.commit()
  127. return redirect(f'/project/{current_project.id}')
  128. if form.del_photo.data:
  129. os.remove(current_project.photo)
  130. current_project.photo = 'static/images/none_project.png'
  131. data_session.commit()
  132. return redirect(f'/project/{current_project.id}/edit')
  133. form.name.data = current_project.name
  134. form.description.data = current_project.description
  135. return render_template('edit_project.html', title='Изменение проекта', form=form, list_users=list_users,
  136. staff=staff, project=current_project)
  137. else:
  138. abort(403)
  139. else:
  140. abort(404)
  141. else:
  142. return redirect('/login')
  143. @app.route('/project/<int:id_project>')
  144. def project(id_project):
  145. if current_user.is_authenticated:
  146. data_session = db_session.create_session()
  147. current_project = data_session.query(Projects).filter(Projects.id == id_project).first()
  148. if current_project:
  149. staff = data_session.query(StaffProjects).filter(StaffProjects.project == current_project.id).all()
  150. if current_user.id == current_project.creator or current_user.id in list(map(lambda x: x.user, staff)):
  151. staff = list(map(lambda x: get_user_data(x), data_session.query(User).filter(
  152. User.id.in_(list(map(lambda x: x.user, staff)))).all())) if staff else []
  153. quests = data_session.query(Quests).filter(Quests.project == current_project.id).all()
  154. if quests:
  155. quests.sort(key=lambda x: (x.realized, x.deadline))
  156. quests = list(map(lambda x: overdue_quest_project(x), quests))
  157. return render_template('project.html',
  158. project=current_project,
  159. title=current_project.name,
  160. staff=staff,
  161. quests=quests)
  162. else:
  163. abort(403)
  164. else:
  165. abort(404)
  166. else:
  167. return redirect('/login')
  168. @app.route('/recovery/confirmation/<token>', methods=['GET', 'POST'])
  169. def conf_recovery(token):
  170. try:
  171. user_email = s.loads(token, max_age=86400)
  172. data_session = db_session.create_session()
  173. user = data_session.query(User).filter(User.email == user_email).first()
  174. if user:
  175. form = NewPasswordForm()
  176. if form.validate_on_submit():
  177. if form.password.data != form.repeat_password.data:
  178. return render_template('recovery.html', title='Восстановление', form=form, recovery=0,
  179. message='Пароли не совпадают')
  180. status_password = check_password(form.password.data)
  181. if status_password != 'OK':
  182. return render_template('recovery.html', title='Восстановление', form=form, recovery=0,
  183. message=str(status_password))
  184. user.set_password(form.password.data)
  185. data_session.commit()
  186. mail(f'Для аккаунта {user.login}, успешно был обновлен пароль', user.email,
  187. 'Изменение пароля')
  188. return redirect('/login?message=Пароль обновлен')
  189. return render_template('recovery.html', title='Восстановление', form=form, recovery=0, message='')
  190. else:
  191. return redirect('/login?message=Пользователь не найден&danger=True')
  192. except SignatureExpired:
  193. return redirect('/login?message=Срок действия ссылки истек&danger=True')
  194. @app.route('/recovery', methods=['GET', 'POST'])
  195. def recovery():
  196. if not current_user.is_authenticated:
  197. form = RecoveryForm()
  198. if form.validate_on_submit():
  199. token = s.dumps(form.email.data)
  200. link_conf = url_for('conf_recovery', token=token, _external=True)
  201. mail(f'Для сбросы пароля пройдите по ссылке: {link_conf}', form.email.data,
  202. 'Восстановление доступа')
  203. return redirect('/login?message=Мы выслали ссылку для сброса вам на почту')
  204. return render_template('recovery.html', title='Восстановление пароля', form=form, recovery=True, message='')
  205. else:
  206. return redirect('/')
  207. @app.route('/project/<int:id_project>/delete', methods=['GET', 'POST'])
  208. def delete_project(id_project):
  209. if current_user.is_authenticated:
  210. data_session = db_session.create_session()
  211. project_del = data_session.query(Projects).filter(Projects.id == id_project).first()
  212. if project_del:
  213. if project_del.creator == current_user.id:
  214. form = DeleteProjectForm()
  215. if form.validate_on_submit():
  216. if form.conf.data != f'delete/{project_del.name}':
  217. return render_template('delete_project.html', title='Удаление проекта', form=form,
  218. project=project_del,
  219. message='Вы не правильно ввели фразу')
  220. staff = data_session.query(StaffProjects).filter(StaffProjects.project == id_project).all()
  221. for i in staff:
  222. data_session.delete(i)
  223. if 'none_project' not in project_del.photo:
  224. os.remove(project_del.photo)
  225. shutil.rmtree(f'static/app_files/all_projects/{str(project_del.id)}')
  226. data_session.delete(project_del)
  227. data_session.commit()
  228. return redirect('/projects')
  229. return render_template('delete_project.html', title='Удаление проекта', form=form, project=project_del,
  230. message='')
  231. else:
  232. abort(403)
  233. else:
  234. abort(404)
  235. else:
  236. return redirect('/login')
  237. @app.route('/user/<string:_login>', methods=['GET', 'POST'])
  238. def user_view(_login):
  239. if current_user.is_authenticated:
  240. data_session = db_session.create_session()
  241. user = data_session.query(User).filter(User.login == _login).first()
  242. if user:
  243. current_projects = data_session.query(Projects).filter(or_(Projects.creator == user.id, Projects.id.in_(
  244. list(map(lambda x: x[0], data_session.query(
  245. StaffProjects.project).filter(
  246. StaffProjects.user == user.id).all()))))).all()
  247. resp = list(map(lambda x: get_projects_data(x), current_projects))
  248. return render_template('user_view.html', title=user.name + ' ' + user.surname, user=user,
  249. list_projects=resp)
  250. else:
  251. abort(404)
  252. else:
  253. return redirect('/login')
  254. @app.route('/projects/new', methods=['GET', 'POST'])
  255. def new_project():
  256. if current_user.is_authenticated:
  257. form = ProjectForm()
  258. data_session = db_session.create_session()
  259. list_users = list(
  260. map(lambda x: get_user_data(x), data_session.query(User).filter(User.id != current_user.id).all()))
  261. if form.validate_on_submit():
  262. currnet_project = Projects(
  263. name=form.name.data,
  264. description=form.description.data,
  265. date_create=datetime.datetime.now(),
  266. creator=current_user.id
  267. )
  268. currnet_project.photo = save_project_logo(
  269. form.logo.data) if form.logo.data else 'static/images/none_project.png'
  270. data_session.add(currnet_project)
  271. data_session.flush()
  272. data_session.refresh(currnet_project)
  273. for i in list_users:
  274. if request.form.getlist(f"choose_{i['login']}") and i['id'] != current_user.id:
  275. new_staffer = StaffProjects(
  276. user=i['id'],
  277. project=currnet_project.id,
  278. role='user',
  279. permission=3
  280. )
  281. data_session.add(new_staffer)
  282. data_session.commit()
  283. os.mkdir(f'static/app_files/all_projects/{str(currnet_project.id)}')
  284. return redirect('/projects')
  285. return render_template('new_project.html', title='Новый проект', form=form, list_users=list_users)
  286. else:
  287. return redirect('/login')
  288. @app.route('/projects', methods=['GET', 'POST'])
  289. def projects():
  290. if current_user.is_authenticated:
  291. find = False
  292. form = FindProjectForm()
  293. data_session = db_session.create_session()
  294. resp = []
  295. current_projects = \
  296. data_session.query(Projects).filter(or_(Projects.creator == current_user.id,
  297. Projects.id.in_(
  298. list(map(lambda x: x[0],
  299. data_session.query(
  300. StaffProjects.project).filter(
  301. StaffProjects.user
  302. == current_user.id).all()))))).all()
  303. if form.validate_on_submit():
  304. new_resp = []
  305. for i in range(len(current_projects)):
  306. if str(form.project.data).lower().strip() in str(current_projects[i].name).lower().strip():
  307. new_resp.append(current_projects[i])
  308. current_projects = new_resp
  309. find = True
  310. resp = list(map(lambda x: get_projects_data(x), current_projects))
  311. return render_template('projects.html', title='Проекты', list_projects=resp, form=form, find=find)
  312. else:
  313. return redirect('/login')
  314. @app.route('/profile', methods=['GET', 'POST'])
  315. def profile():
  316. if current_user.is_authenticated:
  317. form = EditProfileForm(
  318. CombinedMultiDict((request.files, request.form)),
  319. email=current_user.email,
  320. name=current_user.name,
  321. surname=current_user.surname,
  322. about=current_user.about,
  323. birthday=current_user.birthday
  324. )
  325. if form.del_photo.data:
  326. data_session = db_session.create_session()
  327. user = data_session.query(User).filter(User.id == current_user.id).first()
  328. if not user:
  329. return render_template('profile.html', title='Профиль', form=form,
  330. message='Ошибка, пользователь ненайден')
  331. os.remove(current_user.photo)
  332. user.photo = 'static/images/none_logo.png'
  333. data_session.commit()
  334. if form.validate_on_submit():
  335. data_session = db_session.create_session()
  336. user = data_session.query(User).filter(User.id == current_user.id).first()
  337. if not user:
  338. return render_template('profile.html', title='Профиль', form=form,
  339. message='Ошибка, пользователь ненайден')
  340. if form.email.data != current_user.email:
  341. token = s.dumps(form.email.data)
  342. link_conf = url_for('confirmation', token=token, _external=True)
  343. mail(f'Для изменения почты пройдите по ссылке: {link_conf}', form.email.data,
  344. 'Изменение почты')
  345. user.activated = False
  346. user.email = form.email.data
  347. if form.photo.data:
  348. with open(f'static/app_files/user_logo/{current_user.login}.png', 'wb') as file:
  349. form.photo.data.save(file)
  350. user.photo = f'static/app_files/user_logo/{current_user.login}.png'
  351. user.name = form.name.data
  352. user.surname = form.surname.data
  353. user.about = form.about.data
  354. user.birthday = form.birthday.data
  355. data_session.commit()
  356. return redirect('/profile')
  357. return render_template('profile.html', title='Профиль', form=form, message='')
  358. else:
  359. return redirect('/login')
  360. @login_manager.user_loader
  361. def load_user(user_id):
  362. db_sess = db_session.create_session()
  363. return db_sess.query(User).get(user_id)
  364. @app.route('/login', methods=['GET', 'POST'])
  365. def login():
  366. if not current_user.is_authenticated:
  367. message = request.args.get('message') if request.args.get('message') else ''
  368. danger = request.args.get('danger') if request.args.get('danger') else False
  369. form = LoginForm()
  370. if form.validate_on_submit():
  371. data_session = db_session.create_session()
  372. user = data_session.query(User).filter(User.email == form.login.data).first()
  373. if not user:
  374. user = data_session.query(User).filter(User.login == form.login.data).first()
  375. if user and user.check_password(form.password.data):
  376. if user.activated:
  377. login_user(user, remember=form.remember_me.data)
  378. return redirect('/projects')
  379. else:
  380. return render_template('login.html',
  381. message="Ваша почта не подтверждена",
  382. danger=True,
  383. form=form)
  384. return render_template('login.html',
  385. message="Неправильный логин или пароль",
  386. danger=True,
  387. form=form)
  388. return render_template('login.html', title='Авторизация', form=form, message=message,
  389. danger=danger)
  390. else:
  391. return redirect('/projects')
  392. @app.route('/logout')
  393. @login_required
  394. def logout():
  395. logout_user()
  396. return redirect("/")
  397. @app.route('/register', methods=['GET', 'POST'])
  398. def register():
  399. if not current_user.is_authenticated:
  400. form = RegisterForm()
  401. if form.validate_on_submit():
  402. data_session = db_session.create_session()
  403. if data_session.query(User).filter(User.login == form.login.data).first():
  404. return render_template('register.html', form=form, message="Такой пользователь уже есть",
  405. title='Регистрация')
  406. if data_session.query(User).filter(User.email == form.email.data).first():
  407. return render_template('register.html', form=form, message="Такая почта уже есть", title='Регистрация')
  408. status_password = check_password(form.password.data)
  409. if status_password != 'OK':
  410. return render_template('register.html', form=form, message=status_password, title='Регистрация')
  411. user = User(
  412. email=form.email.data,
  413. name=form.name.data,
  414. login=form.login.data,
  415. activity=datetime.datetime.now(),
  416. data_reg=datetime.date.today(),
  417. photo='static/images/none_logo.png',
  418. role=1
  419. )
  420. user.set_password(form.password.data)
  421. data_session.add(user)
  422. data_session.commit()
  423. token = s.dumps(form.email.data)
  424. link_conf = url_for('confirmation', token=token, _external=True)
  425. mail(f'Для завершения регистрации пройдите по ссылке: {link_conf}', form.email.data,
  426. 'Подтверждение регистрации')
  427. return redirect('/login?message=Мы выслали ссылку для подтверждения почты')
  428. return render_template('register.html', form=form, message='', title='Регистрация')
  429. else:
  430. return redirect('/projects')
  431. @app.route('/confirmation/<token>')
  432. def confirmation(token):
  433. try:
  434. user_email = s.loads(token, max_age=86400)
  435. data_session = db_session.create_session()
  436. user = data_session.query(User).filter(User.email == user_email).first()
  437. if user:
  438. user.activated = True
  439. data_session.commit()
  440. return redirect('/login?message=Почта успешно подтверждена')
  441. else:
  442. return redirect('/login?message=Пользователь не найден&danger=True')
  443. except SignatureExpired:
  444. data_session = db_session.create_session()
  445. users = data_session.query(User).filter(
  446. User.activated == 0 and User.activated < datetime.datetime.now() - datetime.timedelta(days=1)).all()
  447. if users:
  448. list(map(lambda x: data_session.delete(x), users))
  449. data_session.commit()
  450. return redirect('/login?message=Срок действия ссылки истек, данные удалены&danger=True')
  451. @app.errorhandler(500)
  452. def internal_server_error(error):
  453. return render_template('page_error.html', title='Ошибка сервера', error='500', message='Технические шоколадки')
  454. @app.errorhandler(404)
  455. def page_not_found(error):
  456. return render_template('page_error.html', title='Страница не найдена', error='404', message='Страница не найдена')
  457. @app.errorhandler(403)
  458. def access_error(error):
  459. return render_template('page_error.html', title='Ошибка доступа', error='403', message='Доступ сюда запрещен')
  460. def main():
  461. db_path = 'db/incepted.db'
  462. db = os.path.exists(db_path)
  463. db_session.global_init(db_path)
  464. if not db:
  465. init_db_default()
  466. serve(app, host='0.0.0.0', port=5000)
  467. if __name__ == '__main__':
  468. main()