Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Implement the api ns for time entries
  • Loading branch information
EliuX committed Mar 25, 2020
commit 21811b3ea8dc17a9842dd539fb04297c41541124
2 changes: 2 additions & 0 deletions requirements/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Flask-Script==2.0.6

# SQL database (MS SQL)
pyodbc==4.0.30
SQLAlchemy==1.3.15
SQLAlchemy-Utils==0.36.3
flask_sqlalchemy==2.4.1

# Handling requests
Expand Down
2 changes: 1 addition & 1 deletion tests/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


class PersonSQLModel(db.Model, AuditedSQLModel):
__tablename__ = 'tests'
__tablename__ = 'test'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=False, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
Expand Down
20 changes: 20 additions & 0 deletions time_tracker_api/activities/activities_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from time_tracker_api.database import CRUDDao


class ActivitiesDao(CRUDDao):
pass


def create_dao() -> ActivitiesDao:
from time_tracker_api.sql_repository import db
from time_tracker_api.sql_repository import SQLCRUDDao, AuditedSQLModel, SQLModel

class ActivitySQLModel(db.Model, SQLModel, AuditedSQLModel):
__tablename__ = 'activity'
id = db.Column(db.Integer, primary_key=True)

class ActivitiesSQLDao(SQLCRUDDao):
def __init__(self):
SQLCRUDDao.__init__(self, ActivitySQLModel)

return ActivitiesSQLDao()
3 changes: 3 additions & 0 deletions time_tracker_api/activities/activities_namespace.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from faker import Faker
from flask_restplus import fields, Resource, Namespace

from time_tracker_api.activities.activities_model import create_dao
from time_tracker_api.api import audit_fields

faker = Faker()
Expand Down Expand Up @@ -40,6 +41,8 @@
activity_response_fields
)

activities_dao = create_dao()


@ns.route('')
class Activities(Resource):
Expand Down
4 changes: 3 additions & 1 deletion time_tracker_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from faker import Faker
from flask import current_app as app
from flask_restplus import Api, fields
from flask_restplus.fields import Raw, to_marshallable_type, MarshallingError
from werkzeug.exceptions import Conflict
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how the two last imports are being used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will remove them


faker = Faker()

Expand Down Expand Up @@ -63,7 +65,7 @@
def handle_db_integrity_error(e):
"""Handles errors related to data consistency"""
if e.code == 'gkpj':
return {'message': 'It already exists.'}, 409
return {'message': 'It already exists or references data that does not exist.'}, 409
else:
return {'message': 'Data integrity issues.'}, 409

Expand Down
2 changes: 1 addition & 1 deletion time_tracker_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ProductionConfig(Config):


class AzureConfig(SQLConfig):
pass
DATABASE_URI = os.environ.get('SQLAZURECONNSTR_DATABASE_URI')


class AzureDevelopmentConfig(DevelopmentConfig, AzureConfig):
Expand Down
18 changes: 1 addition & 17 deletions time_tracker_api/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,7 @@ def __call__(self, *args, **kwargs):


class DatabaseModel:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said in another comment. https://github.com/ioet/time-tracker-api/pull/43/files#r398215021 I think this class is not needed anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will remove then ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

def to_dto(self):
return self


def convert_result_to_dto(f):
def convert_if_necessary(result):
if hasattr(result, 'to_dto'):
return result.to_dto()
elif issubclass(type(result), list):
return list(map(convert_if_necessary, result))
return result

def to_dto(*args, **kw):
result = f(*args, **kw)
return convert_if_necessary(result)

return to_dto
pass


seeder: Seeder = None
Expand Down
6 changes: 2 additions & 4 deletions time_tracker_api/projects/projects_model.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import enum

from flask import Flask

from time_tracker_api.database import CRUDDao


Expand All @@ -18,12 +16,12 @@ class ProjectDao(CRUDDao):
pass


def create_dao(app: Flask) -> ProjectDao:
def create_dao() -> ProjectDao:
from time_tracker_api.sql_repository import db
from time_tracker_api.sql_repository import SQLCRUDDao, AuditedSQLModel, SQLModel

class ProjectSQLModel(db.Model, SQLModel, AuditedSQLModel):
__tablename__ = 'projects'
__tablename__ = 'project'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True, nullable=False)
description = db.Column(db.String(250), unique=False, nullable=False)
Expand Down
30 changes: 15 additions & 15 deletions time_tracker_api/projects/projects_namespace.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from faker import Faker
from flask_restplus import Namespace, Resource, fields
from flask_restplus._http import HTTPStatus

from time_tracker_api import flask_app
from time_tracker_api.api import audit_fields
from time_tracker_api.projects.projects_model import PROJECT_TYPE, create_dao

Expand Down Expand Up @@ -57,51 +57,51 @@
project_response_fields
)

project_dao = create_dao(flask_app)
project_dao = create_dao()


@ns.route('')
class Projects(Resource):
@ns.doc('list_projects')
@ns.marshal_list_with(project, code=200)
@ns.marshal_list_with(project)
def get(self):
"""List all projects"""
return project_dao.get_all(), 200
return project_dao.get_all()

@ns.doc('create_project')
@ns.response(409, 'This project already exists')
@ns.response(400, 'Bad request')
@ns.response(HTTPStatus.CONFLICT, 'This project already exists')
@ns.response(HTTPStatus.BAD_REQUEST, 'Invalid format or structure '
'of the attributes of the project')
@ns.expect(project_input)
@ns.marshal_with(project, code=201)
@ns.marshal_with(project, code=HTTPStatus.CREATED)
def post(self):
"""Create a project"""
return project_dao.create(ns.payload), 201
return project_dao.create(ns.payload)


@ns.route('/<string:id>')
@ns.response(404, 'Project not found')
@ns.response(HTTPStatus.NOT_FOUND, 'This project does not exist')
@ns.response(HTTPStatus.UNPROCESSABLE_ENTITY, 'The data has an invalid format')
@ns.param('id', 'The project identifier')
class Project(Resource):
@ns.doc('get_project')
@ns.response(422, 'The id has an invalid format')
@ns.response(HTTPStatus.UNPROCESSABLE_ENTITY, 'The id has an invalid format')
@ns.marshal_with(project)
def get(self, id):
"""Get a project"""
return project_dao.get(id)

@ns.doc('update_project')
@ns.response(422, 'The data has an invalid format.')
@ns.response(409, 'This project already exists')
@ns.response(HTTPStatus.CONFLICT, 'A project already exists with this new data')
@ns.expect(project_input)
@ns.marshal_with(project)
def put(self, id):
"""Update a project"""
return project_dao.update(id, ns.payload)

@ns.doc('delete_project')
@ns.response(204, 'Project deleted successfully')
@ns.response(422, 'The id has an invalid format')
@ns.response(HTTPStatus.NO_CONTENT, 'Project successfully deleted')
def delete(self, id):
"""Delete a project"""
project_dao.delete(id)
return None, 204
return None, HTTPStatus.NO_CONTENT
16 changes: 5 additions & 11 deletions time_tracker_api/sql_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

from time_tracker_api.database import CRUDDao, Seeder, DatabaseModel, convert_result_to_dto
from time_tracker_api.database import CRUDDao, Seeder, DatabaseModel
from time_tracker_api.security import current_user_id

LIST_SEPARATOR_CHAR = ";"


db: SQLAlchemy = None
SQLModel = None
AuditedSQLModel = None
Expand All @@ -27,12 +30,7 @@ def init_app(app: Flask) -> None:
db = SQLAlchemy(app)

global SQLModel

class SQLModelClass(DatabaseModel):
def to_dto(self):
return self

SQLModel = SQLModelClass
SQLModel = DatabaseModel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking at changes of DatabaseModel Class, and tracing use of SQLModel It seems to me that these two classes are not useful anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever particularity that has to be inherited by the SQLModels should be done throw the SQL model. The DatabaseModel is platform-agnostic. Before we had this to_dto function that was required by such models. This is basically an encapsulation.


global AuditedSQLModel

Expand Down Expand Up @@ -85,19 +83,15 @@ class SQLCRUDDao(CRUDDao):
def __init__(self, model: type):
self.repository = SQLRepository(model)

@convert_result_to_dto
def get_all(self) -> list:
return self.repository.find_all()

@convert_result_to_dto
def get(self, id):
return self.repository.find(id)

@convert_result_to_dto
def create(self, element: dict):
return self.repository.create(element)

@convert_result_to_dto
def update(self, id, data: dict):
return self.repository.update(id, data)

Expand Down
54 changes: 50 additions & 4 deletions time_tracker_api/time_entries/time_entries_model.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
from flask import Flask
from sqlalchemy_utils import ScalarListType

from time_tracker_api.database import CRUDDao

COMMENTS_MAX_NUMBER_CHARS = 500


class TimeEntriesDao(CRUDDao):
pass


def create_dao(app: Flask) -> TimeEntriesDao:
# TODO Create implementation(s)
return TimeEntriesDao()
def create_dao() -> TimeEntriesDao:
from time_tracker_api.sql_repository import db
from time_tracker_api.sql_repository import SQLCRUDDao, AuditedSQLModel, SQLModel

class TimeEntrySQLModel(db.Model, SQLModel, AuditedSQLModel):
__tablename__ = 'time_entry'
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(COMMENTS_MAX_NUMBER_CHARS))
start_date = db.Column(db.DateTime, server_default=db.func.now())
end_date = db.Column(db.DateTime)
project_id = db.Column(db.Integer,
db.ForeignKey('project.id'),
nullable=False)
activity_id = db.Column(db.Integer,
db.ForeignKey('activity.id'),
nullable=False)

technologies = db.Column(ScalarListType())

@property
def running(self):
return self.end_date == None

# @property
# def technologies(self):
# return [str(x) for x in self._technologies.split(';')]
#
# @technologies.setter
# def technologies(self, value):
# if value is Iterable:
# self._technologies = LIST_SEPARATOR_CHAR.join(value)
# elif type(value) == str:
# self._technologies = value
# else:
# raise UnprocessableEntity

def __repr__(self):
return '<Time Entry %r>' % self.start_date

def __str___(self):
return "Time Entry started in \"%s\"" % self.start_date

class TimeEntriesSQLDao(SQLCRUDDao):
def __init__(self):
SQLCRUDDao.__init__(self, TimeEntrySQLModel)

return TimeEntriesSQLDao()
Loading