To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

Commit fe18248b authored by Alexander Dietmüller's avatar Alexander Dietmüller Committed by adietmue
Browse files

Backend: Introduce 'assistants' resource.

parent 5653a29a
...@@ -47,6 +47,8 @@ COURSE_PRICE = 1000 ...@@ -47,6 +47,8 @@ COURSE_PRICE = 1000
# ISO 8601 time format instead of rfc1123 # ISO 8601 time format instead of rfc1123
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
# Email Format
EMAIL_REGEX = '^.+@.+$'
# More Feedback when creating something: Return all fields # More Feedback when creating something: Return all fields
BANDWIDTH_SAVER = False BANDWIDTH_SAVER = False
...@@ -84,6 +86,28 @@ STANDARD_ERRORS = [400, 401, 403, 404, 405, 406, 409, 410, 412, 422, 428] ...@@ -84,6 +86,28 @@ STANDARD_ERRORS = [400, 401, 403, 404, 405, 406, 409, 410, 412, 422, 428]
# Resources # Resources
DOMAIN = { DOMAIN = {
'assistants': {
'user_methods': ['GET'],
'schema': {
'name': {
'type': 'string',
'maxlength': 100,
'required': True,
'nullable': False,
'empty': False,
},
'email': {
'type': 'string',
'maxlength': 100,
'regex': EMAIL_REGEX,
'required': True,
'unique': True,
'nullable': True,
}
}
},
'lectures': { 'lectures': {
'user_methods': ['GET'], 'user_methods': ['GET'],
...@@ -109,18 +133,6 @@ DOMAIN = { ...@@ -109,18 +133,6 @@ DOMAIN = {
'max': 3, 'max': 3,
'required': True 'required': True
}, },
'assistants': {
# List of nethz of assistants
'type': 'list',
'schema': {
'type': 'string',
'maxlength': 10,
'empty': False,
'nullable': False,
},
# TODO: Not the same nethz twice (use new nocopies validator)
# TODO: Is nethz enough here?
}
}, },
}, },
...@@ -140,9 +152,13 @@ DOMAIN = { ...@@ -140,9 +152,13 @@ DOMAIN = {
'required': True, 'required': True,
}, },
'assistant': { 'assistant': {
'type': 'string', 'type': 'objectid',
# TODO: Assistant needs to exist for lecture 'data_relation': {
# TODO: assistant timeslot 'resource': 'assistants',
'field': '_id',
'embeddable': True,
'unique_assistant_booking': True,
},
}, },
'signup': TIMESPAN_SCHEMA, 'signup': TIMESPAN_SCHEMA,
...@@ -152,6 +168,7 @@ DOMAIN = { ...@@ -152,6 +168,7 @@ DOMAIN = {
'schema': TIMESPAN_SCHEMA, 'schema': TIMESPAN_SCHEMA,
'no_time_overlap': True, 'no_time_overlap': True,
'unique_room_booking': True, 'unique_room_booking': True,
'unique_assistant_booking': True,
}, },
'room': { 'room': {
'type': 'string', 'type': 'string',
......
...@@ -35,7 +35,6 @@ class APIValidator(Validator): ...@@ -35,7 +35,6 @@ class APIValidator(Validator):
Furthermore, the user must be a member to use his nethz. Furthermore, the user must be a member to use his nethz.
Only admins can sign up someone else. Only admins can sign up someone else.
""" """
print(get_user())
if enabled and not is_admin(): if enabled and not is_admin():
if value != get_user().get('nethz'): if value != get_user().get('nethz'):
self._error(field, self._error(field,
...@@ -99,13 +98,13 @@ class APIValidator(Validator): ...@@ -99,13 +98,13 @@ class APIValidator(Validator):
if enabled and(value.get('start') > value.get('end')): if enabled and(value.get('start') > value.get('end')):
self._error(field, 'start time must be earlier then end time.') self._error(field, 'start time must be earlier then end time.')
def _validate_unique_room_booking(self, enabled, field, _): def _validate_unique_room_booking(self, enabled, field, value):
"""A room can not be used at the same time by several courses.""" """A room can not be used at the same time by several courses."""
# Get new room and timespans for current course # Compatibility with both room and datetime fields for POST and PATCH
room = self._get_field('room') room = self._get_field('room')
timespans = self._get_field('datetimes') timespans = self._get_field('datetimes')
# Get _id of current course (needed for path) and filter to ignore it # Get _id of current course and filter to ignore it
course_id = self._get_field('_id') course_id = self._get_field('_id')
id_filter = {'_id': {'$ne': course_id}} if course_id else {} id_filter = {'_id': {'$ne': course_id}} if course_id else {}
...@@ -119,7 +118,26 @@ class APIValidator(Validator): ...@@ -119,7 +118,26 @@ class APIValidator(Validator):
if has_overlap(*timespans, *other_timespans): if has_overlap(*timespans, *other_timespans):
self._error(field, "the room '%s' is already occupied by " self._error(field, "the room '%s' is already occupied by "
"another course at the same time") "another course at the same time" % value)
def _validate_unique_assistant_booking(self, enabled, field, value):
"""An assistant can not hold several courses simultaneously."""
# See room booking comments
assistant = self._get_field('assistant')
timespans = self._get_field('datetimes')
course_id = self._get_field('_id')
id_filter = {'_id': {'$ne': course_id}} if course_id else {}
if enabled and timespans and assistant:
course_db = current_app.data.driver.db['courses']
courses = course_db.find({'assistant': assistant, **id_filter})
other_timespans = chain.from_iterable(course['datetimes']
for course in courses)
if has_overlap(*timespans, *other_timespans):
self._error(field, "the assistant '%s' is giving "
"another course at the same time" % value)
def _validate_no_time_overlap(self, enabled, field, value): def _validate_no_time_overlap(self, enabled, field, value):
"""Multiple timeslots of the same course must not overlap.""" """Multiple timeslots of the same course must not overlap."""
......
...@@ -10,7 +10,6 @@ def base_data(app): ...@@ -10,7 +10,6 @@ def base_data(app):
'title': 'Awesome Lecture', 'title': 'Awesome Lecture',
'department': 'itet', 'department': 'itet',
'year': 3, 'year': 3,
'assistants': ['pablo', 'pablone'],
} }
lecture = app.client.post('lectures', lecture = app.client.post('lectures',
data=lecture_data, data=lecture_data,
...@@ -18,8 +17,7 @@ def base_data(app): ...@@ -18,8 +17,7 @@ def base_data(app):
course = { course = {
'lecture': lecture['_id'], 'lecture': lecture['_id'],
'assistant': 'anon', 'room': 'ETZ F 6',
'room': 'ETZ f 6',
'spots': 20, 'spots': 20,
'signup': { 'signup': {
'start': '2021-05-01T10:00:00Z', 'start': '2021-05-01T10:00:00Z',
......
...@@ -12,15 +12,22 @@ def test_create(app): ...@@ -12,15 +12,22 @@ def test_create(app):
'title': "Awesome Lecture", 'title': "Awesome Lecture",
'department': "itet", 'department': "itet",
'year': 3, 'year': 3,
'assistants': ['pablo', 'pablone'],
} }
lecture_response = app.client.post('lectures', lecture_response = app.client.post('lectures',
data=lecture, data=lecture,
assert_status=201) assert_status=201)
assistant = {
'name': "Pablo Pablone",
'email': "pablop@ethz.ch",
}
assistant_response = app.client.post('assistants',
data=assistant,
assert_status=201)
course = { course = {
'lecture': lecture_response['_id'], 'lecture': lecture_response['_id'],
'assistant': 'pablo', 'assistant': assistant_response['_id'],
'room': 'ETZ E 6', 'room': 'ETZ E 6',
'spots': 30, 'spots': 30,
'signup': { 'signup': {
...@@ -53,6 +60,8 @@ def test_create(app): ...@@ -53,6 +60,8 @@ def test_create(app):
data=signup, data=signup,
assert_status=201) assert_status=201)
# Payment is tested separately
def test_no_double_signup(app): def test_no_double_signup(app):
"""Users can signup for several courses, but not for any course twice.""" """Users can signup for several courses, but not for any course twice."""
......
...@@ -12,7 +12,6 @@ def lecture(app): ...@@ -12,7 +12,6 @@ def lecture(app):
'title': "Time and Space", 'title': "Time and Space",
'department': "itet", 'department': "itet",
'year': 2, 'year': 2,
'assistants': ['pablo'],
} }
return app.client.post('lectures', return app.client.post('lectures',
data=lecture, data=lecture,
...@@ -23,7 +22,6 @@ def test_start_time_before_end(app, lecture): ...@@ -23,7 +22,6 @@ def test_start_time_before_end(app, lecture):
"""Test that any start time must come before end time.""" """Test that any start time must come before end time."""
correct = { correct = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
'start': '2019-01-09T10:00:00Z', 'start': '2019-01-09T10:00:00Z',
...@@ -33,7 +31,6 @@ def test_start_time_before_end(app, lecture): ...@@ -33,7 +31,6 @@ def test_start_time_before_end(app, lecture):
wrong = { wrong = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
'start': '2019-02-09T13:00:00Z', 'start': '2019-02-09T13:00:00Z',
...@@ -53,7 +50,6 @@ def courses(app, lecture): ...@@ -53,7 +50,6 @@ def courses(app, lecture):
same_time = { same_time = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
'start': '2019-01-09T10:00:00Z', 'start': '2019-01-09T10:00:00Z',
...@@ -145,7 +141,6 @@ def test_no_double_booking_of_room(app, lecture): ...@@ -145,7 +141,6 @@ def test_no_double_booking_of_room(app, lecture):
room = 'LEE E 12' room = 'LEE E 12'
first = { first = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'room': room, 'room': room,
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
...@@ -156,7 +151,6 @@ def test_no_double_booking_of_room(app, lecture): ...@@ -156,7 +151,6 @@ def test_no_double_booking_of_room(app, lecture):
second = { second = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'room': room, 'room': room,
'spots': 20, 'spots': 20,
'datetimes': [{ 'datetimes': [{
...@@ -174,14 +168,13 @@ def test_no_double_booking_of_room(app, lecture): ...@@ -174,14 +168,13 @@ def test_no_double_booking_of_room(app, lecture):
# Posting only one is ok, but the second one will fail # Posting only one is ok, but the second one will fail
app.client.post('courses', data=first, assert_status=201) app.client.post('courses', data=first, assert_status=201)
print("NOW")
app.client.post('courses', data=second, assert_status=422) app.client.post('courses', data=second, assert_status=422)
# Posting the second course with a different room is ok # Posting the second course with a different room is ok
second['room'] = 'other %s' % room second['room'] = 'other %s' % room
response = app.client.post('courses', data=second, assert_status=201) response = app.client.post('courses', data=second, assert_status=201)
# Patching the time or room without overlap is ok: # Patching without overlap is ok
url = 'courses/%s' % response['_id'] url = 'courses/%s' % response['_id']
separate_room = {'room': 'another different %s' % room} separate_room = {'room': 'another different %s' % room}
response = app.client.patch(url, data=separate_room, response = app.client.patch(url, data=separate_room,
...@@ -189,12 +182,62 @@ def test_no_double_booking_of_room(app, lecture): ...@@ -189,12 +182,62 @@ def test_no_double_booking_of_room(app, lecture):
assert_status=200) assert_status=200)
def test_unique_assistant(app, lecture):
"""Test that an assistant cannot be set for overlapping courses."""
with app.admin():
# Create dummy assistants
assistant = str(app.data.driver.db['assistants'].insert({}))
other_assistant = str(app.data.driver.db['assistants'].insert({}))
another_assistant = str(app.data.driver.db['assistants'].insert({}))
first = {
'lecture': lecture,
'assistant': assistant,
'spots': 10,
'datetimes': [{
'start': '2018-12-06T10:00:00Z',
'end': '2018-12-06T13:00:00Z',
}],
}
second = {
'lecture': lecture,
'assistant': assistant,
'spots': 20,
'datetimes': [{
# Contains the first course
'start': '2018-12-06T9:00:00Z',
'end': '2018-12-06T15:00:00Z',
}, {
'start': '2018-12-10T13:00:00Z',
'end': '2018-12-10T14:00:00Z',
}, {
'start': '2018-12-10T15:00:00Z',
'end': '2018-12-10T16:00:00Z',
}],
}
# Posting only one is ok, but the second one will fail
app.client.post('courses', data=first, assert_status=201)
app.client.post('courses', data=second, assert_status=422)
# Posting the second course with a different assistant is ok
second['assistant'] = other_assistant
response = app.client.post('courses', data=second, assert_status=201)
# Patching without overlap is ok
url = 'courses/%s' % response['_id']
separate_room = {'assistant': another_assistant}
response = app.client.patch(url, data=separate_room,
headers={'If-Match': response['_etag']},
assert_status=200)
@pytest.fixture @pytest.fixture
def patch_courses(app, lecture): def patch_courses(app, lecture):
"""Courses that nearly overlap.""" """Courses that nearly overlap."""
first = { first = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'room': 'ETZ E 1', 'room': 'ETZ E 1',
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
...@@ -205,7 +248,6 @@ def patch_courses(app, lecture): ...@@ -205,7 +248,6 @@ def patch_courses(app, lecture):
# Second course has same room but different time # Second course has same room but different time
second = { second = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'room': 'ETZ E 1', 'room': 'ETZ E 1',
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
...@@ -216,7 +258,6 @@ def patch_courses(app, lecture): ...@@ -216,7 +258,6 @@ def patch_courses(app, lecture):
# Third course has different room but same time # Third course has different room but same time
third = { third = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'room': 'ETZ E 2', 'room': 'ETZ E 2',
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
...@@ -227,7 +268,6 @@ def patch_courses(app, lecture): ...@@ -227,7 +268,6 @@ def patch_courses(app, lecture):
# Control course has different room and time # Control course has different room and time
control = { control = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'room': 'ETZ E 3', 'room': 'ETZ E 3',
'spots': 10, 'spots': 10,
'datetimes': [{ 'datetimes': [{
...@@ -306,7 +346,6 @@ def test_patch_self_overlap(app, lecture): ...@@ -306,7 +346,6 @@ def test_patch_self_overlap(app, lecture):
} }
data = { data = {
'lecture': lecture, 'lecture': lecture,
'assistant': 'pablo',
'room': 'someroom', 'room': 'someroom',
'spots': 10, 'spots': 10,
'datetimes': [time_1, time_2], 'datetimes': [time_1, time_2],
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment