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")