Admin: add and delete a sport (WIP)

This commit is contained in:
Sam 2018-04-30 21:38:09 +02:00
parent b69a55362d
commit e423c355b5
10 changed files with 256 additions and 47 deletions

View File

@ -58,6 +58,45 @@ def get_sport(auth_user_id, sport_id):
return jsonify(response_object), code return jsonify(response_object), code
@activities_blueprint.route('/sports', methods=['POST'])
@authenticate
def post_sport(auth_user_id):
"""Post a sport"""
sport_data = request.get_json()
if not sport_data or sport_data.get('label') is None:
response_object = {
'status': 'error',
'message': 'Invalid payload.'
}
return jsonify(response_object), 400
sports_list = []
try:
new_sport = Sport(label=sport_data.get('label'))
db.session.add(new_sport)
db.session.commit()
sports_list.append({
'id': new_sport.id,
'label': new_sport.label
})
response_object = {
'status': 'created',
'data': {
'sports': sports_list
}
}
code = 201
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
db.session.rollback()
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.'
}
code = 500
return jsonify(response_object), code
@activities_blueprint.route('/sports/<int:sport_id>', methods=['PATCH']) @activities_blueprint.route('/sports/<int:sport_id>', methods=['PATCH'])
@authenticate @authenticate
def update_sport(auth_user_id, sport_id): def update_sport(auth_user_id, sport_id):
@ -106,6 +145,44 @@ def update_sport(auth_user_id, sport_id):
return jsonify(response_object), code return jsonify(response_object), code
@activities_blueprint.route('/sports/<int:sport_id>', methods=['DELETE'])
@authenticate
def delete_sport(auth_user_id, sport_id):
"""Delete a sport"""
sports_list = []
try:
sport = Sport.query.filter_by(id=sport_id).first()
if sport:
db.session.query(Sport).filter_by(id=sport_id).delete()
db.session.commit()
response_object = {
'status': 'no content',
'data': {
'sports': sports_list
}
}
code = 204
print('OK')
print(response_object)
else:
response_object = {
'status': 'not found',
'data': {
'sports': sports_list
}
}
code = 404
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
db.session.rollback()
appLog.error(e)
response_object = {
'status': 'error',
'message': 'Error. Please try again or contact the administrator.'
}
code = 500
return jsonify(response_object), code
@activities_blueprint.route('/activities', methods=['GET']) @activities_blueprint.route('/activities', methods=['GET'])
@authenticate @authenticate
def get_activities(auth_user_id): def get_activities(auth_user_id):

View File

@ -117,6 +117,39 @@ def test_get_a_sport(app):
assert 'cycling' in data['data']['sports'][0]['label'] assert 'cycling' in data['data']['sports'][0]['label']
def test_add_a_sport(app):
add_admin()
client = app.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(dict(
email='admin@example.com',
password='12345678'
)),
content_type='application/json'
)
response = client.post(
'/api/sports',
content_type='application/json',
data=json.dumps(dict(
label='surfing'
)),
headers=dict(
Authorization='Bearer ' + json.loads(
resp_login.data.decode()
)['auth_token']
)
)
data = json.loads(response.data.decode())
assert response.status_code == 201
assert 'created' in data['status']
assert len(data['data']['sports']) == 1
assert 'surfing' in data['data']['sports'][0]['label']
def test_update_a_sport(app): def test_update_a_sport(app):
add_admin() add_admin()
add_sport('cycling') add_sport('cycling')
@ -149,3 +182,28 @@ def test_update_a_sport(app):
assert len(data['data']['sports']) == 1 assert len(data['data']['sports']) == 1
assert 'cycling updated' in data['data']['sports'][0]['label'] assert 'cycling updated' in data['data']['sports'][0]['label']
def test_delete_a_sport(app):
add_admin()
add_sport('cycling')
client = app.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(dict(
email='admin@example.com',
password='12345678'
)),
content_type='application/json'
)
response = client.delete(
'/api/sports/1',
content_type='application/json',
headers=dict(
Authorization='Bearer ' + json.loads(
resp_login.data.decode()
)['auth_token']
)
)
assert response.status_code == 204

View File

@ -1,4 +1,5 @@
import mpwoApi from '../mwpoApi/index' import mpwoApi from '../mwpoApi/index'
import { history } from '../index'
export const setData = (target, data) => ({ export const setData = (target, data) => ({
@ -30,6 +31,24 @@ export function getData(target, id = null) {
} }
} }
export function addData(target, data) {
return function(dispatch) {
if (isNaN(data.id)) {
return dispatch(setError(target, `${target}: Incorrect id`))
}
return mpwoApi
.addData(target, data)
.then(ret => {
if (ret.status === 'created') {
dispatch(setData(target, ret.data))
} else {
dispatch(setError(`${target}: ${ret.status}`))
}
})
.catch(error => dispatch(setError(`${target}: ${error}`)))
}
}
export function updateData(target, data) { export function updateData(target, data) {
return function(dispatch) { return function(dispatch) {
if (isNaN(data.id)) { if (isNaN(data.id)) {
@ -47,3 +66,21 @@ export function updateData(target, data) {
.catch(error => dispatch(setError(`${target}: ${error}`))) .catch(error => dispatch(setError(`${target}: ${error}`)))
} }
} }
export function deleteData(target, id) {
return function(dispatch) {
if (isNaN(id)) {
return dispatch(setError(target, `${target}: Incorrect id`))
}
return mpwoApi
.deleteData(target, id)
.then(ret => {
if (ret.status === 204) {
history.push(`/admin/${target}`)
} else {
dispatch(setError(`${target}: ${ret.status}`))
}
})
.catch(error => dispatch(setError(`${target}: ${error}`)))
}
}

View File

@ -16,17 +16,19 @@ class AdminMenu extends React.Component {
<div className="row"> <div className="row">
<div className="col-md-2" /> <div className="col-md-2" />
<div className="col-md-8 card"> <div className="col-md-8 card">
<ul className="admin-items"> <div className="card-body">
<li> <ul className="admin-items">
<Link <li>
to={{ <Link
pathname: '/admin/sports', to={{
}} pathname: '/admin/sports',
> }}
Sports >
</Link> Sports
</li> </Link>
</ul> </li>
</ul>
</div>
</div> </div>
<div className="col-md-2" /> <div className="col-md-2" />
</div> </div>

View File

@ -1,8 +1,8 @@
import React from 'react' import React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { getData } from '../../actions/index' import { getData } from '../../../actions/index'
import AdminDetail from './generic/AdminDetail' import AdminDetail from '../generic/AdminDetail'
class AdminSports extends React.Component { class AdminSports extends React.Component {
componentDidMount() { componentDidMount() {

View File

@ -1,8 +1,8 @@
import React from 'react' import React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { getData } from '../../actions/index' import { getData } from '../../../actions/index'
import AdminPage from './generic/AdminPage' import AdminPage from '../generic/AdminPage'
class AdminSports extends React.Component { class AdminSports extends React.Component {
componentDidMount() { componentDidMount() {

View File

@ -3,7 +3,7 @@ import { Helmet } from 'react-helmet'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { updateData } from '../../../actions/index' import { deleteData, updateData } from '../../../actions/index'
class AdminDetail extends React.Component { class AdminDetail extends React.Component {
@ -18,6 +18,7 @@ class AdminDetail extends React.Component {
const { const {
message, message,
onDataUpdate, onDataUpdate,
onDataDelete,
results, results,
target, target,
} = this.props } = this.props
@ -92,12 +93,12 @@ class AdminDetail extends React.Component {
<input <input
type="submit" type="submit"
className="btn btn-danger btn-lg btn-block" className="btn btn-danger btn-lg btn-block"
onClick={event => onDataDelete(event, target)}
value="Delete" value="Delete"
/> />
</div> </div>
)} )}
</form> </form>
<Link to={`/admin/${target}`}>Back to the list</Link>
</div> </div>
</div> </div>
<div className="col-md-2" /> <div className="col-md-2" />
@ -115,6 +116,10 @@ export default connect(
message: state.message, message: state.message,
}), }),
dispatch => ({ dispatch => ({
onDataDelete: (e, target) => {
const id = e.target.form.id.value
dispatch(deleteData(target, id))
},
onDataUpdate: (e, target) => { onDataUpdate: (e, target) => {
const data = [].slice const data = [].slice
.call(e.target.form.elements) .call(e.target.form.elements)

View File

@ -28,34 +28,37 @@ export default function AdminPage(props) {
<div className="row"> <div className="row">
<div className="col-md-2" /> <div className="col-md-2" />
<div className="col-md-8 card"> <div className="col-md-8 card">
<table className="table"> <div className="card-body">
<thead> <table className="table">
<tr> <thead>
{tbKeys.map( <tr>
tbKey => <th key={tbKey} scope="col">{tbKey}</th> {tbKeys.map(
)} tbKey => <th key={tbKey} scope="col">{tbKey}</th>
</tr> )}
</thead>
<tbody>
{ results.map((result, idx) => (
<tr key={idx}>
{ Object.keys(result).map(key => {
if (key === 'id') {
return (
<th key={key} scope="row">
<Link to={`/admin/${detailLink}/${result[key]}`}>
{result[key]}
</Link>
</th>
)
}
return <td key={key}>{result[key]}</td>
})
}
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> { results.map((result, idx) => (
<tr key={idx}>
{ Object.keys(result).map(key => {
if (key === 'id') {
return (
<th key={key} scope="row">
<Link to={`/admin/${detailLink}/${result[key]}`}>
{result[key]}
</Link>
</th>
)
}
return <td key={key}>{result[key]}</td>
})
}
</tr>
))}
</tbody>
</table>
<Link to={`/admin/${target}/add`}>Add new item</Link>
</div>
</div> </div>
<div className="col-md-2" /> <div className="col-md-2" />
</div> </div>

View File

@ -3,9 +3,9 @@ import { Helmet } from 'react-helmet'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Redirect, Route, Switch } from 'react-router-dom' import { Redirect, Route, Switch } from 'react-router-dom'
import AdminMenu from './AdminMenu' import AdminMenu from './Sports/AdminMenu'
import AdminSport from './AdminSport' import AdminSport from './Sports/AdminSport'
import AdminSports from './AdminSports' import AdminSports from './Sports/AdminSports'
import AccessDenied from './../Others/AccessDenied' import AccessDenied from './../Others/AccessDenied'
import NotFound from './../Others/NotFound' import NotFound from './../Others/NotFound'
import { isLoggedIn } from '../../utils' import { isLoggedIn } from '../../utils'

View File

@ -15,6 +15,20 @@ export default class MpwoApi {
.catch(error => error) .catch(error => error)
} }
static addData(target, data) {
const request = new Request(`${apiUrl}${target}`, {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.getItem('authToken')}`,
}),
body: JSON.stringify(data)
})
return fetch(request)
.then(response => response.json())
.catch(error => error)
}
static updateData(target, data) { static updateData(target, data) {
const request = new Request(`${apiUrl}${target}/${data.id}`, { const request = new Request(`${apiUrl}${target}/${data.id}`, {
method: 'PATCH', method: 'PATCH',
@ -28,4 +42,17 @@ export default class MpwoApi {
.then(response => response.json()) .then(response => response.json())
.catch(error => error) .catch(error => error)
} }
static deleteData(target, id) {
const request = new Request(`${apiUrl}${target}/${id}`, {
method: 'DELETE',
headers: new Headers({
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.getItem('authToken')}`,
}),
})
return fetch(request)
.then(response => response)
.catch(error => error)
}
} }