diff --git a/app/bd_helper/bd_helper.py b/app/bd_helper/bd_helper.py index 0af9723f..36989a71 100644 --- a/app/bd_helper/bd_helper.py +++ b/app/bd_helper/bd_helper.py @@ -174,6 +174,13 @@ def get_checks_pdf(checks_id): return None +def find_pdf_by_file_id(file_id): + try: + return fs.open_download_stream(file_id) + except NoFile: + return None + + # Deletes checks with given id, returns presentation def delete_check(presentation, checks_id): if checks_id in presentation.checks: diff --git a/app/lti_session_passback/lti/utils.py b/app/lti_session_passback/lti/utils.py index 707bed26..15c27ea9 100644 --- a/app/lti_session_passback/lti/utils.py +++ b/app/lti_session_passback/lti/utils.py @@ -61,7 +61,10 @@ def get_criteria_from_launch(data): detect_additional = custom.get('detect_additional', 'True') criteria = dict((k, custom[k]) for k in all_checks if k in custom) eval_criteria = dict((key, eval(value)) for key, value in criteria.items() if key != 'slides_number') - eval_criteria['slides_number'] = {'sld_num': sld_num[criteria.get('slides_number', 'bsc')], 'detect_additional': eval(detect_additional)} + if criteria.get('slides_number') not in ['bsc', 'msc']: + eval_criteria['slides_number'] = {'sld_num': eval(criteria.get('slides_number')), 'detect_additional': eval(detect_additional)} + else: + eval_criteria['slides_number'] = {'sld_num': sld_num[criteria.get('slides_number', 'bsc')], 'detect_additional': eval(detect_additional)} return eval_criteria def extract_passback_params(data): diff --git a/app/main/checker.py b/app/main/checker.py index e058b37b..70648820 100644 --- a/app/main/checker.py +++ b/app/main/checker.py @@ -12,9 +12,9 @@ def check(presentation, checks, presentation_name): check_names = checks.get_checks().keys() - check_classes = [SldNumCheck(presentation, checks.slides_number), SldEnumCheck(presentation), TitleFormatCheck(presentation), \ - FindDefSld(presentation, key_slide.goals_and_tasks), FindDefSld(presentation, key_slide.approbation), \ - SearchKeyWord(presentation, key_slide.relevance), FindDefSld(presentation, key_slide.conclusion), \ + check_classes = [SldNumCheck(presentation, checks.slides_number), SldEnumCheck(presentation, checks.conv_pdf_fs_id), TitleFormatCheck(presentation, checks.conv_pdf_fs_id), \ + FindDefSld(presentation, key_slide.goals_and_tasks, checks.conv_pdf_fs_id), FindDefSld(presentation, key_slide.approbation, checks.conv_pdf_fs_id), \ + SearchKeyWord(presentation, key_slide.relevance, checks.conv_pdf_fs_id), FindDefSld(presentation, key_slide.conclusion, checks.conv_pdf_fs_id), \ FindTasks(presentation, key_slide.goals_and_tasks, checks.slide_every_task), \ SldSimilarity(presentation, key_slide.goals_and_tasks, key_slide.conclusion, checks.conclusion_actual), FurtherDev(presentation, key_slide.goals_and_tasks, key_slide.conclusion)] diff --git a/app/main/checks/base_check.py b/app/main/checks/base_check.py index 45d72d4d..947691e0 100644 --- a/app/main/checks/base_check.py +++ b/app/main/checks/base_check.py @@ -1,3 +1,5 @@ +from flask import url_for + def answer(mod, value, *args): return { 'pass': bool(mod), @@ -11,3 +13,8 @@ def __init__(self, presentation): def check(self): raise NotImplementedError() + + def format_page_link(self, error): + base_pdf_link = url_for('get_pdf', _id=self.pdf_id) + page = lambda err: f'{base_pdf_link}#page={str(err)}' + return [f'{str(e)}' for e in error] diff --git a/app/main/checks/find_def_sld.py b/app/main/checks/find_def_sld.py index 111149d9..c0211c67 100644 --- a/app/main/checks/find_def_sld.py +++ b/app/main/checks/find_def_sld.py @@ -1,9 +1,10 @@ from app.main.checks.base_check import BaseCheck, answer class FindDefSld(BaseCheck): - def __init__(self, presentation, type_of_slide): + def __init__(self, presentation, type_of_slide, pdf_id): super().__init__(presentation) self.type_of_slide = type_of_slide + self.pdf_id = pdf_id def check(self): found_slides, found_idxs = [], [] @@ -14,4 +15,5 @@ def check(self): if len(found_slides) == 0: return answer(False, None, 'Слайд не найден') else: + found_idxs = self.format_page_link(found_idxs) return answer(True, found_idxs, 'Найден под номером: {}'.format(', '.join(map(str, found_idxs)))) diff --git a/app/main/checks/search_keyword.py b/app/main/checks/search_keyword.py index b9b3427f..a3ab25b9 100644 --- a/app/main/checks/search_keyword.py +++ b/app/main/checks/search_keyword.py @@ -1,12 +1,14 @@ from app.main.checks.base_check import BaseCheck, answer class SearchKeyWord(BaseCheck): - def __init__(self, presentation, key_slide): + def __init__(self, presentation, key_slide, pdf_id): super().__init__(presentation) self.key_slide = key_slide + self.pdf_id = pdf_id def check(self): for i, text in enumerate(self.presentation.get_text_from_slides(), 1): if self.key_slide.lower() in str(text).lower(): - return answer(True, i, 'Найден под номером: {}'.format(i)) + found = self.format_page_link([i]) + return answer(True, i, 'Найден под номером: {}'.format(', '.join(map(str, found)))) return answer(False, None, 'Слайд не найден') diff --git a/app/main/checks/sld_enum.py b/app/main/checks/sld_enum.py index 6c96eba4..c68c6653 100644 --- a/app/main/checks/sld_enum.py +++ b/app/main/checks/sld_enum.py @@ -3,8 +3,9 @@ class SldEnumCheck(BaseCheck): - def __init__(self, presentation): + def __init__(self, presentation, pdf_id): super().__init__(presentation) + self.pdf_id = pdf_id def check(self): error = [] @@ -16,5 +17,6 @@ def check(self): if not error: return answer(True, error, "Пройдена!") else: + error = self.format_page_link(error) return answer(False, error, 'Не пройдена, проблемные слайды: {}'.format(', '.join(map(str, error))), \ 'Убедитесь в корректности формата номеров слайдов') diff --git a/app/main/checks/title_format.py b/app/main/checks/title_format.py index 03ecc083..4c0df18a 100644 --- a/app/main/checks/title_format.py +++ b/app/main/checks/title_format.py @@ -3,17 +3,18 @@ import itertools class TitleFormatCheck(BaseCheck): - def __init__(self, presentation): + def __init__(self, presentation, pdf_id): super().__init__(presentation) self.empty_headers = [] self.len_exceeded = [] + self.pdf_id = pdf_id def exceeded_verdict(self): - return 'Превышение длины: {}'.format(', '.join(map(str, self.len_exceeded))), \ + return 'Превышение длины: {}'.format(', '.join(map(str, self.format_page_link(self.len_exceeded)))), \ 'Убедитесь в корректности заголовка и текста слайда' def empty_verdict(self): - return 'Заголовки не найдены: {}.'.format(', '.join(map(str, self.empty_headers))), \ + return 'Заголовки не найдены: {}.'.format(', '.join(map(str, self.format_page_link(self.empty_headers)))), \ 'Убедитесь, что слайд озаглавлен соответстующим элементом' def get_failing_headers(self): diff --git a/app/servants/data.py b/app/servants/data.py index fd37e9b5..d2851853 100644 --- a/app/servants/data.py +++ b/app/servants/data.py @@ -46,8 +46,8 @@ def upload(request, upload_folder): presentation = get_presentation(presentation_id) checks = create_check(current_user) - check(parse(filename), checks, presentation_name) checks.conv_pdf_fs_id = converted_id + check(parse(filename), checks, presentation_name) presentation, checks_id = add_check(presentation, checks, filename) if delete and exists(filename): diff --git a/app/server.py b/app/server.py index db053fa6..636ae70b 100644 --- a/app/server.py +++ b/app/server.py @@ -171,7 +171,7 @@ def checks(_id): @login_required def get_pdf(_id): try: - file = bd_helper.get_checks_pdf(ObjectId(_id)) + file = bd_helper.find_pdf_by_file_id(ObjectId(_id)) except bson.errors.InvalidId: logger.error('_id exception in fetching pdf occured:', exc_info=True) return render_template("./404.html") diff --git a/assets/scripts/main.js b/assets/scripts/main.js index d8cb99ba..16e435e2 100644 --- a/assets/scripts/main.js +++ b/assets/scripts/main.js @@ -13,6 +13,7 @@ import 'bootstrap-datepicker'; import 'bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' import * as CryptoJS from "crypto-js"; +import * as pdfjsLib from 'pdfjs-dist'; import '../styles/main.css'; diff --git a/assets/scripts/results.js b/assets/scripts/results.js index 3c8d9ca1..6a0ea2d2 100644 --- a/assets/scripts/results.js +++ b/assets/scripts/results.js @@ -1 +1,87 @@ import '../styles/results.css'; +import * as pdfjsLib from 'pdfjs-dist'; +import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry"; + +let pdfDoc, + pageNum, + pageIsRendering, + pageNumIsPending, + scale, + canvas, + ctx, + currentPage; + +pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker; + +const renderPage = num => { + pageIsRendering = true; + + pdfDoc.getPage(num).then(page => { + const viewport = page.getViewport({ scale }); + canvas.height = viewport.height; + canvas.width = viewport.width; + + const renderCtx = { + canvasContext: ctx, + viewport + }; + + page.render(renderCtx).promise.then(() => { + pageIsRendering = false; + + if (pageNumIsPending !== null) { + renderPage(pageNumIsPending); + pageNumIsPending = null; + } + }); + + $('#page-num')[0].textContent = num; + }); +}; + +const queueRenderPage = num => { + if (pageIsRendering) { + pageNumIsPending = num; + } else { + renderPage(num); + } +}; + +const showPrevPage = () => { + if (pageNum <= 1) { + return; + } + pageNum--; + queueRenderPage(pageNum); +}; + +const showNextPage = () => { + if (pageNum >= pdfDoc.numPages) { + return; + } + pageNum++; + queueRenderPage(pageNum); +}; + +if ($("#pdf").length !== 0){ + var href = $("#pdf").attr('href'); + pdfDoc = null; + pageNum = 1; + pageIsRendering = false, + pageNumIsPending = null; + scale = 1.1; + canvas = $("#the-canvas")[0]; + ctx = canvas.getContext("2d"); + + pdfjsLib + .getDocument(href) + .promise.then(pdfDoc_ => { + pdfDoc = pdfDoc_; + + $('#page-count')[0].textContent = pdfDoc.numPages; + renderPage(pageNum); + }); + + $('#prev-page').click(showPrevPage); + $('#next-page').click(showNextPage); +} diff --git a/db_versioning/versions.py b/db_versioning/versions.py index 05701861..42498f70 100644 --- a/db_versioning/versions.py +++ b/db_versioning/versions.py @@ -8,13 +8,13 @@ class Version: @classmethod def update_database(cls, collections, prev_version): """ - must contains (objects from pymongo) + must contains (objects from pymongo) - users - presentations - checks """ raise NotImplementedError() - + @classmethod def to_dict(cls): return dict( @@ -27,7 +27,7 @@ def get_version(version_name): for version in VERSIONS: if version.version == version_name: return version - return None + return None class Version10(Version): @@ -69,7 +69,7 @@ class Version20(Version): @classmethod def update_database(cls, collections, prev_version): if prev_version in (Version10.VERSION_NAME, Version11.VERSION_NAME): - # process all checks of pres and set filename + user + # process all checks of pres and set filename + user for presentation in collections['presentations'].find({}): filename = presentation['name'] user_doc = collections['users'].find_one({'presentations': presentation['_id'] }) @@ -79,7 +79,7 @@ def update_database(cls, collections, prev_version): {"_id": check_id}, { '$set': { 'filename': filename, 'user': user } } ) - + # if we have checks without presentation == after prev loop it doesn't include filename/user field # set default user='moevm', filename='_.pptx' collections['checks'].update( @@ -126,14 +126,43 @@ def update_database(cls, collections, prev_version): else: raise Exception(f'Неподдерживаемый переход с версии {prev_version}') +class Version21(Version): + VERSION_NAME = '2.1' + CHANGES = '0/1 -> T/F; criteria.slides_number: [] -> {}' + + @classmethod + def update_database(cls, collections, prev_version): + if prev_version in (Version10.VERSION_NAME, Version11.VERSION_NAME, Version20.VERSION_NAME): + + #mv from 0/-1 -> T/F + for check in collections['checks'].find({}): + check_dt = Checks(check).get_checks().items() + upd_check = {k: False if v == -1 else v for k, v in check_dt} + collections['checks'].update( + {'_id': check['_id']}, + {'$set': upd_check} + ) + + for user in collections['users'].find(): + criteria_dt = Checks(user['criteria']).get_checks() + upd_criteria = {k: False if v == -1 else True for k, v in criteria_dt.items()} + upd_criteria['slides_number'] = {"sld_num": criteria_dt['slides_number'], "detect_additional": True} if upd_criteria['slides_number'] else False + collections['users'].update( + {'_id': user['_id']}, + {'$set': {'criteria': upd_criteria}} + ) + + else: + raise Exception(f'Неподдерживаемый переход с версии {prev_version}') VERSIONS = { '1.0': Version10, '1.1': Version11, '2.0': Version20, + '2.1': Version21, } -LAST_VERSION = '2.0' +LAST_VERSION = '2.1' for _, ver in VERSIONS.items(): - print(ver.to_dict()) \ No newline at end of file + print(ver.to_dict()) diff --git a/package.json b/package.json index b6a5b62a..6b52e59a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "crypto-js": "^3.1.2", "bootstrap-datepicker": "^1.9.0", "bootstrap-table": "^1.18.3", - "bootstrap-icons": "1.5.0" + "bootstrap-icons": "1.5.0", + "pdfjs-dist": "^2.2.228" + } } diff --git a/templates/results.html b/templates/results.html index e0374079..ad093254 100644 --- a/templates/results.html +++ b/templates/results.html @@ -68,7 +68,7 @@

Результат проверки: {{ "Не проведена" }} {% else %} {% for item in results.slides_enum.get('verdict', ["Пройдена!"] if results.slides_enum.pass else ["Не пройдена"]) %} - {{ item }}
+ {{ item|safe }}
{% endfor %} {% endif %} @@ -85,7 +85,7 @@

Результат проверки: {{ "Не проведена" }} {% else %} {% for item in results.slides_headers.get('verdict', ["Пройдена!"] if results.slides_headers.pass else ["Не пройдена"]) %} - {{ item }}
+ {{ item|safe }}
{% endfor %} {% endif %} @@ -102,7 +102,7 @@

Результат проверки: {{ "Не проведена" }} {% else %} {% for item in results.goals_slide.get('verdict', ["Найден"] if results.goals_slide.pass else ["Не найден!"]) %} - {{ item }}
+ {{ item|safe }}
{% endfor %} {% endif %} @@ -119,7 +119,7 @@

Результат проверки: {{ "Не проведена" }} {% else %} {% for item in results.probe_slide.get('verdict', ["Найден"] if results.probe_slide.pass else ["Не найден!"]) %} - {{ item }}
+ {{ item|safe }}
{% endfor %} {% endif %} @@ -136,7 +136,7 @@

Результат проверки: {{ "Не проведена" }} {% else %} {% for item in results.actual_slide.get('verdict', ["Найден"] if results.actual_slide.pass else ["Не найден!"]) %} - {{ item }}
+ {{ item|safe }}
{% endfor %} {% endif %} @@ -153,7 +153,7 @@

Результат проверки: {{ "Не проведена" }} {% else %} {% for item in results.conclusion_slide.get('verdict', ["Найден"] if results.conclusion_slide.pass else ["Не найден!"]) %} - {{ item }}
+ {{ item|safe }}
{% endfor %} {% endif %} @@ -207,12 +207,31 @@

Результат проверки: {{ item }}
{% endfor %} {% endif %} + {% if results.conv_pdf_fs_id and results.conclusion_along.pass %} + {{ "Найдено на странице: " }} + {% for item in results.conclusion_slide.get('value') %} + {{ item|safe }} + {{ ", " if not loop.last else "" }} + {% endfor %} + {% endif %} + {% if results.conv_pdf_fs_id %} +
+
+ + + + Страница из + +
+ +
+ {% endif %}
Скачать презентацию {% if results.conv_pdf_fs_id %} - PDF + PDF {% endif %}