API: upload a zip archive containing gpx files
This commit is contained in:
parent
276a886a61
commit
d5b79e3c57
@ -9,7 +9,7 @@ from ..users.utils import authenticate, verify_extension
|
|||||||
from .models import Activity
|
from .models import Activity
|
||||||
from .utils import (
|
from .utils import (
|
||||||
ActivityException, create_activity, edit_activity, get_chart_data,
|
ActivityException, create_activity, edit_activity, get_chart_data,
|
||||||
handle_one_activity
|
process_files
|
||||||
)
|
)
|
||||||
|
|
||||||
activities_blueprint = Blueprint('activities', __name__)
|
activities_blueprint = Blueprint('activities', __name__)
|
||||||
@ -146,16 +146,27 @@ def post_activity(auth_user_id):
|
|||||||
activity_file = request.files['file']
|
activity_file = request.files['file']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_activity = handle_one_activity(
|
new_activities = process_files(
|
||||||
auth_user_id, activity_file, activity_data
|
auth_user_id, activity_data, activity_file
|
||||||
)
|
)
|
||||||
response_object = {
|
if len(new_activities) > 0:
|
||||||
'status': 'created',
|
response_object = {
|
||||||
'data': {
|
'status': 'created',
|
||||||
'activities': [new_activity.serialize()]
|
'data': {
|
||||||
|
'activities': [new_activity.serialize()
|
||||||
|
for new_activity in new_activities]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
code = 201
|
||||||
return jsonify(response_object), 201
|
else:
|
||||||
|
response_object = {
|
||||||
|
'status': 'fail',
|
||||||
|
'data': {
|
||||||
|
'activities': []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code = 400
|
||||||
|
return jsonify(response_object), code
|
||||||
except ActivityException as e:
|
except ActivityException as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
if e.e:
|
if e.e:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import zipfile
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import gpxpy.gpx
|
import gpxpy.gpx
|
||||||
@ -248,12 +249,8 @@ def get_new_file_path(auth_user_id, activity_date, old_filename, sport):
|
|||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
def handle_one_activity(auth_user_id, activity_file, activity_data):
|
def process_one_gpx_file(auth_user_id, activity_data, file_path, filename):
|
||||||
filename = secure_filename(activity_file.filename)
|
|
||||||
file_path = get_file_path(auth_user_id, filename)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
activity_file.save(file_path)
|
|
||||||
gpx_data = get_gpx_info(file_path)
|
gpx_data = get_gpx_info(file_path)
|
||||||
|
|
||||||
sport = Sport.query.filter_by(id=activity_data.get('sport_id')).first()
|
sport = Sport.query.filter_by(id=activity_data.get('sport_id')).first()
|
||||||
@ -285,3 +282,43 @@ def handle_one_activity(auth_user_id, activity_file, activity_data):
|
|||||||
raise ActivityException(
|
raise ActivityException(
|
||||||
'fail', 'Error during activity save.', e
|
'fail', 'Error during activity save.', e
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def process_zip_archive(auth_user_id, activity_data, zip_path):
|
||||||
|
extract_dir = os.path.join(
|
||||||
|
current_app.config['UPLOAD_FOLDER'],
|
||||||
|
'activities',
|
||||||
|
str(auth_user_id),
|
||||||
|
'extract')
|
||||||
|
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(extract_dir)
|
||||||
|
|
||||||
|
new_activities = []
|
||||||
|
|
||||||
|
for gpx_file in os.listdir(extract_dir):
|
||||||
|
if ('.' in gpx_file and gpx_file.rsplit('.', 1)[1].lower()
|
||||||
|
in current_app.config.get('ACTIVITY_ALLOWED_EXTENSIONS')):
|
||||||
|
file_path = os.path.join(extract_dir, gpx_file)
|
||||||
|
new_activity = process_one_gpx_file(auth_user_id, activity_data,
|
||||||
|
file_path, gpx_file)
|
||||||
|
new_activities.append(new_activity)
|
||||||
|
|
||||||
|
return new_activities
|
||||||
|
|
||||||
|
|
||||||
|
def process_files(auth_user_id, activity_data, activity_file):
|
||||||
|
filename = secure_filename(activity_file.filename)
|
||||||
|
extension = f".{filename.rsplit('.', 1)[1].lower()}"
|
||||||
|
file_path = get_file_path(auth_user_id, filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
activity_file.save(file_path)
|
||||||
|
except Exception as e:
|
||||||
|
raise ActivityException('error', 'Error during activity file save.', e)
|
||||||
|
|
||||||
|
if extension == ".gpx":
|
||||||
|
return [process_one_gpx_file(
|
||||||
|
auth_user_id, activity_data, file_path, filename
|
||||||
|
)]
|
||||||
|
else:
|
||||||
|
return process_zip_archive(auth_user_id, activity_data, file_path)
|
||||||
|
@ -15,7 +15,7 @@ class BaseConfig:
|
|||||||
current_app.root_path, 'uploads'
|
current_app.root_path, 'uploads'
|
||||||
)
|
)
|
||||||
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
|
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
|
||||||
ACTIVITY_ALLOWED_EXTENSIONS = {'gpx'}
|
ACTIVITY_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
|
||||||
|
|
||||||
|
|
||||||
class DevelopmentConfig(BaseConfig):
|
class DevelopmentConfig(BaseConfig):
|
||||||
|
BIN
mpwo_api/mpwo_api/tests/files/gpx_test.zip
Normal file
BIN
mpwo_api/mpwo_api/tests/files/gpx_test.zip
Normal file
Binary file not shown.
BIN
mpwo_api/mpwo_api/tests/files/gpx_test_folder.zip
Normal file
BIN
mpwo_api/mpwo_api/tests/files/gpx_test_folder.zip
Normal file
Binary file not shown.
BIN
mpwo_api/mpwo_api/tests/files/gpx_test_incorrect.zip
Normal file
BIN
mpwo_api/mpwo_api/tests/files/gpx_test_incorrect.zip
Normal file
Binary file not shown.
@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
@ -645,3 +646,109 @@ def test_add_activity_zero_value(
|
|||||||
|
|
||||||
assert len(data['data']['activities'][0]['segments']) == 0
|
assert len(data['data']['activities'][0]['segments']) == 0
|
||||||
assert len(data['data']['activities'][0]['records']) == 0
|
assert len(data['data']['activities'][0]['records']) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_an_activity_with_zip(app, user_1, sport_1_cycling):
|
||||||
|
file_path = os.path.join(app.root_path, 'tests/files/gpx_test.zip')
|
||||||
|
# 'gpx_test.zip' contains 3 gpx files (same data) and 1 non-gpx file
|
||||||
|
|
||||||
|
with open(file_path, 'rb') as zip_file:
|
||||||
|
client = app.test_client()
|
||||||
|
resp_login = client.post(
|
||||||
|
'/api/auth/login',
|
||||||
|
data=json.dumps(dict(
|
||||||
|
email='test@test.com',
|
||||||
|
password='12345678'
|
||||||
|
)),
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
response = client.post(
|
||||||
|
'/api/activities',
|
||||||
|
data=dict(
|
||||||
|
file=(zip_file, 'gpx_test.zip'),
|
||||||
|
data='{"sport_id": 1}'
|
||||||
|
),
|
||||||
|
headers=dict(
|
||||||
|
content_type='multipart/form-data',
|
||||||
|
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']['activities']) == 3
|
||||||
|
assert 'just an activity' == data['data']['activities'][0]['title']
|
||||||
|
assert_activity_data_with_gpx(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_an_activity_with_zip_folder(app, user_1, sport_1_cycling):
|
||||||
|
file_path = os.path.join(app.root_path, 'tests/files/gpx_test_folder.zip')
|
||||||
|
# 'gpx_test_folder.zip' contains 3 gpx files (same data) and 1 non-gpx file
|
||||||
|
# in a folder
|
||||||
|
|
||||||
|
with open(file_path, 'rb') as zip_file:
|
||||||
|
client = app.test_client()
|
||||||
|
resp_login = client.post(
|
||||||
|
'/api/auth/login',
|
||||||
|
data=json.dumps(dict(
|
||||||
|
email='test@test.com',
|
||||||
|
password='12345678'
|
||||||
|
)),
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
response = client.post(
|
||||||
|
'/api/activities',
|
||||||
|
data=dict(
|
||||||
|
file=(zip_file, 'gpx_test_folder.zip'),
|
||||||
|
data='{"sport_id": 1}'
|
||||||
|
),
|
||||||
|
headers=dict(
|
||||||
|
content_type='multipart/form-data',
|
||||||
|
Authorization='Bearer ' + json.loads(
|
||||||
|
resp_login.data.decode()
|
||||||
|
)['auth_token']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data = json.loads(response.data.decode())
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert 'fail' in data['status']
|
||||||
|
assert len(data['data']['activities']) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_an_activity_with_zip_incorrect_file(app, user_1, sport_1_cycling):
|
||||||
|
file_path = os.path.join(app.root_path, 'tests/files/gpx_test_incorrect.zip') # noqa
|
||||||
|
# 'gpx_test_incorrect.zip' contains 2 gpx files, one is incorrect
|
||||||
|
|
||||||
|
with open(file_path, 'rb') as zip_file:
|
||||||
|
client = app.test_client()
|
||||||
|
resp_login = client.post(
|
||||||
|
'/api/auth/login',
|
||||||
|
data=json.dumps(dict(
|
||||||
|
email='test@test.com',
|
||||||
|
password='12345678'
|
||||||
|
)),
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
response = client.post(
|
||||||
|
'/api/activities',
|
||||||
|
data=dict(
|
||||||
|
file=(zip_file, 'gpx_test_incorrect.zip'),
|
||||||
|
data='{"sport_id": 1}'
|
||||||
|
),
|
||||||
|
headers=dict(
|
||||||
|
content_type='multipart/form-data',
|
||||||
|
Authorization='Bearer ' + json.loads(
|
||||||
|
resp_login.data.decode()
|
||||||
|
)['auth_token']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data = json.loads(response.data.decode())
|
||||||
|
|
||||||
|
assert response.status_code == 500
|
||||||
|
assert 'error' in data['status']
|
||||||
|
assert 'Error during gpx file parsing.' in data['message']
|
||||||
|
assert 'data' not in data
|
||||||
|
Loading…
Reference in New Issue
Block a user