Commit ceb8d7a5 authored by Barbora Kompišová's avatar Barbora Kompišová
Browse files

time handling in the app and Project entity, linked tests

parent b48f3b14
Loading
Loading
Loading
Loading
+30 −7
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ from sqlalchemy import event
from sqlalchemy.orm import validates
import enum
import datetime
from portal.tools import time
# uuid as primary key source:
# https://stackoverflow.com/questions/36806403/cant-render-element-of-type-class-sqlalchemy-dialects-postgresql-base-uuid

@@ -107,15 +108,15 @@ class Project(db.Model, EntityBase):

    # check timestamp comparisons
    def state(self, timestamp=datetime.datetime.now()) -> ProjectState:
        if self.submissions_allowed_from < timestamp < self.submissions_allowed_to:
        if self.submissions_allowed_from <= timestamp < self.submissions_allowed_to:
            return ProjectState.ACTIVE
        elif timestamp > self.archive_from:
        elif timestamp >= self.archive_from:
            return ProjectState.ARCHIVED
        else:
            return ProjectState.INACTIVE

    # date validation: submissions_allowed_from < submissions_allowed_to < archive_from must be true
    @validates(submissions_allowed_from)
    @validates('submissions_allowed_from')
    def validate_submissions_start(self, key, field):
        if self.submissions_allowed_to is not None and field > self.submissions_allowed_to:
                raise PortalDbError()
@@ -123,7 +124,7 @@ class Project(db.Model, EntityBase):
                raise PortalDbError()
        return field

    @validates(submissions_allowed_to)
    @validates('submissions_allowed_to')
    def validate_submissions_end(self, key, field):
        if self.submissions_allowed_from is not None and self.submissions_allowed_from > field:
            raise PortalDbError()
@@ -131,7 +132,7 @@ class Project(db.Model, EntityBase):
            raise PortalDbError()
        return field

    @validates(archive_from)
    @validates('archive_from')
    def validate_archive_start(self, key, field):
        if self.submissions_allowed_from is not None and self.submissions_allowed_from > field:
            raise PortalDbError()
@@ -317,9 +318,9 @@ class Component(db.Model, EntityBase):
    ip_address = db.Column(db.String(50))


# source: http://docs.sqlalchemy.org/en/latest/orm/events.html
@event.listens_for(Submission.user_id, 'set', named=True)
def receive_set(**kw):
    """listen for the 'set' event"""
    submission = kw['target']
    value = kw['value']

@@ -327,7 +328,29 @@ def receive_set(**kw):
        raise PortalDbError()


# Associations - must come after affected class definitions
# Expects that the value being set is received with the correct timezone information.
@event.listens_for(Project.submissions_allowed_from, 'set', named=True, retval=True)
def receive_set(**kw):
    submissions_allowed_from = kw['value']

    return time.strip_seconds(submissions_allowed_from)


@event.listens_for(Project.submissions_allowed_to, 'set', named=True, retval=True)
def receive_set(**kw):
    submissions_allowed_to = kw['value']

    return time.strip_seconds(submissions_allowed_to)


@event.listens_for(Project.archive_from, 'set', named=True, retval=True)
def receive_set(**kw):
    archive_from = kw['value']

    return time.strip_seconds(archive_from)


# Associations - must come after definitions of the affected classes
users_groups = db.Table("users_groups", db.Model.metadata,
                        db.Column("user_id", db.Integer, db.ForeignKey('user.id')),
                        db.Column("group_id", db.Integer, db.ForeignKey('group.id')))

portal/tools/time.py

0 → 100644
+10 −0
Original line number Diff line number Diff line
import datetime

portal_timezone = datetime.timezone(offset=datetime.timedelta(hours=1), name='UTC+1')


NOW = datetime.datetime.now(tz=portal_timezone)


def strip_seconds(time):
    return time.replace(second=0, microsecond=0)
+140 −4
Original line number Diff line number Diff line
from portal.tools.logging import log
from portal.database.models import User, Course, Role, Group, Project, Submission
from portal.database.models import User, Course, Role, Group, Project, Submission, ProjectState
import pytest
from sqlalchemy import exc
import datetime
from datetime import datetime, timedelta, MINYEAR, MAXYEAR
from portal.tools import time

'''
# will be removed later, when javascript functionality is added
@@ -229,7 +230,7 @@ def test_submission_create_valid(session):
    user = User(uco=123, email='foo', username='xfoo')
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    submission = Submission(user=user, project=project, processing_scheduled_for=datetime.datetime.now(), parameters="")
    submission = Submission(user=user, project=project, processing_scheduled_for=datetime.now(), parameters="")
    session.add(submission)
    session.flush()

@@ -244,7 +245,7 @@ def test_submission_update_user(session):
    user2 = User(uco=456, email='bar', username='xbar')
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    submission = Submission(user=user1, project=project, processing_scheduled_for=datetime.datetime.now(), parameters="")
    submission = Submission(user=user1, project=project, processing_scheduled_for=datetime.now(), parameters="")

    session.add_all([submission, user2])
    session.flush()
@@ -650,3 +651,138 @@ def test_relation_user_roles_add_role_same_course(session):
    assert user in role2.users
    assert user in role1.users
    assert role2 in user.roles


# Project timing
def test_project_time_constraints_valid(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = time.NOW - timedelta(hours=1)
    project.submissions_allowed_to = time.NOW
    project.archive_from = time.NOW + timedelta(days=1)

    session.add(project)
    session.flush()
    assert project.submissions_allowed_from < project.submissions_allowed_to
    assert project.submissions_allowed_to < project.archive_from


def test_project_time_constraints_valid_extreme(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = datetime(year=MINYEAR, month=1, day=1, tzinfo=time.portal_timezone)
    project.submissions_allowed_to = time.NOW
    project.archive_from = datetime(year=MAXYEAR, month=12, day=31, tzinfo=time.portal_timezone)

    session.add(project)
    session.flush()
    assert project.submissions_allowed_from < project.submissions_allowed_to
    assert project.submissions_allowed_to < project.archive_from


def test_project_time_constraints_valid_missing_from(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_to = time.NOW
    project.archive_from = time.NOW + timedelta(days=1)

    session.add(project)
    session.flush()
    assert project.submissions_allowed_to < project.archive_from


def test_project_time_constraints_valid_missing_to(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = time.NOW - timedelta(hours=1)
    project.archive_from = time.NOW + timedelta(days=1)

    session.add(project)
    session.flush()
    assert project.submissions_allowed_from < project.archive_from


def test_project_time_constraints_valid_missing_archive(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = time.NOW - timedelta(hours=1)
    project.submissions_allowed_to = time.NOW

    session.add(project)
    session.flush()
    assert project.submissions_allowed_from < project.submissions_allowed_to


def test_project_time_constraints_same_from_to(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = time.NOW
    project.submissions_allowed_to = time.NOW

    session.add(project)
    session.flush()


def test_project_time_constraints_same_to_archive(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_to = time.NOW
    project.archive_from = time.NOW

    session.add(project)
    session.flush()


def test_project_time_constraints_same_from_archive(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = time.NOW
    project.archive_from = time.NOW

    session.add(project)
    session.flush()


def test_project_time_constraints_invalid_from_to():
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = time.NOW
    with pytest.raises(exc.SQLAlchemyError):
        project.submissions_allowed_to = time.NOW - timedelta(minutes=1)


def test_project_time_constraints_invalid_from_archive():
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_from = time.NOW
    with pytest.raises(exc.SQLAlchemyError):
        project.archive_from = time.NOW - timedelta(minutes=1)


def test_project_time_constraints_invalid_to_archive():
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")
    project.submissions_allowed_to = time.NOW
    with pytest.raises(exc.SQLAlchemyError):
        project.archive_from = time.NOW - timedelta(minutes=1)


def test_project_state(session):
    course = Course(name="C++", codename="PB161")
    project = Project(course=course, name="p1", test_files_source="git repo")

    project.submissions_allowed_from = time.NOW - timedelta(days=1)
    project.submissions_allowed_to = time.NOW
    project.archive_from = time.NOW + timedelta(days=1)

    session.add(project)
    session.flush()

    assert project.state(time.NOW - timedelta(days=1)) == ProjectState.ACTIVE
    assert project.state(time.NOW - timedelta(hours=1)) == ProjectState.ACTIVE
    assert project.state(time.NOW - timedelta(minutes=1)) == ProjectState.ACTIVE

    assert project.state(time.NOW) == ProjectState.INACTIVE
    assert project.state(time.NOW - timedelta(days=1, minutes=1)) == ProjectState.INACTIVE

    assert project.state(time.NOW + timedelta(days=1)) == ProjectState.ARCHIVED