Skip to content
Merged
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
fix(time-entries): add owner_id in data when create time-entry
  • Loading branch information
Angeluz-07 committed May 5, 2020
commit e1ed69331d5cde9a97475b1c0daebce4fdff7418
199 changes: 136 additions & 63 deletions time_tracker_api/time_entries/time_entries_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
from azure.cosmos import PartitionKey
from flask_restplus._http import HTTPStatus

from commons.data_access_layer.cosmos_db import CosmosDBDao, CosmosDBRepository, CustomError, current_datetime_str, \
CosmosDBModel, get_date_range_of_month, get_current_year, get_current_month
from commons.data_access_layer.cosmos_db import (
CosmosDBDao,
CosmosDBRepository,
CustomError,
current_datetime_str,
CosmosDBModel,
get_date_range_of_month,
get_current_year,
get_current_month,
)
from commons.data_access_layer.database import EventContext
from time_tracker_api.database import CRUDDao, APICosmosDBDao
from time_tracker_api.security import current_user_id
Expand Down Expand Up @@ -34,10 +42,8 @@ def restart(self, id: str):
'id': 'time_entry',
'partition_key': PartitionKey(path='/tenant_id'),
'unique_key_policy': {
'uniqueKeys': [
{'paths': ['/owner_id', '/end_date', '/deleted']},
]
}
'uniqueKeys': [{'paths': ['/owner_id', '/end_date', '/deleted']}]
},
}


Expand Down Expand Up @@ -66,15 +72,20 @@ def __repr__(self):
return '<Time Entry %r>' % self.start_date # pragma: no cover

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


class TimeEntryCosmosDBRepository(CosmosDBRepository):
def __init__(self):
CosmosDBRepository.__init__(self, container_id=container_definition['id'],
partition_key_attribute='tenant_id',
order_fields=['start_date DESC'],
mapper=TimeEntryCosmosDBModel)
CosmosDBRepository.__init__(
self,
container_id=container_definition['id'],
partition_key_attribute='tenant_id',
order_fields=['start_date DESC'],
mapper=TimeEntryCosmosDBModel,
)

@staticmethod
def create_sql_ignore_id_condition(id: str):
Expand All @@ -93,7 +104,9 @@ def create_sql_date_range_filter(date_range: dict) -> str:
else:
return ''

def find_all(self, event_context: EventContext, conditions: dict, date_range: dict):
def find_all(
self, event_context: EventContext, conditions: dict, date_range: dict
):
custom_sql_conditions = []
custom_sql_conditions.append(
self.create_sql_date_range_filter(date_range)
Expand All @@ -106,9 +119,9 @@ def find_all(self, event_context: EventContext, conditions: dict, date_range: di
event_context=event_context,
conditions=conditions,
custom_sql_conditions=custom_sql_conditions,
custom_params=custom_params
custom_params=custom_params,
)

def on_create(self, new_item_data: dict, event_context: EventContext):
CosmosDBRepository.on_create(self, new_item_data, event_context)

Expand All @@ -122,8 +135,16 @@ def on_update(self, updated_item_data: dict, event_context: EventContext):
self.validate_data(updated_item_data, event_context)
self.replace_empty_value_per_none(updated_item_data)

def find_interception_with_date_range(self, start_date, end_date, owner_id, tenant_id,
ignore_id=None, visible_only=True, mapper: Callable = None):
def find_interception_with_date_range(
self,
start_date,
end_date,
owner_id,
tenant_id,
ignore_id=None,
visible_only=True,
mapper: Callable = None,
):
conditions = {
"owner_id": owner_id,
"tenant_id": tenant_id,
Expand All @@ -136,37 +157,54 @@ def find_interception_with_date_range(self, start_date, end_date, owner_id, tena
params.extend(self.generate_params(conditions))
result = self.container.query_items(
query="""
SELECT * FROM c WHERE ((c.start_date BETWEEN @start_date AND @end_date)
OR (c.end_date BETWEEN @start_date AND @end_date))
{conditions_clause} {ignore_id_condition} {visibility_condition} {order_clause}
""".format(ignore_id_condition=self.create_sql_ignore_id_condition(ignore_id),
visibility_condition=self.create_sql_condition_for_visibility(visible_only),
conditions_clause=self.create_sql_where_conditions(conditions),
order_clause=self.create_sql_order_clause()),
SELECT * FROM c
WHERE ((c.start_date BETWEEN @start_date AND @end_date)
OR (c.end_date BETWEEN @start_date AND @end_date))
{conditions_clause}
{ignore_id_condition}
{visibility_condition}
{order_clause}
""".format(
ignore_id_condition=self.create_sql_ignore_id_condition(
ignore_id
),
visibility_condition=self.create_sql_condition_for_visibility(
visible_only
),
conditions_clause=self.create_sql_where_conditions(conditions),
order_clause=self.create_sql_order_clause(),
),
parameters=params,
partition_key=tenant_id)
partition_key=tenant_id,
)

function_mapper = self.get_mapper_or_dict(mapper)
return list(map(function_mapper, result))

def find_running(self, tenant_id: str, owner_id: str, mapper: Callable = None):
def find_running(
self, tenant_id: str, owner_id: str, mapper: Callable = None
):
conditions = {
"owner_id": owner_id,
"tenant_id": tenant_id,
}
result = self.container.query_items(
query="""
SELECT * from c
WHERE (NOT IS_DEFINED(c.end_date) OR c.end_date = null)
{conditions_clause} {visibility_condition}
OFFSET 0 LIMIT 1
SELECT * from c
WHERE (NOT IS_DEFINED(c.end_date) OR c.end_date = null)
{conditions_clause}
{visibility_condition}
OFFSET 0 LIMIT 1
""".format(
visibility_condition=self.create_sql_condition_for_visibility(True),
visibility_condition=self.create_sql_condition_for_visibility(
True
),
conditions_clause=self.create_sql_where_conditions(conditions),
),
parameters=self.generate_params(conditions),
partition_key=tenant_id,
max_item_count=1)
max_item_count=1,
)

function_mapper = self.get_mapper_or_dict(mapper)
return function_mapper(next(result))
Expand All @@ -176,20 +214,28 @@ def validate_data(self, data, event_context: EventContext):

if data.get('end_date') is not None:
if data['end_date'] <= start_date:
raise CustomError(HTTPStatus.BAD_REQUEST,
description="You must end the time entry after it started")
raise CustomError(
HTTPStatus.BAD_REQUEST,
description="You must end the time entry after it started",
)
if data['end_date'] >= current_datetime_str():
raise CustomError(HTTPStatus.BAD_REQUEST,
description="You cannot end a time entry in the future")

collision = self.find_interception_with_date_range(start_date=start_date,
end_date=data.get('end_date'),
owner_id=event_context.user_id,
tenant_id=event_context.tenant_id,
ignore_id=data.get('id'))
raise CustomError(
HTTPStatus.BAD_REQUEST,
description="You cannot end a time entry in the future",
)

collision = self.find_interception_with_date_range(
start_date=start_date,
end_date=data.get('end_date'),
owner_id=event_context.user_id,
tenant_id=event_context.tenant_id,
ignore_id=data.get('id'),
)
if len(collision) > 0:
raise CustomError(HTTPStatus.UNPROCESSABLE_ENTITY,
description="There is another time entry in that date range")
raise CustomError(
HTTPStatus.UNPROCESSABLE_ENTITY,
description="There is another time entry in that date range",
)


class TimeEntriesCosmosDBDao(APICosmosDBDao, TimeEntriesDao):
Expand All @@ -198,66 +244,93 @@ def __init__(self, repository):

@classmethod
def check_whether_current_user_owns_item(cls, data: dict):
if data.get('owner_id') is not None \
and data.get('owner_id') != cls.current_user_id():
raise CustomError(HTTPStatus.FORBIDDEN,
"The current user is not the owner of this time entry")
if (
data.get('owner_id') is not None
and data.get('owner_id') != cls.current_user_id()
):
raise CustomError(
HTTPStatus.FORBIDDEN,
"The current user is not the owner of this time entry",
)

@classmethod
def checks_owner_and_is_not_stopped(cls, data: dict):
cls.check_whether_current_user_owns_item(data)

if data.get('end_date') is not None:
raise CustomError(HTTPStatus.UNPROCESSABLE_ENTITY, "The specified time entry is already stopped")
raise CustomError(
HTTPStatus.UNPROCESSABLE_ENTITY,
"The specified time entry is already stopped",
)

@classmethod
def checks_owner_and_is_not_started(cls, data: dict):
cls.check_whether_current_user_owns_item(data)

if data.get('end_date') is None:
raise CustomError(HTTPStatus.UNPROCESSABLE_ENTITY, "The specified time entry is already running")
raise CustomError(
HTTPStatus.UNPROCESSABLE_ENTITY,
"The specified time entry is already running",
)

def get_all(self, conditions: dict = {}) -> list:
event_ctx = self.create_event_context("read-many")
conditions.update({"owner_id": event_ctx.user_id})

date_range = self.handle_date_filter_args(args=conditions)
return self.repository.find_all(event_ctx,
conditions=conditions,
date_range=date_range)
return self.repository.find_all(
event_ctx, conditions=conditions, date_range=date_range
)

def get(self, id):
event_ctx = self.create_event_context("read")
return self.repository.find(id, event_ctx, peeker=self.check_whether_current_user_owns_item)
return self.repository.find(
id, event_ctx, peeker=self.check_whether_current_user_owns_item
)

def create(self, data: dict):
event_ctx = self.create_event_context("create")
data['owner_id'] = event_ctx.user_id
return self.repository.create(data, event_ctx)

def update(self, id, data: dict, description=None):
event_ctx = self.create_event_context("update", description)
return self.repository.partial_update(id, data, event_ctx,
peeker=self.check_whether_current_user_owns_item)
return self.repository.partial_update(
id,
data,
event_ctx,
peeker=self.check_whether_current_user_owns_item,
)

def stop(self, id):
event_ctx = self.create_event_context("update", "Stop time entry")
return self.repository.partial_update(id, {
'end_date': current_datetime_str()
}, event_ctx, peeker=self.checks_owner_and_is_not_stopped)
return self.repository.partial_update(
id,
{'end_date': current_datetime_str()},
event_ctx,
peeker=self.checks_owner_and_is_not_stopped,
)

def restart(self, id):
event_ctx = self.create_event_context("update", "Restart time entry")
return self.repository.partial_update(id, {
'end_date': None
}, event_ctx, peeker=self.checks_owner_and_is_not_started)
return self.repository.partial_update(
id,
{'end_date': None},
event_ctx,
peeker=self.checks_owner_and_is_not_started,
)

def delete(self, id):
event_ctx = self.create_event_context("delete")
self.repository.delete(id, event_ctx, peeker=self.check_whether_current_user_owns_item)
self.repository.delete(
id, event_ctx, peeker=self.check_whether_current_user_owns_item
)

def find_running(self):
event_ctx = self.create_event_context("find_running")
return self.repository.find_running(event_ctx.tenant_id, event_ctx.user_id)
return self.repository.find_running(
event_ctx.tenant_id, event_ctx.user_id
)

@staticmethod
def handle_date_filter_args(args: dict) -> dict:
Expand Down