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
database.db
database.db-journal
.idea
npm-debug.log
from datetime import datetime
from base_app.models import User
from backend_app.models.country import Country
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.exchange import Exchange
from backend_app.models.exchangeFeedback import ExchangeFeedback
from backend_app.models.tag import Tag
from backend_app.models.university import University
from backend_app.models.universityDri import UniversityDri
from backend_app.models.universityInfo import UniversityInfo
from backend_app.models.universitySemestersDates import UniversitySemestersDates
from backend_app.models.universityTaggedItem import UniversityTaggedItem
from base_app.models import User
from .loadGeneric import LoadGeneric
......@@ -75,3 +77,91 @@ class LoadUniversityEx(LoadGeneric):
importance_level="++",
)
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 backend_app.models.abstract.base import BaseModel, BaseModelSerializer
from backend_app.models.abstract.base import BaseModel
from backend_app.models.exchange import Exchange
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()
code = models.CharField(max_length=10)
title = models.CharField(default="", null=True, blank=True, max_length=200)
link = models.CharField(null=True, blank=True, max_length=500)
nb_credit = models.PositiveIntegerField()
link = models.URLField(null=True, blank=True, max_length=500)
nb_credit = models.PositiveIntegerField(default=0)
category = models.CharField(null=True, blank=True, max_length=5)
profile = models.CharField(null=True, blank=True, max_length=10)
tsh_profile = models.CharField(null=True, blank=True, max_length=21)
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.db import models
from backend_app.models.abstract.essentialModule import (
EssentialModule,
EssentialModuleSerializer,
EssentialModuleViewSet,
)
from backend_app.models.course import Course, CourseSerializer
from backend_app.models.university import University
from backend_app.models.abstract.essentialModule import EssentialModule
from backend_app.models.course import Course
class CourseFeedback(EssentialModule):
university = models.ForeignKey(University, on_delete=models.PROTECT, default=0)
course = models.ForeignKey(Course, on_delete=models.CASCADE, default=0)
course = models.OneToOneField(
Course, on_delete=models.CASCADE, default=0, related_name="course_feedback"
)
comment = models.TextField(null=True, max_length=1500)
adequation = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)]
default=0, validators=[MaxValueValidator(5), MinValueValidator(-5)]
)
working_dose = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)]
default=0, validators=[MaxValueValidator(5), MinValueValidator(-5)]
)
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 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.university import University
from base_app.models import User
class Exchange(BaseModel):
# This model should be filled with data from the ENT
utc_univ_id = models.ForeignKey(University, on_delete=models.PROTECT)
utc_departure_id = models.IntegerField()
......@@ -25,21 +23,3 @@ class Exchange(BaseModel):
utc_allow_courses = 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 (
EssentialModuleViewSet,
)
from backend_app.models.exchange import Exchange
from backend_app.serializers import ExchangeSerializer
from backend_app.models.university import University
......@@ -31,12 +32,19 @@ class ExchangeFeedback(EssentialModule):
class ExchangeFeedbackSerializer(EssentialModuleSerializer):
exchange = ExchangeSerializer(read_only=True)
class Meta:
model = ExchangeFeedback
fields = "__all__"
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
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.universityInfo import UniversityInfo
from backend_app.models.universitySemestersDates import UniversitySemestersDates
......@@ -19,5 +21,11 @@ def create_user_modules(sender, instance, created, **kwargs):
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_user_modules, sender=User)
post_save.connect(create_course_feedback_modules, sender=Course)
......@@ -3,6 +3,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
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.campusTaggedItem import CampusTaggedItemViewSet
from backend_app.models.city import CityViewSet
......@@ -11,7 +12,7 @@ from backend_app.models.country import CountryViewSet
from backend_app.models.countryDri import CountryDriViewSet
from backend_app.models.countryScholarship import CountryScholarshipViewSet
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.department import DepartmentViewSet
from backend_app.models.exchangeFeedback import ExchangeFeedbackViewSet
......@@ -33,9 +34,17 @@ from backend_app.models.universityTaggedItem import UniversityTaggedItemViewSet
from backend_app.models.userData import UserDataViewSet
from backend_app.models.version import VersionViewSet
from backend_app.permissions.app_permissions import ReadOnly
from backend_app.serializers import CourseFeedbackSerializer
from backend_app.settings.defaults import OBJ_MODERATION_PERMISSIONS
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 = [
UserViewset,
CampusViewSet,
......
import React from "react";
import React, {Component} from "react";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
......@@ -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() {
const { classes } = this.props;
const {classes, exchangeCourses} = this.props;
return (
<>
......@@ -52,22 +32,24 @@ class CourseFeedback extends React.Component {
<TableCell>Cours</TableCell>
<TableCell>Crédits</TableCell>
<TableCell>Langue</TableCell>
<TableCell>Intérêt /10</TableCell>
<TableCell>Intérêt</TableCell>
<TableCell>Dose de travail /10</TableCell>
<TableCell>Facilité à suivre la langue /10</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map(courseData => (
<TableRow key={courseData.id}>
<TableCell scope="courseData">{courseData.courseName}</TableCell>
<TableCell>{courseData.nbCredit}</TableCell>
<TableCell>{courseData.language}</TableCell>
<TableCell>{courseData.interest}</TableCell>
<TableCell>{courseData.work_dose}</TableCell>
<TableCell>{courseData.following_ability}</TableCell>
{
exchangeCourses.map((course, idx) => (
<TableRow key={idx}>
<TableCell scope="course">{course.code}</TableCell>
<TableCell>{course.nb_credit}</TableCell>
<TableCell>langue</TableCell>
<TableCell>{course.course_feedback.adequation}</TableCell>
<TableCell>{course.course_feedback.working_dose}</TableCell>
<TableCell>{course.course_feedback.language_following_ease}</TableCell>
</TableRow>
))}
))
}
</TableBody>
</Table>
</>
......@@ -75,10 +57,12 @@ class CourseFeedback extends React.Component {
}
}
CourseFeedback.propTypes = {
classes: PropTypes.object.isRequired,
exchangeCourses: PropTypes.array.isRequired,
};
export default compose(
withStyles(styles),
withStyles(styles, {withTheme: true}),
)(CourseFeedback);
\ No newline at end of file
......@@ -16,8 +16,6 @@ import Chip from "@material-ui/core/Chip";
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 => ({
paper: {
margin: `${theme.spacing.unit}px auto`,
......@@ -35,17 +33,10 @@ const styles = theme => ({
});
class TruncatedCommentaryOrMore extends React.Component {
constructor(props) {
super(props);
this.state = {
state = {
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)
getNewStateText() {
this.setState({
......@@ -54,6 +45,8 @@ class TruncatedCommentaryOrMore extends React.Component {
}
getMoreTextDiv() {
const {commentary} = this.props;
if (this.state.expanded) {
return <div> {commentary} </div>;
} else {
......@@ -62,77 +55,79 @@ class TruncatedCommentaryOrMore extends React.Component {
}
render() {
let expandedDiv = this.getMoreTextDiv();
let button;
if (this.state.expanded) {
button = <Button onClick={this.getNewStateText}>Lire moins</Button>;
} else {
button = <Button onClick={this.getNewStateText}>Lire plus</Button>;
}
const {commentary} = this.props;
return (
<div>
{expandedDiv}
{button}
{this.getMoreTextDiv()}
{commentary > 100 ?
<>
{this.state.expanded ?
<Button onClick={() => this.getNewStateText()}>Lire moins</Button>
:
<Button onClick={() => this.getNewStateText()}>Lire plus</Button>
}
</>
:
<></>
}
</div>
);
}
}
TruncatedCommentaryOrMore.propTypes = {
commentary: PropTypes.string.isRequired,
};
class PreviousDeparture extends React.Component {
render() {
const {classes, rawModelData} = this.props;
const cleanSemester = `${rawModelData.exchange.semester.toUpperCase()} ${rawModelData.exchange.year}`;
return (
<>
<Chip label="Automne 2018" className={classes.chip} color="primary"/>
<Chip label={cleanSemester} className={classes.chip} color="primary"/>
<span> </span>
<Chip label={rawModelData.speciality} className={classes.chip} color="secondary"></Chip>
<Chip label={rawModelData.exchange.student_major} className={classes.chip} color="secondary"/>
<div className={classes.gradingDiv}>
<Grid container spacing={0}>
<Grid item xs={3}>
<Paper className={classes.paperGrid}>
<Typography>Cadre de vie</Typography>
<Typography>8/10</Typography>
<Typography>Niveau académique</Typography>
<Typography>{rawModelData.academical_level_appreciation}</Typography>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper className={classes.paperGrid}>
<Typography>Accueil des étudiants étrangers</Typography>
<Typography>9/10</Typography>
<Typography>{rawModelData.foreign_student_welcome}</Typography>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper className={classes.paperGrid}>
<Typography>Coût de la vie</Typography>
<Typography>4/10</Typography>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper className={classes.paperGrid}>
<Typography>Intérêt touristique</Typography>
<Typography>3/10</Typography>
<Typography>Intérêt culturel</Typography>
<Typography>{rawModelData.cultural_interest}</Typography>
</Paper>
</Grid>
</Grid>
</div>
<Paper className={classes.paper}>
<Typography className={classes.heading}>Avis général : </Typography>
<TruncatedCommentaryOrMore>
commentary={commentary}
</TruncatedCommentaryOrMore>
<TruncatedCommentaryOrMore commentary={rawModelData.general_comment}/>
</Paper>
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>}>
<Typography className={classes.heading}>Evaluation des cours suivis</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<CourseFeedback
classes={classes}
exchangeCourses={rawModelData.exchange.exchange_courses}
/>
</ExpansionPanelDetails>
</ExpansionPanel>
......@@ -150,20 +145,6 @@ PreviousDeparture.propTypes = {
rawModelData: PropTypes.object.isRequired,
};
// const mapStateToProps = (state) => {
// return {
// };
// };
// const mapDispatchToProps = (dispatch) => {
// return {
// api: {
// },
// };
// };