API & Client: Profile picture

This commit is contained in:
SamR1
2018-01-01 21:54:03 +01:00
parent 8e21fc4112
commit ddb765e02d
14 changed files with 293 additions and 8 deletions

View File

@ -1,3 +1,7 @@
import os
from flask import current_app
class BaseConfig:
"""Base configuration"""
DEBUG = False
@ -6,6 +10,10 @@ class BaseConfig:
BCRYPT_LOG_ROUNDS = 13
TOKEN_EXPIRATION_DAYS = 30
TOKEN_EXPIRATION_SECONDS = 0
UPLOAD_FOLDER = os.path.join(
current_app.root_path, 'uploads'
)
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
class DevelopmentConfig(BaseConfig):

View File

@ -1,5 +1,6 @@
import json
import time
from io import BytesIO
from mpwo_api.tests.base import BaseTestCase
from mpwo_api.tests.utils import add_user, add_user_full
@ -485,3 +486,31 @@ class TestAuthBlueprint(BaseTestCase):
self.assertEqual(response.status_code, 400)
self.assertIn('Invalid payload.', data['message'])
self.assertIn('error', data['status'])
def test_update_user_picture(self):
add_user('test', 'test@test.com', '12345678')
with self.client:
resp_login = self.client.post(
'/api/auth/login',
data=json.dumps(dict(
email='test@test.com',
password='12345678'
)),
content_type='application/json'
)
response = self.client.post(
'/api/auth/picture',
data=dict(
file=(BytesIO(b'avatar'), 'avatar.png')
),
headers=dict(
content_type='multipart/form-data',
authorization='Bearer ' +
json.loads(resp_login.data.decode())['auth_token']
)
)
data = json.loads(response.data.decode())
self.assertTrue(data['status'] == 'success')
self.assertTrue(data['message'] == 'User picture updated.')
self.assertEqual(response.status_code, 200)

View File

@ -1,11 +1,13 @@
import datetime
from flask import Blueprint, current_app, jsonify, request
import os
from flask import Blueprint, current_app, jsonify, request, send_from_directory
from sqlalchemy import exc, or_
from werkzeug.utils import secure_filename
from mpwo_api import appLog, bcrypt, db
from .models import User
from .utils import authenticate, register_controls
from .utils import allowed_picture, authenticate, register_controls
auth_blueprint = Blueprint('auth', __name__)
@ -161,6 +163,7 @@ def get_user_status(user_id):
'bio': user.bio,
'location': user.location,
'birth_date': user.birth_date,
'picture': True if user.picture else False,
}
}
return jsonify(response_object), 200
@ -227,3 +230,81 @@ def edit_user(user_id):
'message': 'Error. Please try again or contact the administrator.'
}
return jsonify(response_object), 500
@auth_blueprint.route('/auth/picture', methods=['POST'])
@authenticate
def edit_picture(user_id):
code = 400
if 'file' not in request.files:
response_object = {'status': 'fail', 'message': 'No file part.'}
return jsonify(response_object), code
file = request.files['file']
if file.filename == '':
response_object = {'status': 'fail', 'message': 'No selected file.'}
return jsonify(response_object), code
if not allowed_picture(file.filename):
response_object = {
'status': 'fail',
'message': 'File extension not allowed.'
}
return jsonify(response_object), code
filename = secure_filename(file.filename)
dirpath = os.path.join(
current_app.config['UPLOAD_FOLDER'],
'pictures',
str(user_id)
)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
filepath = os.path.join(dirpath, filename)
try:
user = User.query.filter_by(id=user_id).first()
if user.picture is not None and os.path.isfile(user.picture):
os.remove(user.picture)
file.save(filepath)
user.picture = filepath
db.session.commit()
response_object = {
'status': 'success',
'message': 'User picture updated.'
}
return jsonify(response_object), 200
except (exc.IntegrityError, ValueError) as e:
db.session.rollback()
appLog.error(e)
response_object = {
'status': 'fail',
'message': 'Error during picture update.'
}
return jsonify(response_object), 500
@auth_blueprint.route('/auth/picture', methods=['DELETE'])
@authenticate
def del_picture(user_id):
try:
user = User.query.filter_by(id=user_id).first()
if os.path.isfile(user.picture):
os.remove(user.picture)
user.picture = None
db.session.commit()
response_object = {
'status': 'success',
'message': 'User picture delete.'
}
return jsonify(response_object), 200
except (exc.IntegrityError, ValueError) as e:
db.session.rollback()
appLog.error(e)
response_object = {
'status': 'fail',
'message': 'Error during picture deletion.'
}
return jsonify(response_object), 500

View File

@ -19,6 +19,7 @@ class User(db.Model):
birth_date = db.Column(db.DateTime, nullable=True)
location = db.Column(db.String(80), nullable=True)
bio = db.Column(db.String(200), nullable=True)
picture = db.Column(db.String(255), nullable=True)
def __repr__(self):
return '<User %r>' % self.username

View File

@ -1,4 +1,4 @@
from flask import Blueprint, jsonify
from flask import Blueprint, jsonify, send_file
from .models import User
@ -52,6 +52,22 @@ def get_single_user(user_id):
return jsonify(response_object), 404
@users_blueprint.route('/users/<user_id>/picture', methods=['GET'])
def get_picture(user_id):
response_object = {
'status': 'fail',
'message': 'User does not exist'
}
try:
user = User.query.filter_by(id=int(user_id)).first()
if not user:
return jsonify(response_object), 404
else:
return send_file(user.picture)
except ValueError:
return jsonify(response_object), 404
@users_blueprint.route('/ping', methods=['GET'])
def ping_pong():
return jsonify({

View File

@ -1,11 +1,17 @@
from functools import wraps
import re
from flask import request, jsonify
from flask import current_app, jsonify, request
from .models import User
def allowed_picture(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in \
current_app.config.get('PICTURE_ALLOWED_EXTENSIONS')
def authenticate(f):
@wraps(f)
def decorated_function(*args, **kwargs):