use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExamService method validateForStudentExamGeneration.
/**
* Validates exercise settings.
*
* @param exam exam which is validated
* @throws BadRequestAlertException an exception if the exam is not configured correctly
*/
public void validateForStudentExamGeneration(Exam exam) throws BadRequestAlertException {
List<ExerciseGroup> exerciseGroups = exam.getExerciseGroups();
long numberOfExercises = exam.getNumberOfExercisesInExam() != null ? exam.getNumberOfExercisesInExam() : 0;
long numberOfOptionalExercises = numberOfExercises - exerciseGroups.stream().filter(ExerciseGroup::getIsMandatory).count();
// Ensure that all exercise groups have at least one exercise
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
if (exerciseGroup.getExercises().isEmpty()) {
throw new BadRequestAlertException("All exercise groups must have at least one exercise", "Exam", "artemisApp.exam.validation.atLeastOneExercisePerExerciseGroup");
}
}
// Check that numberOfExercisesInExam is set
if (exam.getNumberOfExercisesInExam() == null) {
throw new BadRequestAlertException("The number of exercises in the exam is not set.", "Exam", "artemisApp.exam.validation.numberOfExercisesInExamNotSet");
}
// Check that there are enough exercise groups
if (exam.getExerciseGroups().size() < exam.getNumberOfExercisesInExam()) {
throw new BadRequestAlertException("The number of exercise groups is too small", "Exam", "artemisApp.exam.validation.tooFewExerciseGroups");
}
// Check that there are not too much mandatory exercise groups
if (numberOfOptionalExercises < 0) {
throw new BadRequestAlertException("The number of mandatory exercise groups is too large", "Exam", "artemisApp.exam.validation.tooManyMandatoryExerciseGroups");
}
// Ensure that all exercises in an exercise group have the same meaning for the exam score calculation
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
Set<IncludedInOverallScore> meaningsForScoreCalculation = exerciseGroup.getExercises().stream().map(Exercise::getIncludedInOverallScore).collect(Collectors.toSet());
if (meaningsForScoreCalculation.size() > 1) {
throw new BadRequestAlertException("All exercises in an exercise group must have the same meaning for the exam score", "Exam", "artemisApp.exam.validation.allExercisesInExerciseGroupOfSameIncludedType");
}
}
// Check that the exam max points is set
if (exam.getMaxPoints() == 0) {
throw new BadRequestAlertException("The exam max points can not be 0.", "Exam", "artemisApp.exam.validation.maxPointsNotSet");
}
// Ensure that all exercises in an exercise group have the same amount of max points and max bonus points
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
Set<Double> allMaxPoints = exerciseGroup.getExercises().stream().map(Exercise::getMaxPoints).collect(Collectors.toSet());
Set<Double> allBonusPoints = exerciseGroup.getExercises().stream().map(Exercise::getBonusPoints).collect(Collectors.toSet());
if (allMaxPoints.size() > 1 || allBonusPoints.size() > 1) {
throw new BadRequestAlertException("All exercises in an exercise group need to give the same amount of points", "Exam", "artemisApp.exam.validation.allExercisesInExerciseGroupGiveSameNumberOfPoints");
}
}
// Ensure that the sum of all max points of mandatory exercise groups is not bigger than the max points set in the exam
// At this point we are already sure that each exercise group has at least one exercise, all exercises in the group have the same no of points
// and all are of the same calculation type, therefore we can just use any as representation for the group here
Double pointsReachableByMandatoryExercises = 0.0;
Set<ExerciseGroup> mandatoryExerciseGroups = exam.getExerciseGroups().stream().filter(ExerciseGroup::getIsMandatory).collect(Collectors.toSet());
for (ExerciseGroup exerciseGroup : mandatoryExerciseGroups) {
Exercise groupRepresentativeExercise = exerciseGroup.getExercises().stream().findAny().get();
if (groupRepresentativeExercise.getIncludedInOverallScore().equals(IncludedInOverallScore.INCLUDED_COMPLETELY)) {
pointsReachableByMandatoryExercises += groupRepresentativeExercise.getMaxPoints();
}
}
if (pointsReachableByMandatoryExercises > exam.getMaxPoints()) {
throw new BadRequestAlertException("Check that you set the exam max points correctly! The max points a student can earn in the mandatory exercise groups is too big", "Exam", "artemisApp.exam.validation.tooManyMaxPoints");
}
// Ensure that the sum of all max points of all exercise groups is at least as big as the max points set in the exam
Double pointsReachable = 0.0;
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
Exercise groupRepresentativeExercise = exerciseGroup.getExercises().stream().findAny().get();
if (groupRepresentativeExercise.getIncludedInOverallScore().equals(IncludedInOverallScore.INCLUDED_COMPLETELY)) {
pointsReachable += groupRepresentativeExercise.getMaxPoints();
}
}
if (pointsReachable < exam.getMaxPoints()) {
throw new BadRequestAlertException("Check that you set the exam max points correctly! The max points a student can earn in the exercise groups is too low", "Exam", "artemisApp.exam.validation.tooFewMaxPoints");
}
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExerciseGroupResource method updateExerciseGroup.
/**
* PUT /courses/{courseId}/exams/{examId}/exerciseGroups : Update an existing exercise group.
*
* @param courseId the course to which the exercise group belongs to
* @param examId the exam to which the exercise group belongs to
* @param updatedExerciseGroup the exercise group to update
* @return the ResponseEntity with status 200 (OK) and with the body of the updated exercise group
* @throws URISyntaxException if the Location URI syntax is incorrect
*/
@PutMapping("/courses/{courseId}/exams/{examId}/exerciseGroups")
@PreAuthorize("hasRole('EDITOR')")
public ResponseEntity<ExerciseGroup> updateExerciseGroup(@PathVariable Long courseId, @PathVariable Long examId, @RequestBody ExerciseGroup updatedExerciseGroup) throws URISyntaxException {
log.debug("REST request to update an exercise group : {}", updatedExerciseGroup);
if (updatedExerciseGroup.getId() == null) {
return createExerciseGroup(courseId, examId, updatedExerciseGroup);
}
if (updatedExerciseGroup.getExam() == null) {
throw new ConflictException("The exercise group has to belong to an exam.", ENTITY_NAME, "missingExam");
}
examAccessService.checkCourseAndExamAndExerciseGroupAccessElseThrow(Role.EDITOR, courseId, examId, updatedExerciseGroup);
ExerciseGroup result = exerciseGroupRepository.save(updatedExerciseGroup);
return ResponseEntity.ok().headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, result.getTitle())).body(result);
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExerciseGroupResource method deleteExerciseGroup.
/**
* DELETE /courses/{courseId}/exams/{examId}/exerciseGroups/{exerciseGroupId} : Delete the exercise group with the given id.
*
* @param courseId the course to which the exercise group belongs to
* @param examId the exam to which the exercise group belongs to
* @param exerciseGroupId the id of the exercise group to delete
* @param deleteStudentReposBuildPlans boolean which states whether the corresponding student build plans should be deleted
* @param deleteBaseReposBuildPlans boolean which states whether the corresponding base build plans should be deleted
* @return the ResponseEntity with status 200 (OK)
*/
@DeleteMapping("/courses/{courseId}/exams/{examId}/exerciseGroups/{exerciseGroupId}")
@PreAuthorize("hasRole('INSTRUCTOR')")
public ResponseEntity<Void> deleteExerciseGroup(@PathVariable Long courseId, @PathVariable Long examId, @PathVariable Long exerciseGroupId, @RequestParam(defaultValue = "false") boolean deleteStudentReposBuildPlans, @RequestParam(defaultValue = "false") boolean deleteBaseReposBuildPlans) {
log.info("REST request to delete exercise group : {}", exerciseGroupId);
ExerciseGroup exerciseGroup = exerciseGroupRepository.findByIdWithExercisesElseThrow(exerciseGroupId);
examAccessService.checkCourseAndExamAndExerciseGroupAccessElseThrow(Role.INSTRUCTOR, courseId, examId, exerciseGroup);
User user = userRepository.getUser();
AuditEvent auditEvent = new AuditEvent(user.getLogin(), Constants.DELETE_EXERCISE_GROUP, "exerciseGroup=" + exerciseGroup.getTitle());
auditEventRepository.add(auditEvent);
log.info("User {} has requested to delete the exercise group {}", user.getLogin(), exerciseGroup.getTitle());
for (Exercise exercise : exerciseGroup.getExercises()) {
exerciseDeletionService.delete(exercise.getId(), deleteStudentReposBuildPlans, deleteBaseReposBuildPlans);
}
// Remove the exercise group by removing it from the list of exercise groups of the corresponding exam.
// This is necessary as @OrderColumn (exercise_group_order) needs continuous values. Otherwise the client will
// receive null values for the gaps in exam.getExerciseGroups().
Exam exam = examRepository.findByIdWithExerciseGroupsElseThrow(examId);
List<ExerciseGroup> filteredExerciseGroups = exam.getExerciseGroups();
filteredExerciseGroups.removeIf(exGroup -> exGroup.getId().equals(exerciseGroupId));
exam.setExerciseGroups(filteredExerciseGroups);
examRepository.save(exam);
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, exerciseGroup.getTitle())).build();
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExerciseGroupResource method createExerciseGroup.
/**
* POST /courses/{courseId}/exams/{examId}/exerciseGroups : Create a new exercise group.
*
* @param courseId the course to which the exercise group belongs to
* @param examId the exam to which the exercise group belongs to
* @param exerciseGroup the exercise group to create
* @return the ResponseEntity with status 201 (Created) and with the new exerciseGroup as body,
* or with status 400 (Bad Request) if the exerciseGroup has already an ID
* @throws URISyntaxException if the Location URI syntax is incorrect
*/
@PostMapping("/courses/{courseId}/exams/{examId}/exerciseGroups")
@PreAuthorize("hasRole('EDITOR')")
public ResponseEntity<ExerciseGroup> createExerciseGroup(@PathVariable Long courseId, @PathVariable Long examId, @RequestBody ExerciseGroup exerciseGroup) throws URISyntaxException {
log.debug("REST request to create an exercise group : {}", exerciseGroup);
if (exerciseGroup.getId() != null) {
throw new BadRequestAlertException("A new exerciseGroup cannot already have an ID", ENTITY_NAME, "idexists");
}
if (exerciseGroup.getExam() == null) {
throw new ConflictException("The exercise group has to belong no an exam.", ENTITY_NAME, "missingExam");
}
if (!exerciseGroup.getExam().getId().equals(examId)) {
throw new ConflictException("The exam connected to this group does not have the given exam id.", ENTITY_NAME, "wrongExamId");
}
examAccessService.checkCourseAndExamAccessForEditorElseThrow(courseId, examId);
// Save the exerciseGroup as part of the exam to ensure that the order column is set correctly
Exam examFromDB = examRepository.findByIdWithExerciseGroupsElseThrow(examId);
examFromDB.addExerciseGroup(exerciseGroup);
Exam savedExam = examRepository.save(examFromDB);
ExerciseGroup savedExerciseGroup = savedExam.getExerciseGroups().get(savedExam.getExerciseGroups().size() - 1);
return ResponseEntity.created(new URI("/api/courses/" + courseId + "/exams/" + examId + "/exerciseGroups/" + savedExerciseGroup.getId())).headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, savedExerciseGroup.getTitle())).body(savedExerciseGroup);
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ModelingExerciseResource method createModelingExercise.
// TODO: most of these calls should be done in the context of a course
/**
* POST modeling-exercises : Create a new modelingExercise.
*
* @param modelingExercise the modelingExercise to create
* @return the ResponseEntity with status 201 (Created) and with body the new modelingExercise, or with status 400 (Bad Request) if the modelingExercise has already an ID
* @throws URISyntaxException if the Location URI syntax is incorrect
*/
// TODO: we should add courses/{courseId} here
@PostMapping("modeling-exercises")
@PreAuthorize("hasRole('EDITOR')")
public ResponseEntity<ModelingExercise> createModelingExercise(@RequestBody ModelingExercise modelingExercise) throws URISyntaxException {
log.debug("REST request to save ModelingExercise : {}", modelingExercise);
if (modelingExercise.getId() != null) {
throw new BadRequestAlertException("A new modeling exercise cannot already have an ID", ENTITY_NAME, "idexists");
}
if (modelingExercise.getTitle() == null) {
throw new BadRequestAlertException("A new modeling exercise needs a title", ENTITY_NAME, "missingtitle");
}
// validates general settings: points, dates
modelingExercise.validateGeneralSettings();
// Valid exercises have set either a course or an exerciseGroup
modelingExercise.checkCourseAndExerciseGroupExclusivity(ENTITY_NAME);
// Retrieve the course over the exerciseGroup or the given courseId
Course course = courseService.retrieveCourseOverExerciseGroupOrCourseId(modelingExercise);
// Check that the user is authorized to create the exercise
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, course, null);
// if exercise is created from scratch we create new knowledge instance
modelingExercise.setKnowledge(modelAssessmentKnowledgeService.createNewKnowledge());
ModelingExercise result = modelingExerciseRepository.save(modelingExercise);
modelingExerciseService.scheduleOperations(result.getId());
groupNotificationService.checkNotificationsForNewExercise(modelingExercise, instanceMessageSendService);
return ResponseEntity.created(new URI("/api/modeling-exercises/" + result.getId())).headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, result.getId().toString())).body(result);
}
Aggregations