Commit 1237854b authored by Florent Chehab's avatar Florent Chehab

Merge branch 'cleaning' into 'master'

Cleaning

Closes #48 and #47

See merge request !48
parents 909dae0d 827fa5b8
Pipeline #35341 passed with stages
in 3 minutes and 59 seconds
{ {
"python.pythonPath": "env/bin/python",
"files.exclude": { "files.exclude": {
"**/.git": true, "**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/__pycache__": true, "**/__pycache__": true,
"**/*.pyc": true, "**/*.pyc": true,
"htmlcov": false, "backend/htmlcov": false,
"env": true, "backend/.coverage": true,
".coverage": true, "backend/.pytest_cache": true,
".pytest_cache": true,
}, },
"cSpell.language": "en,fr-FR,fr", "cSpell.language": "en,fr-FR,fr",
"python.linting.flake8Enabled": true, "python.linting.flake8Enabled": true,
"python.formatting.provider": "black", "python.formatting.provider": "black",
"python.linting.pylintEnabled": false, "python.linting.pylintEnabled": false,
"python.linting.enabled": true "python.linting.enabled": true
} }
\ No newline at end of file
from rest_framework import viewsets
class DictModeViewSet(viewsets.ModelViewSet):
"""
ViewSet that renders data as dict with keys corresponding to the model
primary key. Instead of list.
"""
BYPASS_DICT_MODE = False
LIST_SHOULD_BE_DETAIL = False
def list(self, request, *args, **kwargs):
response = super(viewsets.ModelViewSet, self).list( # pylint: disable=E1003
request, *args, **kwargs
) # call the original 'list'
if self.LIST_SHOULD_BE_DETAIL:
if len(response.data) == 0:
response.data = dict()
elif len(response.data) == 1:
response.data = response.data[0]
else:
raise Exception(
"There should be no more than one element here check your queryset !"
)
elif not self.BYPASS_DICT_MODE:
pk_attr_name = self.serializer_class.Meta.model._meta.pk.name
response.data = {d[pk_attr_name]: d for d in response.data}
return response
from .DictModeViewSet import DictModeViewSet
from .mySerializerWithJSON import MySerializerWithJSON from .mySerializerWithJSON import MySerializerWithJSON
__all__ = ["DictModeViewSet", "MySerializerWithJSON"] __all__ = ["MySerializerWithJSON"]
from .myModelSerializer import MyModelSerializer from .myModelSerializer import MyModelSerializer
from backend_app.permissions import DEFAULT_VIEWSET_PERMISSIONS from backend_app.permissions import DEFAULT_VIEWSET_PERMISSIONS
from backend_app.custom import DictModeViewSet from rest_framework import viewsets
class MyModelViewSet(DictModeViewSet): class MyModelViewSet(viewsets.ModelViewSet):
serializer_class = MyModelSerializer serializer_class = MyModelSerializer
permission_classes = DEFAULT_VIEWSET_PERMISSIONS permission_classes = DEFAULT_VIEWSET_PERMISSIONS
LIST_SHOULD_BE_DETAIL = False
def list(self, request, *args, **kwargs):
response = super(viewsets.ModelViewSet, self).list( # pylint: disable=E1003
request, *args, **kwargs
) # call the original 'list'
if self.LIST_SHOULD_BE_DETAIL:
if len(response.data) == 0:
response.data = dict()
elif len(response.data) == 1:
response.data = response.data[0]
else:
raise Exception(
"There should be no more than one element in the queryset, check it !"
)
return response
def get_queryset(self): def get_queryset(self):
""" """
Extended default rest framework behavior Extended default rest framework behavior
......
...@@ -33,8 +33,6 @@ class CampusTaggedItemViewSet(TaggedItemViewSet): ...@@ -33,8 +33,6 @@ class CampusTaggedItemViewSet(TaggedItemViewSet):
queryset = CampusTaggedItem.objects.all() # pylint: disable=E1101 queryset = CampusTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CampusTaggedItemSerializer serializer_class = CampusTaggedItemSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
campus_id = self.kwargs["campus_id"] campus_id = self.kwargs["campus_id"]
return self.my_model_queryset.filter(campus=campus_id).distinct() return self.my_model_queryset.filter(campus=campus_id).distinct()
...@@ -34,8 +34,6 @@ class CityTaggedItemViewSet(TaggedItemViewSet): ...@@ -34,8 +34,6 @@ class CityTaggedItemViewSet(TaggedItemViewSet):
queryset = CityTaggedItem.objects.all() # pylint: disable=E1101 queryset = CityTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CityTaggedItemSerializer serializer_class = CityTaggedItemSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
city_id = self.kwargs["city_id"] city_id = self.kwargs["city_id"]
return self.my_model_queryset.filter(city=city_id).distinct() return self.my_model_queryset.filter(city=city_id).distinct()
...@@ -30,8 +30,6 @@ class CountryDriViewSet(BasicModuleViewSet): ...@@ -30,8 +30,6 @@ class CountryDriViewSet(BasicModuleViewSet):
queryset = CountryDri.objects.all() # pylint: disable=E1101 queryset = CountryDri.objects.all() # pylint: disable=E1101
serializer_class = CountryDriSerializer serializer_class = CountryDriSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
country_id = self.kwargs["country_id"] country_id = self.kwargs["country_id"]
return self.my_model_queryset.filter(countries__pk=country_id).distinct() return self.my_model_queryset.filter(countries__pk=country_id).distinct()
...@@ -28,8 +28,6 @@ class CountryScholarshipViewSet(ScholarshipViewSet): ...@@ -28,8 +28,6 @@ class CountryScholarshipViewSet(ScholarshipViewSet):
queryset = CountryScholarship.objects.all() # pylint: disable=E1101 queryset = CountryScholarship.objects.all() # pylint: disable=E1101
serializer_class = CountryScholarshipSerializer serializer_class = CountryScholarshipSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
country_id = self.kwargs["country_id"] country_id = self.kwargs["country_id"]
return self.my_model_queryset.filter(countries__pk=country_id).distinct() return self.my_model_queryset.filter(countries__pk=country_id).distinct()
...@@ -33,8 +33,6 @@ class CountryTaggedItemViewSet(TaggedItemViewSet): ...@@ -33,8 +33,6 @@ class CountryTaggedItemViewSet(TaggedItemViewSet):
queryset = CountryTaggedItem.objects.all() # pylint: disable=E1101 queryset = CountryTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CountryTaggedItemSerializer serializer_class = CountryTaggedItemSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
country_id = self.kwargs["country_id"] country_id = self.kwargs["country_id"]
return self.my_model_queryset.filter(country=country_id).distinct() return self.my_model_queryset.filter(country=country_id).distinct()
...@@ -30,8 +30,6 @@ class UniversityDriViewSet(BasicModuleViewSet): ...@@ -30,8 +30,6 @@ class UniversityDriViewSet(BasicModuleViewSet):
queryset = UniversityDri.objects.all() # pylint: disable=E1101 queryset = UniversityDri.objects.all() # pylint: disable=E1101
serializer_class = UniversityDriSerializer serializer_class = UniversityDriSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
univ_id = self.kwargs["univ_id"] univ_id = self.kwargs["univ_id"]
return self.my_model_queryset.filter(universities__pk=univ_id).distinct() return self.my_model_queryset.filter(universities__pk=univ_id).distinct()
...@@ -32,8 +32,6 @@ class UniversityScholarshipViewSet(ScholarshipViewSet): ...@@ -32,8 +32,6 @@ class UniversityScholarshipViewSet(ScholarshipViewSet):
queryset = UniversityScholarship.objects.all() # pylint: disable=E1101 queryset = UniversityScholarship.objects.all() # pylint: disable=E1101
serializer_class = UniversityScholarshipSerializer serializer_class = UniversityScholarshipSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
univ_id = self.kwargs["univ_id"] univ_id = self.kwargs["univ_id"]
return self.my_model_queryset.filter(universities__pk=univ_id).distinct() return self.my_model_queryset.filter(universities__pk=univ_id).distinct()
...@@ -33,8 +33,6 @@ class UniversityTaggedItemViewSet(TaggedItemViewSet): ...@@ -33,8 +33,6 @@ class UniversityTaggedItemViewSet(TaggedItemViewSet):
queryset = UniversityTaggedItem.objects.all() # pylint: disable=E1101 queryset = UniversityTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = UniversityTaggedItemSerializer serializer_class = UniversityTaggedItemSerializer
BYPASS_DICT_MODE = True
def extend_queryset(self): def extend_queryset(self):
univ_id = self.kwargs["univ_id"] univ_id = self.kwargs["univ_id"]
return self.my_model_queryset.filter(university__pk=univ_id).distinct() return self.my_model_queryset.filter(university__pk=univ_id).distinct()
...@@ -68,7 +68,7 @@ WEBPACK_LOADER = { ...@@ -68,7 +68,7 @@ WEBPACK_LOADER = {
STATICFILES_DIRS = ( STATICFILES_DIRS = (
os.path.join( os.path.join(
BASE_DIR, "static/frontend_app/bundles/" BASE_DIR, "backend/static/frontend_app/bundles/"
), # We do this so that django's collectstatic copies or our bundles to the STATIC_ROOT or syncs them to whatever storage we use. ), # We do this so that django's collectstatic copies or our bundles to the STATIC_ROOT or syncs them to whatever storage we use.
) )
# End of webpack loader related # End of webpack loader related
......
#OutGoing_REX #OutGoing_REX
Django==2.0.10 Django==2.1.7
psycopg2-binary==2.7.4 psycopg2-binary==2.7.7
django-cas-ng==3.6.0 django-cas-ng==3.6.0
djangorestframework==3.8.2 # Django REST Framework djangorestframework==3.9.1
coreapi==2.3.3 # Automatic API doc generation coreapi==2.3.3 # Automatic API doc generation
django-reversion==2.0.13 django-reversion==3.0.3
django-reversion-compare==0.8.4 django-reversion-compare==0.8.6
pytest-django==3.4.7 pytest-django==3.4.7
pytest-cov==2.6.1 pytest-cov==2.6.1
pytest-xdist==1.23.0 pytest-xdist==1.23.0
black django-debug-toolbar==1.11
flake8 pandas==0.24.1
django-debug-toolbar==1.9.1 pyyaml==3.13
pandas
pyyaml
django-extensions==2.1.5 django-extensions==2.1.5
uwsgi uwsgi==2.0.18
dotmap dotmap==1.3.4
django-webpack-loader==0.6.0 django-webpack-loader==0.6.0
ipython # For a better Django shell
# Direct dev depencies
ipython==7.3.0 # For a better Django shell
black==18.9b0
flake8==3.7.6
...@@ -30,7 +30,7 @@ services: ...@@ -30,7 +30,7 @@ services:
- POSTGRES_USER=postgres - POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
# Run the django developpement server on image startup. # Run the django developpement server on image startup.
command: /bin/sh -c "cd backend && ./manage.py runserver 0.0.0.0:8000" command: /bin/sh -c "cd backend && ./manage.py collectstatic --noinput && ./manage.py runserver 0.0.0.0:8000"
depends_on: depends_on:
# Required that the `database` service is up and running. # Required that the `database` service is up and running.
- database - database
...@@ -58,6 +58,8 @@ services: ...@@ -58,6 +58,8 @@ services:
ports: ports:
# Replicate the node server port. More info in ./frontend/server.js # Replicate the node server port. More info in ./frontend/server.js
- 3000:3000 - 3000:3000
# replicate the view stats port
- "8888:8888"
documentation: documentation:
build: ./documentation build: ./documentation
volumes: volumes:
......
{ {
"presets": [ "presets": [
"env", "@babel/preset-env",
"react", "@babel/preset-react",
"es2015",
"stage-1"
], ],
"plugins": [ "plugins": [
"transform-class-properties", "react-hot-loader/babel",
"react-hot-loader/babel" "@babel/plugin-proposal-class-properties",
] ],
} }
\ No newline at end of file
...@@ -35,8 +35,8 @@ module.exports = { ...@@ -35,8 +35,8 @@ module.exports = {
"error", "error",
"always" "always"
], ],
"react/no-unescaped-entities": "warn", "react/no-unescaped-entities": "off", // that one doesn't improve code readability
"react/prop-types": "warn", "react/prop-types": "error",
"react/no-deprecated": "warn" "react/no-deprecated": "warn"
} }
}; };
node_modules node_modules
webpack-stats.json webpack-stats.json
\ No newline at end of file stats.json
This diff is collapsed.
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
"dev": "node server.js", "dev": "node server.js",
"build": "webpack --config webpack.config.build.js", "build": "webpack --config webpack.config.build.js",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"stats": "webpack --profile --json > frontend/static/frontend/stats.json", "stats": "webpack --config webpack.config.build.js --profile --json > stats.json",
"view-stats": "webpack-bundle-analyzer frontend/static/frontend/stats.json" "view-stats": "webpack-bundle-analyzer --host 0.0.0.0 --port 8888 stats.json"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
...@@ -19,60 +19,59 @@ ...@@ -19,60 +19,59 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@material-ui/core": "^3.1.0", "@babel/core": "^7.0.0",
"@material-ui/icons": "^2.0.3", "@date-io/date-fns": "^1.1.0",
"@material-ui/lab": "^3.0.0-alpha.16", "@date-io/luxon": "^1.1.0",
"babel-preset-stage-0": "^6.24.1", "@material-ui/core": "^3.9.2",
"date-fns": "^2.0.0-alpha.16", "@material-ui/icons": "^3.0.2",
"downshift": "^2.2.0", "@material-ui/lab": "^3.0.0-alpha.30",
"date-fns": "^2.0.0-alpha.25",
"downshift": "^3.2.3",
"fuzzysort": "^1.1.4", "fuzzysort": "^1.1.4",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"leaflet": "^1.3.4", "leaflet": "^1.4.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"material-ui-pickers": "^1.0.0-rc.14", "material-ui-pickers": "^2.2.1",
"react": "^16.5.1", "react": "^16.8.2",
"react-awesome-slider": "^0.5.2", "react-awesome-slider": "^0.5.2",
"react-dom": "^16.5.1", "react-dom": "^16.8.2",
"react-leaflet": "^2.0.1", "react-leaflet": "^2.2.1",
"react-markdown": "^3.6.0", "react-markdown": "^4.0.6",
"react-redux": "^5.0.7", "react-redux": "^6.0.1",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-swipeable-views": "^0.12.17", "react-swipeable-views": "^0.13.1",
"redux": "^4.0.0", "redux": "^4.0.1",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"typeface-roboto": "0.0.54" "typeface-roboto": "0.0.54"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^9.1.5", "@babel/core": "^7.3.3",
"babel-core": "^6.26.3", "@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"autoprefixer": "^9.4.8",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-loader": "^7.1.5", "babel-loader": "^8.0.5",
"babel-plugin-transform-class-properties": "^6.24.1", "css-loader": "^2.1.0",
"babel-polyfill": "^6.26.0", "eslint": "^5.14.1",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
"css-loader": "^1.0.0",
"eslint": "^5.13.0",
"eslint-config-google": "^0.9.1",
"eslint-plugin-react": "^7.12.4", "eslint-plugin-react": "^7.12.4",
"file-loader": "^2.0.0", "file-loader": "^3.0.1",
"js-yaml-loader": "^1.0.1", "js-yaml-loader": "^1.0.1",
"mini-css-extract-plugin": "^0.4.2", "mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.9.3", "node-sass": "^4.11.0",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"prop-types": "^15.6.2", "prop-types": "^15.7.2",
"react-hot-loader": "^4.6.5", "react-hot-loader": "^4.7.1",
"react-lorem-component": "^0.13.0", "react-lorem-component": "^0.13.0",
"react-script": "^2.0.5", "react-script": "^2.0.5",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"webpack": "^4.17.0", "webpack": "^4.29.5",
"webpack-bundle-analyzer": "^3.0.2", "webpack-bundle-analyzer": "^3.0.4",
"webpack-bundle-tracker": "^0.4.2-beta", "webpack-bundle-tracker": "^0.4.2-beta",
"webpack-cli": "^3.1.0", "webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.1.14", "webpack-dev-server": "^3.2.0",
"webpack-merge": "^4.2.1" "webpack-merge": "^4.2.1"
} }
} }
...@@ -71,7 +71,7 @@ export default class CrudReducers { ...@@ -71,7 +71,7 @@ export default class CrudReducers {
return is(state, action, self.types.isReadingAll); return is(state, action, self.types.isReadingAll);
} }
function readAllSucceeded(state = { data: Object(), readAt: 0 }, action) { function readAllSucceeded(state = { data: Array(), readAt: 0 }, action) {
return succeeded(state, action, self.types.readAllSucceeded); return succeeded(state, action, self.types.readAllSucceeded);
} }
...@@ -152,7 +152,7 @@ export default class CrudReducers { ...@@ -152,7 +152,7 @@ export default class CrudReducers {
isInvalidated: isInvalidatedSpecific, isInvalidated: isInvalidatedSpecific,
}; };
// Add only appropriate reducers // Add only appropriate reducers
if (this.readOnly !== true) { if (this.readOnly !== true) {
// Creation // Creation
out.isCreating = isCreating; out.isCreating = isCreating;
...@@ -168,4 +168,4 @@ export default class CrudReducers { ...@@ -168,4 +168,4 @@ export default class CrudReducers {
return combineReducers(out); return combineReducers(out);
} }
} }
\ No newline at end of file
...@@ -186,7 +186,7 @@ App.propTypes = { ...@@ -186,7 +186,7 @@ App.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
}; };
// Already load some of the data even if it's not use here.
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
countries: state.api.countriesAll, countries: state.api.countriesAll,
......
import React, { Component } from "react"; import React, { Component } from "react";
import Loading from "./other/Loading"; import Loading from "./other/Loading";
import PropTypes from "prop-types";
// Stores the name of the reducers/actions that result in read data // Stores the name of the reducers/actions that result in read data
const successActionsWithReads = ["readSucceeded", "createSucceeded", "updateSucceeded"]; const successActionsWithReads = ["readSucceeded", "createSucceeded", "updateSucceeded"];
...@@ -222,36 +224,71 @@ class CustomComponentForAPI extends Component { ...@@ -222,36 +224,71 @@ class CustomComponentForAPI extends Component {
return out; return out;
} }
/**
* Access to the read data from the propNames array
* This should be used instead of getAllReadData to optimize things
*
* @param {array} propNames Array of the prop names you want to read data from
* @returns {object}
* @memberof CustomComponentForAPI
*/
getReadDataFor(propNames) {
let out = Object();
propNames.forEach(propName => out[propName] = this.getReadData(propName));
return out;
}
// utilities functions shared by subclasses // utilities functions shared by subclasses
/**
* Function to retrieve all the information relative to a campus
*
* @param {object} campus instance of Campus
* @returns {object} Campus with replaced university, city and country with the matching instance
* @memberof CustomComponentForAPI
*/
joinCampus(campus) { joinCampus(campus) {
const { universities, countries, cities } = this.getAllReadData(); const { universities, countries, cities } = this.getReadDataFor(["universities", "countries", "cities"]);
let res = Object.assign({}, campus); //copy for safety let res = Object.assign({}, campus); //copy for safety
res.university = universities[campus.university]; res.university = universities.find(univ => univ.id = campus.university);
res.city = cities[campus.city]; res.city = cities.find(city => city.id = campus.city);
res.country = countries[res.city.country]; res.country = countries.find(country => country.id = res.city.country);
return res; return res;
} }
/**
* Funciton to get the city and the country of a university given a university id
*
* @param {number} univId
* @returns {object} Object with city and country instance of the university (main campus)
* @memberof CustomComponentForAPI
*/
getUnivCityAndCountry(univId) { getUnivCityAndCountry(univId) {
const univMainCampus = this.findMainCampus(univId);