Commit 0e4c1aef authored by Florent Chehab's avatar Florent Chehab Committed by Florent Chehab

removed(all TODOs):

Some of them were simply removed, oter were fixed.
From now on, the use of TODO is not permitted in the code.
parent 5530363c
Pipeline #36624 passed with stages
in 4 minutes and 54 seconds
# E501 line too long
ignore = E501,W503
exclude =
from .load_all import load_all
# We firest need to define ASSETS_PATH to prevent cyclic imports
from os import path
__all__ = ["load_all"]
ASSETS_PATH = path.join(path.realpath(__file__), "../assets/") # noqa: E402
from .load_all import load_all # noqa: E402
__all__ = ["load_all", "ASSETS_PATH"]
"Pays","Ville","Nom de l'établissement"
"Allemagne","Hamburg","Technische Universitat Hamburg"
"Allemagne","Ilmenau","Technische Universitat Ilmenau"
"Argentine","Buenos Aires","Universidad Del Salvador"
"Australie","Victoria","Swinburne University Of Technology"
"Autriche","Graz","Technische Universitat Graz"
"Belgique","Gent","Universiteit Gent"
"Brésil","Belo Horizonte","Universidade Federal De Minas Gerais"
"Brésil","Campina Grande","Universidade Federal De Campina Grande"
"Brésil","Campinas-Sao Paulo","Universidade Estadual De Campinas (Unicamp)"
"Brésil","Caxias Do Sul","Universidade Caxias Do Sul"
"Brésil","Curitiba","Pontificia Universidade Catolica Do Parana"
"Brésil","Curitiba","Universidade Federal Do Parana"
"Brésil","Curitiba","Universidade Tecnologica Federal Do Parana"
"Brésil","Florianópolis","Universidade Federal De Santa Catarina"
"Brésil","Itajuba","Universidade Federal De Itajuba"
"Brésil","João Pessoa","Universidade Federal Da Paraiba"
"Brésil","Juiz De Fora","Universidade Federal Juiz De Fora"
"Brésil","Lavras","Universidade Federal De Lavras (Ufla)"
"Brésil","Maringa","Universidad Estadual De Maringa"
"Brésil","Recife","Universidade Federal De Pernambuco"
"Brésil","Rio De Janeiro","Universidade Federal De Rio De Janeiro"
"Brésil","Santa Rita Do Sapucaí","Instituto Nacional De Telecommunicaçoes-Inatel"
"Brésil","Sao Paulo","Escola Politecnica Da Universidade De Sao Paulo"
"Brésil","Uberlandia","Universidade Federal De Uberlandia"
"Bulgarie","Sofia","Technical University Of Sofia"
"Bulgarie","Sofia","University Of Chemical Engineering And Mettalurgy"
"Chili","Iquique","Universidad Arturo Prat"
"Chili","Valparaiso","Pontificia Universidad Catolica De Valparaiso"
"Chili","Valparaiso","Universidad De Valparaiso"
"Chili","Valparaiso","Universidad Tecnica Federico Santa Maria"
"Colombie","Bogota","Escuela Colombiana De Ingenieria"
"Colombie","Bucaramanga","Universidad Autonoma De Bucaramanga"
"Colombie","Medellin","Universidad Eafit"
"Coree Du Sud","Daejeon","Kaist Korea Advanced Institute Of Science And Technology"
"Coree Du Sud","Pohang","Pohang University Of Science And Technology"
"Coree Du Sud","Seoul","Hanyang University"
"Coree Du Sud","Seoul","Kookmin University"
"Coree Du Sud","Seoul","Seoul National University Of Science And Technology Seoultech"
"Equateur","Quito","Escuela Politecnica Nacional De Quito"
"Espagne","Barcelone","Universitat Politecnica De Catalunya - Fib-Upc"
"Espagne","Bellatera","Universidad Autonoma De Barcelona"
"Espagne","Lleida","Universidad De Lleida"
"Espagne","Madrid","Universidad Politecnica De Madrid (Etsiinf)"
"Espagne","Murcia","Universidad De Murcia"
"Espagne","Valencia","Universitat Politecnica De Valencia Etsinf"
"Espagne","Zaragoza","Universidad De Zaragoza"
"Finlande","Oulu","Oulu University Of Applied Sciences"
"Finlande","Tampere","Tampere University Of Technology"
"Islande","Reykjavik","Reykjavik University (Ru)"
"Italie","Arcavacata","Universita Della Calabria"
"Italie","Bologna","Universita Di Bologna"
"Italie","Fisciano","Universita Degli Studi Di Salerno"
"Italie","Genova","Universita Degli Studi Di Genova"
"Italie","Milan","Politecnico Di Milano"
"Italie","Modena","Universita Degli Studi Di Modena E Regio Emilia"
"Italie","Napoli","Universita Degli Studi Di Napoli Federico Ii"
"Japon","Chiba","Chiba Institute Of Technology"
"Japon","Chiba","Chiba University"
"Japon","Sendai","Tohoku University"
"Liban","Beyrouth","Universite Libanaise"
"Liban","Kaslik","Université Saint-Esprit De Kaslik"
"Mexique","Aguascalientes","Universidad Autonoma De Aguascalientes"
"Mexique","Ciudad Del Carmen, Campeche","Universidad Autonoma Del Carmen"
"Mexique","Guanajuato","Universidad De Guanajuato"
"Mexique","Merida Yucatan","Universidad Anahuac Mayab"
"Mexique","Mexico","Instituto Politecnico Nacional"
"Mexique","Mexico","Universidad Iberoamericana"
"Mexique","Monterrey","Itesm - Tec De Monterrrey"
"Mexique","Pueblar, Pue","Upaep"
"Mexique","Zacatepec De Hidalgo","Instituto Tecnologico De Zacatepec"
"Norvege","Trondheim","Norwegian University Of Science And Technology"
"Pays-Bas","Enschede","University Twente"
"Pologne","Cracovie","Agh - University Of Science And Technology"
"Pologne","Cracovie","Politechnika Krakowska"
"Pologne","Lodz","Lodz University Of Technology"
"Portugal","Covilhã","Universidade Da Beira Interior"
"République PopulaireDe Chine","Hong Kong","City University Of Hong Kong"
"République PopulaireDe Chine","Shanghaï","Universite De Technologie Sino-Europeenne De L'Universite De Shanghai (Utseus)"
"Republique Tcheque","Prague","Czech Technical University Of Technology"
"Slovaquie","Kosice","Technical University Of Kosice"
"Suede","Goteborg","Chalmers University Of Technology"
"Suede","Lulea","Lulea University Of Technology"
"Suisse","Lausanne","Ecole Polytechnique Federale De Lausanne"
"Taiwan","Hsinchu","National Chiao Tung University"
"Taiwan","Taipei","National Taiwan University Of Science And Technology"
Script to insert the country data in the database
TODO YURK. Use pandas @florent !!
import csv
import os
import time
import reverse_geocoder as rg
from geopy.geocoders import Nominatim
tmp = os.path.join(os.path.realpath(__file__), "../../assets/destinations.csv")
destinations_path = os.path.abspath(tmp)
tmp = os.path.join(
os.path.realpath(__file__), "../../assets/destinations_extracted.csv"
destinations_extracted_path = os.path.abspath(tmp)
if not os.path.isfile(destinations_path):
raise Exception("Missing file containing country data")
with open(destinations_path, "rt") as input:
with open(destinations_extracted_path, "w") as output:
reader = csv.reader(input)
spamwriter = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC)
geolocator = Nominatim()
failed = []
i = 0
for row in reader:
# handle the header
if i == 0:
header = ["university", "city", "country", "lat", "lon"]
i += 1
query = row[2] # + ', ' + row[1] + ', ' + row[0]
while True:
location = geolocator.geocode(query)
except: # noqa: E722
print("error during query, retrying")
if location is not None:
coord = (location.latitude, location.longitude)
res =, verbose=False)
line = [
......@@ -6,6 +6,7 @@ from .loadTags import LoadTags
from .loadCurrencies import LoadCurrencies
from .loadUniversityEx import LoadUniversityEx
__all__ = [
......@@ -15,5 +16,3 @@ __all__ = [
# TODO in all loading files, use a path to the assets folder define somewhere else.
......@@ -6,7 +6,7 @@ class LoadAdminUser(object):
def __init__(self):
Creating admin user by creating a user and setting is_staff is True
didn't enable access to the admin system for an unkown reason.
didn't enable access to the admin system for an unknown reason.
Need to use create_superuser.
user = User.objects.filter(username="admin")
import os
from base_app.models import User
from os.path import abspath, join
import pandas as pd
from backend_app.load_data import ASSETS_PATH
from import Country
from base_app.models import User
from .loadGeneric import LoadGeneric
......@@ -17,12 +17,8 @@ class LoadCountries(LoadGeneric):
self.admin = admin
def load(self):
tmp = os.path.join(os.path.realpath(__file__), "../../assets/country.csv")
country_file_loc = os.path.abspath(tmp)
tmp = os.path.join(
os.path.realpath(__file__), "../../assets/alpha-conv-table.csv"
conv_alpha_file_loc = os.path.abspath(tmp)
country_file_loc = abspath(join(ASSETS_PATH, "country.csv"))
conv_alpha_file_loc = abspath(join(ASSETS_PATH, "alpha-conv-table.csv"))
country_pd = pd.read_csv(
country_file_loc, sep=",", header=0, dtype=object
import csv
import os
from decimal import Decimal
from os.path import abspath, join
from base_app.models import User
from backend_app.load_data import ASSETS_PATH
from backend_app.models.currency import Currency
from base_app.models import User
from .loadGeneric import LoadGeneric
......@@ -18,8 +18,7 @@ class LoadCurrencies(LoadGeneric):
self.admin = admin
def load(self):
tmp = os.path.join(os.path.realpath(__file__), "../../assets/currencies.csv")
currencies_file_loc = os.path.abspath(tmp)
currencies_file_loc = abspath(join(ASSETS_PATH, "currencies.csv"))
with open(currencies_file_loc) as csvfile:
reader = csv.reader(csvfile, quotechar='"')
import os
from base_app.models import User
from os.path import abspath, join
import pandas as pd
from backend_app.load_data import ASSETS_PATH
from backend_app.models.campus import Campus
from import City
from import Country
from import University
from base_app.models import User
from .loadGeneric import LoadGeneric
......@@ -20,11 +20,7 @@ class LoadUniversities(LoadGeneric):
self.admin = admin
def load(self):
tmp = os.path.join(
os.path.realpath(__file__), "../../assets/destinations_extracted.csv"
destinations_path = os.path.abspath(tmp)
destinations_path = abspath(join(ASSETS_PATH, "destinations_extracted.csv"))
data = pd.read_csv(destinations_path, sep=",", header=0, dtype=object).fillna(
......@@ -57,7 +57,7 @@ class LoadUniversityEx(LoadGeneric):
country_scholarship = CountryScholarship(
title="Swiss European Mobility Programme",
type="Bourse du gouvernement suisse",
short_description="Bourse du gouvernement suisse",
This source diff could not be displayed because it is too large. You can view the blob instead.
# Generated by Django 2.1.7 on 2019-03-16 11:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("backend_app", "0001_new-initial")]
operations = [
......@@ -78,7 +78,7 @@ class MyModelSerializer(MySerializerWithJSON):
Validate `MyModel` fields and enforce certain field at the backend level.
TODO unit test this
Checks that the requested moderation level is not higher than the one of the user.
if "obj_moderation_level" in attrs:
......@@ -99,10 +99,14 @@ class MyModelSerializer(MySerializerWithJSON):
return attrs
def set_model_attr_no_moder(self, user, moderated_and_updated):
def set_model_attrs_for_moderation_and_update(
self, user, moderated_and_updated: bool
TODO: rename ?
Overrides model attributes regarding moderation and update.
The moderated field is set to the request user. The moderated_on field is reset to now.
If there was an updated, the updated_by field and updated_on field are also reset.
now =
self.override_validated_data({"moderated_by": user, "moderated_on": now})
......@@ -182,7 +186,9 @@ class MyModelSerializer(MySerializerWithJSON):
moderated_and_updated = True
if self.instance is None:
self.set_model_attr_no_moder(user, moderated_and_updated)
user, moderated_and_updated
instance = super().save(*args, **kwargs)
......@@ -208,7 +214,9 @@ class MyModelSerializer(MySerializerWithJSON):
except PendingModeration.DoesNotExist:
self.set_model_attr_no_moder(user, moderated_and_updated)
user, moderated_and_updated
instance = super().save(*args, **kwargs)
# Performance optimization to know if has pending moderation
......@@ -84,7 +84,6 @@ class VersionSerializer(MySerializerWithJSON):
def get_data(self, obj):
Serilizer for the data field
TODO test
data = obj.serialized_data
......@@ -99,7 +98,9 @@ class VersionSerializer(MySerializerWithJSON):
new_context["view"].action = "list"
return obj_serializer(tmp.object, context=new_context).data
except (DeserializationError, djangoSerializers.SerializerDoesNotExist):
obj.delete() # The version is not valid regarding the model, we should delete it !
# The version is not valid regarding the model, we should delete it !
# This might be due to an updated model.
# Might result in inconsistent nb of versions but that's fine.
return None
......@@ -111,7 +112,6 @@ class VersionSerializer(MySerializerWithJSON):
class VersionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
Viewset for the versions
TODO better presentation
permission_classes = get_viewset_permissions("VersionViewSet")
......@@ -23,8 +23,7 @@ class Scholarship(BasicModule):
Abstract model for scholarships
# TODO change this, don't use python primitive
type = models.CharField(max_length=200)
short_description = models.CharField(max_length=200)
currency = models.ForeignKey(Currency, null=True, on_delete=models.PROTECT)
other_advantages = models.CharField(default="", blank=True, max_length=5000)
......@@ -65,8 +64,10 @@ class ScholarshipSerializer(BasicModuleSerializer):
raise serializers.ValidationError(
"A currency must be specified when there is a value"
if attrs["amount_max"] is not None:
if attrs["amount_max"] < attrs["amount_min"]:
if (
attrs["amount_max"] is not None
and attrs["amount_max"] < attrs["amount_min"]
raise serializers.ValidationError(
"amount_max should be greater or equal than amount_min"
......@@ -18,8 +18,6 @@ class Specialty(MyModel):
class Meta:
unique_together = ("code", "department")
# TODO add documentation : you should add departement.
class SpecialtySerializer(MyModelSerializer):
class Meta:
......@@ -10,7 +10,7 @@ from backend_app.utils import get_viewset_permissions, get_model_config
class Tag(MyModel):
TODO description
Model to store available "tags" in the app.
model_config = get_model_config("Tag")
......@@ -25,8 +25,6 @@ class PreviousDeparture(MyModel):
is_anonymous = models.BooleanField()
courses = JSONField(null=True) # Store data from ENT
# TODO ask the DSI to return an anonymous ID with data so
# when someone delete the link and relink we can rematch the data
class PreviousDepartureSerializer(MyModelSerializer):
......@@ -39,4 +37,3 @@ class PreviousDepartureViewSet(MyModelViewSet):
permission_classes = get_viewset_permissions("PreviousDepartureViewSet")
queryset = PreviousDeparture.objects.all() # pylint: disable=E1101
serializer_class = PreviousDepartureSerializer
# todo if is_anonymous set user to null
......@@ -20,7 +20,6 @@ class PreviousDepartureFeedback(UserRestrictedModule):
integration_comment = models.CharField(default="", blank=True, max_length=5000)
adequation_grate = models.PositiveIntegerField(validators=[MaxValueValidator(20)])
integration_grade = models.PositiveIntegerField(validators=[MaxValueValidator(20)])
# TODO check on save that courses are coherent with Previous Departure
class PreviousDepartureFeedbackSerializer(UserRestrictedModuleSerializer):
......@@ -39,4 +39,3 @@ class RecommendationViewSet(UserRestrictedModuleViewSet):
permission_classes = get_viewset_permissions("RecommendationViewSet")
queryset = Recommendation.objects.all() # pylint: disable=E1101
serializer_class = RecommendationSerializer
......@@ -10,7 +10,6 @@ from backend_app.models.abstract.my_model import (
class UserRestrictedModule(MyModel):
TODO move to abstract folder ?
# RGPD made easy with CASCADE
......@@ -27,9 +26,7 @@ class UserRestrictedModule(MyModel):
class UserRestrictedModuleSerializer(MyModelSerializer):
owner = serializers.HiddenField(
) # TODO check that this works
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = UserRestrictedModule
......@@ -4,7 +4,8 @@ from backend_app.utils import is_member
class IsDriOrNoPost(permissions.BasePermission):
Permission to disallow POST to viewset if request user is not a
member of the DRI group or a staff.
def has_permission(self, request, view):
......@@ -4,7 +4,8 @@ from backend_app.utils import is_member
class IsDriOrReadOnly(permissions.BasePermission):
Permission to make a viewset readonly unless the request user
is a member of the DRI group or staff.
def has_permission(self, request, view):
......@@ -3,7 +3,7 @@ from rest_framework import permissions
class NoPostIfNotStaff(permissions.BasePermission):
Permission to allow POST request to a viewset only if the user is a member of the staff.
def has_permission(self, request, view):
......@@ -5,7 +5,5 @@ from base_app.models import User
def does_user_have_moderation_rights(user: User) -> bool:
Function to know if a user is staff or member of DRI or member of the moderator group.
TODO unit test
return user.is_staff or is_member("DRI", user) or is_member("Moderators", user)
......@@ -6,7 +6,6 @@ from shared import OBJ_MODERATION_PERMISSIONS
def get_user_level(user: User) -> int:
Returns the user level as int.
TODO unit test
if user.is_staff:
......@@ -4,8 +4,6 @@ from base_app.models import User
def is_member(group_name: str, user: User) -> bool:
Function to know if a user is part of a specific group.
TODO unit test
return group_name in user.cached_groups
# before:
......@@ -16,8 +16,6 @@ else:
urlpatterns = []
urlpatterns += [
# TODO remove this before going to full prod
url(r"^role_change/", views.role_change),
url(r"^user/login$", django_cas_ng.views.LoginView.as_view(), name="cas_ng_login"),
import json
import re
from django.contrib.auth.models import Group
from django.http import HttpResponse
from django.shortcuts import render
from backend_app.utils import is_member
def role_change(request):
user = request.user
moderator_group = Group.objects.get(name="Moderators")
dri_group = Group.objects.get(name="DRI")
path = request.path
if bool("dri", path)):
if bool("moderator", path)):
role = "normal"
if is_member("DRI", user):
role = "DRI"
if is_member("Moderators", user):
role = "moderator"
return HttpResponse(json.dumps({"role_actuel": role}))
def index(request):
......@@ -21,3 +21,4 @@ django-webpack-loader==0.6.0
ipython==7.3.0 # For a better Django shell
flake8-todo==0.7 # Also lint TODO notes in python
......@@ -43,6 +43,13 @@ module.exports = {
"react/no-unescaped-entities": "off", // that one doesn't improve code readability
"react/prop-types": "error",
"react/no-deprecated": "error"
"react/no-deprecated": "error",
"no-warning-comments": [
"terms": ["todo", "fixme", "any other term"],
"location": "anywhere"
......@@ -18,7 +18,6 @@ import { NavLink } from "react-router-dom";
export const mainListItems = (
<NavLink to={"/app/"} style={{ textDecoration: "none" }}>