Commit d569450d authored by Florent Chehab's avatar Florent Chehab

API generation for frontend and more standardized js processes

parent 34db017b
...@@ -7,6 +7,9 @@ install_backend: ...@@ -7,6 +7,9 @@ install_backend:
generate_backend: generate_backend:
python ./backend/generate/generate_all.py python ./backend/generate/generate_all.py
generate_frontend:
python ./frontend/generate/generate_frontend_files.py
test_backend: generate_backend test_backend: generate_backend
pytest pytest
......
##### #####
# This python file is used to generate js files for redux # This python file is used to generate js files for redux
import os import os
from os.path import join
from django import template from django import template
import re import re
import yaml
############ ############
# Need to do this first so that Django template engine is working # Need to do this first so that Django template engine is working
...@@ -35,21 +37,19 @@ templates = [ ...@@ -35,21 +37,19 @@ templates = [
'combinedReducers' 'combinedReducers'
] ]
API_BASE = "http://127.0.0.1:8000/api/" API_BASE = "/api/"
with open(join(current_dir, '../../backend/generate/api_config.yml'), 'r') as f:
contexts = [ data = f.read()
{ api_config = yaml.load(data)
'name': 'universities',
'api_url': API_BASE + "university/", contexts = []
}, { for api in api_config:
'name': 'countries', name = api['viewset'].split('ViewSet')[0]
'api_url': API_BASE + "country/" name = name[0].lower() + name[1:]
}, { contexts.append({
'name': 'mainCampus', "name": name,
'api_url': API_BASE + "main_campus/", "api_end_point": API_BASE + api["api_end_point"] + '/'
'read_only': True })
}
]
def read_file(file): def read_file(file):
......
...@@ -29,25 +29,25 @@ import { ...@@ -29,25 +29,25 @@ import {
////////////////////////////////// //////////////////////////////////
// generic function definitions // 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 == ""){ if (pk_required && pk == ""){
throw "pk shouldn't be empty when requesting a specific element"; throw "pk shouldn't be empty when requesting a specific element";
} }
return (dispatch) => { return (dispatch) => {
dispatch(_IsLoading(true)); dispatch(_IsLoading(true));
let token = Cookies.get('csrftoken');
fetch(api_url) fetch(api_end_point, {credentials: 'same-origin',headers: {'X-CSRFToken': token}})
.then((response) => { .then((response) => {
if (!response.ok) { if (!response.ok) {
throw Error(response.statusText); throw Error(response.statusText);
} }
dispatch(_IsLoading(false));
return response; return response;
}) })
.then((response) => response.json()) .then((response) => response.json())
.then((obj) => { .then((obj) => {
dispatch(_Invalidated(false)); dispatch(_Invalidated(false));
dispatch(_FetchDataSuccess(obj)); dispatch(_FetchDataSuccess(obj));
dispatch(_IsLoading(false));
}) })
.catch(() => dispatch(_HasError(true))); .catch(() => dispatch(_HasError(true)));
}; };
...@@ -55,7 +55,7 @@ function _FetchData(pk, api_url, _IsLoading, _FetchDataSuccess, _Invalidated, _H ...@@ -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) => { return (dispatch) => {
let method = "POST"; let method = "POST";
let pk = ""; let pk = "";
...@@ -66,7 +66,7 @@ function _ElSaveData(data, api_url, _IsLoading, _FetchDataSuccess, _Invalidated, ...@@ -66,7 +66,7 @@ function _ElSaveData(data, api_url, _IsLoading, _FetchDataSuccess, _Invalidated,
dispatch(_ElIsSaving(true)); dispatch(_ElIsSaving(true));
let token = Cookies.get('csrftoken'); let token = Cookies.get('csrftoken');
fetch(api_url+pk, { fetch(api_end_point+pk, {
method: method, method: method,
credentials: 'same-origin', credentials: 'same-origin',
headers: { headers: {
...@@ -81,13 +81,13 @@ function _ElSaveData(data, api_url, _IsLoading, _FetchDataSuccess, _Invalidated, ...@@ -81,13 +81,13 @@ function _ElSaveData(data, api_url, _IsLoading, _FetchDataSuccess, _Invalidated,
throw Error(response.statusText); throw Error(response.statusText);
} }
dispatch(_ElIsSaving(false));
return response; return response;
}) })
.then((response) => response.json()) .then((response) => response.json())
.then((_El) => { .then((_El) => {
dispatch(_ElInvalidated(false)); dispatch(_ElInvalidated(false));
dispatch(_ElSaveDataSuccess(_El)); dispatch(_ElSaveDataSuccess(_El));
dispatch(_ElIsSaving(false));
}) })
.catch(() => dispatch(_ElHasError(true))); .catch(() => dispatch(_ElHasError(true)));
}; };
...@@ -131,7 +131,7 @@ export function {{obj.name}}FetchDataSuccess({{obj.name}}) { ...@@ -131,7 +131,7 @@ export function {{obj.name}}FetchDataSuccess({{obj.name}}) {
export function {{obj.name}}FetchData() { export function {{obj.name}}FetchData() {
return _FetchData( return _FetchData(
"", "",
"{{obj.api_url}}", "{{obj.api_end_point}}",
{{obj.name}}IsLoading, {{obj.name}}IsLoading,
{{obj.name}}FetchDataSuccess, {{obj.name}}FetchDataSuccess,
{{obj.name}}Invalidated, {{obj.name}}Invalidated,
...@@ -176,7 +176,7 @@ export function {{obj.name}}ElFetchDataSuccess({{obj.name}}El) { ...@@ -176,7 +176,7 @@ export function {{obj.name}}ElFetchDataSuccess({{obj.name}}El) {
export function {{obj.name}}ElFetchData(pk) { export function {{obj.name}}ElFetchData(pk) {
return _FetchData( return _FetchData(
pk, pk,
"{{obj.api_url}}", "{{obj.api_end_point}}",
{{obj.name}}ElIsLoading, {{obj.name}}ElIsLoading,
{{obj.name}}ElFetchDataSuccess, {{obj.name}}ElFetchDataSuccess,
{{obj.name}}ElInvalidated, {{obj.name}}ElInvalidated,
...@@ -207,7 +207,7 @@ export function {{obj.name}}ElSaveDataSuccess({{obj.name}}El) { ...@@ -207,7 +207,7 @@ export function {{obj.name}}ElSaveDataSuccess({{obj.name}}El) {
} }
export function {{obj.name}}ElSaveData(data) { 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 %} {% endif %}
......
...@@ -65,11 +65,11 @@ export function {{obj.name}}ElIsLoading(state = false, action) { ...@@ -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) { switch (action.type) {
case {{obj.NAME}}_FETCH_DATA_SUCCESS: case {{obj.NAME}}_FETCH_DATA_SUCCESS:
return { return {
{{obj.name}}: action.{{obj.name}}, data: action.{{obj.name}},
fetchedAt: action.{{obj.name}}FetchedAt fetchedAt: action.{{obj.name}}FetchedAt
} }
...@@ -100,11 +100,11 @@ export function {{obj.name}}ElInvalidated(state = false, action) { ...@@ -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) { switch (action.type) {
case {{obj.NAME}}_EL_FETCH_DATA_SUCCESS: case {{obj.NAME}}_EL_FETCH_DATA_SUCCESS:
return { return {
{{obj.name}}El: action.{{obj.name}}El, data: action.{{obj.name}}El,
fetchedAt: action.{{obj.name}}ElFetchedAt fetchedAt: action.{{obj.name}}ElFetchedAt
} }
...@@ -128,11 +128,11 @@ export function {{obj.name}}ElIsSaving(state = false, action) { ...@@ -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) { switch (action.type) {
case {{obj.NAME}}_EL_SAVING_DATA_SUCCESS: case {{obj.NAME}}_EL_SAVING_DATA_SUCCESS:
return { return {
{{obj.name}}El: action.{{obj.name}}El, data: action.{{obj.name}}El,
fetchedAt: action.{{obj.name}}ElSavedAt fetchedAt: action.{{obj.name}}ElSavedAt
} }
......
...@@ -17,7 +17,7 @@ import SchoolIcon from '@material-ui/icons/School'; ...@@ -17,7 +17,7 @@ import SchoolIcon from '@material-ui/icons/School';
import { mainListItems, secondaryListItems } from './template/listItems'; import { mainListItems, secondaryListItems } from './template/listItems';
import { connect } from "react-redux"; import { connect } from "react-redux";
import Loading from './other/Loading'; import MyComponent from './MyComponent'
// import route Components here // import route Components here
...@@ -27,7 +27,7 @@ import { ...@@ -27,7 +27,7 @@ import {
} from 'react-router-dom'; } from 'react-router-dom';
import { import {
countriesFetchData, countryFetchData,
} from '../generated/actions'; } from '../generated/actions';
...@@ -97,10 +97,10 @@ const styles = theme => ({ ...@@ -97,10 +97,10 @@ const styles = theme => ({
myPaper: { myPaper: {
padding: 16 padding: 16
}, },
null:{} null: {}
}); });
class App extends React.Component { class App extends MyComponent {
state = { state = {
open: true, open: true,
}; };
...@@ -113,32 +113,8 @@ class App extends React.Component { ...@@ -113,32 +113,8 @@ class App extends React.Component {
this.setState({ open: false }); this.setState({ open: false });
}; };
componentDidMount() { myRender() {
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 />;
}
const { classes } = this.props; const { classes } = this.props;
return ( return (
...@@ -204,7 +180,9 @@ const mapStateToProps = (state) => { ...@@ -204,7 +180,9 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
fetchData: () => dispatch(countriesFetchData()), fetchData: {
countries: () => dispatch(countryFetchData())
}
}; };
}; };
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import Loading from './other/Loading';
class MyComponent extends Component { class MyComponent extends Component {
checkProps(val) { checkProps(val) {
for (let el in this.props) { for (let el in this.props) {
let prop = this.props[el]; let prop = this.props[el];
if (val in prop && prop[val]) { if (prop && val in prop && prop[val]) {
return true; return true;
} }
} }
return false; return false;
} }
checkPropsHasError() { checkPropsHasError() {
return this.checkProps('hasError'); return this.checkProps('hasError');
} }
...@@ -18,6 +20,48 @@ class MyComponent extends Component { ...@@ -18,6 +20,48 @@ class MyComponent extends Component {
checkPropsIsLoading() { checkPropsIsLoading() {
return this.checkProps('isLoading'); 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();
}
} }
......
...@@ -17,8 +17,8 @@ class UnivMarkers extends Component { ...@@ -17,8 +17,8 @@ class UnivMarkers extends Component {
render() { render() {
let universities = this.props.universities; let universities = this.props.universities.fetched.data;
let mainCampus = this.props.mainCampus; let mainCampus = this.props.mainCampus.fetched.data;
let selected_main_campus = []; let selected_main_campus = [];
for (let main_campus_pk in mainCampus) { for (let main_campus_pk in mainCampus) {
...@@ -50,17 +50,8 @@ class UnivMarkers extends Component { ...@@ -50,17 +50,8 @@ class UnivMarkers extends Component {
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return { return {
universities: state.universities.fetched.universities, universities: state.universities,
universitiesFetchedAt: state.universities.fetched.fetchedAt, mainCampus: state.mainCampus
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,
}; };
}; };
......
...@@ -5,7 +5,7 @@ import Loading from '../other/Loading'; ...@@ -5,7 +5,7 @@ import Loading from '../other/Loading';
import { Map, TileLayer, Marker, Popup, LayersControl, FeatureGroup, Circle, LayerGroup } from 'react-leaflet'; import { Map, TileLayer, Marker, Popup, LayersControl, FeatureGroup, Circle, LayerGroup } from 'react-leaflet';
// import MarkerClusterGroup from 'react-leaflet-markercluster'; // import MarkerClusterGroup from 'react-leaflet-markercluster';
import { import {
universitiesFetchData, universityFetchData,
mainCampusFetchData, mainCampusFetchData,
} from '../../generated/actions'; } from '../../generated/actions';
import UnivMarkers from './UnivMakers'; import UnivMarkers from './UnivMakers';
...@@ -20,30 +20,6 @@ class UnivMap extends MyComponent { ...@@ -20,30 +20,6 @@ class UnivMap extends MyComponent {
}; };
} }
componentDidMount() {
let univ_data = this.props.universities.fetched.universities;
if (univ_data.length == 0 || this.props.universities.invalidated) {
this.props.fetchUniversities();
}
let main_campus_data = this.props.mainCampus.fetched.mainCampus;
if (main_campus_data.length == 0 || this.props.mainCampus.invalidated) {
this.props.fetchMainCampus();
}
}
componentDidUpdate() {
// TODO ajouter expire date
if (this.props.universities.invalidated) {
this.props.fetchUniversities();
}
if (this.props.mainCampus.invalidated) {
this.props.fetchMainCampus();
}
}
componentWillUnmount() { componentWillUnmount() {
let l = this.state.leaflet_instance; let l = this.state.leaflet_instance;
...@@ -76,18 +52,11 @@ class UnivMap extends MyComponent { ...@@ -76,18 +52,11 @@ class UnivMap extends MyComponent {
})) }))
} }
render() { myRender() {
if (this.checkPropsHasError()) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.checkPropsIsLoading()) {
return <Loading />;
}
let stamen_name = "Stamen Watercolor"; let stamen_name = "Stamen Watercolor";
let osm_fr_name = "OpenStreetMap France"; let osm_fr_name = "OpenStreetMap France";
let esri_name = "Esri WorldImagery"; let esri_name = "Esri WorldImagery";
return ( 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)}> <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 position="topright">
...@@ -145,8 +114,10 @@ const mapStateToProps = (state) => { ...@@ -145,8 +114,10 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
fetchUniversities: () => dispatch(universitiesFetchData()), fetchData: {
fetchMainCampus: () => dispatch(mainCampusFetchData()), universities: () => dispatch(universityFetchData()),
mainCampus: () => dispatch(mainCampusFetchData())
},
saveMainMap: (pos) => dispatch(saveMainMapPosition(pos)), saveMainMap: (pos) => dispatch(saveMainMapPosition(pos)),
}; };
}; };
......
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { import {
universitiesReducers, universityReducers,
mainCampusReducers, mainCampusReducers,
countriesReducers, countryReducers,
countriesElReducers, countryElReducers,
universitiesElReducers universityElReducers
} from '../generated/combinedReducers'; } from '../generated/combinedReducers';
import { import {
...@@ -17,10 +17,10 @@ const appReducers = combineReducers({ ...@@ -17,10 +17,10 @@ const appReducers = combineReducers({
}) })
const rootReducer = combineReducers({ const rootReducer = combineReducers({
countries: countriesReducers, countries: countryReducers,
countriesEl: countriesElReducers, countryEl: countryElReducers,
universities: universitiesReducers, universities: universityReducers,
universitiesEl: universitiesElReducers, universityEl: universityElReducers,
mainCampus: mainCampusReducers, mainCampus: mainCampusReducers,
app: appReducers app: appReducers
}) })
......
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