Commit 2c2b1ccc authored by Martin Juhás's avatar Martin Juhás
Browse files

Merge branch '115-make-milestone-final-field-not-required' into 'dev'

Resolve "Make milestone final field not required"

See merge request inject/backend!113
parents 5bef30d6 6d4443a6
Loading
Loading
Loading
Loading
+10 −28
Original line number Diff line number Diff line
import json
import os.path
import shutil
from io import BytesIO
@@ -8,14 +7,11 @@ from zipfile import ZipFile

from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client
from django.test import Client, override_settings
from django.urls import reverse
from graphene_django.utils import camelize
from graphene_django.utils.testing import graphql_query

from common_lib.graphql import (
    GraphQLApiTestCase,
    DeleteDefinitionAction,
    CreateExerciseAction,
)
from common_lib.utils import get_model, get_definition_id
@@ -56,23 +52,14 @@ def upload_definition(name: str) -> int:
    return get_definition_id(response.status_code, response.content)


def delete_definition(definition_id: int, url: str) -> None:
    action = DeleteDefinitionAction({"definition_id": definition_id})
    response = graphql_query(
        action.query_string(),
        variables=camelize(action.get_variables()),
        graphql_url=url,
    )
    if response.status_code != 200:
        raise Exception(
            f"Failed to delete definition: {response.status_code} {response.contet}"
        )

    content: dict = json.loads(response.content)
    if content.get("errors", None) is not None:
        raise Exception(f"Failed to delete definition: {content}")
TEST_DATA_STORAGE = "create_exercise_test_data"


@override_settings(
    DATA_STORAGE=TEST_DATA_STORAGE,
    DEFINITION_STORAGE=os.path.join(TEST_DATA_STORAGE, "definitions"),
    EXERCISE_STORAGE=os.path.join(TEST_DATA_STORAGE, "exercises"),
)
class CreateExerciseTestCase(GraphQLApiTestCase):
    roles_definition: Definition
    emails_definition: Definition
@@ -87,18 +74,13 @@ class CreateExerciseTestCase(GraphQLApiTestCase):

    @classmethod
    def tearDownClass(cls):
        delete_definition(cls.roles_definition.id, cls.GRAPHQL_URL)
        delete_definition(cls.emails_definition.id, cls.GRAPHQL_URL)
        # now that we override the storage path specifically for tests,
        # we can simply just remove the whole directory
        shutil.rmtree(settings.DATA_STORAGE)

    def create_exercise(self, action: CreateExerciseAction) -> Exercise:
        self.run_action(action)
        exercise_id = action.result["exercise"]["id"]
        # we need to explicitly remove the directories that have been created during exercise creation
        # because each of the test function runs with a "clean" db,
        # which only includes the data from the setUpTestData function,
        # unlike definition directories, the exercise directories do not get deleted
        # during the deletion of the definitions, because they do not exist at that point
        shutil.rmtree(os.path.join(settings.EXERCISE_STORAGE, exercise_id))
        return get_model(Exercise, id=exercise_id)

    def test_invalid_create_exercise(self):
+0 −6
Original line number Diff line number Diff line
@@ -359,9 +359,7 @@ def _validate_milestones(
    ) is not None:
        return err

    final_count = 0
    for milestone in milestones:  # type: YamlMilestone
        final_count += milestone.final
        if not milestone.team_visible and milestone.roles != "":
            return Err(
                "Milestones, which are not visible to teams should not have "
@@ -377,10 +375,6 @@ def _validate_milestones(
            ) is not None:
                return err

    if final_count == 0:
        return Err(
            "Improperly configured milestones.yml, at least one milestone must be set as final"
        )
    return None


+2 −0
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ change: rename PerformTeamToolAction mutation to UseTool #94
change: reach_milestone is replaced by fields activate_milestone and deactivate_milestone #98
change: add option to override some definition config values #109
change: add an optional parameter 'instructor' to file download REST API endpoint #119
feat: add mutation for milestone modification #116
fix: fix move time not working for exercise loop #106

new feature: REST API endpoints for database export and import #79
+15 −2
Original line number Diff line number Diff line
from typing import List, Optional
import ast
from typing import List

from django.utils import timezone

from common_lib.exc_handler import RunningExerciseOperationException
from common_lib.subscription_handler import SubscriptionHandler
from common_lib.utils import (
    get_model,
@@ -11,7 +12,6 @@ from common_lib.utils import (
)
from exercise.models import Team, MilestoneState
from exercise_definition.models import Milestone
from common_lib.exc_handler import RunningExerciseOperationException


def is_milestone_visible(team: Team, milestone: Milestone) -> bool:
@@ -128,3 +128,16 @@ def update_milestones(
            ],
            t,
        )


def instructor_modify_milestone(
    team_id: int, milestone: str, activate: bool
) -> None:
    team = get_model(Team, id=team_id)
    if not team.exercise.running:
        raise RunningExerciseOperationException(
            "Cannot modify milestone state in an exercise that is not currently running"
        )
    update_milestones(
        team, milestone if activate else "", milestone if not activate else ""
    )
+4 −1
Original line number Diff line number Diff line
@@ -59,4 +59,7 @@ def elapsed_exercise_minutes() -> int:
    if running_exercise is None or running_exercise.exercise_start is None:
        return -1

    return (timezone.now() - running_exercise.exercise_start).seconds // 60
    current_time = timezone.now() + timedelta(
        seconds=running_exercise.time_delta
    )
    return (current_time - running_exercise.exercise_start).seconds // 60
Loading