Skip to content

Commit v0.29

kwmccabe edited this page Apr 17, 2018 · 7 revisions

v0.29 - API module, Flask-HTTPAuth


Files changed (11)

File web/app/init.py MODIFIED

  • Register API module with Flask Blueprint.
  • All API routes begin with /api.
+    from .api import api as api_blueprint
+    app.register_blueprint(api_blueprint, url_prefix='/api')

File web/app/api/init.py ADDED

  • Initialize API module.
  • Import authentication and errors for access control.
  • Import item and user for output routes.
+from flask import Blueprint
+
+api = Blueprint('api', __name__, template_folder='templates')
+
+from . import authentication, errors, item, user

File web/app/api/authentication.py ADDED

  • Initialize HTTPBasicAuth, scoped within the API module.
  • Provide methods for @auth.verify_password and @auth.error_handler decorators.
  • verify_password() leverages the existing verify_password() method in UserModel.
  • auth_error() calls the unauthorized() method in errors.py.
  • before_request() checks that current user exists and is active, or calls the forbidden() method in errors.py.
+from flask import g
+from flask_httpauth import HTTPBasicAuth
+from ..user.models import UserModel
+from . import api
+from .errors import forbidden, unauthorized
+
+auth = HTTPBasicAuth()
+
+@auth.verify_password
+def verify_password(email,password):
+    if not email:
+        return False
+    user = UserModel.query.filter_by(user_email=email).first()
+    if not user:
+        return False
+    g.current_user = user
+    return user.verify_password(password)
+
+
+@auth.error_handler
+def auth_error():
+    return unauthorized('Invalid Credentials')
+
+
+@api.before_request
+@auth.login_required
+def before_request():
+    if not g.current_user.active:
+        return forbidden('Inactive Account')

File web/app/api/errors.py ADDED

  • Generate JSON responses for unauthorized and forbidden errors.
+from flask import jsonify, request
+
+def unauthorized(message):
+    response = jsonify({ \
+        'code': 401, 'name': 'Unauthorized', 'message': message, 'url': request.url \
+        #, 'request': dir(request) \
+        })
+    response.status_code = 401
+    return response
+
+def forbidden(message):
+    response = jsonify({ \
+        'code': 403, 'name': 'Forbidden', 'message': message, 'url': request.url \
+        #, 'request': dir(request) \
+        })
+    response.status_code = 403
+    return response

File web/app/api/item.py ADDED

  • Create route /item/ to return all items in JSON format.
  • Create route /item/<int:id>/ to return single item in JSON format.
+from flask import jsonify
+from .. import db
+from ..item.models import ItemModel
+from . import api
+
+
+@api.route('/item/')
+def get_items():
+    rows = db.session.query(ItemModel)
+    return jsonify({ 'item': [item.to_json() for item in rows] })
+
+@api.route('/item/<int:id>/')
+def get_item(id):
+    item = ItemModel.query.get_or_404(id)
+    return jsonify(item.to_json())

File web/app/api/user.py ADDED

  • Create route /user/ to return all users in JSON format.
  • Create route /user/<int:id>/ to return single user in JSON format.
+from flask import jsonify
+from .. import db
+from ..user.models import UserModel
+from . import api
+
+
+@api.route('/user/')
+def get_users():
+    rows = db.session.query(UserModel)
+    return jsonify({ 'user': [user.to_json() for user in rows] })
+
+@api.route('/user/<int:id>/')
+def get_user(id):
+    user = UserModel.query.get_or_404(id)
+    return jsonify(user.to_json())

File web/app/item/models.py MODIFIED

  • Activate some API urls within to_json().
     def to_json(self):
         json_item = {
-            #'url': url_for('api.get_item', id=self.id),
+            'url': url_for('api.get_item', id=self.id),

...

-            #'owner_url': url_for('api.get_user', id=self.owner_id),
+            'owner_url': url_for('api.get_user', id=self.owner_id),
             'owner_id'  : self.owner_id,
             #'users_url': url_for('api.get_item_users', id=self.id),
-            'users_count': self.item_users.count()
+            'users_count': len(self.item_users)
         }
         return json_item 

File web/app/item/views.py MODIFIED

 from flask_login import current_user, login_required
 from jinja2 import TemplateNotFound
 from .. import db, flash_errors
+from ..decorators import get_list_opts
+from ..user.models import UserModel
 from . import item
 from .models import ItemModel, ItemUserModel, get_owner_id_choices
-from ..user.models import UserModel
 from .forms import CreatItemForm, EditItemForm
-from ..decorators import get_list_opts

File web/app/user/models.py MODIFIED

  • Activate some API urls within to_json().
     def to_json(self):
         json_user = {
-            #'url': url_for('api.get_user', id=self.id),
+            'url': url_for('api.get_user', id=self.id),

...

             #'items_url': url_for('api.get_user_items', id=self.id),
-            'items_count': self.user_items.count()
+            'items_count': len(self.user_items)

File web/flaskapp.py MODIFIED

  • Update the page_not_found() function to return JSON for bad API requests.
-from flask import current_app, flash, render_template
+from flask import current_app, flash, jsonify, render_template, request

...

 # Page Not Found
 @app.errorhandler(404)
 def page_not_found(e):
+    if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
+        response = jsonify({ \
+            'code': e.code, 'name': e.name, 'message': e.description, 'url': request.url \
+            #, 'error': dir(e), 'request': dir(request) \
+            })
+        response.status_code = 404
+        return response
     return render_template('404.html', error=e), 404

File web/requirements.txt MODIFIED

  • Add Flask-HTTPAuth to pip install.
 Flask-Bootstrap==3.3.7.1
+Flask-HTTPAuth==3.2.3
 Flask-Login==0.4.1

Commit-v0.28 | Commit-v0.29 | Commit-v0.30

Clone this wiki locally