Skip to content
This repository has been archived by the owner on Jan 20, 2024. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
Add support for task trees
  • Loading branch information
tdemin committed Jun 13, 2019
1 parent 27f71d5 commit ac75f1b
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 14 deletions.
1 change: 1 addition & 0 deletions project_amber/const.py
Expand Up @@ -15,3 +15,4 @@

MSG_TASK_NOT_FOUND = "This task does not exist"
MSG_TEXT_NOT_SPECIFIED = "No text specified"
MSG_TASK_DANGEROUS = "Potentially dangerous operation"
24 changes: 17 additions & 7 deletions project_amber/handlers/task.py
Expand Up @@ -27,7 +27,8 @@ def handle_task_request():
"id": 456,
"text": "Some text",
"status": 0,
"last_mod": 12346
"last_mod": 12346,
"parent_id": 123
}
]
}
Expand All @@ -53,6 +54,8 @@ def handle_task_request():
"status": task.status,
"last_mod": task.last_mod_time
})
if not task.parent_id is None:
tasksList[len(tasksList) - 1]["parent_id"] = task.parent_id
return dumps({
"tasks": tasksList
})
Expand All @@ -74,35 +77,42 @@ def handle_task_id_request(task_id: int):
does not exist):
```
{
"id": 1,
"id": 123,
"text": "Some text",
"status": 1,
"last_mod": 123456 // timestamp
"last_mod": 123456, // timestamp
"parent_id": 11 // if applicable
}
```
On PATCH and DELETE the user will get HTTP 200 with an empty response. On
PATCH, this request body is expected (all of the parameters are optional):
```
{
"text": "New task text",
"status": 1 // new status
"status": 1, // new status
"parent_id": 123 // if applicable
}
```
"""
user = handleChecks()
if request.method == "GET":
task = getTask(task_id, user.id)
return dumps({
response = {
"id": task.id,
"text": task.text,
"status": task.status,
"last_mod": task.last_mod_time
})
}
if not task.parent_id is None:
response["parent_id"] = task.parent_id
return dumps(response)
if request.method == "PATCH":
text = request.json.get("text")
status = request.json.get("status")
parent_id = request.json.get("parent_id")
# these are fine to be `None`
updateTask(task_id, user.id, text=text, status=status)
updateTask(task_id, user.id,
text=text, status=status, parent_id=parent_id)
return EMPTY_RESP
if request.method == "DELETE":
removeTask(task_id, user.id)
Expand Down
46 changes: 39 additions & 7 deletions project_amber/helpers/task.py
@@ -1,8 +1,8 @@
from time import time

from project_amber.const import MSG_TASK_NOT_FOUND
from project_amber.const import MSG_TASK_NOT_FOUND, MSG_TASK_DANGEROUS
from project_amber.db import db
from project_amber.errors import NotFound
from project_amber.errors import NotFound, BadRequest
from project_amber.models.task import Task

def addTask(text: str, status: int, uid: int) -> int:
Expand All @@ -11,16 +11,21 @@ def addTask(text: str, status: int, uid: int) -> int:
"""
task_time = time()
task = Task(owner=uid, text=text, creation_time=task_time, \
last_mod_time=task_time, status=status)
last_mod_time=task_time, status=status, gen=0)
db.session.add(task)
db.session.commit()
return task.id

def getTask(task_id: int, uid: int) -> Task:
def getTask(task_id: int, uid: int = None) -> Task:
"""
Returns an instance of `Task`, given the ID and the owner UID.
Returns an instance of `Task`, given the ID and the owner UID. If the UID
is `None`, returns the instance no matter who the owner is.
"""
task = db.session.query(Task).filter_by(id=task_id, owner=uid).one_or_none()
task_query = db.session.query(Task).filter_by(id=task_id)
if not uid is None:
task = task_query.filter_by(owner=uid).one_or_none()
else:
task = task_query.one_or_none()
if task is None:
raise NotFound(MSG_TASK_NOT_FOUND)
return task
Expand All @@ -36,6 +41,21 @@ def getTasks(uid: int, text: str = None) -> list:
return req.all()
return req.filter(Task.text.ilike("%{0}%".format(text))).all()

def updateChildren(task_id: int):
"""
Updates generations for the children nodes of a task subtree. This is a very
expensive recursive operation.
"""
task = getTask(task_id, None)
if not task.parent_id is None:
parent = getTask(task.parent_id, None)
task.gen = parent.gen + 1
else:
task.gen = 0
children = db.session.query(Task).filter_by(parent_id=task_id).all()
for child in children:
updateChildren(child.id)

def updateTask(task_id: int, uid: int, **kwargs) -> int:
"""
Updates the task details. Returns its ID.
Expand All @@ -45,14 +65,26 @@ def updateTask(task_id: int, uid: int, **kwargs) -> int:
task.text = kwargs["text"]
if "status" in kwargs and not kwargs["status"] is None:
task.status = kwargs["status"]
if "parent_id" in kwargs and not kwargs["parent_id"] is None:
# TODO: we limit changing parent IDs to prevent circular deps,
# can this be done better?
new_parent = getTask(kwargs["parent_id"], uid)
if new_parent.gen > task.gen:
raise BadRequest(MSG_TASK_DANGEROUS)
task.parent_id = new_parent.id
updateChildren(task.id)
task.last_mod_time = time()
db.session.commit()
return task_id

def removeTask(task_id: int, uid: int) -> int:
"""
Removes a task. Returns its ID on success.
Removes a task. Returns its ID on success. Removes all children as well. Is
recursive, and so is expensive.
"""
children = db.session.query(Task).filter_by(parent_id=task_id).all()
for child in children:
removeTask(child.id, uid)
task = getTask(task_id, uid)
db.session.delete(task)
db.session.commit()
Expand Down
9 changes: 9 additions & 0 deletions project_amber/models/task.py
Expand Up @@ -8,9 +8,18 @@ class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
text = db.Column(db.String(65536)) # TODO: probably subject to increase
gen = db.Column(db.Integer, nullable=False)
parent_id = db.Column(db.Integer, db.ForeignKey("task.id"))
status = db.Column(db.Integer, nullable=False)
creation_time = db.Column(db.Integer, nullable=False)
last_mod_time = db.Column(db.Integer, nullable=False)
def is_child(self) -> bool:
"""
Helper method. Simply checks whether the task is of gen 0 or not.
"""
if self.gen > 0:
return True
return False
def __repr__(self):
return "<Task id='%d' owner='%d' text='%s' status='%d' created='%d'>" \
% self.id, self.owner, self.text, self.status, self.creation_time

0 comments on commit ac75f1b

Please sign in to comment.