Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
  • release
2 results

Target

Select target project
  • courseeval/EvaP
1 result
Select Git revision
  • main
  • release
2 results
Show changes
Commits on Source (7)
Showing
with 133 additions and 110 deletions
import argparse
import os
import subprocess # nosec
import sys
import unittest
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.core.management.base import BaseCommand, CommandError
from django.test.runner import DiscoverRunner
......@@ -59,8 +58,8 @@ class Command(BaseCommand):
print(f"Could not find {command[0]} command", file=self.stderr)
except KeyboardInterrupt:
pass
except subprocess.CalledProcessError as error:
sys.exit(error.returncode)
except subprocess.CalledProcessError as e:
raise CommandError("Error during command execution", returncode=e.returncode) from e
def compile(self, watch=False, fresh=False, **_options):
static_directory = settings.STATICFILES_DIRS[0]
......@@ -93,4 +92,6 @@ class Command(BaseCommand):
# Enable debug mode as otherwise a collectstatic beforehand would be necessary,
# as missing static files would result into an error.
test_runner = RenderPagesRunner(debug_mode=True)
test_runner.run_tests([])
failed_tests = test_runner.run_tests([])
if failed_tests > 0:
raise CommandError("Failures during render_pages")
......@@ -14,7 +14,7 @@ from evap.staff.tests.utils import WebTestStaffMode
@override_settings(INSTITUTION_EMAIL_DOMAINS=["institution.com", "student.institution.com"])
class SampleXlsTests(WebTestStaffMode):
class SampleTableImport(WebTestStaffMode):
@classmethod
def setUpTestData(cls):
cls.manager = make_manager()
......@@ -24,13 +24,13 @@ class SampleXlsTests(WebTestStaffMode):
Degree.objects.filter(name_de="Bachelor").update(import_names=["Bachelor", "B. Sc."])
Degree.objects.filter(name_de="Master").update(import_names=["Master", "M. Sc."])
def test_sample_xls(self):
def test_sample_semester_file(self):
page = self.app.get(reverse("staff:semester_import", args=[self.semester.pk]), user=self.manager)
original_user_count = UserProfile.objects.count()
form = page.forms["semester-import-form"]
form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample.xls"),)
form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample.xlsx"),)
page = form.submit(name="operation", value="test")
form = page.forms["semester-import-form"]
......@@ -40,13 +40,13 @@ class SampleXlsTests(WebTestStaffMode):
self.assertEqual(UserProfile.objects.count(), original_user_count + 4)
def test_sample_user_xls(self):
def test_sample_user_file(self):
page = self.app.get("/staff/user/import", user=self.manager)
original_user_count = UserProfile.objects.count()
form = page.forms["user-import-form"]
form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample_user.xls"),)
form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample_user.xlsx"),)
page = form.submit(name="operation", value="test")
form = page.forms["user-import-form"]
......
import io
import xlwt
import openpyxl
# fmt: off
duplicate_user_import_filedata = {
'Users': [
......@@ -120,6 +122,14 @@ test_enrollment_data_degree_merge_filedata = {
]
}
test_enrollment_data_error_merge_filedata = {
'MA Belegungen': [
['Degree', 'Student last name', 'Student first name', 'Student email address', 'Course kind', 'Course is graded', 'Course name (de)', 'Course name (en)', 'Responsible title', 'Responsible last name', 'Responsible first name', 'Responsible email address'],
['Grandmaster', 'Quid', 'Bastius', 'bastius.quid@external.example.com', 'jaminar', 'probably not', 'Bauen', 'Build', '', 'Sed', 'Diam', '345@external.example.com'],
['Beginner,Bachelor', 'Lorem', 'Ipsum', 'ipsum.lorem@institution.example.com', 'jaminar', 'probably not', 'Bauen', 'Build', '', 'Sed', 'Diam', '345@external.example.com'],
],
}
test_enrollment_data_import_names_filedata = {
'MA Belegungen': [
['Degree', 'Student last name', 'Student first name', 'Student email address', 'Course kind', 'Course is graded', 'Course name (de)', 'Course name (en)', 'Responsible title', 'Responsible last name', 'Responsible first name', 'Responsible email address'],
......@@ -152,14 +162,17 @@ valid_user_courses_import_filedata = {
]
}
# fmt: on
def create_memory_excel_file(data):
memory_excel_file = io.BytesIO()
workbook = xlwt.Workbook()
workbook = openpyxl.Workbook()
for sheet_name, sheet_data in data.items():
sheet = workbook.add_sheet(sheet_name)
for (row_num, row_data) in enumerate(sheet_data):
for (column_num, cell_data) in enumerate(row_data):
sheet.write(row_num, column_num, cell_data)
sheet = workbook.create_sheet(sheet_name)
for row_num, row_data in enumerate(sheet_data, 1):
for column_num, cell_data in enumerate(row_data, 1):
# openpyxl rows start at 1
sheet.cell(row=row_num, column=column_num).value = cell_data
workbook.save(memory_excel_file)
return memory_excel_file.getvalue()
File deleted
File added
File deleted
File added
......@@ -101,7 +101,11 @@ class ImportForm(forms.Form):
vote_start_datetime = forms.DateTimeField(label=_("Start of evaluation"), localize=True, required=False)
vote_end_date = forms.DateField(label=_("End of evaluation"), localize=True, required=False)
excel_file = forms.FileField(label=_("Excel file"), required=False)
excel_file = forms.FileField(
label=_("Excel file"),
required=False,
widget=forms.FileInput(attrs={"accept": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}),
)
class UserImportForm(forms.Form):
......
from collections import OrderedDict, defaultdict
from dataclasses import dataclass
from enum import Enum
from io import BytesIO
from typing import Dict, Set
import xlrd
import openpyxl
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import transaction
......@@ -13,7 +14,7 @@ from django.utils.translation import gettext_lazy
from evap.evaluation.models import Contribution, Course, CourseType, Degree, Evaluation, UserProfile
from evap.evaluation.tools import clean_email
from evap.staff.tools import ImportType, create_user_list_html_string_for_message, merge_dictionaries_of_sets
from evap.staff.tools import ImportType, create_user_list_html_string_for_message
def sorted_messages(messages):
......@@ -223,33 +224,36 @@ class ExcelImporter:
# (otherwise, testing is a pain)
self.users = OrderedDict()
def read_book(self, file_content):
def read_book(self, file_content: bytes):
try:
self.book = xlrd.open_workbook(file_contents=file_content)
except xlrd.XLRDError as e:
self.book = openpyxl.load_workbook(BytesIO(file_content))
except Exception as e: # pylint: disable=broad-except
self.errors[ImporterError.SCHEMA].append(_("Couldn't read the file. Error: {}").format(e))
def check_column_count(self, expected_column_count):
for sheet in self.book.sheets():
if sheet.nrows <= self.skip_first_n_rows:
for sheet in self.book:
if sheet.max_row <= self.skip_first_n_rows:
continue
if sheet.ncols != expected_column_count:
if sheet.max_column != expected_column_count:
self.errors[ImporterError.SCHEMA].append(
_("Wrong number of columns in sheet '{}'. Expected: {}, actual: {}").format(
sheet.name, expected_column_count, sheet.ncols
sheet.title, expected_column_count, sheet.max_column
)
)
def for_each_row_in_excel_file_do(self, row_function):
for sheet in self.book.sheets():
for sheet in self.book:
try:
for row in range(self.skip_first_n_rows, sheet.nrows):
# see https://stackoverflow.com/questions/2077897/substitute-multiple-whitespace-with-single-whitespace-in-python
row_function([" ".join(cell.split()) for cell in sheet.row_values(row)], sheet, row)
self.success_messages.append(_("Successfully read sheet '%s'.") % sheet.name)
for row_index in range(self.skip_first_n_rows, sheet.max_row):
values = [cell.value if cell.value is not None else "" for cell in sheet[row_index + 1]]
# see https://stackoverflow.com/questions/2077898/substitute-multiple-whitespace-with-single-whitespace-in-python
cleaned_values = [" ".join(value.split()) for value in values]
row_function(cleaned_values, sheet, row_index)
self.success_messages.append(_("Successfully read sheet '%s'.") % sheet.title)
except Exception:
self.warnings[ImporterWarning.GENERAL].append(
_("A problem occured while reading sheet {}.").format(sheet.name)
_("A problem occured while reading sheet {}.").format(sheet.title)
)
raise
self.success_messages.append(_("Successfully read Excel file."))
......@@ -350,14 +354,14 @@ class ExcelImporter:
"""
Checks that all cells after the skipped rows contain string values (not floats or integers).
"""
for sheet in self.book.sheets():
for row in range(self.skip_first_n_rows, sheet.nrows):
if not all(isinstance(cell, str) for cell in sheet.row_values(row)):
for sheet in self.book:
for row_idx, row in enumerate(sheet.values, 1):
if not all(isinstance(cell, str) or cell is None for cell in row):
self.errors[ImporterError.SCHEMA].append(
_(
"Wrong data type in sheet '{}' in row {}."
" Please make sure all cells are string types, not numerical."
).format(sheet.name, row + 1)
).format(sheet.title, row_idx)
)
......@@ -383,36 +387,43 @@ class EnrollmentImporter(ExcelImporter):
is_graded=data[5],
responsible_email=responsible_data.email,
)
self.associations[(sheet.name, row)] = (student_data, responsible_data, evaluation_data)
self.associations[(sheet.title, row)] = (student_data, responsible_data, evaluation_data)
def process_evaluation(self, evaluation_data, sheet, row):
def process_evaluation(self, evaluation_data, sheetname, row):
evaluation_id = evaluation_data.name_en
if evaluation_id not in self.evaluations:
if evaluation_data.name_de in self.names_de:
self.errors[ImporterError.COURSE].append(
_('Sheet "{}", row {}: The German name for course "{}" already exists for another course.').format(
sheet, row + 1, evaluation_data.name_en
sheetname, row + 1, evaluation_data.name_en
)
)
else:
self.evaluations[evaluation_id] = evaluation_data
self.names_de.add(evaluation_data.name_de)
else:
if evaluation_data.equals_except_for_degrees(self.evaluations[evaluation_id]):
known_data = self.evaluations[evaluation_id]
if evaluation_data.equals_except_for_degrees(known_data):
self.warnings[ImporterWarning.DEGREE].append(
_(
'Sheet "{}", row {}: The course\'s "{}" degree differs from it\'s degree in a previous row.'
" Both degrees have been set for the course."
).format(sheet, row + 1, evaluation_data.name_en)
).format(sheetname, row + 1, evaluation_data.name_en)
)
self.evaluations[evaluation_id].degrees |= evaluation_data.degrees
self.evaluations[evaluation_id].errors = merge_dictionaries_of_sets(
self.evaluations[evaluation_id].errors, evaluation_data.errors
)
elif evaluation_data != self.evaluations[evaluation_id]:
known_data.degrees |= evaluation_data.degrees
assert evaluation_data.errors.keys() <= {"degrees", "course_type", "is_graded"}
assert known_data.errors.get("course_type") == evaluation_data.errors.get("course_type")
assert known_data.errors.get("is_graded") == evaluation_data.errors.get("is_graded")
degree_errors = known_data.errors.get("degrees", set()) | evaluation_data.errors.get("degrees", set())
if len(degree_errors) > 0:
known_data.errors["degrees"] = degree_errors
elif evaluation_data != known_data:
self.errors[ImporterError.COURSE].append(
_('Sheet "{}", row {}: The course\'s "{}" data differs from it\'s data in a previous row.').format(
sheet, row + 1, evaluation_data.name_en
sheetname, row + 1, evaluation_data.name_en
)
)
......@@ -435,6 +446,9 @@ class EnrollmentImporter(ExcelImporter):
self.errors[ImporterError.COURSE].append(
_("Course {} does already exist in this semester.").format(evaluation_data.name_de)
)
assert evaluation_data.errors.keys() <= {"degrees", "course_type", "is_graded"}
if "degrees" in evaluation_data.errors:
missing_degree_names |= evaluation_data.errors["degrees"]
if "course_type" in evaluation_data.errors:
......@@ -568,15 +582,15 @@ class UserImporter(ExcelImporter):
def read_one_user(self, data, sheet, row):
user_data = UserData(title=data[0], first_name=data[1], last_name=data[2], email=data[3], is_responsible=False)
self.associations[(sheet.name, row)] = user_data
self.associations[(sheet.title, row)] = user_data
if user_data not in self._read_user_data:
self._read_user_data[user_data] = (sheet.name, row)
self._read_user_data[user_data] = (sheet.title, row)
else:
orig_sheet, orig_row = self._read_user_data[user_data]
warningstring = _(
"The duplicated row {row} in sheet '{sheet}' was ignored. It was first found in sheet '{orig_sheet}' on row {orig_row}."
).format(
sheet=sheet.name,
sheet=sheet.title,
row=row + 1,
orig_sheet=orig_sheet,
orig_row=orig_row + 1,
......
......@@ -16,7 +16,7 @@
<h4 class="card-title">{% trans 'Import participants' %}</h4>
<h6 class="card-subtitle mb-2 text-muted">{% trans 'From Excel file' %}</h6>
<p class="card-text">
{% trans 'Upload Excel file with participant data' %} (<a href="{% url 'staff:download_sample_xls' 'sample_user.xls' %}">{% trans 'Download sample file' %}</a>,
{% trans 'Upload Excel file with participant data' %} (<a href="{% url 'staff:download_sample_file' 'sample_user.xlsx' %}">{% trans 'Download sample file' %}</a>,
<button type="button" class="btn-link" onclick="copyHeaders(['Title', 'First name', 'Last name', 'Email'])">{% trans 'Copy headers to clipboard' %}</button>).
{% trans 'This will create all containing users.' %}
</p>
......@@ -65,7 +65,7 @@
<h6 class="card-subtitle mb-2 text-muted">{% trans 'From Excel file' %}</h6>
<p class="card-text">
{% trans 'Upload Excel file with contributor data' %}
(<a href="{% url 'staff:download_sample_xls' 'sample_user.xls' %}">{% trans 'Download sample file' %}</a>,
(<a href="{% url 'staff:download_sample_file' 'sample_user.xlsx' %}">{% trans 'Download sample file' %}</a>,
<button type="button" class="btn-link" onclick="copyHeaders(['Title', 'First name', 'Last name', 'Email'])">{% trans 'Copy headers to clipboard' %}</button>).
{% trans 'This will create all containing users.' %}
</p>
......
......@@ -18,7 +18,7 @@
<div class="card-body">
<p>
{% trans 'Upload Excel file' %}
(<a href="{% url 'staff:download_sample_xls' 'sample.xls' %}">{% trans 'Download sample file' %}</a>,
(<a href="{% url 'staff:download_sample_file' 'sample.xlsx' %}">{% trans 'Download sample file' %}</a>,
<button type="button" class="btn-link" onClick="copyHeaders(['Degree', 'Participant last name', 'Participant first name', 'Participant email address', 'Course kind', 'Course is graded', 'Course name (de)', 'Course name (en)', 'Responsible title', 'Responsible last name', 'Responsible first name', 'Responsible email address'])">
{% trans 'Copy headers to clipboard' %}</button>).
{% trans 'This will create all containing participants, contributors and courses and connect them. It will also set the entered values as default for all evaluations.' %}
......
......@@ -83,7 +83,7 @@
</div>
<div class="col-answer col-lg-8 col-xl-7 d-flex">
<div class="vote-inputs">
<textarea id="preview-textarea"></textarea>
<textarea id="preview-textarea" class="form-control"></textarea>
</div>
</div>
</div>
......
......@@ -20,7 +20,7 @@
<div class="card-body">
<p>
{% trans 'Upload Excel file' %}
(<a href="{% url 'staff:download_sample_xls' 'sample_user.xls' %}">{% trans 'Download sample file' %}</a>,
(<a href="{% url 'staff:download_sample_file' 'sample_user.xlsx' %}">{% trans 'Download sample file' %}</a>,
<button type="button" class="btn-link" onclick="copyHeaders(['Title', 'First name', 'Last name', 'Email'])">{% trans 'Copy headers to clipboard' %}</button>).
{% trans 'This will create all contained users.' %}
</p>
......
......@@ -13,8 +13,8 @@ from evap.staff.tools import ImportType
class TestUserImporter(TestCase):
filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/valid_user_import.xls")
filename_invalid = os.path.join(settings.BASE_DIR, "staff/fixtures/invalid_user_import.xls")
filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/valid_user_import.xlsx")
filename_invalid = os.path.join(settings.BASE_DIR, "staff/fixtures/invalid_user_import.xlsx")
filename_random = os.path.join(settings.BASE_DIR, "staff/fixtures/random.random")
# valid user import tested in tests/test_views.py, TestUserImportView
......@@ -104,7 +104,7 @@ class TestUserImporter(TestCase):
self.assertEqual(errors_test, errors_no_test)
self.assertEqual(
errors_test[ImporterError.SCHEMA],
["Couldn't read the file. Error: Unsupported format, or corrupt file: Expected BOF record; found b'42\\n'"],
["Couldn't read the file. Error: File is not a zip file"],
)
self.assertEqual(UserProfile.objects.count(), original_user_count)
......@@ -243,6 +243,17 @@ class TestEnrollmentImporter(TestCase):
course = Course.objects.get(name_de="Bauen")
self.assertSetEqual(set(course.degrees.all()), set(Degree.objects.filter(name_de__in=["Master", "Bachelor"])))
def test_errors_are_merged(self):
excel_content = excel_data.create_memory_excel_file(excel_data.test_enrollment_data_error_merge_filedata)
__, warnings, errors = EnrollmentImporter.process(
excel_content, self.semester, self.vote_start_datetime, self.vote_end_date, test_run=False
)
self.assertIn("Both degrees have been set for the course", "".join(warnings[ImporterWarning.DEGREE]))
self.assertIn("is probably not, but must be", "".join(errors[ImporterError.IS_GRADED]))
self.assertIn("jaminar", "".join(errors[ImporterError.COURSE_TYPE_MISSING]))
self.assertIn("Beginner", "".join(errors[ImporterError.DEGREE_MISSING]))
self.assertIn("Grandmaster", "".join(errors[ImporterError.DEGREE_MISSING]))
def test_course_type_and_degrees_are_retrieved_with_import_names(self):
excel_content = excel_data.create_memory_excel_file(excel_data.test_enrollment_data_import_names_filedata)
......@@ -297,7 +308,7 @@ class TestEnrollmentImporter(TestCase):
self.assertEqual(errors_test, errors_no_test)
self.assertEqual(
errors_test[ImporterError.SCHEMA],
["Couldn't read the file. Error: Unsupported format, or corrupt file: Expected BOF record; found b'42\\n'"],
["Couldn't read the file. Error: File is not a zip file"],
)
self.assertEqual(UserProfile.objects.count(), original_user_count)
......
......@@ -7,12 +7,7 @@ from model_bakery import baker
from evap.evaluation.models import Contribution, Course, Evaluation, UserProfile
from evap.evaluation.tests.tools import WebTest
from evap.rewards.models import RewardPointGranting, RewardPointRedemption
from evap.staff.tools import (
delete_navbar_cache_for_users,
merge_dictionaries_of_sets,
merge_users,
remove_user_from_represented_and_ccing_users,
)
from evap.staff.tools import delete_navbar_cache_for_users, merge_users, remove_user_from_represented_and_ccing_users
class NavbarCacheTest(WebTest):
......@@ -262,9 +257,3 @@ class RemoveUserFromRepresentedAndCCingUsersTest(TestCase):
self.assertEqual([set(user1.delegates.all()), set(user1.cc_users.all())], [{delete_user}, {delete_user}])
self.assertEqual([set(user2.delegates.all()), set(user2.cc_users.all())], [{delete_user}, {delete_user}])
self.assertEqual(len(messages), 4)
class MiscellaneousToolsTest(TestCase):
def test_merge_dictionaries_of_sets(self):
self.assertEqual(merge_dictionaries_of_sets({"a": set([1])}, {"b": set([2])}), {"a": set([1]), "b": set([2])})
self.assertEqual(merge_dictionaries_of_sets({"a": set([1])}, {"a": set([2])}), {"a": set([1, 2])})
import datetime
import os
from io import BytesIO
from unittest.mock import PropertyMock, patch
import openpyxl
import xlrd
from django.conf import settings
from django.contrib.auth.models import Group
......@@ -51,8 +53,8 @@ from evap.staff.views import get_evaluations_with_prefetched_data
from evap.student.models import TextAnswerWarning
class TestDownloadSampleXlsView(WebTestStaffMode):
url = "/staff/download_sample_xls/sample.xls"
class TestDownloadSampleFileView(WebTestStaffMode):
url = "/staff/download_sample_file/sample.xlsx"
email_placeholder = "institution.com"
@classmethod
......@@ -63,13 +65,16 @@ class TestDownloadSampleXlsView(WebTestStaffMode):
page = self.app.get(self.url, user=self.manager)
found_institution_domains = 0
book = xlrd.open_workbook(file_contents=page.body)
for sheet in book.sheets():
for row in sheet.get_rows():
book = openpyxl.load_workbook(BytesIO(page.body))
for sheet in book:
for row in sheet.values:
for cell in row:
value = cell.value
self.assertNotIn(self.email_placeholder, value)
if "@" + settings.INSTITUTION_EMAIL_DOMAINS[0] in value:
if cell is None:
continue
self.assertNotIn(self.email_placeholder, cell)
if "@" + settings.INSTITUTION_EMAIL_DOMAINS[0] in cell:
found_institution_domains += 1
self.assertEqual(found_institution_domains, 2)
......@@ -413,8 +418,8 @@ class TestUserBulkUpdateView(WebTestStaffMode):
class TestUserImportView(WebTestStaffMode):
url = "/staff/user/import"
filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/valid_user_import.xls")
filename_invalid = os.path.join(settings.BASE_DIR, "staff/fixtures/invalid_user_import.xls")
filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/valid_user_import.xlsx")
filename_invalid = os.path.join(settings.BASE_DIR, "staff/fixtures/invalid_user_import.xlsx")
filename_random = os.path.join(settings.BASE_DIR, "staff/fixtures/random.random")
@classmethod
......@@ -1925,8 +1930,8 @@ class TestEvaluationPreviewView(WebTestStaffModeWith200Check):
class TestEvaluationImportPersonsView(WebTestStaffMode):
url = "/staff/semester/1/evaluation/1/person_management"
url2 = "/staff/semester/1/evaluation/2/person_management"
filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/valid_user_import.xls")
filename_invalid = os.path.join(settings.BASE_DIR, "staff/fixtures/invalid_user_import.xls")
filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/valid_user_import.xlsx")
filename_invalid = os.path.join(settings.BASE_DIR, "staff/fixtures/invalid_user_import.xlsx")
filename_random = os.path.join(settings.BASE_DIR, "staff/fixtures/random.random")
@classmethod
......
import os
from datetime import date, datetime, timedelta
from enum import Enum
from typing import Any, Dict, Set
from django.conf import settings
from django.contrib import messages
......@@ -369,11 +368,3 @@ def remove_user_from_represented_and_ccing_users(user, ignored_users=None, test_
cc_user.cc_users.remove(user)
remove_messages.append(_("Removed {} from the CC users of {}.").format(user.full_name, cc_user.full_name))
return remove_messages
def merge_dictionaries_of_sets(a: Dict[Any, Set], b: Dict[Any, Set]) -> Dict[Any, Set]:
return {
**a,
**b,
**({key: (a[key] | b[key]) for key in a if key in b}),
}
......@@ -83,7 +83,7 @@ urlpatterns = [
path("faq/", views.faq_index, name="faq_index"),
path("faq/<int:section_id>", views.faq_section, name="faq_section"),
path("download_sample_xls/<str:filename>", views.download_sample_xls, name="download_sample_xls"),
path("download_sample_file/<str:filename>", views.download_sample_file, name="download_sample_file"),
path("export_contributor_results/<int:contributor_id>", views.export_contributor_results_view, name="export_contributor_results"),
......
......@@ -5,6 +5,7 @@ from dataclasses import dataclass
from datetime import date, datetime
from typing import Any, Container, Dict, Optional
import openpyxl
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied, SuspiciousOperation
......@@ -21,8 +22,6 @@ from django.utils.translation import get_language
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy, ngettext
from django.views.decorators.http import require_POST
from xlrd import open_workbook
from xlutils.copy import copy as copy_workbook
from evap.contributor.views import export_contributor_results
from evap.evaluation.auth import manager_required, reviewer_required, staff_permission_required
......@@ -2229,25 +2228,21 @@ def faq_section(request, section_id):
@manager_required
def download_sample_xls(_request, filename):
def download_sample_file(_request, filename):
email_placeholder = "institution.com"
if filename not in ["sample.xls", "sample_user.xls"]:
if filename not in ["sample.xlsx", "sample_user.xlsx"]:
raise SuspiciousOperation("Invalid file name.")
read_book = open_workbook(settings.STATICFILES_DIRS[0] + "/" + filename, formatting_info=True)
write_book = copy_workbook(read_book)
for sheet_index in range(read_book.nsheets):
read_sheet = read_book.sheet_by_index(sheet_index)
write_sheet = write_book.get_sheet(sheet_index)
for row in range(read_sheet.nrows):
for col in range(read_sheet.ncols):
value = read_sheet.cell(row, col).value
if email_placeholder in value:
write_sheet.write(row, col, value.replace(email_placeholder, settings.INSTITUTION_EMAIL_DOMAINS[0]))
book = openpyxl.load_workbook(filename=settings.STATICFILES_DIRS[0] + "/" + filename)
for sheet in book:
for row in sheet:
for cell in row:
if cell.value is not None:
cell.value = cell.value.replace(email_placeholder, settings.INSTITUTION_EMAIL_DOMAINS[0])
response = FileResponse(filename, content_type="application/vnd.ms-excel")
write_book.save(response)
response = FileResponse(filename, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
book.save(response)
return response
......
File deleted