Commit a01ff1ca authored by Segolene Brisemeur's avatar Segolene Brisemeur Committed by Florent Chehab

feat(Feedback on courses): backend and frontend WIP

* Add new models in backend : Course, CourseFeedback, Exchange, ExchangeFeedback
* Begin front end for previous departure tab

#29 in progress
parent 357cece1
Pipeline #37997 passed with stages
in 4 minutes and 38 seconds
......@@ -12,14 +12,16 @@ from backend_app.models.country import Country
from backend_app.models.countryDri import CountryDri
from backend_app.models.countryScholarship import CountryScholarship
from backend_app.models.countryTaggedItem import CountryTaggedItem
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.department import Department
from backend_app.models.for_testing.moderation import ForTestingModeration
from backend_app.models.for_testing.versioning import ForTestingVersioning
from backend_app.models.offer import Offer
from backend_app.models.pendingModeration import PendingModeration
from backend_app.models.previousDeparture import PreviousDeparture
from backend_app.models.previousDepartureFeedback import PreviousDepartureFeedback
from backend_app.models.exchange import Exchange
from backend_app.models.exchangeFeedback import ExchangeFeedback
from backend_app.models.recommendation import Recommendation
from backend_app.models.recommendationList import RecommendationList
from backend_app.models.specialty import Specialty
......@@ -42,12 +44,14 @@ ALL_MODELS = [
CountryDri,
CountryScholarship,
CountryTaggedItem,
Course,
CourseFeedback,
Currency,
Department,
Offer,
PendingModeration,
PreviousDeparture,
PreviousDepartureFeedback,
Exchange,
ExchangeFeedback,
Recommendation,
RecommendationList,
Specialty,
......
This diff is collapsed.
# Generated by Django 2.1.7 on 2019-04-06 14:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("backend_app", "0002_auto_20190324_2123"),
("backend_app", "0002_auto_20190405_2131"),
]
operations = []
# Generated by Django 2.1.7 on 2019-04-07 08:10
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("backend_app", "0003_merge_20190406_1636")]
operations = [
migrations.RenameField(
model_name="coursefeedback", old_name="work_dose", new_name="working_dose"
),
migrations.RemoveField(model_name="course", name="description"),
migrations.RemoveField(model_name="course", name="has_pending_moderation"),
migrations.RemoveField(model_name="course", name="moderated_by"),
migrations.RemoveField(model_name="course", name="moderated_on"),
migrations.RemoveField(model_name="course", name="obj_moderation_level"),
migrations.RemoveField(model_name="course", name="updated_by"),
migrations.RemoveField(model_name="course", name="updated_on"),
migrations.RemoveField(model_name="coursefeedback", name="departure"),
migrations.RemoveField(model_name="exchange", name="has_pending_moderation"),
migrations.RemoveField(model_name="exchange", name="is_anonymous"),
migrations.RemoveField(model_name="exchange", name="moderated_by"),
migrations.RemoveField(model_name="exchange", name="moderated_on"),
migrations.RemoveField(model_name="exchange", name="obj_moderation_level"),
migrations.RemoveField(model_name="exchange", name="updated_by"),
migrations.RemoveField(model_name="exchange", name="updated_on"),
migrations.RemoveField(model_name="exchangefeedback", name="departure"),
migrations.RemoveField(model_name="exchangefeedback", name="id"),
migrations.AddField(
model_name="course",
name="category",
field=models.CharField(blank=True, max_length=5, null=True),
),
migrations.AddField(
model_name="coursefeedback",
name="course",
field=models.ForeignKey(
default=0,
on_delete=django.db.models.deletion.CASCADE,
to="backend_app.Course",
),
),
migrations.AddField(
model_name="coursefeedback",
name="university",
field=models.ForeignKey(
default=0,
on_delete=django.db.models.deletion.PROTECT,
to="backend_app.University",
),
),
migrations.AddField(
model_name="exchangefeedback",
name="exchange",
field=models.OneToOneField(
default=0,
on_delete=django.db.models.deletion.CASCADE,
primary_key=True,
related_name="feedbacks",
serialize=False,
to="backend_app.Exchange",
),
),
migrations.AddField(
model_name="exchangefeedback",
name="university",
field=models.ForeignKey(
default=0,
on_delete=django.db.models.deletion.PROTECT,
to="backend_app.University",
),
),
migrations.AlterField(
model_name="exchange",
name="semester",
field=models.CharField(
choices=[("a", "autumn"), ("p", "spring")], default="a", max_length=5
),
),
migrations.AlterField(
model_name="exchange",
name="student_major",
field=models.CharField(max_length=20),
),
migrations.AlterField(
model_name="exchange",
name="student_minor",
field=models.CharField(max_length=7),
),
migrations.AlterField(
model_name="exchange",
name="student_option",
field=models.CharField(max_length=7),
),
migrations.AlterField(
model_name="exchange",
name="user",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),
]
from django.db import models
from backend_app.models.abstract.base import BaseModel, BaseModelSerializer
from backend_app.models.exchange import Exchange
class Course(BaseModel):
utc_exchange_id = models.OneToOneField(Exchange, on_delete=models.CASCADE)
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()
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 backend_app.models.abstract.essentialModule import (
EssentialModule,
EssentialModuleSerializer,
EssentialModuleViewSet,
)
from backend_app.models.course import Course, CourseSerializer
from backend_app.models.university import University
class CourseFeedback(EssentialModule):
university = models.ForeignKey(University, on_delete=models.PROTECT, default=0)
course = models.ForeignKey(Course, on_delete=models.CASCADE, default=0)
comment = models.TextField(null=True, max_length=1500)
adequation = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)]
)
working_dose = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)]
)
language_following_ease = models.IntegerField(
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.shared import SEMESTER_OPTIONS
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()
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
year = models.PositiveIntegerField(default=2018)
semester = models.CharField(max_length=5, choices=SEMESTER_OPTIONS, default="a")
duration = models.PositiveIntegerField()
dual_degree = models.BooleanField()
master_obtained = models.BooleanField()
student_major = models.CharField(max_length=20)
student_minor = models.CharField(max_length=7)
student_option = models.CharField(max_length=7)
utc_allow_courses = models.BooleanField()
utc_allow_login = models.BooleanField()
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",
)
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from backend_app.models.abstract.essentialModule import (
EssentialModule,
EssentialModuleSerializer,
EssentialModuleViewSet,
)
from backend_app.models.exchange import Exchange
from backend_app.models.university import University
class ExchangeFeedback(EssentialModule):
university = models.ForeignKey(University, on_delete=models.PROTECT, default=0)
exchange = models.OneToOneField(
Exchange,
on_delete=models.CASCADE,
related_name="feedbacks",
primary_key=True,
null=False,
default=0,
)
general_comment = models.TextField(null=True, max_length=1500)
academical_level_appreciation = models.IntegerField(
validators=[MaxValueValidator(5), MinValueValidator(-5)]
)
foreign_student_welcome = models.PositiveIntegerField(
validators=[MaxValueValidator(10)]
)
cultural_interest = models.PositiveIntegerField(validators=[MaxValueValidator(10)])
class ExchangeFeedbackSerializer(EssentialModuleSerializer):
class Meta:
model = ExchangeFeedback
fields = "__all__"
class ExchangeFeedbackViewSet(EssentialModuleViewSet):
queryset = ExchangeFeedback.objects.all() # pylint: disable=E1101
serializer_class = ExchangeFeedbackSerializer
end_point_route = "exchangeFeedbacks"
from django.db import models
from backend_app.models.specialty import Specialty
from backend_app.models.university import University
from backend_app.permissions.app_permissions import ReadOnly
from base_app.models import User
from backend_app.fields import JSONField
from backend_app.models.abstract.essentialModule import (
EssentialModule,
EssentialModuleSerializer,
EssentialModuleViewSet,
)
from backend_app.models.shared import SEMESTER_OPTIONS
class PreviousDeparture(EssentialModule):
# This model should be filled with data from the ENT
university = models.ForeignKey(University, on_delete=models.PROTECT)
specialty = models.ForeignKey(Specialty, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
# Useful to relink a departure with a user
Utc_departure_id = models.IntegerField()
year = models.PositiveIntegerField(default=2018)
semester = models.CharField(max_length=2, choices=SEMESTER_OPTIONS, default="a")
is_anonymous = models.BooleanField()
courses = JSONField(null=True) # Store data from ENT
class PreviousDepartureSerializer(EssentialModuleSerializer):
class Meta:
model = PreviousDeparture
fields = "__all__"
class PreviousDepartureViewSet(EssentialModuleViewSet):
queryset = PreviousDeparture.objects.all() # pylint: disable=E1101
serializer_class = PreviousDepartureSerializer
permission_classes = (ReadOnly,)
end_point_route = "universitiesPreviousDepartures"
from django.core.validators import MaxValueValidator
from django.db import models
from backend_app.fields import JSONField
from backend_app.models.previousDeparture import PreviousDeparture
from backend_app.models.userRestrictedModule import (
UserRestrictedModule,
UserRestrictedModuleSerializer,
UserRestrictedModuleViewSet,
)
class PreviousDepartureFeedback(UserRestrictedModule):
departure = models.OneToOneField(PreviousDeparture, on_delete=models.CASCADE)
courses_and_courses_feedback = JSONField(default=dict)
adequation_comment = models.CharField(default="", blank=True, max_length=5000)
integration_comment = models.CharField(default="", blank=True, max_length=5000)
adequation_grate = models.PositiveIntegerField(validators=[MaxValueValidator(20)])
integration_grade = models.PositiveIntegerField(validators=[MaxValueValidator(20)])
class PreviousDepartureFeedbackSerializer(UserRestrictedModuleSerializer):
class Meta:
model = PreviousDepartureFeedback
fields = "__all__"
class PreviousDepartureFeedbackViewSet(UserRestrictedModuleViewSet):
queryset = PreviousDepartureFeedback.objects.all() # pylint: disable=E1101
serializer_class = PreviousDepartureFeedbackSerializer
end_point_route = "universitiesPreviousDepartureFeedbacks"
......@@ -11,6 +11,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.currency import CurrencyViewSet
from backend_app.models.department import DepartmentViewSet
from backend_app.models.for_testing.moderation import ForTestingModerationViewSet
......@@ -20,10 +21,7 @@ from backend_app.models.pendingModeration import (
PendingModerationViewSet,
PendingModerationObjViewSet,
)
from backend_app.models.previousDeparture import PreviousDepartureViewSet
from backend_app.models.previousDepartureFeedback import (
PreviousDepartureFeedbackViewSet,
)
from backend_app.models.exchangeFeedback import ExchangeFeedbackViewSet
from backend_app.models.recommendation import RecommendationViewSet
from backend_app.models.recommendationList import RecommendationListViewSet
from backend_app.models.specialty import SpecialtyViewSet
......@@ -51,13 +49,13 @@ ALL_API_VIEWSETS = [
CountryDriViewSet,
CountryScholarshipViewSet,
CountryTaggedItemViewSet,
CourseFeedbackViewSet,
CurrencyViewSet,
DepartmentViewSet,
OfferViewSet,
PendingModerationViewSet,
PendingModerationObjViewSet,
PreviousDepartureViewSet,
PreviousDepartureFeedbackViewSet,
ExchangeFeedbackViewSet,
RecommendationViewSet,
RecommendationListViewSet,
SpecialtyViewSet,
......
......@@ -20,7 +20,7 @@ import CoverGallery from "./otherComponents/CoverGallery";
import GeneralInfoTab from "./tabs/GeneralInfoTab";
// import UniversityMoreTab from "./tabs/UniversityMoreTab";
// import CampusesCitiesTab from "./tabs/CampusesCitiesTab";
// import PreviousDeparturesTab from "./tabs/PreviousDeparturesTab";
import PreviousDeparturesTab from "./tabs/PreviousDeparturesTab";
import ScholarshipsTab from "./tabs/ScholarshipsTab";
// import MoreTab from "./tabs/MoreTab";
......@@ -104,7 +104,7 @@ class UniversityTemplate extends Component {
</AppBar>
<TabContainer visible={value === 0}> <GeneralInfoTab visible={value === 0} /> </TabContainer>
{/* {<TabContainer visible={value === 1}> <UniversityMoreTab visible={value === 1} /> </TabContainer>} */}
{/* {value === 2 && <TabContainer> <PreviousDeparturesTab /> </TabContainer>} */}
{<TabContainer visible={value === 2}> <PreviousDeparturesTab visible={value === 3} /> </TabContainer>}
{<TabContainer visible={value === 3}> <ScholarshipsTab visible={value === 3} /> </TabContainer>}
{/* {value === 4 && <TabContainer> <CampusesCitiesTab /> </TabContainer>} */}
{/* {value === 5 && <TabContainer> <MoreTab /> </TabContainer>} */}
......
import React from "react";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import PropTypes from "prop-types";
import compose from "recompose/compose";
import withStyles from "@material-ui/core/styles/withStyles";
const styles = theme => ({
table: {
margin: theme.spacing.unit * 10,
flex: "auto",
}
});
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 React.Component {
render() {
const { classes } = this.props;
return (
<>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell>Cours</TableCell>
<TableCell>Crédits</TableCell>
<TableCell>Langue</TableCell>
<TableCell>Intérêt /10</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>
</TableRow>
))}
</TableBody>
</Table>
</>
);
}
}
CourseFeedback.propTypes = {
classes: PropTypes.object.isRequired,
};
export default compose(
withStyles(styles),
)(CourseFeedback);
\ No newline at end of file
import React from "react";
import PropTypes from "prop-types";
import withStyles from "@material-ui/core/styles/withStyles";
import ExpansionPanel from "@material-ui/core/ExpansionPanel";
import ExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
import ExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
import Typography from "@material-ui/core/Typography";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import Divider from "@material-ui/core/Divider";
import Typography from "@material-ui/core/Typography";
import compose from "recompose/compose";
import Paper from "@material-ui/core/Paper";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
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 => ({
root: {
width: "100%",
paper: {
margin: `${theme.spacing.unit}px auto`,
padding: theme.spacing.unit * 2,
},
heading: {
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
paperGrid: {
textAlign: "center",
padding: theme.spacing.unit * 3,
width: "100%",
background: "#767676",
},
gradingDiv: {
margin: theme.spacing.unit,
}
});
function SimpleExpansionPanel(props) {
const { classes } = props;
return (
<div className={classes.root}>
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
<Typography className={classes.heading}><em>En lire d'avantage...</em> </Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Divider />
<Typography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada lacus ex,
sit amet blandit leo lobortis eget.
</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
</div>
);
class TruncatedCommentaryOrMore extends React.Component {
constructor(props) {
super(props);
this.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({
expanded: !this.state.expanded
});
}
getMoreTextDiv() {
if (this.state.expanded) {
return <div> {commentary} </div>;
} else {
return <div>{commentary.slice(0, 100)}...</div>;
}
}
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>;
}
return (
<div>
{expandedDiv}
{button}
</div>
);
}
}
SimpleExpansionPanel.propTypes = {
class PreviousDeparture extends React.Component {
render() {
const {classes, rawModelData} = this.props;
return (
<>
<Chip label="Automne 2018" className={classes.chip} color="primary"/>
<span> </span>
<Chip label={rawModelData.speciality} className={classes.chip} color="secondary"></Chip>
<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>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper className={classes.paperGrid}>
<Typography>Accueil des étudiants étrangers</Typography>
<Typography>9/10</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>
</Paper>
</Grid>
</Grid>
</div>
<Paper className={classes.paper}>
<Typography className={classes.heading}>Avis général : </Typography>
<TruncatedCommentaryOrMore>
commentary={commentary}
</TruncatedCommentaryOrMore>
</Paper>
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>}>