API & Client: add an activity w/o gpx

This commit is contained in:
Sam 2018-05-08 18:20:41 +02:00
parent 23aa501785
commit d6bb413fb6
9 changed files with 210 additions and 6 deletions

View File

@ -207,7 +207,7 @@ def post_activity_no_gpx(auth_user_id):
user_id=auth_user_id, user_id=auth_user_id,
sport_id=activity_data.get('sport_id'), sport_id=activity_data.get('sport_id'),
activity_date=datetime.strptime( activity_date=datetime.strptime(
activity_data.get('activity_date'), '%d/%m/%Y'), activity_data.get('activity_date'), '%Y-%m-%d'),
duration=timedelta(seconds=activity_data.get('duration')) duration=timedelta(seconds=activity_data.get('duration'))
) )
new_activity.moving = new_activity.duration new_activity.moving = new_activity.duration

View File

@ -76,5 +76,6 @@ class Activity(db.Model):
"descent": float(self.descent) if self.descent else None, "descent": float(self.descent) if self.descent else None,
"ascent": float(self.ascent) if self.ascent else None, "ascent": float(self.ascent) if self.ascent else None,
"max_speed": float(self.max_speed) if self.max_speed else None, "max_speed": float(self.max_speed) if self.max_speed else None,
"ave_speed": float(self.ave_speed) if self.ave_speed else None "ave_speed": float(self.ave_speed) if self.ave_speed else None,
"with_gpx": self.gpx is not None
} }

View File

@ -55,12 +55,14 @@ def test_get_all_activities_for_authenticated_user(app):
assert 1 == data['data']['activities'][0]['user_id'] assert 1 == data['data']['activities'][0]['user_id']
assert 2 == data['data']['activities'][0]['sport_id'] assert 2 == data['data']['activities'][0]['sport_id']
assert '0:17:04' == data['data']['activities'][0]['duration'] assert '0:17:04' == data['data']['activities'][0]['duration']
assert data['data']['activities'][0]['with_gpx'] is False
assert 'creation_date' in data['data']['activities'][1] assert 'creation_date' in data['data']['activities'][1]
assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
assert 1 == data['data']['activities'][1]['user_id'] assert 1 == data['data']['activities'][1]['user_id']
assert 1 == data['data']['activities'][1]['sport_id'] assert 1 == data['data']['activities'][1]['sport_id']
assert '1:40:00' == data['data']['activities'][1]['duration'] assert '1:40:00' == data['data']['activities'][1]['duration']
assert data['data']['activities'][0]['with_gpx'] is False
def test_get_activities_for_authenticated_user_no_activity(app): def test_get_activities_for_authenticated_user_no_activity(app):
@ -133,6 +135,22 @@ def test_add_an_activity_gpx(app):
assert response.status_code == 201 assert response.status_code == 201
assert 'created' in data['status'] assert 'created' in data['status']
assert len(data['data']['activities']) == 1
assert 'creation_date' in data['data']['activities'][0]
assert 'Tue, 13 Mar 2018 12:44:45 GMT' == data['data']['activities'][0]['activity_date'] # noqa
assert 1 == data['data']['activities'][0]['user_id']
assert 1 == data['data']['activities'][0]['sport_id']
assert '0:04:10' == data['data']['activities'][0]['duration']
assert data['data']['activities'][0]['ascent'] == 0.4
assert data['data']['activities'][0]['ave_speed'] == 4.6
assert data['data']['activities'][0]['descent'] == 23.4
assert data['data']['activities'][0]['distance'] == 0.32
assert data['data']['activities'][0]['max_alt'] == 998.0
assert data['data']['activities'][0]['max_speed'] == 5.09
assert data['data']['activities'][0]['min_alt'] == 975.0
assert data['data']['activities'][0]['moving'] == '0:04:10'
assert data['data']['activities'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is True
def test_add_an_activity_gpx_invalid_file(app): def test_add_an_activity_gpx_invalid_file(app):
@ -252,7 +270,7 @@ def test_add_an_activity_no_gpx(app):
data=json.dumps(dict( data=json.dumps(dict(
sport_id=1, sport_id=1,
duration=3600, duration=3600,
activity_date='15/05/2018', activity_date='2018-05-15',
distance=10 distance=10
)), )),
headers=dict( headers=dict(
@ -280,6 +298,7 @@ def test_add_an_activity_no_gpx(app):
assert data['data']['activities'][0]['min_alt'] is None assert data['data']['activities'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] == '1:00:00' assert data['data']['activities'][0]['moving'] == '1:00:00'
assert data['data']['activities'][0]['pauses'] is None assert data['data']['activities'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False
def test_add_an_activity_no_gpx_invalid_payload(app): def test_add_an_activity_no_gpx_invalid_payload(app):
@ -396,6 +415,7 @@ def test_get_an_activity_without_gpx(app):
assert data['data']['activities'][0]['min_alt'] is None assert data['data']['activities'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] is None assert data['data']['activities'][0]['moving'] is None
assert data['data']['activities'][0]['pauses'] is None assert data['data']['activities'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False
def test_get_an_activity_with_gpx(app): def test_get_an_activity_with_gpx(app):
@ -452,3 +472,4 @@ def test_get_an_activity_with_gpx(app):
assert data['data']['activities'][0]['min_alt'] == 975.0 assert data['data']['activities'][0]['min_alt'] == 975.0
assert data['data']['activities'][0]['moving'] == '0:04:10' assert data['data']['activities'][0]['moving'] == '0:04:10'
assert data['data']['activities'][0]['pauses'] is None assert data['data']['activities'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is True

View File

@ -19,6 +19,18 @@ export const addActivity = form => dispatch => mpwoApi
.catch(error => dispatch(setError(`activities: ${error}`))) .catch(error => dispatch(setError(`activities: ${error}`)))
export const addActivityWithoutGpx = form => dispatch => mpwoApi
.addActivityWithoutGpx(form)
.then(ret => {
if (ret.status === 'created') {
history.push('/')
} else {
dispatch(setError(`activities: ${ret.message}`))
}
})
.catch(error => dispatch(setError(`activities: ${error}`)))
export const getActivityGpx = activityId => dispatch => { export const getActivityGpx = activityId => dispatch => {
if (activityId) { if (activityId) {
return mpwoApi return mpwoApi

View File

@ -3,16 +3,33 @@ import { Helmet } from 'react-helmet'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import FormWithGpx from './ActivityForms/FormWithGpx' import FormWithGpx from './ActivityForms/FormWithGpx'
import FormWithoutGpx from './ActivityForms/FormWithoutGpx'
import { getData } from '../../actions/index' import { getData } from '../../actions/index'
class AddActivity extends React.Component { class AddActivity extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
withGpx: true,
}
}
componentDidMount() { componentDidMount() {
this.props.loadSports() this.props.loadSports()
} }
handleRadioChange (changeEvent) {
this.setState({
withGpx:
changeEvent.target.name === 'withGpx'
? changeEvent.target.value : !changeEvent.target.value
})
}
render() { render() {
const { message, sports } = this.props const { message, sports } = this.props
const { withGpx } = this.state
return ( return (
<div> <div>
<Helmet> <Helmet>
@ -32,7 +49,37 @@ class AddActivity extends React.Component {
Add a sport Add a sport
</h2> </h2>
<div className="card-body"> <div className="card-body">
<FormWithGpx sports={sports} /> <form>
<div className="form-group row">
<div className="col">
<label className="radioLabel">
<input
type="radio"
name="withGpx"
checked={withGpx}
onChange={event => this.handleRadioChange(event)}
/>
with gpx file
</label>
</div>
<div className="col">
<label className="radioLabel">
<input
type="radio"
name="withoutGpx"
checked={!withGpx}
onChange={event => this.handleRadioChange(event)}
/>
without gpx file
</label>
</div>
</div>
</form>
{withGpx ? (
<FormWithGpx sports={sports} />
) : (
<FormWithoutGpx sports={sports} />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -43,7 +43,8 @@ class ActivityDisplay extends React.Component {
<p> <p>
<i className="fa fa-clock-o" aria-hidden="true" />{' '} <i className="fa fa-clock-o" aria-hidden="true" />{' '}
Duration: {activity.duration} {' '} Duration: {activity.duration} {' '}
{activity.pauses !== '0:00:00' && ( {activity.pauses !== '0:00:00' &&
activity.pauses !== null && (
`(pauses: ${activity.pauses})` `(pauses: ${activity.pauses})`
)} )}
</p> </p>
@ -56,14 +57,18 @@ class ActivityDisplay extends React.Component {
Average speed: {activity.ave_speed} km/h -{' '} Average speed: {activity.ave_speed} km/h -{' '}
Max speed : {activity.max_speed} km/h Max speed : {activity.max_speed} km/h
</p> </p>
{activity.min_alt && activity.max_alt && (
<p><i className="fi-mountains" />{' '} <p><i className="fi-mountains" />{' '}
Min altitude: {activity.min_alt}m -{' '} Min altitude: {activity.min_alt}m -{' '}
Max altitude: {activity.max_alt}m Max altitude: {activity.max_alt}m
</p> </p>
)}
{activity.ascent && activity.descent && (
<p><i className="fa fa-location-arrow" />{' '} <p><i className="fa fa-location-arrow" />{' '}
Ascent: {activity.ascent}m -{' '} Ascent: {activity.ascent}m -{' '}
Descent: {activity.descent}m Descent: {activity.descent}m
</p> </p>
)}
</div> </div>
</div> </div>
</div> </div>
@ -73,7 +78,11 @@ class ActivityDisplay extends React.Component {
Map Map
</div> </div>
<div className="card-body"> <div className="card-body">
<ActivityMap activity={activity} /> {activity.with_gpx ? (
<ActivityMap activity={activity} />
) : (
'No map'
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,96 @@
import React from 'react'
import { connect } from 'react-redux'
import { addActivityWithoutGpx } from '../../../actions/activities'
import { history } from '../../../index'
function FormWithoutGpx (props) {
const { onAddSport, sports } = props
return (
<form
onSubmit={event => event.preventDefault()}
>
<div className="form-group">
<label>
Sport:
<select
className="form-control input-lg"
name="sport_id"
required
>
<option value="" />
{sports.map(sport => (
<option key={sport.id} value={sport.id}>
{sport.label}
</option>
))}
</select>
</label>
</div>
<div className="form-group">
<label>
Activity Date:
<input
name="activity_date"
className="form-control input-lg"
type="date"
/>
</label>
</div>
<div className="form-group">
<label>
Duration:
<input
name="duration"
className="form-control input-lg"
type="text"
/>
</label>
</div>
<div className="form-group">
<label>
Distance (km):
<input
name="distance"
className="form-control input-lg"
type="number"
/>
</label>
</div>
<input
type="submit"
className="btn btn-primary btn-lg btn-block"
onClick={event => onAddSport(event)}
value="Submit"
/>
<input
type="submit"
className="btn btn-secondary btn-lg btn-block"
onClick={() => history.go(-1)}
value="Cancel"
/>
</form>
)
}
export default connect(
() => ({ }),
dispatch => ({
onAddSport: e => {
const data = [].slice
.call(e.target.form.elements)
.reduce(function(map, obj) {
if (obj.name) {
if (obj.name === 'duration' || obj.name === 'distance') {
map[obj.name] = +obj.value
} else {
map[obj.name] = obj.value
}
}
return map
}, {})
dispatch(addActivityWithoutGpx(data))
},
})
)(FormWithoutGpx)

View File

@ -64,3 +64,7 @@ input, textarea {
.leaflet-container { .leaflet-container {
height: 240px; height: 240px;
} }
.radioLabel {
text-align: center;
}

View File

@ -15,6 +15,20 @@ export default class MpwoApi {
.catch(error => error) .catch(error => error)
} }
static addActivityWithoutGpx(data) {
const request = new Request(`${apiUrl}activities/no_gpx`, {
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 getActivityGpx(activityId) { static getActivityGpx(activityId) {
const request = new Request(`${apiUrl}activities/${activityId}/gpx`, { const request = new Request(`${apiUrl}activities/${activityId}/gpx`, {
method: 'GET', method: 'GET',