"""
Contains views for the ``/users`` endpoint.
"""
import os
from flask import request, abort, jsonify, g
from flask_restful import Resource
from auth import auth
from config import default_config as conf
from database import User, ContextManagedSession
from decorators import restful_pagination
from views import SchemaDefinedResource
from json_schema_parser import JsonSchemaValidator
__author__ = 'Michal Kononenko'
database_session = ContextManagedSession(bind=conf.DATABASE_ENGINE)
[docs]class UserContainer(SchemaDefinedResource):
"""
Maps the /users endpoint's ``GET`` and ``POST`` requests, allowing API
consumers to create new users
"""
schema = {
"$schema": "http://json-schema.org/draft-04/hyper-schema",
"title": "List of Users",
"description": "Contains methods for listing and creating users",
"type": ["object"],
"definitions": {
"username": {
"description": "The name of the user",
"example": "root",
"readOnly": True,
"type": ["string"]
},
"password": {
"description": "The user's password",
"example": "toor",
"readOnly": True,
"type": ["string"]
},
"date_created": {
"description": "The date when the user was last created",
"example": "2014-01-01T12:00:00Z",
"readOnly": True,
"format": "date-time",
"type": ["string"]
},
"projects": {
"description": "The projects run by the user",
"type": ["array"]
},
"email": {
"description": "The email adress of the user",
"example": "scott@tiger.com",
"readOnly": True,
"format": "email",
"type": ["string"]
}
},
"links": [
{
"title": "List of Projects",
"description": "Return a list of users",
"href": "/users",
"method": "GET",
"rel": "self",
"targetSchema": {
"$ref": "#/definitions/user"
}
}
],
"properties": {
"date_created": {
"$ref": "#/definitions/date_created"
},
"username": {
"$ref": "#/definitions/username"
},
"projects": {
"$ref": "#/definitions/projects"
}
}
}
post_schema_validator = JsonSchemaValidator(
os.path.join(conf.JSON_SCHEMA_PATH, 'users', 'users_post.json')
)
def __init__(self):
SchemaDefinedResource.__init__(self)
@staticmethod
[docs] def parse_search_query_params(request):
"""
This method enables text search over the results returned by a
``GET`` request on the ``/users`` endpoint. It parses query parameters
from :class:`Flask.request`, a container for the HTTP request sent into
the method. Currently, the query parameters parsed here are
- starts_with: A string stating what the username should begin with
- contains: A string specifiying what the username should contain
- ends_with: A string stating what the username should end with
:param request: The flask request from which query parameters need to
be retrieved
:return: A ``%`` delineated search string ready for insertion as a
parameter into a SQL or SQLAlchemy query language's ``LIKE`` clause
"""
contains = request.args.get('contains')
if contains is not None:
return '%%%s%%' % contains
search_string = ''
starts_with = request.args.get('starts_with')
if starts_with is not None:
search_string = '%s%s' % ('%s%%' % starts_with, search_string)
ends_with = request.args.get('ends_with')
if ends_with is not None:
search_string = '%s%s' % (search_string, '%%%s' % ends_with)
if starts_with is None and ends_with is None:
search_string = '%%%%'
return search_string
@auth.login_required
@database_session()
@restful_pagination()
def get(self, session, pag_args):
"""
Process a GET request for the /users endpoint
**Example request**
.. sourcecode:: http
GET /api/v1/users HTTP/1.1
Host: example.com
Content-Type: application/json
**Example response**
.. sourcecode:: http
Vary: Accept
Content-Type: application/json
.. include:: /schemas/users/examples/get.json
:query contains: A string specifying what substring should be contained
in the username. Default is ``''``
:query starts_with: The substring that the username should start with.
Default is ``''``.
:query page: The number of the page to be displayed. Defaults to ``1``.
:query items_per_page: The number of items to be displayed on this page
of the results. Default is ``1000`` items.
:statuscode 200: no error
:param Session session: The database session used to make the request
:param PaginationArgs pag_args: The pagination arguments generated by
the ``@restful_pagination()`` decorator, injected into the function
at runtime
:return: A flask response object with the search request parsed
"""
like_string = self.parse_search_query_params(request)
user_query = session.query(
User
).filter(
User.username.like(like_string)
).order_by(
User.id
).limit(
pag_args.items_per_page
).offset(
pag_args.offset
)
users = user_query.all()
user_count = user_query.count()
response = jsonify({'users': [user.get for user in users]})
response.headers['Count'] = user_count
return response
@database_session()
def post(self, session):
"""
Create a new user
**Example Request**
.. sourcecode:: http
HTTP/1.1
Content-Type: application/json
{
"username": "scott",
"password": "tiger",
"email": "scott@tiger.com"
}
**Example Response**
.. sourcecode:: http
HTTP/1.1 201 CREATED
Content-Type: application/json
{
"username": "scott",
"email": "scott@tiger.com
}
:statuscode 201: The new user has been created successfully
:statuscode 400: The request could not be completed due to poor JSON
:statuscode 422: The request was correct, but the user could not be
created due to the fact that a user with that username already
exists
:param Session session: The database session that will be used to
make the request
"""
if not self.post_schema_validator.validate_dict(request.json)[0]:
abort(400)
username = request.json.get('username')
password = request.json.get('password')
email = request.json.get('email')
user = User(username, password, email)
session.add(user)
response = jsonify({'user': username, 'email': email})
response.status_code = 201
return response
[docs]class UserView(Resource):
"""
Maps the ``/users/<username>`` endpoint
"""
@auth.login_required
@database_session()
def get(self, session, username_or_id):
"""
Returns information for a given user
**Example Response**
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{
"username": "scott"
"email": "scott@tiger.com"
"projects": [
{
"name": "NMR Experiment 1",
"description": "Measure this thing in NMR",
"date_created": "2015-01-01T12:00:00Z",
"owner": {
"username": "scott",
"email": "scott@tiger.com"
}
}
]
}
:param Session session: The database session to be used in the endpoint
:param int or str username_or_id: The username or user_id of the user
for which data needs to be retrieved
"""
try:
username_or_id = int(username_or_id)
except ValueError:
username_or_id = str(username_or_id)
if isinstance(username_or_id, int):
user = session.query(User).filter_by(
id=username_or_id).first()
else:
user = session.query(User).filter_by(
username=username_or_id
).first()
response = jsonify(user.get_full)
return response