Commit e9e8ecbe authored by Florent Chehab's avatar Florent Chehab

Merge branch 'better_api_integration' into 'master'

Better api integration

See merge request chehabfl/outgoing_rex!24
parents 6ebf4368 86794957
Pipeline #26849 passed with stages
in 2 minutes and 32 seconds
......@@ -7,6 +7,9 @@ install_backend:
generate_backend:
python ./backend/generate/generate_all.py
generate_frontend:
python ./frontend/generate/generate_frontend_files.py
test_backend: generate_backend
pytest
......
# THIS FILE IS USED TO GENERATE OTHER VERY IMPORTANT FILES
# BOTH IN THE FRONTEND AND IN THE BACKEND !
# TAKE CARE WHEN MODYFING IT ;)
# model : the model name (may be null)
# viewset : the viewset name for the api
# api_end_pont : the main part of the url for making request to the api
# This string will also be used for naming variables in JS !!
# So no weird characters there please...
# versionned: boolean to specify wether this model is versionned or not
# api_attr : to specify some attributes that may be captured
# and used in the viewset
# requires_testing: boolean to tell if this viewset is only availble in
# a testing environment.
- model: Country
viewset: CountryViewSet
import_location: location
api_end_point: country
api_end_point: countries
- model: City
viewset: CityViewSet
import_location: location
api_end_point: city
- model: Tag
viewset: TagViewSet
import_location: tag
api_end_point: tag
api_end_point: cities
- model: UniversityTaggedItem
viewset: UniversityTaggedItemViewSet
- model: University
viewset: UniversityViewSet
import_location: university
api_end_point: university_more_tmp
versionned: true
api_end_point: universities
- model: CampusTaggedItem
viewset: CampusTaggedItemViewSet
- model: Campus
viewset: CampusViewSet
import_location: university
api_end_point: campus_more_tmp
api_end_point: campuses
versionned: true
- model: CountryTaggedItem
viewset: CountryTaggedItemViewSet
import_location: location
api_end_point: country_more_tmp
versionned: true
- model: CityTaggedItem
viewset: CityTaggedItemViewSet
import_location: location
api_end_point: city_more_tmp
versionned: true
- model: UserData
viewset: UserDataViewSet
import_location: user
api_end_point: userData
api_name: user-data-detail
- model: Tag
viewset: TagViewSet
import_location: tag
api_end_point: tags
- model: Currency
viewset: CurrencyViewSet
import_location: location
api_end_point: currency
api_end_point: currencies
- model: Department
viewset: DepartmentViewSet
import_location: other_core
api_end_point: other/department
api_end_point: departments
- model: Specialty
viewset: SpecialtyViewSet
import_location: other_core
api_end_point: other/specialty
api_end_point: specialties
- model: Semester
viewset: SemesterViewSet
import_location: other_core
api_end_point: other/semester
api_end_point: semesters
- model: Offer
viewset: OfferViewSet
import_location: other_core
api_end_point: other/offer
api_end_point: offers
- model: CountryTaggedItem
viewset: CountryTaggedItemViewSet
import_location: location
api_end_point: countriesTaggedItems
versionned: true
- model: CountryScholarship
viewset: CountryScholarshipViewSet
import_location: location
api_end_point: country_more/scholarship
api_end_point: countriesScholarships
versionned: true
- model: CountryDri
viewset: CountryDriViewSet
import_location: location
api_end_point: country_more/dri
api_end_point: countriesDri
versionned: true
- model: University
viewset: UniversityViewSet
import_location: university
api_end_point: university
- model: Campus
viewset: CampusViewSet
import_location: university
api_end_point: campus
- model: CityTaggedItem
viewset: CityTaggedItemViewSet
import_location: location
api_end_point: citiesTaggedItems
versionned: true
- model: null
viewset: MainCampusViewSet
- model: UniversityTaggedItem
viewset: UniversityTaggedItemViewSet
import_location: university
api_end_point: main_campus
read_only: true
api_end_point: universitiesTaggedItems
versionned: true
- model: UniversityScholarship
viewset: UniversityScholarshipViewSet
import_location: university
api_end_point: university_more/scholarship
api_end_point: universitiesScholarships
versionned: true
- model: UniversityInfo
viewset: UniversityInfoViewSet
import_location: university
api_end_point: university_more/info
api_end_point: universitiesInfo
versionned: true
- model: UniversitySemestersDates
viewset: UniversitySemestersDatesViewSet
import_location: university
api_end_point: university_more/semesters_dates
api_end_point: universitiesSemestersDates
versionned: true
- model: UniversityDri
viewset: UniversityDriViewSet
import_location: university
api_end_point: university_more/dri
api_end_point: universitiesDri
versionned: true
- model: null
viewset: UniversityModulesViewSet
import_location: university
api_end_point: university_more/all
api_end_point: universitiesAll
api_attr: (?P<univ_id>[0-9]+)
api_name: university_details
- model: CampusTaggedItem
viewset: CampusTaggedItemViewSet
import_location: university
api_end_point: campusesTaggedItems
versionned: true
- model: null
viewset: MainCampusViewSet
import_location: university
api_end_point: mainCampuses
read_only: true
- model: Recommendation
viewset: RecommendationViewSet
import_location: user
api_end_point: user/recommendation
api_end_point: userRecommendations
- model: RecommendationList
viewset: RecommendationListViewSet
import_location: user
api_end_point: user/recommendations_list
api_end_point: userRecommendationLists
- model: PreviousDeparture
viewset: PreviousDepartureViewSet
import_location: user
api_end_point: university_more/previous_departure
api_end_point: universitiesPreviousDepartures
- model: PreviousDepartureFeedback
viewset: PreviousDepartureFeedbackViewSet
import_location: user
api_end_point: university_more/previous_departure_feedback
api_end_point: universitiesPreviousDepartureFeedback
- model: UserData
viewset: UserDataViewSet
import_location: user
api_end_point: user/data
api_name: user-data-detail
- model: PendingModeration
viewset: PendingModerationViewSet
import_location: my_model
api_end_point: pending_moderation
api_end_point: pendingModeration
- model: Version
viewset: VersionViewSet
import_location: my_model
api_end_point: version
api_end_point: versions
api_attr: (?P<content_type_id>[0-9]+)/(?P<object_pk>[0-9A-Za-z]+)
api_name: versions-list
api_name: versionsList
ignore_in_admin: true
- model: ForTestingModeration
......
#####
# This python file is used to generate js files for redux
import os
from os.path import join
from django import template
import re
import yaml
############
# Need to do this first so that Django template engine is working
......@@ -35,21 +37,21 @@ templates = [
'combinedReducers'
]
API_BASE = "http://127.0.0.1:8000/api/"
contexts = [
{
'name': 'universities',
'api_url': API_BASE + "university/",
}, {
'name': 'countries',
'api_url': API_BASE + "country/"
}, {
'name': 'mainCampus',
'api_url': API_BASE + "main_campus/",
'read_only': True
}
]
API_BASE = "/api/"
with open(join(current_dir, '../../backend/generate/api_config.yml'), 'r') as f:
data = f.read()
api_config = yaml.load(data)
contexts = []
for api in api_config:
if "requires_testing" in api and api["requires_testing"]:
continue
name = api['viewset'].split('ViewSet')[0]
name = name[0].lower() + name[1:]
contexts.append({
"name": api["api_end_point"],
"api_end_point": API_BASE + api["api_end_point"] + '/',
})
def read_file(file):
......
......@@ -29,25 +29,25 @@ import {
//////////////////////////////////
// generic function definitions
function _FetchData(pk, api_url, _IsLoading, _FetchDataSuccess, _Invalidated, _HasError, pk_required=false) {
function _FetchData(pk, api_end_point, _IsLoading, _FetchDataSuccess, _Invalidated, _HasError, pk_required=false) {
if (pk_required && pk == ""){
throw "pk shouldn't be empty when requesting a specific element";
}
return (dispatch) => {
dispatch(_IsLoading(true));
fetch(api_url)
let token = Cookies.get('csrftoken');
fetch(api_end_point, {credentials: 'same-origin',headers: {'X-CSRFToken': token}})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(_IsLoading(false));
return response;
})
.then((response) => response.json())
.then((obj) => {
dispatch(_Invalidated(false));
dispatch(_FetchDataSuccess(obj));
dispatch(_IsLoading(false));
})
.catch(() => dispatch(_HasError(true)));
};
......@@ -55,7 +55,7 @@ function _FetchData(pk, api_url, _IsLoading, _FetchDataSuccess, _Invalidated, _H
function _ElSaveData(data, api_url, _IsLoading, _FetchDataSuccess, _Invalidated, _HasError) {
function _ElSaveData(data, api_end_point, _IsLoading, _FetchDataSuccess, _Invalidated, _HasError) {
return (dispatch) => {
let method = "POST";
let pk = "";
......@@ -66,7 +66,7 @@ function _ElSaveData(data, api_url, _IsLoading, _FetchDataSuccess, _Invalidated,
dispatch(_ElIsSaving(true));
let token = Cookies.get('csrftoken');
fetch(api_url+pk, {
fetch(api_end_point+pk, {
method: method,
credentials: 'same-origin',
headers: {
......@@ -81,13 +81,13 @@ function _ElSaveData(data, api_url, _IsLoading, _FetchDataSuccess, _Invalidated,
throw Error(response.statusText);
}
dispatch(_ElIsSaving(false));
return response;
})
.then((response) => response.json())
.then((_El) => {
dispatch(_ElInvalidated(false));
dispatch(_ElSaveDataSuccess(_El));
dispatch(_ElIsSaving(false));
})
.catch(() => dispatch(_ElHasError(true)));
};
......@@ -131,7 +131,7 @@ export function {{obj.name}}FetchDataSuccess({{obj.name}}) {
export function {{obj.name}}FetchData() {
return _FetchData(
"",
"{{obj.api_url}}",
"{{obj.api_end_point}}",
{{obj.name}}IsLoading,
{{obj.name}}FetchDataSuccess,
{{obj.name}}Invalidated,
......@@ -176,7 +176,7 @@ export function {{obj.name}}ElFetchDataSuccess({{obj.name}}El) {
export function {{obj.name}}ElFetchData(pk) {
return _FetchData(
pk,
"{{obj.api_url}}",
"{{obj.api_end_point}}",
{{obj.name}}ElIsLoading,
{{obj.name}}ElFetchDataSuccess,
{{obj.name}}ElInvalidated,
......@@ -207,7 +207,7 @@ export function {{obj.name}}ElSaveDataSuccess({{obj.name}}El) {
}
export function {{obj.name}}ElSaveData(data) {
return _ElSaveData(data, "{{obj.api_url}}", {{obj.name}}ElIsLoading, {{obj.name}}ElFetchDataSuccess, {{obj.name}}ElInvalidated, {{obj.name}}ElHasError)
return _ElSaveData(data, "{{obj.api_end_point}}", {{obj.name}}ElIsLoading, {{obj.name}}ElFetchDataSuccess, {{obj.name}}ElInvalidated, {{obj.name}}ElHasError)
}
{% endif %}
......
......@@ -65,11 +65,11 @@ export function {{obj.name}}ElIsLoading(state = false, action) {
}
}
export function {{obj.name}}Fetched(state = { {{obj.name}}: [], {{obj.name}}FetchedAt: null }, action) {
export function {{obj.name}}Fetched(state = { data: Object(), fetchedAt: null }, action) {
switch (action.type) {
case {{obj.NAME}}_FETCH_DATA_SUCCESS:
return {
{{obj.name}}: action.{{obj.name}},
data: action.{{obj.name}},
fetchedAt: action.{{obj.name}}FetchedAt
}
......@@ -100,11 +100,11 @@ export function {{obj.name}}ElInvalidated(state = false, action) {
}
}
export function {{obj.name}}ElFetched(state = { {{obj.name}}El: [], {{obj.name}}ElFetchedAt: null }, action) {
export function {{obj.name}}ElFetched(state = { data: Object(), fetchedAt: null }, action) {
switch (action.type) {
case {{obj.NAME}}_EL_FETCH_DATA_SUCCESS:
return {
{{obj.name}}El: action.{{obj.name}}El,
data: action.{{obj.name}}El,
fetchedAt: action.{{obj.name}}ElFetchedAt
}
......@@ -128,11 +128,11 @@ export function {{obj.name}}ElIsSaving(state = false, action) {
}
}
export function {{obj.name}}ElSaved(state = { {{obj.name}}El: [], {{obj.name}}ElSavedAt: null }, action) {
export function {{obj.name}}ElSaved(state = { data: Object(), savedAt: null }, action) {
switch (action.type) {
case {{obj.NAME}}_EL_SAVING_DATA_SUCCESS:
return {
{{obj.name}}El: action.{{obj.name}}El,
data: action.{{obj.name}}El,
fetchedAt: action.{{obj.name}}ElSavedAt
}
......
......@@ -17,7 +17,7 @@ import SchoolIcon from '@material-ui/icons/School';
import { mainListItems, secondaryListItems } from './template/listItems';
import { connect } from "react-redux";
import Loading from './other/Loading';
import MyComponent from './MyComponent'
// import route Components here
......@@ -97,10 +97,10 @@ const styles = theme => ({
myPaper: {
padding: 16
},
null:{}
null: {}
});
class App extends React.Component {
class App extends MyComponent {
state = {
open: true,
};
......@@ -113,32 +113,8 @@ class App extends React.Component {
this.setState({ open: false });
};
componentDidMount() {
if (this.props.countries.fetched.countries.length == 0 || this.props.invalidated) {
this.props.fetchData();
}
}
componentDidUpdate() {
// TODO ajouter expire date
if (this.props.countries.invalidated) {
this.props.countries.fetchData();
}
}
componentWillUnmount() {
}
render() {
if (this.props.countries.hasError) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.countries.isLoading || this.props.countries.invalidated) {
return <Loading />;
}
myRender() {
const { classes } = this.props;
return (
......@@ -204,7 +180,9 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch(countriesFetchData()),
fetchData: {
countries: () => dispatch(countriesFetchData())
}
};
};
......
import React, { Component } from 'react';
import Loading from './other/Loading';
class MyComponent extends Component {
checkProps(val) {
for (let el in this.props) {
let prop = this.props[el];
if (val in prop && prop[val]) {
if (prop && val in prop && prop[val]) {
return true;
}
}
return false;
}
checkPropsHasError() {
return this.checkProps('hasError');
}
......@@ -18,6 +20,48 @@ class MyComponent extends Component {
checkPropsIsLoading() {
return this.checkProps('isLoading');
}
checkPropsInvalidated() {
return this.checkProps('invalidated');
}
loadPropsIfNeeded(){
for (let prop_key in this.props) {
let prop = this.props[prop_key];
if (prop && 'fetched' in prop){
if ( (!prop.fetched.fetchedAt) || prop.invalidated){
if (!prop.isLoading){
this.props.fetchData[prop_key]();
}
}
}
}
}
componentDidMount() {
this.loadPropsIfNeeded();
this.myComponentDidMount();
}
myComponentDidMount(){};
componentDidUpdate() {
// TODO ajouter expire date
this.loadPropsIfNeeded();
this.myComponentDidMount();
}
myComponentDidMount(){};
render() {
if (this.checkPropsHasError()) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.checkPropsIsLoading() || this.checkPropsInvalidated()) {
return <Loading />;
}
return this.myRender();
}
}
......
......@@ -7,8 +7,8 @@ import { Map, TileLayer, Marker, Popup, LayersControl, FeatureGroup, Circle, Lay
import {
universitiesFetchData,
universitiesInvalidated,
mainCampusFetchData,
mainCampusInvalidated
mainCampusesFetchData,
mainCampusesInvalidated
} from '../../generated/actions';
......@@ -17,12 +17,12 @@ class UnivMarkers extends Component {
render() {
let universities = this.props.universities;
let mainCampus = this.props.mainCampus;
let universities = this.props.universities.fetched.data;
let mainCampuses = this.props.mainCampuses.fetched.data;
let selected_main_campus = [];
for (let main_campus_pk in mainCampus) {
let campus = mainCampus[main_campus_pk]
for (let main_campus_pk in mainCampuses) {
let campus = mainCampuses[main_campus_pk]
let univ = universities[campus.university]
if (univ && campus) {
selected_main_campus.push({
......@@ -50,17 +50,8 @@ class UnivMarkers extends Component {
const mapStateToProps = (state) => {
return {
universities: state.universities.fetched.universities,
universitiesFetchedAt: state.universities.fetched.fetchedAt,
universitiesHasError: state.universities.hasError,
universitiesIsLoading: state.universities.isLoading,
universitiesInvalidated: state.universities.invalidated,
mainCampus: state.mainCampus.fetched.mainCampus,
mainCampusFetchedAt: state.mainCampus.fetched.fetchedAt,
mainCampusHasError: state.mainCampus.hasError,
mainCampusIsLoading: state.mainCampus.isLoading,
mainCampusInvalidated: state.mainCampus.invalidated,
universities: state.universities,
mainCampuses: state.mainCampuses
};
};
......
import React from 'react';
import MyComponent from '../MyComponent'
import { connect } from "react-redux";
import Loading from '../other/Loading';
import { Map, TileLayer, Marker, Popup, LayersControl, FeatureGroup, Circle, LayerGroup } from 'react-leaflet';
// import MarkerClusterGroup from 'react-leaflet-markercluster';
import { Map, TileLayer, LayersControl, LayerGroup } from 'react-leaflet';
import {
universitiesFetchData,
mainCampusFetchData,
mainCampusesFetchData,
} from '../../generated/actions';
import UnivMarkers from './UnivMakers';
......@@ -20,30 +19,6 @@ class UnivMap extends MyComponent {
};