def validate_query(self, key, value, op):
        """
        Validate parameters passed to any endpoint for filtering.

        For filtering by delete the parameter should be a boolean
        For filtering by date the date should be a valid date
        The filter operator should be valid

        delete Example
        ==============
        Correct:
        http://127.0.0.1:5000/api/v1/asset-categories/stats?where=
        deleted,eq,true

        Incorrect and will throw an error and return a response:
        http://127.0.0.1:5000/api/v1/asset-categories/stats?where=
        deleted,eq,Invalid_delete_value

        date example
        =============
        Correct:
        http://127.0.0.1:5000/api/v1/asset-categories/stats?where=
        created_at,eq,218-06-19 12:06:43.339809

        Incorrect and will throw an error and return a response:
        http://127.0.0.1:5000/api/v1/asset-categories/stats?where=
        created_at,eq,invalid_date

        Invalid operator example
        ========================
        http://127.0.0.1:5000/api/v1/asset-categories/stats?where=
        deleted,invalid_operator,true

        The essence of this is to validate parameters before making a call to
        the database, if the parameters are invalid then we dont make any
        database query which therefore improves perfomance
        """
        fields = [
            'deleted', 'created_at', 'updated_at', 'deleted_at', 'warranty'
        ]

        if op not in self.mapper:
            raise ValidationError(
                dict(message=filter_errors['INVALID_OPERATOR']))

        try:
            if key in fields:
                if key in ('created_at', 'updated_at', 'deleted_at',
                           'warranty'):
                    datetime.strptime(value, "%Y-%m-%d")
                elif key == 'deleted' and value not in ('true', 'false'):
                    raise ValidationError(
                        dict(
                            message=filter_errors['INVALID_DELETE_ATTRIBUTE']))
        except ValueError:
            raise ValidationError(
                dict(message=filter_errors['INVALID_DATE'].format(value)))
Example #2
0
    def post(self):
        """ Endpoint to create new Task"""

        data = request.get_json()
        title = data.get('title')
        formatted_date = convert_date_to_date_time(data['due_date'])
        schema = TaskSchema()
        project_id = data['projectId']
        project = Project.get_or_404(project_id)
        task_exist = Task.find_by_title_and_project_id(title=title,
                                                       project_id=project.id)
        if task_exist:
            raise ValidationError(
                {
                    "message":
                    serialization_messages['exists'].format('Task with title')
                }, 409)
        if not check_date_difference(formatted_date, project.due_date):
            raise ValidationError(
                {
                    "message":
                    "Tasks cannot be created under this project because of dat difference"
                }, 401)
        assignee_list = check_assignee(data.get('task_assignees'),
                                       project.assignees)
        if assignee_list is not None:
            user_list = assign_user(assignee_list)
            data['task_assignees'] = user_list
        else:
            data['task_assignees'] = []
        assignee_ids = data['task_assignees'] if data[
            'task_assignees'] is not None else []
        del data['task_assignees']
        task_data = schema.load_object_into_schema(data)

        task = Task()
        task.title = data['title']
        task.description = data['description']
        task.due_date = data['due_date']
        task.task_assignees = assignee_ids
        task.project_id = project.id
        task.save()

        task.save()

        return response('success',
                        message=success_messages['created'].format('Task'),
                        data=schema.dump(task).data,
                        status_code=201)
Example #3
0
def check_id_valid(**kwargs):
  for key in kwargs:
    if key.endswith("_id") and not is_id_valid(kwargs.get(key, None)):
      raise ValidationError({
        "status": "error",
        "message": ERROR_MSG["invalid_id"]
      }, 400)
Example #4
0
    def post(self):
        """ Endpoint to create new project"""
        data = request.get_json()
        title = data.get('title')
        user = get_jwt_identity()
        project_exist = Project.find_by_title_and_user(title=title,
                                                       user_id=user.get('id'))
        if project_exist:
            raise ValidationError(
                {
                    "message":
                    serialization_messages['exists'].format(
                        'Project with title')
                }, 409)
        data['createdBy'] = user.get("id")
        project = Project()
        user_list = assign_user(data.get('assignees'))
        convert_date_to_date_time(data['due_date'])

        data['assignees'] = user_list if user_list is not None else []
        assignee_ids = data['assignees']
        del data['assignees']
        schema = ProjectSchema()
        project_data = schema.load_object_into_schema(data)

        project.created_by = data['createdBy']
        project.title = data['title']
        project.description = data['description']
        project.due_date = data['due_date']
        project.assignees = assignee_ids
        project.save()
        return response('success',
                        message=success_messages['created'].format('Project'),
                        data=schema.dump(project).data,
                        status_code=201)
Example #5
0
    def post(self):
        """login endpoint
        """
        mapper = {
            'testing': self.get_user_testing,
            'development': self.get_user,
            'production': self.get_user
        }

        request_data = request.get_json()

        user_schema = UserSchema(only=["username", "password"])
        user_data = user_schema.load_object_into_schema(request_data)

        user = mapper[FLASK_ENV](user_data["username"])

        token, error = generate_token(user, user_data)

        if error:
            return ValidationError(error).to_dict(), 401

        return {
            "status": "success",
            "message": SUCCESS_MSG["login"],
            "token": token
        }, 200
Example #6
0
def validate_pagination_args(arg_value, arg_name):
    """
    Validates if the query strings are valid.

    Arguments:
        arg_value (string): Query string value
        arg_name (string): Query string name

    Raises:
        ValidationError: Use to raise exception if any error occur

    Returns:
        (int) -- Returns True or False
    """

    if arg_name == 'limit' and arg_value == 'None':
        return 10  # Defaults limit to 10 if not provided
    if arg_name == 'page' and arg_value == 'None':
        return 1  # Defaults page to 1 if not provided

    # Checks if the arg is >= 1
    if arg_value.isdigit() and int(arg_value) > 0:
        return int(arg_value)
    else:
        raise ValidationError({
            'message':
            serialization_errors['invalid_query_strings'].format(
                arg_name, arg_value)
        })
Example #7
0
def validate_duplicate(model, **kwargs):
    """
    Checks if model instance already exists in database

    Parameters:
         model(object): model to run validation on
         kwargs(dict): keyword arguments containing fields to filter query by
    """

    record_id = kwargs.get('id')
    kwargs.pop('id', None)  # remove id from kwargs if found or return None

    query = dict(deleted=False, **kwargs)
    if record_id:
        result = model.query.filter_by(**query).filter(
            model.id == record_id).first(
            )  # selects the first query object for model records

        if result:
            return None  # return None if query object is found

    result = model.query.filter_by(**query).first()
    if result:
        raise ValidationError(
            {
                'message':
                serialization_errors['exists'].format(
                    f'{re.sub(r"(?<=[a-z])[A-Z]+",lambda x: f" {x.group(0).lower()}" , model.__name__)}'
                )
            }, 409)
Example #8
0
    def load_object_into_schema(self, data, partial=False):
        """Helper function to load python objects into schema"""
        data, errors = self.load(data, partial=partial)
        if errors:
            raise ValidationError(
                dict(errors=errors, message='An error occurred'), 400)

        return data
Example #9
0
    def get(cls, id):
        """
			return entries by id
		"""
        value = cls.query.filter_by(id=id, deleted=False).first()
        if value is None:
            raise ValidationError({'message': f'{cls.__name__} not found'})
        return value
Example #10
0
    def get_all(cls):
        """
			return all entries
		"""
        value = cls.query.filter_by(deleted=False).order_by(
            cls.due_date.asc()).all()
        if value is None:
            raise ValidationError({'message': f'{cls.__name__} not found'})
        return value
Example #11
0
    def get_username_or_404(cls, username):
        """Get user by username or return 404
        """
        record = cls.query.filter_by(username=username).first()

        if not record:
            raise ValidationError(
                {"message": ERROR_MSG["not_found"].format("User")}, 404)

        return record
Example #12
0
    def filter_query(self, args):
        """
        Returns filtered database entries.
        An example of filter_condition is: User._query('name,like,john').
        Apart from 'like', other comparators are
        eq(equal to), ne(not equal to), lt(less than), le(less than or equal
        to)
        gt(greater than), ge(greater than or equal to)
        :param filter_condition:
        :return: an array of filtered records
        """

        raw_filters = args.getlist('where')
        result = self.query

        for raw in raw_filters:
            try:
                key, op, value = raw.split(',', 3)
            except ValueError:
                raise ValidationError(
                    dict(message=filter_errors['INVALID_FILTER_FORMAT'].format(
                        raw)))

            self.validate_query(key, value, op)

            column = getattr(self.model, key, None)
            json_field = getattr(self.model, 'custom_attributes', None)
            db_filter = self.mapper.get(op)

            if not column and not json_field:
                raise ValidationError(
                    dict(message=filter_errors['INVALID_COLUMN'].format(key)))
            elif not column and json_field:
                result = result.filter(
                    db_filter(
                        self.model.custom_attributes[key].astext.cast(Unicode),
                        value))
            elif str(column.type) == 'DATETIME':
                result = result.filter(db_filter(func.date(column), value))
            else:
                result = result.filter(db_filter(column, value))

        return result
Example #13
0
 def decorated_function(*args, **kwargs):
     """Function with decorated function mutations."""
     for key in kwargs:
         if key.endswith('_id') and not is_valid_id(kwargs.get(key, None)):
             raise ValidationError(
                 {
                     'status': 'error',
                     'message': serialization_errors['invalid_id']
                 }, 400)
     return func(*args, **kwargs)
Example #14
0
def generate_token(user, user_data):
    """
    This method generates a jwt token on login
    it authenticates/validate the user
    returns token
    """
    password = user.password
    candidate_password = user_data['password']

    if sha256_crypt.verify(candidate_password, password):
        exp_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
        token = jwt.encode(
            {
                'id': user.id,
                'username': user.username,
                'exp': exp_time
            }, os.getenv("SECRET"))
        return token.decode(CHARSET)
    else:
        error = ValidationError({'message': ERROR_MESSAGES['AUTH_ERROR']}, 401)
        return error.to_dict()
Example #15
0
    def delete(self, task_id):
        """
		Delete a single task
		:param task_id:
		:return:
		"""
        user = get_jwt_identity()
        task_exists = Task.find_by_id(task_id)
        if task_exists is None:
            raise ValidationError({'message': 'Task not found'})
        Project.delete_item(task_exists)

        return {'status': 'success', 'message': 'Task deleted successfully'}
Example #16
0
    def delete(self, project_id):
        """
		Delete a single project
		:param project_id:
		:return:
		"""
        user = get_jwt_identity()
        project_exists = Project.find_by_id_and_user(project_id,
                                                     user.get('id'))
        if project_exists is None:
            raise ValidationError({'message': 'Project thing not found'})
        Project.delete_item(project_exists)

        return {'status': 'success', 'message': 'Project deleted successfully'}
Example #17
0
    def get(self):
        """
		Get all tasks
		:param None:
		:return: Tasks List
		"""
        # user = get_jwt_identity()
        schema = TaskSchema(many=True)
        tasks = Task.get_all()
        if tasks is None:
            raise ValidationError({'message': 'No Task Found'})
        return response('success',
                        success_messages['retrieved'].format('Tasks'),
                        schema.dump(tasks).data)
Example #18
0
    def get_or_404(cls, id):
        """Return entries by id
        """

        record = cls.query.get(id)

        if not record:
            raise ValidationError(
                {
                    'message':
                    f'{re.sub(r"(?<=[a-z])[A-Z]+",lambda x: f" {x.group(0).lower()}" , cls.__name__)} not found'
                }, 404)

        return record
Example #19
0
    def get(self):
        """
		Get all Project
		:param None:
		:return: Project object
		"""
        # user = get_jwt_identity()
        schema = ProjectSchema(many=True)
        projects = Project.get_all()
        if projects is None:
            raise ValidationError({'message': 'No Project Found'})
        projects_list = schema.dump(projects).data
        return response('success',
                        success_messages['retrieved'].format('Projects'),
                        projects_list)
Example #20
0
    def get(self, task_id):
        """
		Get a single task
		:param task_id:
		:return: Task object
		"""
        user = get_jwt_identity()

        schema = TaskSchema()
        task = Task.get(task_id)
        if task is None:
            raise ValidationError({'message': 'Task not found'})
        return response('success',
                        success_messages['retrieved'].format('Task'),
                        schema.dump(task).data)
Example #21
0
    def get_or_404(cls, instance_id):
        """
			Gets an instance by id or returns 404
			:param instance_id: the id of instance to get
			:return: return instance or 404
		"""
        instance = cls.query.filter_by(id=instance_id).first()
        if not instance:
            raise ValidationError(
                {
                    'message':
                    f'{re.sub(r"(?<=[a-z])[A-Z]+", lambda x: f" {x.group(0).lower()}", cls.__name__)} not found'
                    # noqa
                },
                404)
        return instance
Example #22
0
    def get(self, project_id):
        """
		Get a single project
		:param project_id:
		:return: Project object
		"""
        user = get_jwt_identity()

        schema = ProjectSchema()
        project = Project.get(project_id)
        if project is None:
            raise ValidationError({'message': 'Project not found'})
        project = schema.dump(project).data
        return response('success',
                        success_messages['retrieved'].format('Project'),
                        project)
def validate_duplicate(model, **kwargs):
    """
    Checks if model instance already exists in database

    Parameters:
         model(object): model to run validation on
         kwargs(dict): keyword arguments containing fields to filter query by
    """

    result = model.query.filter_by(deleted=False, **kwargs).first()
    if result:
        raise ValidationError({
            'message':
            serialization_errors['exists'].format(
                f'{re.sub(r"(?<=[a-z])[A-Z]+",lambda x: f" {x.group(0).lower()}" , model.__name__)}'
            )
        }, 409)
Example #24
0
	def post(self):
		"""
		An endpoint to authenticate user
		:return: dict(user data)
		"""
		request_data = request.get_json()
		email = request_data.get('email')
		password = request_data.get('password')
		user = User.find_by_email(email)
		if not user or not bcrypt.check_password_hash(user.password, password):
			raise ValidationError({'message': serialization_messages['invalid_user_data']}, 400)
		token = generate_token(user)
		data = {
			'token': token,
			'user': schema.dump(user).data
		}

		return {
			'status': 'success',
			'message': success_messages['retrieved'].format('User'),
			'data': data
			}, 200
Example #25
0
	def post(self):
		"""
		An endpoint to register a user
		"""
		request_data = request.get_json()
		user_data = schema.load_object_into_schema(request_data)

		email = request_data.get('email')
		user_exist = User.find_by_email(email)
		if user_exist:
			raise ValidationError({
				"message": serialization_messages['exists'].format('User')
			}, 409)
		user = User(**user_data)
		user.save()

		token = generate_token(user)
		data = {
			'token': token,
			'user': schema.dump(user).data
		}

		return response('success', message=success_messages['created'].format('User'), data=data, status_code=201)
Example #26
0
 def patch(self, project_id):
     """ Endpoint to update project"""
     request_data = request.get_json()
     user = get_jwt_identity()
     project = Project.get(project_id)
     if user.get('id') != project.created_by:
         raise ValidationError({
             'message':
             'Unauthorized user, you cannot perform this operation'
         })
     schema = ProjectSchema(context={'id': project_id})
     if 'assignees' in request_data:
         user_list = assign_user(request_data.get('assignees'))
         assignees = user_list if user_list is not None else []
         del request_data['assignees']
         data = schema.load_object_into_schema(request_data, partial=True)
         data['assignees'] = assignees
     else:
         data = schema.load_object_into_schema(request_data, partial=True)
     project.update_(**data)
     return response('success',
                     message=success_messages['updated'].format('Project'),
                     data=schema.dump(project).data,
                     status_code=200)
Example #27
0
    def get(self):
        """
        Search Asset by date and warranty
        """
        qry_keys = ('start', 'end', 'warranty_start', 'warranty_end')
        qry_dict = dict(request.args)

        for key in qry_dict:
            if key not in qry_keys:
                raise ValidationError(
                    dict(message=filter_errors['INVALID_COLUMN'].format(key)))

        start = request.args.get('start')
        end = request.args.get('end')
        warranty_start = request.args.get('warranty_start')
        warranty_end = request.args.get('warranty_end')

        qry_list = []
        if start:
            qry_list.append(('where', f'created_at,ge,{start}'))
        if end:
            qry_list.append(('where', f'created_at,le,{end}'))
        if warranty_start:
            qry_list.append(('where', f'warranty,ge,{warranty_start}'))
        if warranty_end:
            qry_list.append(('where', f'warranty,le,{warranty_end}'))

        args = ImmutableMultiDict(qry_list)
        assets = Asset._query(args)

        asset_schema = AssetSchema(many=True, exclude=EXCLUDED_FIELDS)

        return {
            'status': 'success',
            'data': asset_schema.dump(assets).data
        }, 200
Example #28
0
def pagination_helper(model,
                      schema,
                      extra_query=None,
                      exclude=EXCLUDED_FIELDS,
                      only=None):
    """
    Paginates records of a model.

    Arguments:
        model (class): Model to be paginated
        schema (class) -- Schema to be used for serilization

    Keyword Arguments:
        extra_query (dict): Contains extra query to be performed on the model
                            (default: {None})

        example: {"asset_category_id": "-LHYVNP2yx8oIOJFzXS4", "deleted": False}

    Returns:
        (tuple): Returns a tuple containing the paginated data and the
                paginated meta object or returns a tuple of None depending on
                whether the limit and page object is provided
    """

    # Validates if the query strings are digits and the digits are >= 1
    limit = validate_pagination_args(request.args.get('limit', 'None'),
                                     'limit')
    current_page_count = validate_pagination_args(
        request.args.get('page', 'None'), 'page'
    )  # assign the page query string to the variable current_page_count

    query = model.query_(request.args)

    # Removes the trailing / at the end of the root url
    root_url = request.url_root[:-1]
    # Removes the trailing / at the begining of the url path
    url_path = request.path[1:]
    base_url = f'{root_url}/{url_path}'
    current_page_url = request.url

    records_query = query.filter_by(deleted=False)

    # Checks if they are extra queries to perform on the model
    if extra_query and isinstance(extra_query, dict):
        try:
            records_query = records_query.filter_by(**extra_query)
        except:
            # Raise a validation error if the keys in the extra queries are not part of the models fields
            raise ValidationError(
                {'message': serialization_errors['invalid_field']})

    records_count = records_query.count()
    first_page = f'{base_url}?page=1&limit={limit}'
    pages_count = ceil(records_count / limit)

    # when there are no records the default page_count should still be 1
    if pages_count == 0:
        pages_count = 1

    meta_message = None

    if current_page_count > pages_count:
        # If current_page_count > pages_count set current_page_count to pages_count
        current_page_count = pages_count
        first_page = f'{base_url}?page=1&limit={limit}'
        current_page_url = f'{base_url}?page={pages_count}&limit={limit}'
        meta_message = serialization_errors['last_page_returned']

    offset = (current_page_count - 1) * limit

    records = records_query.offset(offset).limit(limit)

    # pagination meta object
    pagination_object = {
        "firstPage": first_page,
        "currentPage": current_page_url,
        "nextPage": "",
        "previousPage": "",
        "page": current_page_count,
        "pagesCount": pages_count,
        "totalCount": records_count
    }

    previous_page_count = current_page_count - 1
    next_page_count = current_page_count + 1

    next_page_url = f'{base_url}?page={next_page_count}&limit={limit}'
    previous_page_url = f'{base_url}?page={previous_page_count}&limit={limit}'  # noqa

    if current_page_count > 1:
        # if current_page_count > 1 there should be a previous page url
        pagination_object['previousPage'] = previous_page_url

    if pages_count >= next_page_count:
        # if pages_count >= next_page_count there should be a next page url
        pagination_object['nextPage'] = next_page_url

    if meta_message:
        pagination_object['message'] = meta_message

    data = schema(many=True, exclude=exclude, only=only).dump(records).data

    return data, pagination_object
Example #29
0
 def create_asset_category(self, data):
     """Return asset category object after successful loading of data"""
     result = AssetCategory.query.filter_by(name=data['name']).first()
     if not result:
         return AssetCategory(**data)
     raise ValidationError({'message': 'Asset Category already exist'}, 409)