Commit d85bfe58 authored by Florent Chehab's avatar Florent Chehab

Merge branch 'cleaning_for_alpha' into 'master'

Cleaning for alpha

See merge request chehabfl/outgoing_rex!39
parents fc561ea9 40532924
Pipeline #27425 failed with stages
in 2 minutes and 39 seconds
......@@ -17,7 +17,7 @@ build_frontend: generate_frontend_files
npm run build
test_backend: generate_backend
pytest
pytest general/ frontend/ backend/
test_backend_server:
pytest -n 4 general/ frontend/ backend/ --cov-report html
......
from .loading_scripts import LoadGroups
from .loading_scripts import LoadAdminUser
from .loading_scripts import LoadCurrencies
from .loading_scripts import LoadCountries
......@@ -9,6 +10,7 @@ import reversion
def load_all():
with reversion.create_revision():
LoadGroups()
admin = LoadAdminUser().get()
LoadCurrencies(admin).load()
LoadCountries(admin).load()
......
from .loadGroups import LoadGroups # noqa: F401
from .loadAdminUser import LoadAdminUser # noqa: F401
from .loadCountries import LoadCountries # noqa: F401
from .loadUniversities import LoadUniversities # noqa: F401
......
from django.contrib.auth.models import Group
class LoadGroups(object):
def __init__(self):
Group.objects.get_or_create(name='Moderators')
Group.objects.get_or_create(name='DRI')
......@@ -3,7 +3,8 @@ from backend.models.university import University
from backend.models.university import UniversityDri
from backend.models.university import UniversityInfo
from backend.models.university import UniversitySemestersDates
from backend.models.university import UniversityScholarship
from backend.models.country import CountryScholarship
from backend.models.country import Country
from backend.models.university import UniversityTaggedItem
from backend.models.currency import Currency
......@@ -21,9 +22,13 @@ class LoadUniversityEx(LoadGeneric):
EPFL = University.objects.get(acronym='EPFL')
CHF = Currency.objects.get(pk='CHF')
ACCOMMODATION_TAG = Tag.objects.get(name='accommodation')
SWITZERLAND = Country.objects.get(pk="CH")
univ_dri_1 = UniversityDri(
comment="Attention les cours de master sont 100% en anglais")
title="Cours en anglais",
importance_level='+',
comment="Les cours de master en computer science sont 100% en anglais",
)
univ_dri_1.save()
univ_dri_1.universities.add(EPFL)
self.add_info_and_save(univ_dri_1, self.admin)
......@@ -37,19 +42,24 @@ class LoadUniversityEx(LoadGeneric):
university=EPFL)
usd.autumn_begin = datetime.strptime("17/09/2018", '%d/%m/%Y')
usd.autumn_end = datetime.strptime("29/01/2019", '%d/%m/%Y')
usd.useful_links = [
{"url": "https://memento.epfl.ch/academic-calendar",
"description": "Site de l'EPFL"}
]
self.add_info_and_save(usd, self.admin)
univ_scholarship_1 = UniversityScholarship(
type="Swiss European...",
country_scholarship = CountryScholarship(
title="Swiss European Mobility Programme",
type="Bourse du gouvernement suisse",
currency=CHF,
frequency='s',
amount_min=2200,
amount_max=2200,
comment="Il n'y a rien à faire pour l'obtenir"
comment="Bourse attribuée de manière automatique."
)
univ_scholarship_1.save()
univ_scholarship_1.universities.add(EPFL)
self.add_info_and_save(univ_scholarship_1, self.admin)
country_scholarship.save()
country_scholarship.countries.add(SWITZERLAND)
self.add_info_and_save(country_scholarship, self.admin)
univ_tag_1 = UniversityTaggedItem(
university=EPFL,
......
......@@ -10,13 +10,19 @@ L'application est « agnostique » au type de base de donnée employée. Alors q
aujourd'hui stockés sous forme de texte car aucune requête n'aurait besoin de les parcourir ; ainsi, seul des champs par défaut de django sont employés.
#### SQlite
Pour utiliser SQlite comme SGBD, il suffit de commenter le bloc qui concerne Postgresql dans le fichier de configuration visible ici : `./general/settings.py` :
Pour utiliser SQlite comme SGBD, il suffit de commenter le bloc qui concerne Postgresql dans le fichier de configuration visible ici : `./general/settings/development.py` :
```python
## POSTGRESQL
# If bellow fails, your need to add DATABASE_URL and ENV to your virutalenv
locals()['DATABASES']['default'] = dj_database_url.config(
conn_max_age=django_heroku.MAX_CONN_AGE, ssl_require=not dev_env)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'outgoing',
'USER': 'outgoing',
'PASSWORD': 'outgoing',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
```
et de décommenter celui-ci :
```python
......@@ -60,15 +66,6 @@ host all all 127.0.0.1/32 trust
Pour un fonctionnement simplifié avec ce projet il faut procéder aux changement suivants.
_Petite commande à faire au début : nous allons avoir besoin de votre nom d'utilisateur._
```bash
whoami
```
C'est par le résultat de cette commande qu'il faudra que vous remplaciez chaque `<login>` dans les autres commandes.
Changer d'utilisateur pour l'utilisateur classique de `postgresql` :
```bash
su - postgres
```
......@@ -84,19 +81,20 @@ Si vous ne voyez pas `postgres=#` c'est qu'il y a un problème, vérifier votre
Créer un utilisateur correspondant à votre utilisateur/login classique dans postgre :
```sql
CREATE USER <login>;
CREATE USER outgoing;
ALTER USER outgoing WITH PASSWORD 'outgoing';
```
Donner lui le droit de créer des bases de données (utile lors des tests en local) :
```sql
ALTER USER <login> CREATEDB;
ALTER USER outgoing CREATEDB;
```
Créer une base de données qui lui sera associée :
```sql
CREATE DATABASE <login>;
GRANT ALL on DATABASE <login> to <login>;
CREATE DATABASE outgoing;
GRANT ALL on DATABASE outgoing to outgoing;
```
Sortez de `psql` (`\q`) et retrouver votre utilisateur normal (`CRTL` + `D`).
......@@ -117,37 +115,19 @@ Afin d'avoir des environnements reproductibles il est **fortement** suggéré d'
(Ce projet est développé sur `python-3.6`)
```bash
python3.6 -m venv <chemin-du-venv>
python3.6 -m venv ./env
```
_(chemin = "chemin" + "nom-du-venv")_ Nous vous suggérons de prendre `<chemin-du-venv> = ./env`.
Cet environnement virtuel doit être **systématiquement** activé lorsque vous travaillez sur ce projet :
```bash
source <chemin-du-venv>/bin/activate
source ./env/bin/activate
```
Les lignes de votre console doivent alors commencez par `(<nom-du-venv>)`.
_Pour le désactiver, faîtes : `deactivate`_.
!> Il est essentiel à ce stade de modifier votre environnement virtuel pour ajouter des variables d'environnement spécifiques, **pour que ce projet fonctionne correctement**.
Éditer le fichier `<chemin-du-venv>/bin/activate` :
- À la fin de la fonction `deactivate ()`, avant le `}` de fin, rajouter les deux linges :
```bash
unset ENV
unset DATABASE_URL
```
- À la fin du fichier, ajouter les lignes :
```bash
export DATABASE_URL=postgres://$(whoami)@127.0.0.1:5432/$(whoami)
export ENV=development
```
Cela permet d'ajouter et d'enlever automatiquement des variables d'environnement **essentielles** au fonctionnement du projet.
### git
Il ne vous reste plus qu'à cloner le projet :
......@@ -190,11 +170,11 @@ _Collectez_ les éléments statistiques :
_Checkez_ le système :
```nash
./manage.py check
make check_backend
```
```bash
./manage.py test
make test_backend
```
Tout ce qu'il y a jusqu'ici **doit** fonctionner. :smile:
......@@ -211,4 +191,4 @@ Si vous n'avez jamais travailler avec Django, un tutoriel [s'impose](https://tut
## Déploiement _externe_
À ce jour le déploiement externe est réalisé sur la plateforme proposée par [Heroku](https://www.heroku.com) à l'adresse : [http://heroku-badge.herokuapp.com/](http://heroku-badge.herokuapp.com/).
\ No newline at end of file
À ce jour, le déploiement est réalisé sur un petit vps [outgoing-utc.floflo.ch](outgoing-utc.floflo.ch)
\ No newline at end of file
import React from 'react';
import MyComponent from '../MyComponent'
import {connect} from "react-redux";
import { connect } from "react-redux";
import { Map, TileLayer, LayersControl, LayerGroup } from 'react-leaflet';
import UnivMarkers from './UnivMakers';
......@@ -8,107 +8,124 @@ import UnivMarkers from './UnivMakers';
import { saveMainMapPosition } from '../../actions/map'
class UnivMap extends MyComponent {
constructor() {
super();
this.state = {
leaflet_instance: null
};
}
componentWillUnmount() {
let l = this.state.leaflet_instance;
if (l) {
let selected_layer = "";
if (this.state.selected_layer) {
selected_layer = this.state.selected_layer;
} else {
selected_layer = this.props.map.selected_layer;
}
let center = [l.getCenter().lat, l.getCenter().lng];
this.props.saveMainMap({
zoom: l.getZoom(),
center,
selected_layer
})
}
}
saveLeafletInstance = (l) => {
this.setState(Object.assign({},this.state, {
leaflet_instance: l,
}))
}
constructor() {
super();
this.state = {
leaflet_instance: null,
height: 800,
};
}
saveSelectedLayer = (e) => {
this.setState(Object.assign({},this.state, {
selected_layer: e.name,
}))
updateDimensions() {
try {
const height = window.innerHeight - document.getElementById('MySuperMap').getBoundingClientRect().y;
this.setState({ height: Math.round(0.9 * height) })
}
myRender() {
let stamen_name = "Stamen Watercolor";
let osm_fr_name = "OpenStreetMap France";
let esri_name = "Esri WorldImagery";
return (
<Map center={this.props.map.center} zoom={this.props.map.zoom} style={{ height: "800px" }} whenReady={(e) => this.saveLeafletInstance(e.target)} onBaselayerchange={(e) => this.saveSelectedLayer(e)}>
<LayersControl position="topright">
<LayersControl.BaseLayer name={osm_fr_name} checked={this.props.map.selected_layer == osm_fr_name}>
<TileLayer
attribution='&copy; Openstreetmap France | &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
maxZoom={20}
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={stamen_name} checked={this.props.map.selected_layer == stamen_name}>
<LayerGroup>
<TileLayer
attribution='Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png"
minZoom={1}
maxZoom={18}
subdomains='abcd'
/>
<TileLayer
attribution='Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://stamen-tiles-{s}.a.ssl.fastly.net/toner-labels/{z}/{x}/{y}.png"
minZoom={1}
subdomains='abcd'
maxZoom={18}
/>
</LayerGroup>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={esri_name} checked={this.props.map.selected_layer == esri_name}>
<TileLayer
attribution="Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
/>
</LayersControl.BaseLayer>
</LayersControl>
<UnivMarkers />
</Map>
);
catch (err) { }
}
componentWillMount() {
this.updateDimensions()
}
myComponentDidMount() {
window.addEventListener("resize", this.updateDimensions.bind(this))
}
componentWillUnmount() {
let l = this.state.leaflet_instance;
if (l) {
let selected_layer = "";
if (this.state.selected_layer) {
selected_layer = this.state.selected_layer;
} else {
selected_layer = this.props.map.selected_layer;
}
let center = [l.getCenter().lat, l.getCenter().lng];
this.props.saveMainMap({
zoom: l.getZoom(),
center,
selected_layer
})
}
window.removeEventListener("resize", this.updateDimensions.bind(this))
}
saveLeafletInstance = (l) => {
this.setState(Object.assign({}, this.state, {
leaflet_instance: l,
}))
}
saveSelectedLayer = (e) => {
this.setState(Object.assign({}, this.state, {
selected_layer: e.name,
}))
}
myRender() {
let stamen_name = "Stamen Watercolor";
let osm_fr_name = "OpenStreetMap France";
let esri_name = "Esri WorldImagery";
return (
<Map id={"MySuperMap"} center={this.props.map.center} zoom={this.props.map.zoom} style={{ height: this.state.height }} whenReady={(e) => this.saveLeafletInstance(e.target)} onBaselayerchange={(e) => this.saveSelectedLayer(e)}>
<LayersControl position="topright">
<LayersControl.BaseLayer name={osm_fr_name} checked={this.props.map.selected_layer == osm_fr_name}>
<TileLayer
attribution='&copy; Openstreetmap France | &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
maxZoom={20}
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={stamen_name} checked={this.props.map.selected_layer == stamen_name}>
<LayerGroup>
<TileLayer
attribution='Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png"
minZoom={1}
maxZoom={18}
subdomains='abcd'
/>
<TileLayer
attribution='Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://stamen-tiles-{s}.a.ssl.fastly.net/toner-labels/{z}/{x}/{y}.png"
minZoom={1}
subdomains='abcd'
maxZoom={18}
/>
</LayerGroup>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name={esri_name} checked={this.props.map.selected_layer == esri_name}>
<TileLayer
attribution="Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
/>
</LayersControl.BaseLayer>
</LayersControl>
<UnivMarkers />
</Map>
);
}
}
const mapStateToProps = (state) => {
return {
map: state.app.mainMap
};
return {
map: state.app.mainMap
};
};
const mapDispatchToProps = (dispatch) => {
return {
saveMainMap: (pos) => dispatch(saveMainMapPosition(pos)),
};
return {
saveMainMap: (pos) => dispatch(saveMainMapPosition(pos)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UnivMap);
......@@ -12,23 +12,50 @@ const styles = theme => ({
});
const source = `
Les objectifs de ce service sont :
- 1
- 2
- 3
# Attention
**Ce service est à l'heure actuelle au stade de version _alpha_ afin de montrer certaines fonctionnalités. Les données seront vraisemblablement remises à zéro lors du passage à la phase _beta_ (quand toutes les fonctionnalités seront en place).**
Si vous trouvez des bugs ou si vous avez des suggestions, merci de les signaler [ici](https://gitlab.utc.fr/chehabfl/outgoing_rex/issues) ou par mail à l'adresse [florent.chehab@etu.utc.fr](mailto:florent.chehab@etu.utc.fr).
Pour rendre plus parlantes certaines fonctionnalités liées à la modération (grandement paramétrable) des informations, durant cette phase _alpha_ vous pouvez rejoindre les différents groupes d'accès tout seul :
- Pour rejoindre le groupe des modérateurs, cliquez [ici](/role_change/moderator/)
- Pour rejoindre le groupe d'accès « DRI », cliquez [ici](/role_change/dri/)
- Pour rejoindre le groupe _classique_, cliquez [ici](/role_change/normal/)
Les données actuellement présente sur la plateforme sont extraites d'un document récapitulant les destinations offertes aux GI il y a quelques semestre de cela ; et elles sont complétés par mes ajouts personnels (voir en particulier pour l'EPFL).
## Fonctionnalités manquantes
Voici les fonctionnalités qui seront rajoutées « prochainement » :
- Possibilité de filtrer les universités sur la page avec la carte ou celle avec la recherche. Les critères seront (ou devraient être) : destinations disponibles à tel semestre, destinations où sont partis des étudiants de telles branches/filières, destinations ouvertes à des étudiants de telles branches/filières, niveau de langue requis, etc.
- Possibilité de faire des listes commentées avec des universités et de les partager (ou non).
- Ajout des autres modules sur les pages des universités.
- Gestion des retours des étudiants sur leurs départs.
- Meilleure compatibilité avec les mobiles.
NB : les images de couverture sur les pages des universités seront aussi différentes ! (actuellement cet élément n'est pas _connecté_ au serveur)
Âge des données de l'UTC :
--------
| **Feature** | **Support** |
| ------ | ----------- |
| Ancien départs | ✔ |
| Départs possibles | ✔ |
| Informatio sur les universités | ✔ |
L'objectif est de mettre en place la phase _beta_ d'ici la prochaine session de candidature pour les départs à l'étranger.
[Rendez-vous sur le GitLab de l'UTC !](https://gitlab.utc.fr)
--------
Les objectifs de ce service sont :
- Regrouper les informations sur les départs à l'étranger réalisés par les étudiants de l'UTC ;
- Les renders accessibles et commensurables.
`;
// Âge des données de l'UTC :
// | **Feature** | **Support** |
// | ------ | ----------- |
// | Ancien départs | ✔ |
// | Départs possibles | ✔ |
// | Informatio sur les universités | ✔ |
class PageHome extends React.Component {
render() {
const { classes } = this.props;
......@@ -36,11 +63,10 @@ class PageHome extends React.Component {
<Paper className={classes.myPaper}>
<Typography variant="display2">
Bienvenue sur <i>Outgoing REX</i>
Bienvenue sur <em>Outgoing REX</em>
</Typography>
<Markdown source={source} />
</Paper>
);
}
......
......@@ -25,11 +25,11 @@ class PageMap extends React.Component {
Exploration Cartographique
</Typography>
</Grid>
<Grid item xs={1}>
{/* <Grid item xs={1}>
<UnivMapReloadButton />
</Grid>
</Grid> */}
</Grid>
<Filter />
{/* <Filter /> */}
<UnivMap />
</Paper>
);
......
......@@ -25,7 +25,7 @@ class PageSearch extends React.Component {
</Typography>
</Grid>
</Grid>
<Filter />
{/* <Filter /> */}
<Search />
</Paper>
);
......
......@@ -29,7 +29,7 @@ export const mainListItems = (
<ListItemIcon>
<SearchIcon />
</ListItemIcon>
<ListItemText primary="Rechercher" />
<ListItemText primary="Recherche" />
</ListItem>
</NavLink>
......
......@@ -59,14 +59,14 @@ class Scholarship extends React.Component {
if (symbol) {
return symbol;
} else {
return ' '+currency;
return ' ' + currency;
}
}
convertAmountToEur(amount) {
const { currencies, currency } = this.props;
const rate = currencies[currency].one_EUR_in_this_currency;
return Math.trunc(amount / rate);
return Math.trunc(amount / rate);
}
getAmounts() {
......@@ -119,7 +119,7 @@ class Scholarship extends React.Component {
<div>
<Typography className={classes.item} variant='headline' > Autre(s) avantage(s) : </Typography>
{
otherAdvantages != '' && otherAdvantages !== null?
otherAdvantages != '' && otherAdvantages !== null ?
<Markdown source={otherAdvantages} />
:
<Typography variant='caption'><em>Aucun autre avantage a été notifié.</em></Typography>
......@@ -129,10 +129,11 @@ class Scholarship extends React.Component {
}
render() {
const { classes, theme } = this.props;
const { comment } = this.props
const { type, comment, classes, theme } = this.props;
return (
<div>
<Typography className={classes.item} variant='headline'>{type}</Typography>
{this.renderFinancialAdvantage()}
<div style={{ height: 2 * theme.spacing.unit }} />
{this.renderOtherAdvantages()}
......
......@@ -4,7 +4,7 @@ from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic.base import RedirectView
import django_cas_ng.views
from . import views
if settings.DEBUG:
import debug_toolbar
......@@ -15,6 +15,8 @@ else:
urlpatterns = []
urlpatterns += [
# TODO remove this before going to full prod
url(r'^role_change/', views.role_change),
url(r'^admin/', admin.site.urls),
url(r'^user/login$',
django_cas_ng.views.login,
......
from django.contrib.auth.models import Group
from backend.utils import is_member
from django.http import HttpResponse
import re
import json
def role_change(request):
user = request.user
moderator_group = Group.objects.get(name='Moderators')
dri_group = Group.objects.get(name='DRI')
moderator_group.user_set.remove(user)
dri_group.user_set.remove(user)
path = request.path
if (bool(re.search('dri', path))):
dri_group.user_set.add(user)
if (bool(re.search('moderator', path))):
moderator_group.user_set.add(user)
role = "normal"
if is_member("DRI", user):
role = 'DRI'
if is_member('Moderators', user):
role = 'moderator'
return HttpResponse(json.dumps({
'role_actuel': role
}))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment