diff --git a/.env_example b/.env_example index 653025ea..0e58b0a1 100644 --- a/.env_example +++ b/.env_example @@ -1,5 +1,3 @@ -RECAPTCHA_SITE_KEY=123 -RECAPTCHA_SECRET_KEY=123 SECRET_KEY=123 ADMIN_PASSWORD=admin SIGNUP_PAGE_ENABLED=False diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8d871b3..f985d7c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - name: Build system images (non-pulling) run: | # build base image - docker build -f Dockerfile_base -t dvivanov/dis-base:v0.3 . + docker build -f Dockerfile_base -t dvivanov/dis-base:v0.5 . - name: Build docker-compose run: | cp .env_example .env diff --git a/.github/workflows/collect_commits.yml b/.github/workflows/collect_commits.yml deleted file mode 100644 index 4082c7bb..00000000 --- a/.github/workflows/collect_commits.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Collect Commits -on: - push: - -jobs: - collect-commits: - runs-on: [self-hosted, mse] - steps: - - name: Collect info - run: | - cd /data/ - mkdir -p '${{github.repository}}' - - echo '${{ toJson(github) }}' > "./run.json" - - python3 get_info.py diff --git a/.gitignore b/.gitignore index 57966ff7..dd2a34fe 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ __pycache__/ .idea venv +.venv +.vscode *.pyc files/* @@ -14,7 +16,5 @@ node_modules src/ .env -/VERSION.json +VERSION.json -app/main/mse22/converted_files/ -/app/main/mse22/for_testing/test/.pytest_cache/ diff --git a/README.md b/README.md index 8aa9524e..cd28d245 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ ## Environment - To `.env` in root: ``` -RECAPTCHA_SITE_KEY=... -RECAPTCHA_SECRET_KEY=... SECRET_KEY=... ADMIN_PASSWORD=... SIGNUP_PAGE_ENABLED=... diff --git a/app/main/check_packs/base_criterion_pack.py b/app/main/check_packs/base_criterion_pack.py index b48e811c..c387ce40 100644 --- a/app/main/check_packs/base_criterion_pack.py +++ b/app/main/check_packs/base_criterion_pack.py @@ -18,7 +18,8 @@ def __init__(self, raw_criterions, file_type, min_score=1.0, name=None, **kwargs def init(self, file_info): # create criterion objects, ignore errors - validation was performed earlier - self.criterions, errors = init_criterions(self.raw_criterions, file_type=self.file_type, file_info=file_info) + file_info['file_type'] = self.file_type + self.criterions, errors = init_criterions(self.raw_criterions, file_info=file_info) def check(self): result = [] diff --git a/app/main/check_packs/pack_config.py b/app/main/check_packs/pack_config.py index 91e08134..1c722a79 100644 --- a/app/main/check_packs/pack_config.py +++ b/app/main/check_packs/pack_config.py @@ -50,6 +50,7 @@ ["empty_task_page_check"], ["water_in_the_text_check"], ["report_task_tracker"], + ["report_3_level_in_content_check"], ] DEFAULT_TYPE = 'pres' diff --git a/app/main/check_packs/utils.py b/app/main/check_packs/utils.py index 41e105d8..94de6606 100644 --- a/app/main/check_packs/utils.py +++ b/app/main/check_packs/utils.py @@ -6,11 +6,12 @@ logger = getLogger('root_logger') -def init_criterions(criterions, file_type, file_info={}): +def init_criterions(criterions, file_info): """ criterions = [[criterion_id, criterion_params], ...] # criterion_params is dict """ try: + file_type = file_info['file_type'] existing_criterions = AVAILABLE_CHECKS.get(file_type['type'], {}) errors = [] initialized_checks = [] diff --git a/app/main/checks/base_check.py b/app/main/checks/base_check.py index 5e431d10..302795f1 100644 --- a/app/main/checks/base_check.py +++ b/app/main/checks/base_check.py @@ -13,7 +13,6 @@ def answer(mod, *args): class BaseCriterion: description = None label = None - file_type = None id = None priority = False # if priority criterion is failed -> check is failed @@ -21,6 +20,7 @@ def __init__(self, file_info): self.file = file_info.get('file') self.filename = file_info.get('filename', '') self.pdf_id = file_info.get('pdf_id') + self.file_type = file_info.get('file_type') def check(self): raise NotImplementedError() @@ -36,8 +36,8 @@ def name(self): class BasePresCriterion(BaseCriterion): - file_type = 'pres' + pass class BaseReportCriterion(BaseCriterion): - file_type = {'type': 'report', 'report_type': 'VKR'} + pass \ No newline at end of file diff --git a/app/main/checks/presentation_checks/__init__.py b/app/main/checks/presentation_checks/__init__.py index 52bd5f73..8a0a64fb 100644 --- a/app/main/checks/presentation_checks/__init__.py +++ b/app/main/checks/presentation_checks/__init__.py @@ -17,3 +17,4 @@ from .name_of_image_check import PresImageCaptureCheck from .task_tracker import TaskTracker from .overview_in_tasks import OverviewInTasks +from .decimal_places import PresDecimalPlacesCheck \ No newline at end of file diff --git a/app/main/checks/presentation_checks/decimal_places.py b/app/main/checks/presentation_checks/decimal_places.py new file mode 100644 index 00000000..0187222c --- /dev/null +++ b/app/main/checks/presentation_checks/decimal_places.py @@ -0,0 +1,16 @@ +from app.utils.decimal_places_check import DecimalPlacesCheck +from ..base_check import BasePresCriterion, answer + +class PresDecimalPlacesCheck(BasePresCriterion): + label = 'Проверка на избыточное количество десятичных знаков' + description = 'Проверка на избыточное количество десятичных знаков в числах' + id = 'decimal_places_check' + + def __init__(self, file_info, max_decimal_places=2, max_violations=3): + super().__init__(file_info) + self.checker = DecimalPlacesCheck(file_info, max_decimal_places, max_violations) + + def check(self): + total_violations, detected_pages = self.checker.find_violations_in_texts(enumerate(self.file.get_text_from_slides(), start=1)) + result_str, result_score = self.checker.get_result_msg_and_score(total_violations, detected_pages, self.format_page_link) + return answer(result_score, result_str) diff --git a/app/main/checks/presentation_checks/image_share.py b/app/main/checks/presentation_checks/image_share.py index 60e51330..be62b088 100644 --- a/app/main/checks/presentation_checks/image_share.py +++ b/app/main/checks/presentation_checks/image_share.py @@ -23,4 +23,3 @@ def check(self): ограничение - {round(self.limit, 2)}') else: return answer(True, f'Пройдена!') - return answer(False, 'Во время обработки произошла критическая ошибка') diff --git a/app/main/checks/report_checks/__init__.py b/app/main/checks/report_checks/__init__.py index 30f22617..3b5914f9 100644 --- a/app/main/checks/report_checks/__init__.py +++ b/app/main/checks/report_checks/__init__.py @@ -34,3 +34,5 @@ from .task_tracker import ReportTaskTracker from .paragraphs_count_check import ReportParagraphsCountCheck from .template_name import ReportTemplateNameCheck +from .check_chapters_3_level import ReportСhaptersLevel3ContentCheck +from .decimal_places import ReportDecimalPlacesCheck diff --git a/app/main/checks/report_checks/banned_words_check.py b/app/main/checks/report_checks/banned_words_check.py index 351b403e..58c67cd3 100644 --- a/app/main/checks/report_checks/banned_words_check.py +++ b/app/main/checks/report_checks/banned_words_check.py @@ -5,12 +5,13 @@ class ReportBannedWordsCheck(BaseReportCriterion): label = "Проверка наличия запретных слов в тексте отчёта" - description = 'Запрещено упоминание слова "мы"' + description = 'Запрещено упоминание определенных "опасных" слов' id = 'banned_words_check' def __init__(self, file_info, headers_map=None): super().__init__(file_info) self.words = [] + self.warned_words = [] self.min_count = 0 self.max_count = 0 if headers_map: @@ -21,12 +22,14 @@ def __init__(self, file_info, headers_map=None): def late_init(self): self.headers_main = self.file.get_main_headers(self.file_type['report_type']) if self.headers_main in StyleCheckSettings.CONFIGS.get(self.config): - self.words = [morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['banned_words']] + self.words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['banned_words']} + self.warned_words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['warned_words']} self.min_count = StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['min_count_for_banned_words_check'] self.max_count = StyleCheckSettings.CONFIGS.get(self.config)[self.headers_main]['max_count_for_banned_words_check'] else: if 'any_header' in StyleCheckSettings.CONFIGS.get(self.config): - self.words = [morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)['any_header']['banned_words']] + self.words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)['any_header']['banned_words']} + self.warned_words = {morph.normal_forms(word)[0] for word in StyleCheckSettings.CONFIGS.get(self.config)['any_header']['warned_words']} self.min_count = StyleCheckSettings.CONFIGS.get(self.config)['any_header']['min_count_for_banned_words_check'] self.max_count = StyleCheckSettings.CONFIGS.get(self.config)['any_header']['max_count_for_banned_words_check'] @@ -34,29 +37,35 @@ def check(self): if self.file.page_counter() < 4: return answer(False, "В отчете недостаточно страниц. Нечего проверять.") self.late_init() - detected_lines = {} result_str = f'Запрещенные слова: {"; ".join(self.words)}
' - count = 0 + banned_counter = {'words': self.words, 'detected_lines': {}, 'count': 0} + warned_counter = {'words': self.warned_words,'detected_lines': {}, 'count': 0} for k, v in self.file.pdf_file.get_text_on_page().items(): lines_on_page = re.split(r'\n', v) for index, line in enumerate(lines_on_page): - words_on_line = re.split(r'[^\w-]+', line) - words_on_line = [morph.normal_forms(word)[0] for word in words_on_line] - count_banned_words = set(words_on_line).intersection(self.words) - if count_banned_words: - count += len(count_banned_words) - if k not in detected_lines.keys(): - detected_lines[k] = [] - detected_lines[k].append(f'Строка {index + 1}: {line} [{"; ".join(count_banned_words)}]') - if len(detected_lines): + words_on_line = {morph.normal_forms(word)[0] for word in re.split(r'[^\w-]+', line)} + for counter in (banned_counter, warned_counter): + count_banned_words = words_on_line.intersection(counter['words']) + if count_banned_words: + counter['count'] += len(count_banned_words) + if k not in counter['detected_lines'].keys(): + counter['detected_lines'][k] = [] + counter['detected_lines'][k].append(f'Строка {index + 1}: {line} [{"; ".join(count_banned_words)}]') + if len(banned_counter['detected_lines']): result_str += 'Обнаружены запретные слова!

' - for k, v in detected_lines.items(): - result_str += f'Страница №{k}:
{"
".join(detected_lines[k])}

' + for k, v in banned_counter['detected_lines'].items(): + result_str += f'Страница №{k}:
{"
".join(banned_counter['detected_lines'][k])}

' else: result_str = 'Пройдена!' + + if len(warned_counter['detected_lines']): + result_str += f'

Обнаружены потенциально опасные слова (не влияют на результат проверки)!
Обратите внимание, что их использование возможно только в подтвержденных случаях: {"; ".join(self.warned_words)}

' + for k, v in warned_counter['detected_lines'].items(): + result_str += f'Страница №{k}:
{"
".join(warned_counter['detected_lines'][k])}

' + result_score = 1 - if count > self.min_count: - if count <= self.max_count: + if banned_counter['count'] > self.min_count: + if banned_counter['count'] <= self.max_count: result_score = 0.5 else: result_score = 0 diff --git a/app/main/checks/report_checks/banned_words_in_literature.py b/app/main/checks/report_checks/banned_words_in_literature.py index 498afa56..4b3d24a6 100644 --- a/app/main/checks/report_checks/banned_words_in_literature.py +++ b/app/main/checks/report_checks/banned_words_in_literature.py @@ -27,6 +27,7 @@ def check(self): if self.file.page_counter() < 4: return answer(False, "В отчете недостаточно страниц. Нечего проверять.") detected_words_dict = {} + # TODO: проверить совместимость / дублируемость LR и VKR if self.file_type['report_type'] == 'LR': list_of_literature = self.find_literature() if len(list_of_literature) == 0: @@ -51,7 +52,7 @@ def check(self): else: detected_words_dict[child_number] = banned_word else: - return answer(False, 'Во время обработки произошла критическая ошибка') + return answer(False, 'Во время обработки произошла критическая ошибка - указан неверный тип работы в наборе критериев') if detected_words_dict: result_str = "" for i in sorted(detected_words_dict.keys()): diff --git a/app/main/checks/report_checks/chapters.py b/app/main/checks/report_checks/chapters.py index e822d7fc..52b54526 100644 --- a/app/main/checks/report_checks/chapters.py +++ b/app/main/checks/report_checks/chapters.py @@ -13,7 +13,7 @@ class ReportChapters(BaseReportCriterion): def __init__(self, file_info): super().__init__(file_info) self.headers = [] - self.target_styles = StyleCheckSettings.VKR_CONFIG + self.target_styles = StyleCheckSettings.VKR_CONFIG if (self.file_type['report_type'] == 'VKR') else StyleCheckSettings.LR_CONFIG self.target_styles = list(map(lambda elem: { "style": self.construct_style_from_description(elem["style"]) }, self.target_styles.values())) @@ -29,7 +29,7 @@ def __init__(self, file_info): level += 1 def late_init(self): - self.headers = self.file.make_chapters(self.file_type['report_type']) + self.headers = self.file.make_chapters()#self.file_type['report_type']) @staticmethod def construct_style_from_description(style_dict): @@ -57,38 +57,35 @@ def check(self): return answer(False, "В отчете недостаточно страниц. Нечего проверять.") self.late_init() result_str = '' - if self.file_type['report_type'] == 'VKR': - if not len(self.headers): - return answer(False, "Не найдено ни одного заголовка.

Проверьте корректность использования стилей.") - for header in self.headers: - marked_style = 0 - for key in self.docx_styles.keys(): - if not marked_style: - for style_name in self.docx_styles[key]: - if header["style"].find(style_name) >= 0: - if self.style_regex[key].match(header["text"]): - marked_style = 1 - err = self.style_diff(header["styled_text"], self.target_styles[key]["style"]) - err = list(map(lambda msg: f'Стиль "{header["style"]}": ' + msg, err)) - result_str += ("
".join(err) + "
" if len(err) else "") - break + if not len(self.headers): + return answer(False, "Не найдено ни одного заголовка.

Проверьте корректность использования стилей.") + for header in self.headers: + marked_style = 0 + for key in self.docx_styles.keys(): if not marked_style: - err = f"Заголовок \"{header['text']}\": " - err += f'Стиль "{header["style"]}" не соответстует ни одному из стилей заголовков.' - result_str += (str(err) + "
") + for style_name in self.docx_styles[key]: + if header["style"].find(style_name) >= 0: + if self.style_regex[key].match(header["text"]): + marked_style = 1 + err = self.style_diff(header["styled_text"], self.target_styles[key]["style"]) + err = list(map(lambda msg: f'Стиль "{header["style"]}": ' + msg, err)) + result_str += ("
".join(err) + "
" if len(err) else "") + break + if not marked_style: + err = f"Заголовок \"{header['text']}\": " + err += f'Стиль "{header["style"]}" не соответствует ни одному из стилей заголовков.' + result_str += (str(err) + "
") - if not result_str: - return answer(True, "Форматирование заголовков соответствует требованиям.") - else: - result_string = f'Найдены ошибки в оформлении заголовков:
{result_str}
' - result_string += ''' - Попробуйте сделать следующее: - - ''' - return answer(False, result_string) + if not result_str: + return answer(True, "Форматирование заголовков соответствует требованиям.") else: - return answer(False, 'Во время обработки произошла критическая ошибка') + result_string = f'Найдены ошибки в оформлении заголовков:
{result_str}
' + result_string += ''' + Попробуйте сделать следующее: + + ''' + return answer(False, result_string) diff --git a/app/main/checks/report_checks/check_chapters_3_level.py b/app/main/checks/report_checks/check_chapters_3_level.py new file mode 100644 index 00000000..709aff9c --- /dev/null +++ b/app/main/checks/report_checks/check_chapters_3_level.py @@ -0,0 +1,51 @@ +from ..base_check import BaseReportCriterion, answer + +class ReportСhaptersLevel3ContentCheck(BaseReportCriterion): + label = "Проверка содержания на наличия объктов 3 уровня" + description = "В содержании не должно быть объектов третьего уровня" + id = 'report_3_level_in_content_check' + + def __init__(self, file_info): + super().__init__(file_info) + + + def check(self): + try: + headers = self.file.make_chapters(self.file_type['report_type']) + + if not headers: + return answer(False, "Не найдено ни одного заголовка.") + + level_3_count = 0 + bool_content_find = False + for header in headers: + if header["text"].upper() == "СОДЕРЖАНИЕ": + bool_content_find = True + level_3_count = self._count_level_3_headers(header["child"]) + break + + if not bool_content_find: + return answer(False, "Не найдено заголовка 'Содержание'") + + if level_3_count > 0: + result_str = f"Найдено {level_3_count} заголовков 3 уровня и выше. " + result_str += "Содержание должно содержать только заголовки 1 и 2 уровня.
" + return answer(False, result_str) + + return answer(True, "Все заголовки соответствуют требованиям (1-2 уровень)") + + except Exception as e: + return answer(False, f"Ошибка при проверке: {str(e)}") + + def _count_level_3_headers(self, content): + count = 0 + + for header in content: + if self._is_level_3_or_higher(header): + count += 1 + count += self._count_level_3_headers(header["child"]) + + return count + + def _is_level_3_or_higher(self, header): + return header["level"] >= 3 diff --git a/app/main/checks/report_checks/decimal_places.py b/app/main/checks/report_checks/decimal_places.py new file mode 100644 index 00000000..7e6b8823 --- /dev/null +++ b/app/main/checks/report_checks/decimal_places.py @@ -0,0 +1,16 @@ +from app.utils.decimal_places_check import DecimalPlacesCheck +from ..base_check import BaseReportCriterion, answer + +class ReportDecimalPlacesCheck(BaseReportCriterion): + label = 'Проверка на избыточное количество десятичных знаков' + description = 'Проверка на избыточное количество десятичных знаков в числах' + id = 'decimal_places_check' + + def __init__(self, file_info, max_decimal_places=2, max_violations=3): + super().__init__(file_info) + self.checker = DecimalPlacesCheck(file_info, max_decimal_places, max_violations) + + def check(self): + total_violations, detected_pages = self.checker.find_violations_in_texts(self.file.pdf_file.get_text_on_page().items()) + result_str, result_score = self.checker.get_result_msg_and_score(total_violations, detected_pages, self.format_page_link) + return answer(result_score, result_str) diff --git a/app/main/checks/report_checks/headers_at_page_top_check.py b/app/main/checks/report_checks/headers_at_page_top_check.py index d699cd1b..677c337e 100644 --- a/app/main/checks/report_checks/headers_at_page_top_check.py +++ b/app/main/checks/report_checks/headers_at_page_top_check.py @@ -2,7 +2,7 @@ class ReportHeadersAtPageTopCheck(BaseReportCriterion): - label = "Проверка расположения разделов первого уровня с новой страницы" + label = "Проверка расположения разделов второго уровня с новой страницы" description = '' id = "headers_at_page_top_check" @@ -23,6 +23,7 @@ def check(self): return answer(False, "В отчете недостаточно страниц. Нечего проверять.") result = True result_str = "" + # TODO: проверить совместимость / дублируемость LR и VKR if self.file_type["report_type"] == 'LR': for header in self.headers: found = False @@ -77,7 +78,7 @@ def check(self): f"Заголовок второго уровня \"{header['text']}\" " f"находится не в начале страницы или занимает больше двух строк.") else: - result_str = "Во время обработки произошла критическая ошибка" + result_str = "Во время обработки произошла критическая ошибка - указан неверный тип работы в наборе критериев" return answer(False, result_str) if not result_str: diff --git a/app/main/checks/report_checks/image_references.py b/app/main/checks/report_checks/image_references.py index f9f68e6b..a55d66ad 100644 --- a/app/main/checks/report_checks/image_references.py +++ b/app/main/checks/report_checks/image_references.py @@ -21,22 +21,19 @@ def check(self): if self.file.page_counter() < 4: return answer(False, "В отчете недостаточно страниц. Нечего проверять.") result_str = '' - if self.file_type['report_type'] == 'VKR': - self.late_init_vkr() - if not len(self.headers): - return answer(False, "Не найдено ни одного заголовка.

Проверьте корректность использования стилей.") - number_of_images, all_numbers = self.count_images_vkr() - count_file_image_object = self.file.pdf_file.get_image_num() - if count_file_image_object and not number_of_images: - return answer(False, f'В отчёте найдено {count_file_image_object} рисунков, но не найдено ни одной подписи рисунка.

Если в вашей работе присутствуют рисунки, убедитесь, что для их подписи был ' - f'использован стиль {self.image_style}, и формат: ' - f'"Рисунок <Номер рисунка> — <Название рисунка>".') - elif not number_of_images: - return answer(True, f'Не найдено ни одного рисунка.

Если в вашей работе присутствуют рисунки, убедитесь, что для их подписи был ' - f'использован стиль {self.image_style}, и формат: ' - f'"Рисунок <Номер рисунка> — <Название рисунка>".') - else: - return answer(False, 'Во время обработки произошла критическая ошибка') + self.late_init_vkr() + if not len(self.headers): + return answer(False, "Не найдено ни одного заголовка.

Проверьте корректность использования стилей.") + number_of_images, all_numbers = self.count_images_vkr() + count_file_image_object = self.file.pdf_file.get_image_num() + if count_file_image_object and not number_of_images: + return answer(False, f'В отчёте найдено {count_file_image_object} рисунков, но не найдено ни одной подписи рисунка.

Если в вашей работе присутствуют рисунки, убедитесь, что для их подписи был ' + f'использован стиль {self.image_style}, и формат: ' + f'"Рисунок <Номер рисунка> — <Название рисунка>".') + elif not number_of_images: + return answer(True, f'Не найдено ни одного рисунка.

Если в вашей работе присутствуют рисунки, убедитесь, что для их подписи был ' + f'использован стиль {self.image_style}, и формат: ' + f'"Рисунок <Номер рисунка> — <Название рисунка>".') references = self.search_references() if len(references.symmetric_difference(all_numbers)) == 0: diff --git a/app/main/checks/report_checks/literature_references.py b/app/main/checks/report_checks/literature_references.py index bcd89792..c0243d48 100644 --- a/app/main/checks/report_checks/literature_references.py +++ b/app/main/checks/report_checks/literature_references.py @@ -36,6 +36,7 @@ def check(self): number_of_sources = 0 start_literature_par = 0 result_str = '' + # TODO: проверить совместимость / дублируемость LR и VKR if self.file_type['report_type'] == 'LR': start_literature_par = self.find_start_paragraph() if start_literature_par: @@ -51,7 +52,7 @@ def check(self): start_literature_par = header["number"] number_of_sources = self.count_sources_vkr(header) else: - return answer(False, 'Во время обработки произошла критическая ошибка') + return answer(False, 'Во время обработки произошла критическая ошибка - указан неверный тип работы в наборе критериев') if not number_of_sources: return answer(False, f'В Списке использованных источников не найдено ни одного источника.

Проверьте корректность использования нумированного списка.') diff --git a/app/main/checks/report_checks/main_character_check.py b/app/main/checks/report_checks/main_character_check.py index c596e999..47914a6f 100644 --- a/app/main/checks/report_checks/main_character_check.py +++ b/app/main/checks/report_checks/main_character_check.py @@ -82,12 +82,6 @@ def extract_table_contents(self, table): contents.append("|".join(row_text)) return contents - def calculate_find_value(self, table, index): - count = int((len(table) - index - 2) / 2) - if count >= 0: - return count - return 0 - def check_table(self, check_list, table, table_num): for item in check_list: for i, line in enumerate(table): @@ -105,10 +99,7 @@ def check_table(self, check_list, table, table_num): continue elif item["key"] in ["Зав. кафедрой", "Консультант"] and item["found_key"] > 0: - if item["key"] == "Консультант": - if item["found_key"] == 1: - item["find"] += self.calculate_find_value(table, i) for value in item["value"]: - if re.search(value, line): + if "Руководитель" not in line and re.search(value, line): # исключаем из поиска строки с рукодителем item["found_value"] += 1 item["logs"] += f"'{item['key']}': значение компоненты '{value}' найдено в строке '{line}' в таблице №{table_num}
" diff --git a/app/main/checks/report_checks/main_text_check.py b/app/main/checks/report_checks/main_text_check.py index bc73b149..8ff371e5 100644 --- a/app/main/checks/report_checks/main_text_check.py +++ b/app/main/checks/report_checks/main_text_check.py @@ -51,38 +51,35 @@ def check(self): return answer(False, "В отчете недостаточно страниц. Нечего проверять.") self.late_init() result_str = '' - if self.file_type['report_type'] == 'VKR': - if not len(self.headers): - return answer(False, - "Не найдено ни одного заголовка.

Проверьте корректность использования стилей.") - for header in self.headers: - if header["text"].find("ПРИЛОЖЕНИЕ") >= 0: - break - err_string = '' - for child in header["child"]: - marked_style = 0 - for i in range(len(self.main_text_styles)): - if child["style"].find(self.main_text_styles[i]) >= 0: - marked_style = 1 - err = self.style_diff(child["styled_text"], self.target_styles[i]["style"]) - err = list(map(lambda msg: f'Стиль "{child["style"]}": ' + msg, err)) - err_string += ("
".join(err) + "
" if len(err) else "") - break - if not marked_style: - err = f"Абзац \"{child['text'][:17] + '...' if len(child['text']) > 20 else child['text']}\": " - err += f'Стиль "{child["style"]}" не соответстует ни одному из стилей основного текста.' - err_string += (str(err) + "
") - if err_string: - result_str += f'    Ошибки в разделе {header["text"]}:
' - result_str += err_string - if not result_str: - return answer(True, "Форматирование текста соответствует требованиям.") - else: - result_str += f'

Перечень допустимых стилей основного текста (Названия как в документе):' \ - f'

{"
".join(x for x in self.main_text_styles_names)}' - result_str += f'

Если в вашем документе нет какого-либо из перечисленных стилей, перенесите ' \ - f'текст пояснительной записки в документ с последней версией ' \ - f'шаблона.' - return answer(False, result_str) + if not len(self.headers): + return answer(False, + "Не найдено ни одного заголовка, что не позволило определить разделы отчета.

Проверьте корректность использования стилей.") + for header in self.headers: + if header["text"].find("ПРИЛОЖЕНИЕ") >= 0: + break + err_string = '' + for child in header["child"]: + marked_style = 0 + for i in range(len(self.main_text_styles)): + if child["style"].find(self.main_text_styles[i]) >= 0: + marked_style = 1 + err = self.style_diff(child["styled_text"], self.target_styles[i]["style"]) + err = list(map(lambda msg: f'Стиль "{child["style"]}": ' + msg, err)) + err_string += ("
".join(err) + "
" if len(err) else "") + break + if not marked_style: + err = f"Абзац \"{child['text'][:17] + '...' if len(child['text']) > 20 else child['text']}\": " + err += f'Стиль "{child["style"]}" не соответстует ни одному из стилей основного текста.' + err_string += (str(err) + "
") + if err_string: + result_str += f'    Ошибки в разделе {header["text"]}:
' + result_str += err_string + if not result_str: + return answer(True, "Форматирование текста соответствует требованиям.") else: - return answer(False, 'Во время обработки произошла критическая ошибка') + result_str += f'

Перечень допустимых стилей основного текста (Названия как в документе):' \ + f'

{"
".join(x for x in self.main_text_styles_names)}' + result_str += f'

Если в вашем документе нет какого-либо из перечисленных стилей, перенесите ' \ + f'текст пояснительной записки в документ с последней версией ' \ + f'шаблона.' + return answer(False, result_str) diff --git a/app/main/checks/report_checks/short_sections_check.py b/app/main/checks/report_checks/short_sections_check.py index 53729cfb..b50c5f30 100644 --- a/app/main/checks/report_checks/short_sections_check.py +++ b/app/main/checks/report_checks/short_sections_check.py @@ -48,6 +48,7 @@ def check(self): return answer(False, "В отчете недостаточно страниц. Нечего проверять.") result = True result_str = "" + # TODO: проверить совместимость / дублируемость LR и VKR if self.file_type['report_type'] == 'LR': self.late_init() if self.cutoff_line is None: @@ -109,7 +110,7 @@ def check(self): result_str = "Все обязательные разделы достигают рекомендуемой длины." return answer(result, result_str) else: - return answer(False, 'Во время обработки произошла критическая ошибка') + return answer(False, 'Во время обработки произошла критическая ошибка - указан неверный тип работы в наборе критериев') def build_header_hierarchy(self): cutoff_index = 0 diff --git a/app/main/checks/report_checks/style_check_settings.py b/app/main/checks/report_checks/style_check_settings.py index 7c3a61b5..31775d75 100644 --- a/app/main/checks/report_checks/style_check_settings.py +++ b/app/main/checks/report_checks/style_check_settings.py @@ -8,16 +8,17 @@ class StyleCheckSettings: HEADER_2_NUM_REGEX = "^[1-9][0-9]*\\.([1-9][0-9]*\\ )([\\w\\s]+)$" HEADER_NUM_REGEX = "^\\d.+$" HEADER_REGEX = "^\\D+.+$" - HEADER_1_REGEX = "^()([\\w\\s]+)$" - HEADER_2_REGEX = "^()([\\w\\s]+)\\.$" - STD_BANNED_WORDS = ['мы', 'моя', 'мои', 'моё', 'наш', 'наши', + HEADER_1_REGEX = r"^([1-9][0-9]*\.([1-9][0-9]*\.)){0,1}([\w\s]+)$" + HEADER_2_REGEX = r"^([1-9][0-9]*\.([1-9][0-9]*\.)*){0,1}([\w\s]+)$" + STD_BANNED_WORDS = ('мы', 'моя', 'мои', 'моё', 'наш', 'наши', 'аттач', 'билдить', 'бинарник', 'валидный', 'дебаг', 'деплоить', 'десктопное', 'железо', - 'исходники', 'картинка', 'консольное', 'конфиг', 'кусок', 'либа', 'лог', 'мануал', 'машина', + 'исходники', 'картинка', 'консольное', 'конфиг', 'кусок', 'либа', 'лог', 'мануал', 'отнаследованный', 'парсинг', 'пост', 'распаковать', 'сбоит', 'скачать', 'склонировать', 'скрипт', 'тестить', 'тул', 'тула', 'тулза', 'фиксить', 'флажок', 'флаг', 'юзкейс', 'продакт', 'продакшн', - 'прод', 'фидбек', 'дедлайн', 'дэдлайн', 'оптимально', 'оптимальный', 'надежный', 'интуитивный', + 'прод', 'фидбек', 'дедлайн', 'дэдлайн', 'оптимально', 'надежный', 'интуитивный', 'хороший', 'плохой', 'идеальный', 'быстро', 'медленно', 'какой-нибудь', 'некоторый', 'почти' - ] # TODO: list of "warning" words + ) + STD_WARNED_WORDS = ('машина', 'оптимальный') # TODO: list of "warning" words STD_MIN_LIT_REF = 1 STD_MAX_LIT_REF = 1000 #just in case for future edit HEADER_1_STYLE = { @@ -84,10 +85,10 @@ class StyleCheckSettings: "line_spacing": 1.0 } TABLE_CAPTION_STYLE_VKR = { - "alignment": WD_ALIGN_PARAGRAPH.JUSTIFY, + "alignment": WD_ALIGN_PARAGRAPH.LEFT, "font_name": "Times New Roman", "font_size_pt": 14.0, - "first_line_indent_cm": 1.25, + "first_line_indent_cm": 0.0, "line_spacing": 1.0 } @@ -95,24 +96,17 @@ class StyleCheckSettings: LR_CONFIG = { 'any_header': { - "style": HEADER_1_STYLE, - "docx_style": ["heading 1"], - "headers": ["Исходный код программы"], - "unify_regex": APPENDIX_UNIFY_REGEX, - "regex": APPENDIX_REGEX, + "style": HEADER_2_STYLE, + "docx_style": ["heading 3", "heading 4"], + "headers": ["Цель работы", "Выполнение работы", "Выводы"], + "unify_regex": HEADER_2_REGEX, + "regex": HEADER_2_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, 'min_ref_for_literature_references_check': STD_MIN_LIT_REF, 'mах_ref_for_literature_references_check': STD_MAX_LIT_REF - }, - 'second_header': - { - "style": HEADER_2_STYLE, - "docx_style": ["heading 2"], - "headers": ["Цель работы", "Выполнение работы", "Выводы"], - "unify_regex": None, - "regex": HEADER_1_REGEX, } } @@ -125,6 +119,7 @@ class StyleCheckSettings: "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, 'min_ref_for_literature_references_check': STD_MIN_LIT_REF, @@ -149,7 +144,8 @@ class StyleCheckSettings: "ПЛАН РАБОТЫ НА ВЕСЕННИЙ СЕМЕСТР", "ОТЗЫВ РУКОВОДИТЕЛЯ", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"], "unify_regex": None, "regex": HEADER_REGEX, - "banned_words": STD_BANNED_WORDS + ['доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'] + "banned_words": STD_BANNED_WORDS + ('доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'), + "warned_words": STD_WARNED_WORDS }, } @@ -162,7 +158,8 @@ class StyleCheckSettings: "ПЛАН РАБОТЫ НА ОСЕННИЙ СЕМЕСТР", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"], "unify_regex": None, "regex": HEADER_REGEX, - "banned_words": STD_BANNED_WORDS + ['доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'] + "banned_words": STD_BANNED_WORDS + ('доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'), + "warned_words": STD_WARNED_WORDS }, } @@ -175,7 +172,8 @@ class StyleCheckSettings: "ПЛАН РАБОТЫ НА ВЕСЕННИЙ СЕМЕСТР", "ОТЗЫВ РУКОВОДИТЕЛЯ", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ"], "unify_regex": None, "regex": HEADER_REGEX, - "banned_words": STD_BANNED_WORDS + ['доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'] + "banned_words": STD_BANNED_WORDS + ('доработать', 'доработка', 'переписать', 'рефакторинг', 'исправление'), + "warned_words": STD_WARNED_WORDS }, } @@ -193,6 +191,7 @@ class StyleCheckSettings: "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, }, @@ -208,6 +207,7 @@ class StyleCheckSettings: "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_count_for_banned_words_check': 3, 'max_count_for_banned_words_check': 6, } @@ -230,6 +230,7 @@ class StyleCheckSettings: "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_ref_for_literature_references_check': 1, 'mах_ref_for_literature_references_check': 1000, #just for future possible edit 'min_count_for_banned_words_check': 2, @@ -249,6 +250,7 @@ class StyleCheckSettings: "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_ref_for_literature_references_check': 3, 'mах_ref_for_literature_references_check': 1000, #just for future possible edit 'min_count_for_banned_words_check': 2, @@ -268,6 +270,7 @@ class StyleCheckSettings: "unify_regex": None, "regex": HEADER_REGEX, "banned_words": STD_BANNED_WORDS, + "warned_words": STD_WARNED_WORDS, 'min_ref_for_literature_references_check': 5, 'mах_ref_for_literature_references_check': 1000, #just for future possible edit 'min_count_for_banned_words_check': 2, @@ -287,12 +290,12 @@ class StyleCheckSettings: "style": LISTING_STYLE }, { - "name": "Подпись рисунка", + "name": "вкр_подпись для рисунков", "style": IMAGE_CAPTION_STYLE }, { - "name": "Подпись таблицы", - "style": TABLE_CAPTION_STYLE + "name": "вкр_подпись таблицы", + "style": TABLE_CAPTION_STYLE_VKR } ] diff --git a/app/main/checks/report_checks/table_references.py b/app/main/checks/report_checks/table_references.py index 05afb271..d0861600 100644 --- a/app/main/checks/report_checks/table_references.py +++ b/app/main/checks/report_checks/table_references.py @@ -21,17 +21,14 @@ def check(self): if self.file.page_counter() < 4: return answer(False, "В отчете недостаточно страниц. Нечего проверять.") result_str = '' - if self.file_type['report_type'] == 'VKR': - self.late_init_vkr() - if not len(self.headers): - return answer(False, "Не найдено ни одного заголовка.

Проверьте корректность использования стилей.") - number_of_tables, all_numbers = self.count_tables_vkr() - if not number_of_tables: - return answer(True, f'Не найдено ни одной таблицы.

Если в вашей работе присутствуют таблицы, убедитесь, что для их подписи был ' - f'использован стиль {self.table_style} и формат ' - f'"Таблица <Номер таблицы> -- <Название таблицы>".') - else: - return answer(False, 'Во время обработки произошла критическая ошибка') + self.late_init_vkr() + if not len(self.headers): + return answer(False, "Не найдено ни одного заголовка, что не позволило определить разделы отчета.

Проверьте корректность использования стилей.") + number_of_tables, all_numbers = self.count_tables_vkr() + if not number_of_tables: + return answer(True, f'Не найдено ни одной таблицы.

Если в вашей работе присутствуют таблицы, убедитесь, что для их подписи был ' + f'использован стиль {self.table_style} и формат ' + f'"Таблица <Номер таблицы> -- <Название таблицы>".') references = self.search_references() if len(references.symmetric_difference(all_numbers)) == 0: return answer(True, f"Пройдена!") diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index ac30dee4..d300d131 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -43,28 +43,27 @@ def __make_paragraphs(self, paragraphs): tmp_paragraphs.append(Paragraph(paragraphs[i])) return tmp_paragraphs - def make_chapters(self, work_type): + def make_chapters(self, work_type='VKR'): if not self.chapters: tmp_chapters = [] - if work_type == 'VKR': - # find headers - header_ind = -1 - par_num = 0 - head_par_ind = -1 - for par_ind in range(len(self.styled_paragraphs)): - head_par_ind += 1 - style_name = self.paragraphs[par_ind].paragraph_style_name.lower() - if style_name.find("heading") >= 0: - header_ind += 1 - par_num = 0 - tmp_chapters.append({"style": style_name, "text": self.styled_paragraphs[par_ind]["text"].strip(), - "styled_text": self.styled_paragraphs[par_ind], "number": head_par_ind, - "child": []}) - elif header_ind >= 0: - par_num += 1 - tmp_chapters[header_ind]["child"].append( - {"style": style_name, "text": self.styled_paragraphs[par_ind]["text"], - "styled_text": self.styled_paragraphs[par_ind], "number": head_par_ind}) + # find headers + header_ind = -1 + par_num = 0 + head_par_ind = -1 + for par_ind in range(len(self.styled_paragraphs)): + head_par_ind += 1 + style_name = self.paragraphs[par_ind].paragraph_style_name.lower() + if style_name.find("heading") >= 0: + header_ind += 1 + par_num = 0 + tmp_chapters.append({"style": style_name, "text": self.styled_paragraphs[par_ind]["text"].strip(), + "styled_text": self.styled_paragraphs[par_ind], "number": head_par_ind, + "child": []}) + elif header_ind >= 0: + par_num += 1 + tmp_chapters[header_ind]["child"].append( + {"style": style_name, "text": self.styled_paragraphs[par_ind]["text"], + "styled_text": self.styled_paragraphs[par_ind], "number": head_par_ind}) self.chapters = tmp_chapters return self.chapters diff --git a/app/routes/api.py b/app/routes/api.py index fac54056..a84da7c6 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -60,7 +60,7 @@ def api_criteria_pack(): if file_type == DEFAULT_REPORT_TYPE_INFO['type']: file_type_info['report_type'] = report_type if report_type in REPORT_TYPES else DEFAULT_REPORT_TYPE_INFO[ 'report_type'] - inited, err = init_criterions(raw_criterions, file_type=file_type_info) + inited, err = init_criterions(raw_criterions, file_info={"file_type": file_type_info}) if len(raw_criterions) != len(inited) or err: msg = f"При инициализации набора {pack_name} возникли ошибки. JSON-конфигурация: '{raw_criterions}'. Успешно инициализированные: {inited}. Возникшие ошибки: {err}." return {'data': msg, 'time': datetime.now()}, 400 diff --git a/app/routes/results.py b/app/routes/results.py index 91cc5b7f..18a08c24 100644 --- a/app/routes/results.py +++ b/app/routes/results.py @@ -3,6 +3,7 @@ from time import time from flask import Blueprint, Response, render_template +from flask_login import current_user, login_required from wsgiref.handlers import format_date_time as format_date from app.db import db_methods @@ -16,6 +17,7 @@ @results_bp.route("/", methods=["GET"]) +@login_required def results_main(_id): try: oid = ObjectId(_id) @@ -24,11 +26,15 @@ def results_main(_id): return render_template("./404.html") check = db_methods.get_check(oid) if check is not None: - # show processing time for user - avg_process_time = None if check.is_ended else db_methods.get_average_processing_time() - return render_template("./results.html", navi_upload=True, results=check, - columns=TABLE_COLUMNS, avg_process_time=avg_process_time, - stats=format_check(check.pack())) + # show check only for author or admin or api_access_token + if current_user.is_admin or current_user.username == check.user or check.user == "api_access_token": + # show processing time for user + avg_process_time = None if check.is_ended else db_methods.get_average_processing_time() + return render_template("./results.html", navi_upload=True, results=check, + columns=TABLE_COLUMNS, avg_process_time=avg_process_time, + stats=format_check(check.pack())) + else: + return "У вас нет прав на просмотр результатов чужих проверок", 403 else: logger.info("Запрошенная проверка не найдена: " + _id) return render_template("./404.html") diff --git a/app/routes/upload.py b/app/routes/upload.py index 521b3d16..b81ba313 100644 --- a/app/routes/upload.py +++ b/app/routes/upload.py @@ -15,7 +15,7 @@ @login_required def upload_main(): if request.method == "POST": - if current_user.is_LTI or True: # app.recaptcha.verify(): - disable captcha (cause no login) + if current_user.is_LTI or current_user.is_admin: return run_task() else: abort(401) diff --git a/app/server.py b/app/server.py index bc6a737f..986dcd48 100644 --- a/app/server.py +++ b/app/server.py @@ -15,7 +15,6 @@ request, url_for) from flask_login import (LoginManager, current_user, login_required, login_user, logout_user) -from flask_recaptcha import ReCaptcha import servants.user as user from app.utils import format_check_for_table, check_file @@ -59,7 +58,6 @@ app = Flask(__name__, static_folder="./../src/", template_folder="./templates/") app.config.from_pyfile('settings.py') -app.recaptcha = ReCaptcha(app=app) app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['CELERY_RESULT_BACKEND'] = os.environ.get("CELERY_RESULT_BACKEND", "redis://localhost:6379") diff --git a/app/settings.py b/app/settings.py index 3792f9d2..1a54230b 100644 --- a/app/settings.py +++ b/app/settings.py @@ -30,9 +30,6 @@ # setup variables ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', '') -RECAPTCHA_ENABLED = True -RECAPTCHA_SITE_KEY = os.environ.get('RECAPTCHA_SITE_KEY', '') -RECAPTCHA_SECRET_KEY = os.environ.get('RECAPTCHA_SECRET_KEY', '') SECRET_KEY = os.environ.get('SECRET_KEY', '') SIGNUP_PAGE_ENABLED = os.environ.get('SIGNUP_PAGE_ENABLED', 'True') == 'True' diff --git a/app/templates/404.html b/app/templates/404.html index bf8140dd..ad648890 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -4,7 +4,7 @@ {% block main %}
- Страница не найдена! +

Запрашиваемый ресурс не найден

diff --git a/app/templates/upload.html b/app/templates/upload.html index f4a140d0..7a020be8 100644 --- a/app/templates/upload.html +++ b/app/templates/upload.html @@ -51,9 +51,6 @@
{{ uploading_label }}
aria-valuenow="10" aria-valuemin="0" aria-valuemax="100" style="width: 10%"> - {% if not (current_user.is_LTI or current_user.is_admin) %} - {{ recaptcha }} - {% endif %}

Общий объем загружаемых файлов не должен превышать {{ config.MAX_CONTENT_LENGTH//1024//1024 }} Mб.

diff --git a/app/utils/decimal_places_check.py b/app/utils/decimal_places_check.py new file mode 100644 index 00000000..b4ed1650 --- /dev/null +++ b/app/utils/decimal_places_check.py @@ -0,0 +1,139 @@ +import re +from collections import defaultdict + + +class DecimalPlacesCheck: + DECIMAL_PATTERN = r'\b\d{1,3}(?:[.,]\d{3})*(?:[.,]\d+)?\b' + + def __init__(self, file_info, max_decimal_places=2, max_violations=3): + self.file_type = file_info['file_type']['type'] + self.max_decimal_places = max_decimal_places + self.max_violations = max_violations + + def is_valid_number(self, match, text): + start_pos = match.start() + end_pos = match.end() + + char_before = text[start_pos - 1] if start_pos > 0 else ' ' + + if char_before == '.': + return False + + if end_pos < len(text): + char_after = text[end_pos] + if char_after == '.' and end_pos + 1 < len(text) and text[end_pos + 1].isdigit(): + return False + + return True + + def count_decimal_places(self, number_str): + normalized = number_str.replace(',', '.') + + if '.' in normalized: + decimal_part = normalized.split('.')[1] + return len(decimal_part) + + return 0 + + def find_violations_in_text(self, text): + violations = [] + matches = re.finditer(self.DECIMAL_PATTERN, text) + + for match in matches: + if not self.is_valid_number(match, text): + continue + + number_str = match.group() + decimal_places = self.count_decimal_places(number_str) + + if decimal_places > self.max_decimal_places: + violations.append((number_str, decimal_places, match)) + + return violations + + def find_violations_in_lines(self, lines): + violations = [] + + for line_idx, line in enumerate(lines): + line_violations = self.find_violations_in_text(line) + + for number_str, decimal_places, _ in line_violations: + violations.append((line_idx, number_str, decimal_places, line)) + + return violations + + def find_violations_in_texts(self, texts): + pages = defaultdict(list) + total_violations = 0 + + for idx, text in texts: + lines = re.split(r'\n', text) + violations = self.find_violations_in_lines(lines) + + for line_idx, number_str, decimal_places, line in violations: + total_violations += 1 + violation_msg = self.format_violation_message( + line_idx, line, number_str, decimal_places + ) + pages[idx].append(violation_msg) + + return (total_violations, pages) + + def highlight_number(self, line, number_str): + return line.replace(number_str, f'{number_str}', 1) + + def format_violation_message(self, line_idx, line, number_str, decimal_places): + highlighted_line = self.highlight_number(line, number_str) + return ( + f'Строка {line_idx + 1}: {highlighted_line} ' + f'(найдено {decimal_places} знаков после запятой, ' + f'максимум: {self.max_decimal_places})' + ) + + def format_success_message(self): + return ( + f'Проверка пройдена! Все числа имеют допустимое количество ' + f'десятичных знаков (не более {self.max_decimal_places}).' + ) + + def format_failure_message(self, total_violations, + violations_by_location, + format_page_link_fn=None): + result_str = ( + f'Найдены числа с избыточным количеством десятичных знаков!
' + f'Максимально допустимое количество знаков после запятой: {self.max_decimal_places}
' + f'Максимально допустимое количество нарушений: {self.max_violations}
' + f'Всего нарушений: {total_violations}

' + ) + + for location_num, violations in violations_by_location.items(): + if format_page_link_fn: + location_str = f'Страница {format_page_link_fn([location_num])}' + else: + location_str = f'Страница №{location_num}' + + result_str += f'{location_str}:
' + result_str += '
'.join(violations) + result_str += '

' + + return result_str + + def get_result_msg_and_score(self, total_violations, detected_pages, format_page_link_fn=None): + if total_violations > self.max_violations: + result_str = self.format_failure_message( + total_violations, + detected_pages, + format_page_link_fn + ) + result_score = 0 + elif total_violations > 0: + result_str = self.format_failure_message( + total_violations, + detected_pages, + format_page_link_fn + ) + result_score = 1.0 + else: + result_str = self.format_success_message() + result_score = 1.0 + return result_str, result_score \ No newline at end of file diff --git a/assets/scripts/upload.js b/assets/scripts/upload.js index 1db21586..ba5eb3f8 100644 --- a/assets/scripts/upload.js +++ b/assets/scripts/upload.js @@ -44,9 +44,6 @@ const showBdOverwhelmedMessage = () => { alert('База данных перегружена (недостаточно места для загрузки новых файлов). Свяжитесь с администратором'); } -const showRecaptchaMessage = () => { - alert('Пройдите recaptcha, чтобы продолжить!'); -} const resetFileUpload = () => { pdf_uploaded = false; @@ -138,10 +135,6 @@ async function upload() { } formData.append("file", file); formData.append("file_type", file_type); - if ($('div.g-recaptcha').length) { - let response = grecaptcha.getResponse(); - formData.append("g-recaptcha-response", response); - } const bar = $("#uploading_progress"); $("#uploading_progress_holder").css("display", "block"); @@ -198,12 +191,8 @@ async function upload() { } upload_button.click(async () => { - if ($('div.g-recaptcha').length && grecaptcha.getResponse().length === 0) { - showRecaptchaMessage(); - } else { - upload_button.prop("disabled", true); - await upload(); - } + upload_button.prop("disabled", true); + await upload(); }); function toggleTable(tableId) { diff --git a/assets/styles/404.css b/assets/styles/404.css index c833cc62..384902ba 100644 --- a/assets/styles/404.css +++ b/assets/styles/404.css @@ -1,3 +1,3 @@ #middle-container { - background-color: black; + background-color: rgb(255, 255, 255); } diff --git a/requirements.txt b/requirements.txt index ca44fa76..28962a73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ docx2python~=2.0.4 filetype==1.2.0 Flask==2.0.3 flask-login==0.5.0 -flask-recaptcha==0.4.2 flask-security==3.0.0 flower==1.2.0 fsspec==2022.2.0 @@ -20,7 +19,7 @@ numpy==1.26.4 oauthlib~=3.1.0 odfpy==1.4.1 odfpy==1.4.1 -pandas~=2.0.3 +pandas==3.0.0 pdfplumber==0.6.1 PyMuPDF==1.26.6 PyPDF2~=3.0.1 diff --git a/tests/test_recheck.py b/tests/test_recheck.py new file mode 100644 index 00000000..4fc8135c --- /dev/null +++ b/tests/test_recheck.py @@ -0,0 +1,36 @@ +import time +from basic_selenium_test import BasicSeleniumTest +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + + +class RecheckTestSelenium(BasicSeleniumTest): + + def test_recheck_file(self): + check_id = self.open_statistic() + if check_id: + URL = self.get_url(f"/recheck/{check_id}") + self.get_driver().get(URL) + obj = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.ID, "results_title")) + ) + if "Производится проверка файла" in obj.text: + start_time = time.time() + max_time = 240 + while (time.time() - start_time) < max_time: + time.sleep(10) + try: + obj = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.ID, "results_table")) + ) + if obj is not None: + self.assertNotEquals(obj, None) + return + except: + continue + self.fail("Result of check is not found") + else: + self.fail("No checking status after /recheck") + else: + self.skipTest("No check in system for testing recheck")