API & Client: add an activity w/o gpx
This commit is contained in:
parent
23aa501785
commit
d6bb413fb6
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
@ -64,3 +64,7 @@ input, textarea {
|
|||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
height: 240px;
|
height: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.radioLabel {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user