Got CSV reading to work properly.
This commit is contained in:
parent
2d9932eeb4
commit
7dff57ac98
6
pom.xml
6
pom.xml
|
@ -79,6 +79,12 @@
|
|||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.15</version>
|
||||
</dependency>
|
||||
<!-- CSV Format dependency -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -27,6 +27,11 @@ public class TeachingAssistantAssistantApplication implements CommandLineRunner
|
|||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
System.out.println("Running startup...");
|
||||
|
||||
//
|
||||
// String exampleDate = "2019/04/15 4:13:41 PM GMT+2 ";
|
||||
// // Parse the timestamp.
|
||||
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd h:mm:ss a O ");
|
||||
// ZonedDateTime dateTime = ZonedDateTime.parse(exampleDate, formatter);
|
||||
// System.out.println("Read time: " + dateTime.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ public class Courses {
|
|||
consumes = "application/x-www-form-urlencoded"
|
||||
)
|
||||
public String post(@ModelAttribute Course course) {
|
||||
System.out.println("Object submitted: " + course);
|
||||
this.courseRepository.save(course);
|
||||
return "courses/entity";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses;
|
||||
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.TeamRepository;
|
||||
import nl.andrewlalis.teaching_assistant_assistant.util.team_importing.StudentTeamImporter;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Controller for importing students from a CSV sheet.
|
||||
*/
|
||||
@Controller
|
||||
public class ImportStudents {
|
||||
|
||||
private CourseRepository courseRepository;
|
||||
|
||||
private TeamRepository teamRepository;
|
||||
|
||||
protected ImportStudents(CourseRepository courseRepository, TeamRepository teamRepository) {
|
||||
this.courseRepository = courseRepository;
|
||||
this.teamRepository = teamRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/courses/{code}/import_students")
|
||||
public String get(@PathVariable String code, Model model) {
|
||||
Optional<Course> optionalCourse = this.courseRepository.findByCode(code);
|
||||
optionalCourse.ifPresent(course -> model.addAttribute("course", course));
|
||||
return "courses/import_students";
|
||||
}
|
||||
|
||||
@PostMapping(
|
||||
value = "/courses/{code}/import_students"
|
||||
)
|
||||
public String post(@PathVariable String code, @RequestParam MultipartFile file) {
|
||||
Optional<Course> optionalCourse = this.courseRepository.findByCode(code);
|
||||
|
||||
if (!optionalCourse.isPresent()) {
|
||||
System.out.println("No course found.");
|
||||
return "redirect:/courses";
|
||||
}
|
||||
|
||||
try {
|
||||
List<StudentTeam> studentTeams = StudentTeamImporter.importFromCSV(file.getInputStream(), optionalCourse.get());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "redirect:/courses/{code}";
|
||||
}
|
||||
}
|
|
@ -77,12 +77,12 @@ public class Course extends BasicEntity {
|
|||
this.code = code;
|
||||
}
|
||||
|
||||
public void addStudentGroup(StudentTeam group) {
|
||||
this.studentTeams.add(group);
|
||||
public void addStudentTeam(StudentTeam team) {
|
||||
this.studentTeams.add(team);
|
||||
}
|
||||
|
||||
public void addTeachingAssistantGroup(TeachingAssistantTeam group) {
|
||||
this.teachingAssistantTeams.add(group);
|
||||
public void addTeachingAssistantTeam(TeachingAssistantTeam team) {
|
||||
this.teachingAssistantTeams.add(team);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -26,6 +26,9 @@ public abstract class Person extends BasicEntity {
|
|||
@Column
|
||||
private String emailAddress;
|
||||
|
||||
@Column
|
||||
private String githubUsername;
|
||||
|
||||
/**
|
||||
* The list of teams that this person belongs to. Because a person can belong to more than one team, it is implied
|
||||
* that each person exists in only one location in the database. Therefore, if one person is enrolled in two courses
|
||||
|
@ -50,12 +53,14 @@ public abstract class Person extends BasicEntity {
|
|||
* @param firstName The person's first name.
|
||||
* @param lastName The person's last name.
|
||||
* @param emailAddress The person's email address.
|
||||
* @param githubUsername The person's github username;
|
||||
*/
|
||||
public Person(String firstName, String lastName, String emailAddress) {
|
||||
public Person(String firstName, String lastName, String emailAddress, String githubUsername) {
|
||||
this();
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.emailAddress = emailAddress;
|
||||
this.githubUsername = githubUsername;
|
||||
}
|
||||
|
||||
public void assignToTeam(Team team) {
|
||||
|
@ -82,8 +87,40 @@ public abstract class Person extends BasicEntity {
|
|||
return this.emailAddress;
|
||||
}
|
||||
|
||||
public String getGithubUsername() {
|
||||
return this.githubUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if two Persons are equal. They are considered equal when all of the basic identifying information
|
||||
* about the person is the same, regardless of case.
|
||||
* @param o The other object.
|
||||
* @return True if the other object is the same person, or false if not.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (o instanceof Person) {
|
||||
Person p = (Person) o;
|
||||
return (
|
||||
this.getFirstName().equalsIgnoreCase(p.getFirstName())
|
||||
&& this.getLastName().equalsIgnoreCase(p.getLastName())
|
||||
&& this.getEmailAddress().equalsIgnoreCase(p.getEmailAddress())
|
||||
&& this.getGithubUsername().equalsIgnoreCase(p.getGithubUsername())
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getFirstName() + ' ' + this.getLastName() + '[' + this.getId() + ']';
|
||||
return "First Name: " + this.getFirstName()
|
||||
+ ", Last Name: " + this.getLastName()
|
||||
+ ", Email: " + this.getEmailAddress()
|
||||
+ ", Github Username: " + this.getGithubUsername();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewlalis.teaching_assistant_assistant.model.people;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
|
||||
/**
|
||||
|
@ -8,13 +9,52 @@ import javax.persistence.Entity;
|
|||
@Entity
|
||||
public class Student extends Person {
|
||||
|
||||
/**
|
||||
* The student's unique student number, as given by the university.
|
||||
*/
|
||||
@Column(unique = true, nullable = false)
|
||||
private int studentNumber;
|
||||
|
||||
/**
|
||||
* Default constructor for JPA.
|
||||
*/
|
||||
protected Student() {}
|
||||
|
||||
public Student(String firstName, String lastName, String emailAddress) {
|
||||
super(firstName, lastName, emailAddress);
|
||||
/**
|
||||
* Constructs a new student with all the properties of a Person, and any extra properties.
|
||||
* @param firstName The student's first name.
|
||||
* @param lastName The student's last name.
|
||||
* @param emailAddress The student's email address.
|
||||
* @param githubUsername The student's Github username.
|
||||
* @param studentNumber The student's unique student number.
|
||||
*/
|
||||
public Student(String firstName, String lastName, String emailAddress, String githubUsername, int studentNumber) {
|
||||
super(firstName, lastName, emailAddress, githubUsername);
|
||||
|
||||
this.studentNumber = studentNumber;
|
||||
}
|
||||
|
||||
public int getStudentNumber() {
|
||||
return studentNumber;
|
||||
}
|
||||
|
||||
public void setStudentNumber(int studentNumber) {
|
||||
this.studentNumber = studentNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if two students are equal. They are considered equal if their person attributes are the same, or
|
||||
* their student-specific attributes are equal.
|
||||
* @param o The other object.
|
||||
* @return True if the other object is the same student.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o) || this.getStudentNumber() == ((Student) o).getStudentNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", Student Number: " + this.getStudentNumber();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ public class TeachingAssistant extends Person {
|
|||
|
||||
}
|
||||
|
||||
public TeachingAssistant(String firstName, String lastName, String emailAddress) {
|
||||
super(firstName, lastName, emailAddress);
|
||||
public TeachingAssistant(String firstName, String lastName, String githubUsername, String emailAddress) {
|
||||
super(firstName, lastName, emailAddress, githubUsername);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,11 +56,11 @@ public class CourseGenerator extends TestDataGenerator<Course> {
|
|||
List<StudentTeam> studentTeams = this.generateStudentTeams();
|
||||
List<TeachingAssistantTeam> teachingAssistantTeams = this.generateTeachingAssistantTeams();
|
||||
for (StudentTeam team : studentTeams) {
|
||||
course.addStudentGroup(team);
|
||||
course.addStudentTeam(team);
|
||||
team.setCourse(course);
|
||||
}
|
||||
for (TeachingAssistantTeam team : teachingAssistantTeams) {
|
||||
course.addTeachingAssistantGroup(team);
|
||||
course.addTeachingAssistantTeam(team);
|
||||
team.setCourse(course);
|
||||
}
|
||||
return course;
|
||||
|
|
|
@ -12,6 +12,6 @@ public class StudentGenerator extends PersonGenerator<Student> {
|
|||
public Student generate() {
|
||||
String firstName = this.getRandomFirstName();
|
||||
String lastName = this.getRandomLastName();
|
||||
return new Student(firstName, lastName, this.getRandomEmailAddress(firstName, lastName));
|
||||
return new Student(firstName, lastName, this.getRandomEmailAddress(firstName, lastName), null, this.getRandomInteger(0, 100000000));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,6 @@ public class TeachingAssistantGenerator extends PersonGenerator<TeachingAssistan
|
|||
public TeachingAssistant generate() {
|
||||
String firstName = this.getRandomFirstName();
|
||||
String lastName = this.getRandomLastName();
|
||||
return new TeachingAssistant(firstName, lastName, this.getRandomEmailAddress(firstName, lastName));
|
||||
return new TeachingAssistant(firstName, lastName, null, this.getRandomEmailAddress(firstName, lastName));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package nl.andrewlalis.teaching_assistant_assistant.util.team_importing;
|
||||
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* Represents a pair of students, one of which has indicated that the other is their partner.
|
||||
*/
|
||||
public class StudentRecordEntry {
|
||||
|
||||
private Student student;
|
||||
|
||||
private Student partnerStudent;
|
||||
|
||||
private ZonedDateTime dateTime;
|
||||
|
||||
public StudentRecordEntry(ZonedDateTime dateTime, Student student, Student partnerStudent) {
|
||||
this.student = student;
|
||||
this.partnerStudent = partnerStudent;
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
public Student getStudent() {
|
||||
return this.student;
|
||||
}
|
||||
|
||||
public Student getPartnerStudent() {
|
||||
return this.partnerStudent;
|
||||
}
|
||||
|
||||
public ZonedDateTime getDateTime() {
|
||||
return this.dateTime;
|
||||
}
|
||||
|
||||
public boolean hasPartner() {
|
||||
return this.partnerStudent != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Entry at: " + this.dateTime.toString() + "\n\tStudent: " + this.getStudent() + "\n\tPreferred partner: " + this.getPartnerStudent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package nl.andrewlalis.teaching_assistant_assistant.util.team_importing;
|
||||
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
|
||||
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Provides some methods to streamline the process of transforming a CSV file of student data into a list of valid teams
|
||||
* which can be used for a course.
|
||||
*/
|
||||
public class StudentTeamImporter {
|
||||
|
||||
/**
|
||||
* Imports data from the given File Input Stream into the course.
|
||||
* @param fileInputStream An input stream representing the contents of a CSV file.
|
||||
* @param course The course to add the students to.
|
||||
* @return A list of valid teams that the given file describes.
|
||||
*/
|
||||
public static List<StudentTeam> importFromCSV(InputStream fileInputStream, Course course) throws IOException {
|
||||
Iterable<CSVRecord> records = CSVFormat.DEFAULT
|
||||
.withSkipHeaderRecord()
|
||||
.withHeader("timestamp", "email", "name", "number", "github_username", "has_partner", "partner_name", "partner_number", "partner_email", "partner_github_username")
|
||||
.parse(new InputStreamReader(fileInputStream));
|
||||
|
||||
List<StudentRecordEntry> studentEntries = extractStudentsFromRecords(records);
|
||||
|
||||
for (StudentRecordEntry entry : studentEntries) {
|
||||
System.out.println(entry.toString());
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all student data from a list of records, and automatically discards outdated responses (those where the
|
||||
* same student submitted more than once).
|
||||
* @param records The list of records in the CSV file.
|
||||
* @return A mapping for each timestamp to
|
||||
*/
|
||||
private static List<StudentRecordEntry> extractStudentsFromRecords(Iterable<CSVRecord> records) {
|
||||
List<StudentRecordEntry> studentEntries = new ArrayList<>();
|
||||
|
||||
for (CSVRecord record : records) {
|
||||
// Parse the actual student.
|
||||
Student s = parseStudentRecordData(record.get("name"), record.get("email"), record.get("github_username"), record.get("number"));
|
||||
|
||||
// Parse the student's preferred partner, if they exist.
|
||||
Student preferredPartner = null;
|
||||
if (record.get("has_partner").equalsIgnoreCase("yes")) {
|
||||
preferredPartner = parseStudentRecordData(record.get("partner_name"), record.get("partner_email"), record.get("partner_github_username"), record.get("partner_number"));
|
||||
}
|
||||
|
||||
// Parse the timestamp.
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd h:mm:ss a O ");
|
||||
ZonedDateTime dateTime = ZonedDateTime.parse(record.get("timestamp") + ' ', formatter);
|
||||
// A space is added because of a java bug: https://stackoverflow.com/questions/37287103/why-does-gmt8-fail-to-parse-with-pattern-o-despite-being-copied-straight-ou
|
||||
|
||||
studentEntries.add(new StudentRecordEntry(dateTime, s, preferredPartner));
|
||||
}
|
||||
|
||||
studentEntries = removeDuplicateEntries(studentEntries);
|
||||
|
||||
return studentEntries;
|
||||
}
|
||||
|
||||
|
||||
private static List<StudentRecordEntry> removeDuplicateEntries(List<StudentRecordEntry> studentEntries) {
|
||||
List<StudentRecordEntry> uniqueStudentEntries = new ArrayList<>();
|
||||
|
||||
for (StudentRecordEntry entry : studentEntries) {
|
||||
// Check for if the current entry's student already exists.
|
||||
boolean duplicateFound = false;
|
||||
for (StudentRecordEntry existingEntry : uniqueStudentEntries) {
|
||||
if (entry.getStudent().equals(existingEntry.getStudent())) {
|
||||
duplicateFound = true;
|
||||
// Check if the existing entry is older than the new one; it should be overwritten.
|
||||
if (existingEntry.getDateTime().isBefore(entry.getDateTime())) {
|
||||
uniqueStudentEntries.remove(existingEntry);
|
||||
uniqueStudentEntries.add(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicateFound) {
|
||||
uniqueStudentEntries.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueStudentEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a student object from given entries in a record obtained from a CSV file.
|
||||
* @param name The name value.
|
||||
* @param email The email value.
|
||||
* @param githubUsername The github_username value.
|
||||
* @param studentNumber The number value.
|
||||
* @return A student object constructed from the given data.
|
||||
*/
|
||||
private static Student parseStudentRecordData(String name, String email, String githubUsername, String studentNumber) {
|
||||
// Extract a sensible first and last name.
|
||||
String[] nameSegments = name.split(" ");
|
||||
String firstName = "No First Name Given";
|
||||
String lastName = "No Last Name Given";
|
||||
|
||||
if (nameSegments.length > 0) {
|
||||
firstName = nameSegments[0];
|
||||
}
|
||||
|
||||
if (nameSegments.length > 1) {
|
||||
lastName = String.join(" ", Arrays.copyOfRange(nameSegments, 1, nameSegments.length));
|
||||
}
|
||||
|
||||
// Extract a sensible github username.
|
||||
String githubURL = "https://github.com/";
|
||||
|
||||
if (githubUsername.startsWith(githubURL)) {
|
||||
githubUsername = githubUsername.substring(githubURL.length());
|
||||
}
|
||||
|
||||
// Create the student.
|
||||
return new Student(firstName, lastName, email, githubUsername, Integer.parseInt(studentNumber));
|
||||
}
|
||||
|
||||
}
|
|
@ -48,7 +48,7 @@
|
|||
<a th:href="@{/courses/{code}/import_students(code=${course.getCode()})}">Import students from CSV</a>
|
||||
</div>
|
||||
<div class="sidebar_block">
|
||||
<a th:href="@{/courses/{code}/import_teaching_assistants(code=${course.getCode()})}">Import teaching assistants from CSV</a>
|
||||
<a th:disabled="true" th:href="@{/courses/{code}/import_teaching_assistants(code=${course.getCode()})}">Import teaching assistants from CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar_content})}">
|
||||
<head>
|
||||
<title>Import Students via CSV</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<p>
|
||||
Please select a CSV file to import.
|
||||
</p>
|
||||
|
||||
<form method="post" action="#" enctype="multipart/form-data" th:action="@{/courses/{code}/import_students(code=${course.getCode()})}">
|
||||
<label for="file_input">File:</label>
|
||||
<input id="file_input" type="file" name="file" accept="text/csv"/>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="sidebar_content">
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue