Newer
Older
self.get_page_grade_history_url_by_ordinal(
page_ordinal, course_identifier, flow_session_id),
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
return resp
def assertSubmitHistoryItemsCount( # noqa
self, page_ordinal, expected_count, course_identifier=None,
flow_session_id=None):
resp = self.get_page_submit_history_by_ordinal(
page_ordinal, course_identifier, flow_session_id)
import json
result = json.loads(resp.content.decode())["result"]
self.assertEqual(len(result), expected_count)
def assertGradeHistoryItemsCount( # noqa
self, page_ordinal, expected_count,
course_identifier=None,
flow_session_id=None,
if course_identifier is None:
course_identifier = self.get_default_course_identifier()
switch_to = self.get_default_instructor_user(course_identifier)
else:
switch_to = self.get_logged_in_user()
with self.temporarily_switch_to_user(switch_to):
resp = self.get_page_grade_history_by_ordinal(
page_ordinal, course_identifier, flow_session_id)
import json
result = json.loads(resp.content.decode())["result"]
self.assertEqual(len(result), expected_count)
def get_update_course_url(self, course_identifier=None):
if course_identifier is None:
course_identifier = self.get_default_course_identifier()
return reverse("relate-update_course", args=[course_identifier])
def post_update_course_content(self, commit_sha,
fetch_update=False,
prevent_discarding_revisions=True,
force_login_instructor=True,
course=None,
):
# course instead of course_identifier because we need to do
# refresh_from_db
course = course or self.get_default_course()
try:
commit_sha = commit_sha.decode()
except Exception:
pass
data = {"new_sha": [commit_sha]}
if not prevent_discarding_revisions:
data["prevent_discarding_revisions"] = ["on"]
if not fetch_update:
data["update"] = ["Update"]
else:
data["fetch_update"] = ["Fetch and update"]
force_login_user = None
if force_login_instructor:
force_login_user = self.get_default_instructor_user(course.identifier)
with self.temporarily_switch_to_user(force_login_user):
response = self.c.post(
self.get_update_course_url(course.identifier), data)
course.refresh_from_db()
def get_page_data_by_page_id(
self, page_id, course_identifier=None, flow_session_id=None):
flow_params = self.get_flow_params(course_identifier, flow_session_id)
return FlowPageData.objects.get(
flow_session_id=flow_params["flow_session_id"], page_id=page_id)
def get_page_visits(self, course_identifier=None,
flow_session_id=None, page_ordinal=None, page_id=None,
**kwargs):
query_kwargs = {}
if kwargs.get("answer_visit", False):
query_kwargs.update({"answer__isnull": False})
flow_params = self.get_flow_params(course_identifier, flow_session_id)
query_kwargs.update({"flow_session_id": flow_params["flow_session_id"]})
if page_ordinal is not None:
query_kwargs.update({"page_data__page_ordinal": page_ordinal})
elif page_id is not None:
query_kwargs.update({"page_data__page_id": page_id})
return FlowPageVisit.objects.filter(**query_kwargs)
def get_last_answer_visit(self, course_identifier=None,
flow_session_id=None, page_ordinal=None,
page_id=None, assert_not_none=True):
result_qset = self.get_page_visits(course_identifier,
flow_session_id, page_ordinal, page_id,
answer_visit=True).order_by('-pk')[:1]
if result_qset:
result = result_qset[0]
else:
result = None
if assert_not_none:
self.assertIsNotNone(result, "The query returns None")
return result
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
def download_all_submissions_url(self, flow_id, course_identifier):
params = {"course_identifier": course_identifier,
"flow_id": flow_id}
return reverse("relate-download_all_submissions", kwargs=params)
def get_download_all_submissions(self, flow_id, course_identifier=None):
if course_identifier is None:
course_identifier = self.get_default_course_identifier()
return self.c.get(
self.download_all_submissions_url(flow_id, course_identifier))
def post_download_all_submissions_by_group_page_id(
self, group_page_id, flow_id, course_identifier=None, **kwargs):
"""
:param group_page_id: format: group_id/page_id
:param flow_id:
:param course_identifier:
:param kwargs: for updating the default post_data
:return: response
"""
if course_identifier is None:
course_identifier = self.get_default_course_identifier()
data = {'restrict_to_rules_tag': '<<<ALL>>>',
'which_attempt': 'last',
'extra_file': '', 'download': 'Download',
'page_id': group_page_id,
'non_in_progress_only': 'on'}
data.update(kwargs)
return self.c.post(
self.download_all_submissions_url(flow_id, course_identifier),
data=data
)
def get_flow_page_analytics(self, flow_id, group_id, page_id,
course_identifier=None):
if course_identifier is None:
course_identifier = self.get_default_course_identifier()
params = {"course_identifier": course_identifier,
"flow_id": flow_id,
"group_id": group_id,
"page_id": page_id}
return self.c.get(reverse("relate-page_analytics", kwargs=params))
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
class SingleCourseTestMixin(CoursesTestMixinBase):
courses_setup_list = SINGLE_COURSE_SETUP_LIST
@classmethod
def setUpTestData(cls): # noqa
super(SingleCourseTestMixin, cls).setUpTestData()
assert len(cls.course_qset) == 1
cls.course = cls.course_qset.first()
cls.instructor_participation = Participation.objects.filter(
course=cls.course,
roles__identifier="instructor",
status=participation_status.active
).first()
assert cls.instructor_participation
cls.student_participation = Participation.objects.filter(
course=cls.course,
roles__identifier="student",
status=participation_status.active
).first()
assert cls.student_participation
cls.ta_participation = Participation.objects.filter(
course=cls.course,
roles__identifier="ta",
status=participation_status.active
).first()
assert cls.ta_participation
cls.c.logout()
cls.course_page_url = cls.get_course_page_url()
def setUp(self): # noqa
super(SingleCourseTestMixin, self).setUp()
# reload objects created during setUpTestData in case they were modified in
# tests. Ref: https://goo.gl/AuzJRC#django.test.TestCase.setUpTestData
self.course.refresh_from_db()
self.instructor_participation.refresh_from_db()
self.student_participation.refresh_from_db()
self.ta_participation.refresh_from_db()
@classmethod
def get_default_course(cls):
return cls.course
@classmethod
def get_default_course_identifier(cls):
return cls.get_default_course().identifier
def copy_course_dict_and_set_attrs_for_post(self, attrs_dict={}):
from course.models import Course
kwargs = Course.objects.first().__dict__
kwargs.update(attrs_dict)
import six
for k, v in six.iteritems(kwargs):
if v is None:
kwargs[k] = ""
return kwargs
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
class TwoCourseTestMixin(CoursesTestMixinBase):
courses_setup_list = []
@classmethod
def setUpTestData(cls): # noqa
super(TwoCourseTestMixin, cls).setUpTestData()
assert len(cls.course_qset) == 2, (
"'courses_setup_list' should contain two courses")
cls.course1 = cls.course_qset.first()
cls.course1_instructor_participation = Participation.objects.filter(
course=cls.course1,
roles__identifier="instructor",
status=participation_status.active
).first()
assert cls.course1_instructor_participation
cls.course1_student_participation = Participation.objects.filter(
course=cls.course1,
roles__identifier="student",
status=participation_status.active
).first()
assert cls.course1_student_participation
cls.course1_ta_participation = Participation.objects.filter(
course=cls.course1,
roles__identifier="ta",
status=participation_status.active
).first()
assert cls.course1_ta_participation
cls.course1_page_url = cls.get_course_page_url(cls.course1.identifier)
cls.course2 = cls.course_qset.last()
cls.course2_instructor_participation = Participation.objects.filter(
course=cls.course2,
roles__identifier="instructor",
status=participation_status.active
).first()
assert cls.course2_instructor_participation
cls.course2_student_participation = Participation.objects.filter(
course=cls.course2,
roles__identifier="student",
status=participation_status.active
).first()
assert cls.course2_student_participation
cls.course2_ta_participation = Participation.objects.filter(
course=cls.course2,
roles__identifier="ta",
status=participation_status.active
).first()
assert cls.course2_ta_participation
cls.course2_page_url = cls.get_course_page_url(cls.course2.identifier)
cls.c.logout()
def setUp(self): # noqa
super(TwoCourseTestMixin, self).setUp()
# reload objects created during setUpTestData in case they were modified in
# tests. Ref: https://goo.gl/AuzJRC#django.test.TestCase.setUpTestData
self.course1.refresh_from_db()
self.course1_instructor_participation.refresh_from_db()
self.course1_student_participation.refresh_from_db()
self.course1_ta_participation.refresh_from_db()
self.course2.refresh_from_db()
self.course2_instructor_participation.refresh_from_db()
self.course2_student_participation.refresh_from_db()
self.course2_ta_participation.refresh_from_db()
class SingleCoursePageTestMixin(SingleCourseTestMixin):
# This serves as cache
_default_session_id = None
@property
def flow_id(self):
raise NotImplementedError
@classmethod
def update_default_flow_session_id(cls, course_identifier):
cls._default_session_id = cls.default_flow_params["flow_session_id"]
def get_default_flow_session_id(self, course_identifier):
if self._default_session_id is not None:
return self._default_session_id
self._default_session_id = self.get_latest_session_id(course_identifier)
return self._default_session_id
class TwoCoursePageTestMixin(TwoCourseTestMixin):
_course1_default_session_id = None
_course2_default_session_id = None
@property
def flow_id(self):
raise NotImplementedError
def get_default_flow_session_id(self, course_identifier):
if course_identifier == self.course1.identifier:
if self._course1_default_session_id is not None:
return self._course1_default_session_id
self._course1_default_session_id = (
self.get_last_session_id(course_identifier))
return self._course1_default_session_id
if course_identifier == self.course2.identifier:
if self._course2_default_session_id is not None:
return self._course2_default_session_id
self._course2_default_session_id = (
self.get_last_session_id(course_identifier))
return self._course2_default_session_id
@classmethod
def update_default_flow_session_id(cls, course_identifier):
new_session_id = cls.default_flow_params["flow_session_id"]
if course_identifier == cls.course1.identifier:
cls._course1_default_session_id = new_session_id
elif course_identifier == cls.course2.identifier:
cls._course2_default_session_id = new_session_id
class FallBackStorageMessageTestMixin(object):
# In case other message storage are used, the following is the default
# storage used by django and RELATE. Tests which concerns the message
# should not include this mixin.
storage = 'django.contrib.messages.storage.fallback.FallbackStorage'
def setUp(self): # noqa
self.settings_override = override_settings(MESSAGE_STORAGE=self.storage)
self.settings_override.enable()
def tearDown(self): # noqa
self.settings_override.disable()
def get_listed_storage_from_response(self, response):
return list(self.get_response_context_value_by_name(response, 'messages'))
def clear_message_response_storage(self, response):
# this should only be used for debug, because we are using private method
# which might change
try:
storage = self.get_response_context_value_by_name(response, 'messages')
except AssertionError:
# message doesn't exist in response context
return
if hasattr(storage, '_loaded_data'):
storage._loaded_data = []
elif hasattr(storage, '_loaded_message'):
storage._loaded_messages = []
if hasattr(storage, '_queued_messages'):
storage._queued_messages = []
self.assertEqual(len(storage), 0)
def assertResponseMessagesCount(self, response, expected_count): # noqa
storage = self.get_listed_storage_from_response(response)
self.assertEqual(len(storage), expected_count)
def assertResponseMessagesEqual(self, response, expected_messages): # noqa
storage = self.get_listed_storage_from_response(response)
if not isinstance(expected_messages, list):
expected_messages = [expected_messages]
self.assertEqual(len([m for m in storage]), len(expected_messages))
self.assertEqual([m.message for m in storage], expected_messages)
def assertResponseMessagesEqualRegex(self, response, expected_message_regexs): # noqa
storage = self.get_listed_storage_from_response(response)
if not isinstance(expected_message_regexs, list):
expected_message_regexs = [expected_message_regexs]
self.assertEqual(len([m for m in storage]), len(expected_message_regexs))
messages = [m.message for m in storage]
for idx, m in enumerate(messages):
six.assertRegex(self, m, expected_message_regexs[idx])
def assertResponseMessagesContains(self, response, expected_messages): # noqa
storage = self.get_listed_storage_from_response(response)
if isinstance(expected_messages, str):
expected_messages = [expected_messages]
messages = [m.message for m in storage]
for em in expected_messages:
self.assertIn(em, messages)
def assertResponseMessageLevelsEqual(self, response, expected_levels): # noqa
storage = self.get_listed_storage_from_response(response)
self.assertEqual([m.level for m in storage], expected_levels)
def debug_print_response_messages(self, response):
"""
For debugging :class:`django.contrib.messages` objects in post response
:param response: response
"""
try:
storage = self.get_listed_storage_from_response(response)
print("\n-----------message start (%i total)-------------"
% len(storage))
for m in storage:
print(m.message)
print("-----------message end-------------\n")
except KeyError:
print("\n-------no message----------")
Andreas Klöckner
committed
class SubprocessRunpyContainerMixin(object):
"""
This mixin is used to fake a runpy container, only needed when
the TestCase include test(s) for code questions
"""
@classmethod
def setUpClass(cls): # noqa
if six.PY2:
from unittest import SkipTest
raise SkipTest("In process fake container is configured for "
"PY3 only, since currently runpy docker only "
"provide PY3 envrionment")
Andreas Klöckner
committed
super(SubprocessRunpyContainerMixin, cls).setUpClass()
"course.page.code.SPAWN_CONTAINERS_FOR_RUNPY", False)
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
cls.faked_container_patch.start()
python_executable = os.getenv("PY_EXE")
if not python_executable:
import sys
python_executable = sys.executable
import subprocess
args = [python_executable,
os.path.abspath(
os.path.join(
os.path.dirname(__file__), os.pardir,
"docker-image-run-py", "runpy")),
]
cls.faked_container_process = subprocess.Popen(
args,
stdout=subprocess.DEVNULL,
# because runpy prints to stderr
stderr=subprocess.DEVNULL
)
cls.faked_container_patch.start()
@classmethod
def tearDownClass(cls): # noqa
Andreas Klöckner
committed
super(SubprocessRunpyContainerMixin, cls).tearDownClass()
if sys.platform.startswith("win"):
# Without these lines, tests on Appveyor hanged when all tests
# finished. However, On nix platforms, these lines resulted in test
# failure when there were more than one TestCases which were using
# this mixin.
cls.faked_container_patch.stop()
cls.faked_container_process.kill()
def improperly_configured_cache_patch():
# can be used as context manager or decorator
if six.PY3:
built_in_import_path = "builtins.__import__"
import builtins # noqa
else:
built_in_import_path = "__builtin__.__import__"
import __builtin__ as builtins # noqa
built_in_import = builtins.__import__
def my_disable_cache_import(name, globals=None, locals=None, fromlist=(),
level=0):
if name == "django.core.cache":
raise ImproperlyConfigured()
return built_in_import(name, globals, locals, fromlist, level)
return mock.patch(built_in_import_path, side_effect=my_disable_cache_import)
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
# {{{ admin
ADMIN_TWO_COURSE_SETUP_LIST = deepcopy(TWO_COURSE_SETUP_LIST)
# switch roles
ADMIN_TWO_COURSE_SETUP_LIST[1]["participations"][0]["role_identifier"] = "ta"
ADMIN_TWO_COURSE_SETUP_LIST[1]["participations"][1]["role_identifier"] = "instructor" # noqa
class AdminTestMixin(TwoCourseTestMixin):
courses_setup_list = ADMIN_TWO_COURSE_SETUP_LIST
none_participation_user_create_kwarg_list = (
NONE_PARTICIPATION_USER_CREATE_KWARG_LIST)
@classmethod
def setUpTestData(cls): # noqa
super(AdminTestMixin, cls).setUpTestData() # noqa
# create 2 participation (with new user) for course1
from tests.factories import ParticipationFactory
cls.course1_student_participation2 = (
ParticipationFactory.create(course=cls.course1))
cls.course1_student_participation3 = (
ParticipationFactory.create(course=cls.course1))
cls.instructor1 = cls.course1_instructor_participation.user
cls.instructor2 = cls.course2_instructor_participation.user
assert cls.instructor1 != cls.instructor2
# grant all admin permissions to instructors
from django.contrib.auth.models import Permission
for user in [cls.instructor1, cls.instructor2]:
user.is_staff = True
user.save()
for perm in Permission.objects.all():
user.user_permissions.add(perm)
@classmethod
def get_admin_change_list_view_url(cls, app_name, model_name):
return reverse("admin:%s_%s_changelist" % (app_name, model_name))
@classmethod
def get_admin_change_view_url(cls, app_name, model_name, args=None):
if args is None:
args = []
return reverse("admin:%s_%s_change" % (app_name, model_name), args=args)
def get_admin_form_fields(self, response):
"""
Return a list of AdminFields for the AdminForm in the response.
"""
admin_form = response.context['adminform']
fieldsets = list(admin_form)
field_lines = []
for fieldset in fieldsets:
field_lines += list(fieldset)
fields = []
for field_line in field_lines:
fields += list(field_line)
return fields
def get_admin_form_fields_names(self, response):
return [f.field.name for f in self.get_admin_form_fields(response)]
def get_changelist(self, request, model, modeladmin):
from django.contrib.admin.views.main import ChangeList
return ChangeList(
request, model, modeladmin.list_display,
modeladmin.list_display_links, modeladmin.list_filter,
modeladmin.date_hierarchy, modeladmin.search_fields,
modeladmin.list_select_related, modeladmin.list_per_page,
modeladmin.list_max_show_all, modeladmin.list_editable, modeladmin,
)
def get_filterspec_list(self, request, changelist=None, model=None,
modeladmin=None):
if changelist is None:
assert request and model and modeladmin
changelist = self.get_changelist(request, model, modeladmin)
filterspecs = changelist.get_filters(request)[0]
filterspec_list = []
for filterspec in filterspecs:
choices = tuple(c['display'] for c in filterspec.choices(changelist))
filterspec_list.append(choices)
return filterspec_list
# }}}
# vim: fdm=marker