"""For a given resources, classes in the module intend to create the following routes : GET resource/ -> to retrieve one POST resource -> to create one PUT resource/ -> to update one DELETE resource/ -> to delete one GET resources -> to retrieve several POST resources -> to create several PUT resources -> to update several DELETE resources -> to delete several """ import ast import logging from functools import wraps from werkzeug.exceptions import Unauthorized, BadRequest, Forbidden, NotFound from flask import request from flask.ext.restful import Resource, reqparse from flask.ext.login import current_user from web.views.common import admin_permission, api_permission, \ login_user_bundle, jsonify from web.controllers import UserController logger = logging.getLogger(__name__) def authenticate(func): @wraps(func) def wrapper(*args, **kwargs): if request.authorization: ucontr = UserController() try: user = ucontr.get(login=request.authorization.username) except NotFound: raise Forbidden("Couldn't authenticate your user") if not ucontr.check_password(user, request.authorization.password): raise Forbidden("Couldn't authenticate your user") if not user.is_active: raise Forbidden("User is desactivated") login_user_bundle(user) if current_user.is_authenticated: return func(*args, **kwargs) raise Unauthorized() return wrapper class PyAggAbstractResource(Resource): method_decorators = [authenticate, jsonify] controller_cls = None attrs = None @property def controller(self): if admin_permission.can(): return self.controller_cls() return self.controller_cls(current_user.id) def reqparse_args(self, right, req=None, strict=False, default=True, allow_empty=False): """ strict: bool if True will throw 400 error if args are defined and not in request default: bool if True, won't return defaults args: dict the args to parse, if None, self.attrs will be used """ try: in_values = req.json if req else (request.json or {}) if not in_values and allow_empty: return {} except BadRequest: if allow_empty: return {} raise parser = reqparse.RequestParser() if self.attrs is not None: attrs = self.attrs elif admin_permission.can(): attrs = self.controller_cls._get_attrs_desc('admin') elif api_permission.can(): attrs = self.controller_cls._get_attrs_desc('api', right) else: attrs = self.controller_cls._get_attrs_desc('base', right) assert attrs, "No defined attrs for %s" % self.__class__.__name__ for attr_name, attr in attrs.items(): if not default and attr_name not in in_values: continue else: parser.add_argument(attr_name, location='json', **attr) #return parser.parse_args(req=req, strict=strict) return attrs class PyAggResourceNew(PyAggAbstractResource): @api_permission.require(http_exception=403) def post(self): """Create a single new object""" return self.controller.create(**self.reqparse_args(right='write')), 201 class PyAggResourceExisting(PyAggAbstractResource): def get(self, obj_id=None): """Retreive a single object""" return self.controller.get(id=obj_id) def put(self, obj_id=None): """update an object, new attrs should be passed in the payload""" args = self.reqparse_args(right='write', default=False) if not args: raise BadRequest() return self.controller.update({'id': obj_id}, args), 200 def delete(self, obj_id=None): """delete a object""" self.controller.delete(obj_id) return None, 204 class PyAggResourceMulti(PyAggAbstractResource): def get(self): """retrieve several objects. filters can be set in the payload on the different fields of the object, and a limit can be set in there as well """ args = {} try: limit = request.json.pop('limit', 10) order_by = request.json.pop('order_by', None) except Exception: attrs = self.reqparse_args(right='read', default=False) for k, v in request.args.items(): if k in attrs.keys(): if attrs[k]['type'] in [bool, int]: args[k] = ast.literal_eval(v) else: args[k] = v limit = request.args.get('limit', 10) order_by = request.args.get('order_by', None) query = self.controller.read(**args) if order_by: query = query.order_by(order_by) if limit: query = query.limit(limit) return [res for res in query] @api_permission.require(http_exception=403) def post(self): """creating several objects. payload should be: >>> payload [{attr1: val1, attr2: val2}, {attr1: val1, attr2: val2}] """ status, fail_count, results = 200, 0, [] class Proxy: pass for attrs in request.json: try: Proxy.json = attrs args = self.reqparse_args('write', req=Proxy, default=False) obj = self.controller.create(**args) results.append(obj) except Exception as error: fail_count += 1 results.append(str(error)) if fail_count == len(results): # all failed => 500 status = 500 elif fail_count: # some failed => 206 status = 206 return results, status def put(self): """updating several objects. payload should be: >>> payload [[obj_id1, {attr1: val1, attr2: val2}] [obj_id2, {attr1: val1, attr2: val2}]] """ status, results = 200, [] class Proxy: pass for obj_id, attrs in request.json: try: Proxy.json = attrs args = self.reqparse_args('write', req=Proxy, default=False) result = self.controller.update({'id': obj_id}, args) if result: results.append('ok') else: results.append('nok') except Exception as error: results.append(str(error)) if results.count('ok') == 0: # all failed => 500 status = 500 elif results.count('ok') != len(results): # some failed => 206 status = 206 return results, status def delete(self): """will delete several objects, a list of their ids should be in the payload""" status, results = 204, [] for obj_id in request.json: try: self.controller.delete(obj_id) results.append('ok') except Exception as error: status = 206 results.append(error) # if no operation succeded, it's not partial anymore, returning err 500 if status == 206 and results.count('ok') == 0: status = 500 return results, status