Commit 1dd31731 authored by Segolene Brisemeur's avatar Segolene Brisemeur Committed by Florent Chehab

feat(course-feedback connected to back)

parent df33a510
Pipeline #39259 passed with stages
in 3 minutes and 24 seconds
...@@ -12,3 +12,4 @@ htmlcov ...@@ -12,3 +12,4 @@ htmlcov
database.db database.db
database.db-journal database.db-journal
.idea .idea
npm-debug.log
from datetime import datetime from datetime import datetime
from base_app.models import User
from backend_app.models.country import Country from backend_app.models.country import Country
from backend_app.models.countryScholarship import CountryScholarship from backend_app.models.countryScholarship import CountryScholarship
from backend_app.models.course import Course
from backend_app.models.courseFeedback import CourseFeedback
from backend_app.models.currency import Currency from backend_app.models.currency import Currency
from backend_app.models.exchange import Exchange
from backend_app.models.exchangeFeedback import ExchangeFeedback
from backend_app.models.tag import Tag from backend_app.models.tag import Tag
from backend_app.models.university import University from backend_app.models.university import University
from backend_app.models.universityDri import UniversityDri from backend_app.models.universityDri import UniversityDri
from backend_app.models.universityInfo import UniversityInfo from backend_app.models.universityInfo import UniversityInfo
from backend_app.models.universitySemestersDates import UniversitySemestersDates from backend_app.models.universitySemestersDates import UniversitySemestersDates
from backend_app.models.universityTaggedItem import UniversityTaggedItem from backend_app.models.universityTaggedItem import UniversityTaggedItem
from base_app.models import User
from .loadGeneric import LoadGeneric from .loadGeneric import LoadGeneric
...@@ -75,3 +77,91 @@ class LoadUniversityEx(LoadGeneric): ...@@ -75,3 +77,91 @@ class LoadUniversityEx(LoadGeneric):
importance_level="++", importance_level="++",
) )
self.add_info_and_save(univ_tag_1, self.admin) self.add_info_and_save(univ_tag_1, self.admin)
exchange1 = Exchange.objects.create(
utc_univ_id=EPFL,
utc_departure_id=1,
user=self.admin,
year=2019,
semester="a",
duration=1,
dual_degree=False,
master_obtained=False,
student_major="GI",
student_minor="FDD",
student_option="No",
utc_allow_courses=False,
utc_allow_login=False,
)
ExchangeFeedback.objects.create(
university=EPFL,
exchange=exchange1,
general_comment="Very good",
academical_level_appreciation=5,
foreign_student_welcome=5,
cultural_interest=5,
)
exchange2 = Exchange.objects.create(
utc_univ_id=EPFL,
utc_departure_id=2,
user=self.admin,
year=2018,
semester="a",
duration=1,
dual_degree=False,
master_obtained=False,
student_major="GI",
student_minor="FDD",
student_option="No",
utc_allow_courses=False,
utc_allow_login=True,
)
ExchangeFeedback.objects.create(
university=EPFL,
exchange=exchange2,
general_comment="Very good trop bien",
academical_level_appreciation=4,
foreign_student_welcome=3,
cultural_interest=4,
)
course1 = Course.objects.create(
exchange=exchange1,
utc_exchange_id=1,
course_id=1,
code="COM-401",
title="Applied data science",
link="",
nb_credit=5,
category="TM",
profile="PSF",
tsh_profile="",
student_login="chehabfl",
)
Course.objects.create(
exchange=exchange1,
utc_exchange_id=1,
course_id=2,
code="COM-480",
title="Data vizualization",
link="",
nb_credit=5,
category="TM",
profile="PSF",
tsh_profile="",
student_login="chehabfl",
)
CourseFeedback.objects.update_or_create(
course=course1,
defaults=dict(
comment="Trop bien",
adequation=5,
working_dose=4,
language_following_ease=3,
),
)
# Generated by Django 2.1.7 on 2019-05-04 15:52
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('backend_app', '0005_auto_20190423_2027'),
]
operations = [
migrations.RemoveField(
model_name='coursefeedback',
name='is_psf_credit',
),
migrations.RemoveField(
model_name='coursefeedback',
name='university',
),
migrations.RemoveField(
model_name='exchange',
name='courses',
),
migrations.AddField(
model_name='course',
name='exchange',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='exchange_courses', to='backend_app.Exchange'),
),
migrations.AlterField(
model_name='course',
name='link',
field=models.URLField(blank=True, max_length=500, null=True),
),
migrations.AlterField(
model_name='course',
name='nb_credit',
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name='course',
name='utc_exchange_id',
field=models.IntegerField(),
),
migrations.AlterField(
model_name='coursefeedback',
name='adequation',
field=models.IntegerField(default=0, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(-5)]),
),
migrations.AlterField(
model_name='coursefeedback',
name='course',
field=models.OneToOneField(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='course_feedback', to='backend_app.Course'),
),
migrations.AlterField(
model_name='coursefeedback',
name='language_following_ease',
field=models.IntegerField(default=0, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(-5)]),
),
migrations.AlterField(
model_name='coursefeedback',
name='working_dose',
field=models.IntegerField(default=0, validators=[django.core.validators.MaxValueValidator(5), django.core.validators.MinValueValidator(-5)]),
),
]
from django.db import models from django.db import models
from backend_app.models.abstract.base import BaseModel, BaseModelSerializer from backend_app.models.abstract.base import BaseModel
from backend_app.models.exchange import Exchange from backend_app.models.exchange import Exchange
class Course(BaseModel): class Course(BaseModel):
utc_exchange_id = models.OneToOneField(Exchange, on_delete=models.CASCADE) exchange = models.ForeignKey(
Exchange, on_delete=models.CASCADE, related_name="exchange_courses", null=True
)
utc_exchange_id = models.IntegerField()
course_id = models.IntegerField() course_id = models.IntegerField()
code = models.CharField(max_length=10) code = models.CharField(max_length=10)
title = models.CharField(default="", null=True, blank=True, max_length=200) title = models.CharField(default="", null=True, blank=True, max_length=200)
link = models.CharField(null=True, blank=True, max_length=500) link = models.URLField(null=True, blank=True, max_length=500)
nb_credit = models.PositiveIntegerField() nb_credit = models.PositiveIntegerField(default=0)
category = models.CharField(null=True, blank=True, max_length=5) category = models.CharField(null=True, blank=True, max_length=5)
profile = models.CharField(null=True, blank=True, max_length=10) profile = models.CharField(null=True, blank=True, max_length=10)
tsh_profile = models.CharField(null=True, blank=True, max_length=21) tsh_profile = models.CharField(null=True, blank=True, max_length=21)
student_login = models.CharField(null=True, blank=True, max_length=8) student_login = models.CharField(null=True, blank=True, max_length=8)
class CourseSerializer(BaseModelSerializer):
class Meta:
model = Course
fields = "__all__"
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from backend_app.models.abstract.essentialModule import ( from backend_app.models.abstract.essentialModule import EssentialModule
EssentialModule, from backend_app.models.course import Course
EssentialModuleSerializer,
EssentialModuleViewSet,
)
from backend_app.models.course import Course, CourseSerializer
from backend_app.models.university import University
class CourseFeedback(EssentialModule): class CourseFeedback(EssentialModule):
university = models.ForeignKey(University, on_delete=models.PROTECT, default=0) course = models.OneToOneField(
course = models.ForeignKey(Course, on_delete=models.CASCADE, default=0) Course, on_delete=models.CASCADE, default=0, related_name="course_feedback"
)
comment = models.TextField(null=True, max_length=1500) comment = models.TextField(null=True, max_length=1500)
adequation = models.IntegerField( adequation = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)] default=0, validators=[MaxValueValidator(5), MinValueValidator(-5)]
) )
working_dose = models.IntegerField( working_dose = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)] default=0, validators=[MaxValueValidator(5), MinValueValidator(-5)]
) )
language_following_ease = models.IntegerField( language_following_ease = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)] default=0, validators=[MaxValueValidator(5), MinValueValidator(-5)]
) )
is_psf_credit = models.BooleanField()
class CourseFeedbackSerializer(EssentialModuleSerializer):
course = CourseSerializer(many=True)
class Meta:
model = CourseFeedback
fields = "__all__"
class CourseFeedbackViewSet(EssentialModuleViewSet):
queryset = CourseFeedback.objects.all().prefetch_related() # pylint: disable=E1101
serializer_class = CourseFeedbackSerializer
end_point_route = "courseFeedbacks"
from django.db import models from django.db import models
from backend_app.models.university import University
from base_app.models import User
from backend_app.fields import JSONField
from backend_app.models.abstract.base import BaseModel, BaseModelSerializer from backend_app.models.abstract.base import BaseModel
from backend_app.models.shared import SEMESTER_OPTIONS from backend_app.models.shared import SEMESTER_OPTIONS
from backend_app.models.university import University
from base_app.models import User
class Exchange(BaseModel): class Exchange(BaseModel):
# This model should be filled with data from the ENT # This model should be filled with data from the ENT
utc_univ_id = models.ForeignKey(University, on_delete=models.PROTECT) utc_univ_id = models.ForeignKey(University, on_delete=models.PROTECT)
utc_departure_id = models.IntegerField() utc_departure_id = models.IntegerField()
...@@ -25,21 +23,3 @@ class Exchange(BaseModel): ...@@ -25,21 +23,3 @@ class Exchange(BaseModel):
utc_allow_courses = models.BooleanField() utc_allow_courses = models.BooleanField()
utc_allow_login = models.BooleanField() utc_allow_login = models.BooleanField()
# No specific validation of the JSON is required here
courses = JSONField(null=True) # Store data from ENT
class ExchangeSerializer(BaseModelSerializer):
class Meta:
model = Exchange
fields = BaseModelSerializer.Meta.fields + (
"year",
"semester",
"duration",
"dual_degree",
"master_obtained",
"student_major",
"studentminor",
"student_option",
)
...@@ -7,6 +7,7 @@ from backend_app.models.abstract.essentialModule import ( ...@@ -7,6 +7,7 @@ from backend_app.models.abstract.essentialModule import (
EssentialModuleViewSet, EssentialModuleViewSet,
) )
from backend_app.models.exchange import Exchange from backend_app.models.exchange import Exchange
from backend_app.serializers import ExchangeSerializer
from backend_app.models.university import University from backend_app.models.university import University
...@@ -31,12 +32,19 @@ class ExchangeFeedback(EssentialModule): ...@@ -31,12 +32,19 @@ class ExchangeFeedback(EssentialModule):
class ExchangeFeedbackSerializer(EssentialModuleSerializer): class ExchangeFeedbackSerializer(EssentialModuleSerializer):
exchange = ExchangeSerializer(read_only=True)
class Meta: class Meta:
model = ExchangeFeedback model = ExchangeFeedback
fields = "__all__" fields = "__all__"
class ExchangeFeedbackViewSet(EssentialModuleViewSet): class ExchangeFeedbackViewSet(EssentialModuleViewSet):
queryset = ExchangeFeedback.objects.all() # pylint: disable=E1101 queryset = ExchangeFeedback.objects.all().prefetch_related(
"exchange",
"exchange__exchange_courses",
"exchange__exchange_courses__course_feedback",
) # pylint: disable=E1101
serializer_class = ExchangeFeedbackSerializer serializer_class = ExchangeFeedbackSerializer
end_point_route = "exchangeFeedbacks" end_point_route = "exchangeFeedbacks"
filterset_fields = ("university",)
from backend_app.models.abstract.base import BaseModelSerializer
from backend_app.models.abstract.essentialModule import EssentialModuleSerializer
from backend_app.models.course import Course
from backend_app.models.courseFeedback import CourseFeedback
from backend_app.models.exchange import Exchange
class CourseFeedbackSerializer(EssentialModuleSerializer):
class Meta:
model = CourseFeedback
fields = EssentialModuleSerializer.Meta.fields + (
"comment",
"adequation",
"working_dose",
"language_following_ease",
)
class CourseSerializer(BaseModelSerializer):
course_feedback = CourseFeedbackSerializer(many=False, read_only=True)
class Meta:
model = Course
fields = BaseModelSerializer.Meta.fields + (
"course_feedback",
"code",
"title",
"link",
"nb_credit",
"category",
"profile",
"tsh_profile",
)
read_only_fields = ("course_feedback",)
class ExchangeSerializer(BaseModelSerializer):
exchange_courses = CourseSerializer(many=True, read_only=True)
class Meta:
model = Exchange
fields = BaseModelSerializer.Meta.fields + (
"year",
"semester",
"duration",
"dual_degree",
"master_obtained",
"student_major",
"student_minor",
"student_option",
"exchange_courses",
)
from backend_app.models.course import Course
from backend_app.models.courseFeedback import CourseFeedback
from backend_app.models.university import University from backend_app.models.university import University
from backend_app.models.universityInfo import UniversityInfo from backend_app.models.universityInfo import UniversityInfo
from backend_app.models.universitySemestersDates import UniversitySemestersDates from backend_app.models.universitySemestersDates import UniversitySemestersDates
...@@ -19,5 +21,11 @@ def create_user_modules(sender, instance, created, **kwargs): ...@@ -19,5 +21,11 @@ def create_user_modules(sender, instance, created, **kwargs):
UserData.objects.create(owner=instance) UserData.objects.create(owner=instance)
def create_course_feedback_modules(sender, instance, created, **kwargs):
if created:
CourseFeedback.objects.create(course=instance)
post_save.connect(create_univ_modules, sender=University) post_save.connect(create_univ_modules, sender=University)
post_save.connect(create_user_modules, sender=User) post_save.connect(create_user_modules, sender=User)
post_save.connect(create_course_feedback_modules, sender=Course)
...@@ -3,6 +3,7 @@ from rest_framework.response import Response ...@@ -3,6 +3,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from backend_app.checks import check_viewsets from backend_app.checks import check_viewsets
from backend_app.models.abstract.essentialModule import EssentialModuleViewSet
from backend_app.models.campus import CampusViewSet, MainCampusViewSet from backend_app.models.campus import CampusViewSet, MainCampusViewSet
from backend_app.models.campusTaggedItem import CampusTaggedItemViewSet from backend_app.models.campusTaggedItem import CampusTaggedItemViewSet
from backend_app.models.city import CityViewSet from backend_app.models.city import CityViewSet
...@@ -11,7 +12,7 @@ from backend_app.models.country import CountryViewSet ...@@ -11,7 +12,7 @@ from backend_app.models.country import CountryViewSet
from backend_app.models.countryDri import CountryDriViewSet from backend_app.models.countryDri import CountryDriViewSet
from backend_app.models.countryScholarship import CountryScholarshipViewSet from backend_app.models.countryScholarship import CountryScholarshipViewSet
from backend_app.models.countryTaggedItem import CountryTaggedItemViewSet from backend_app.models.countryTaggedItem import CountryTaggedItemViewSet
from backend_app.models.courseFeedback import CourseFeedbackViewSet from backend_app.models.courseFeedback import CourseFeedback
from backend_app.models.currency import CurrencyViewSet from backend_app.models.currency import CurrencyViewSet
from backend_app.models.department import DepartmentViewSet from backend_app.models.department import DepartmentViewSet
from backend_app.models.exchangeFeedback import ExchangeFeedbackViewSet from backend_app.models.exchangeFeedback import ExchangeFeedbackViewSet
...@@ -33,9 +34,17 @@ from backend_app.models.universityTaggedItem import UniversityTaggedItemViewSet ...@@ -33,9 +34,17 @@ from backend_app.models.universityTaggedItem import UniversityTaggedItemViewSet
from backend_app.models.userData import UserDataViewSet from backend_app.models.userData import UserDataViewSet
from backend_app.models.version import VersionViewSet from backend_app.models.version import VersionViewSet
from backend_app.permissions.app_permissions import ReadOnly from backend_app.permissions.app_permissions import ReadOnly
from backend_app.serializers import CourseFeedbackSerializer
from backend_app.settings.defaults import OBJ_MODERATION_PERMISSIONS from backend_app.settings.defaults import OBJ_MODERATION_PERMISSIONS
from base_app.models import UserViewset from base_app.models import UserViewset
class CourseFeedbackViewSet(EssentialModuleViewSet):
queryset = CourseFeedback.objects.all().prefetch_related() # pylint: disable=E1101
serializer_class = CourseFeedbackSerializer
end_point_route = "courseFeedbacks"
ALL_API_VIEWSETS = [ ALL_API_VIEWSETS = [
UserViewset, UserViewset,
CampusViewSet, CampusViewSet,
......
import React from "react"; import React, {Component} from "react";
import Table from "@material-ui/core/Table"; import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody"; import TableBody from "@material-ui/core/TableBody";
...@@ -18,31 +18,11 @@ const styles = theme => ({ ...@@ -18,31 +18,11 @@ const styles = theme => ({
} }
}); });
const data = [
{
id: 1,
courseName: "Cours de statistique",
nbCredit: "7",
language: "Anglais",
interest: "7",
work_dose: "10",
following_ability: "5"
},
{
id: 2,
courseName: "Architecture d'application internet",
nbCredit: "6",
language: "Norvégien",
interest: "9",
work_dose: "5",
following_ability: "2"
}
];
class CourseFeedback extends Component {
class CourseFeedback extends React.Component {
render() { render() {
const { classes } = this.props; const {classes, exchangeCourses} = this.props;
return ( return (
<> <>
...@@ -52,22 +32,24 @@ class CourseFeedback extends React.Component { ...@@ -52,22 +32,24 @@ class CourseFeedback extends React.Component {
<TableCell>Cours</TableCell> <TableCell>Cours</TableCell>
<TableCell>Crédits</TableCell> <TableCell>Crédits</TableCell>
<TableCell>Langue</TableCell> <TableCell>Langue</TableCell>
<TableCell>Intérêt /10</TableCell> <TableCell>Intérêt</TableCell>
<TableCell>Dose de travail /10</TableCell> <TableCell>Dose de travail /10</TableCell>
<TableCell>Facilité à suivre la langue /10</TableCell> <TableCell>Facilité à suivre la langue /10</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{data.map(courseData => ( {
<TableRow key={courseData.id}> exchangeCourses.map((course, idx) => (
<TableCell scope="courseData">{courseData.courseName}</TableCell> <TableRow key={idx}>
<TableCell>{courseData.nbCredit}</TableCell> <TableCell scope="course">{course.code}</TableCell>
<TableCell>{courseData.language}</TableCell> <TableCell>{course.nb_credit}</TableCell>
<TableCell>{courseData.interest}</TableCell> <TableCell>langue</TableCell>
<TableCell>{courseData.work_dose}</TableCell> <TableCell>{course.course_feedback.adequation}</TableCell>
<TableCell>{courseData.following_ability}</TableCell> <TableCell>{course.course_feedback.working_dose}</TableCell>
<TableCell>{course.course_feedback.language_following_ease}</TableCell>
</TableRow> </TableRow>
))} ))
}
</TableBody> </TableBody>
</Table> </Table>
</> </>
...@@ -75,10 +57,12 @@ class CourseFeedback extends React.Component { ...@@ -75,10 +57,12 @@ class CourseFeedback extends React.Component {
} }
} }
CourseFeedback.propTypes = { CourseFeedback.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
exchangeCourses: PropTypes.array.isRequired,
}; };
export default compose( export default compose(
withStyles(styles), withStyles(styles, {withTheme: true}),
)(CourseFeedback); )(CourseFeedback);
\ No newline at end of file
...@@ -16,8 +16,6 @@ import Chip from "@material-ui/core/Chip"; ...@@ -16,8 +16,6 @@ import Chip from "@material-ui/core/Chip";
import CourseFeedback from "./CourseFeedback"; import CourseFeedback from "./CourseFeedback";
let commentary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
const styles = theme => ({ const styles = theme => ({
paper: { paper: {
margin: `${theme.spacing.unit}px auto`, margin: `${theme.spacing.unit}px auto`,
...@@ -35,17 +33,10 @@ const styles = theme => ({ ...@@ -35,17 +33,10 @@ const styles = theme => ({
}); });
class TruncatedCommentaryOrMore extends React.Component { class TruncatedCommentaryOrMore extends React.Component {
state = {
constructor(props) {
super(props);
this.state = {
expanded: false expanded: false
}; };
this.getNewStateText = this.getNewStateText.bind(this);
this.getMoreTextDiv = this.getMoreTextDiv.bind(this);
}
// Get the opposite of the actual text state (if expanded then unexpanded) // Get the opposite of the actual text state (if expanded then unexpanded)
getNewStateText() { getNewStateText() {
this.setState({ this.setState({
...@@ -54,6 +45,8 @@ class TruncatedCommentaryOrMore extends React.Component { ...@@ -54,6 +45,8 @@ class TruncatedCommentaryOrMore extends React.Component {
} }
getMoreTextDiv() { getMoreTextDiv() {
const {commentary} = this.props;
if (this.state.expanded) { if (this.state.expanded) {
return <div> {commentary} </div>; return <div> {commentary} </div>;
} else { } else {
...@@ -62,77 +55,79 @@ class TruncatedCommentaryOrMore extends React.Component { ...@@ -62,77 +55,79 @@ class TruncatedCommentaryOrMore extends React.Component {
} }
render() { render() {
let expandedDiv = this.getMoreTextDiv(); const {commentary} = this.props;
let button;
if (this.state.expanded) {
button = <Button onClick={this.getNewStateText}>Lire moins</Button>;
} else {