Skip to content

Commit

Permalink
Reference todo lists with slugs (#785)
Browse files Browse the repository at this point in the history
* Add slug field to the Todo model

* Add comment on a new slug field

* Update exporting data to spreadsheet to view todo's slug variable

* Add slug column to the beginning of the spreadsheet and handle saving this field to the todo

* Fix tests

* Small fix

* Fix tests and add a check for todo slug to be assigned

* Update comment text

* Add a test for a slug being added through the spreadsheet

* Get slug field using get method
  • Loading branch information
junusheva committed Jun 7, 2021
1 parent 67fe26e commit 7269d35
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 21 deletions.
18 changes: 18 additions & 0 deletions orchestra/migrations/0095_todo_slug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2021-06-01 10:41

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('orchestra', '0094_rename_team_messages_url'),
]

operations = [
migrations.AddField(
model_name='todo',
name='slug',
field=models.CharField(blank=True, max_length=255, null=True),
),
]
5 changes: 5 additions & 0 deletions orchestra/models/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,10 @@ class Todo(TodoMixin, BaseModel):
activity_log (str)
A JSON blob that records the user actions
with this todo
slug (str)
A unique identifier for each todo list item.
It is used to refer and retrieve specific
to-do items.
Constraints:
Expand Down Expand Up @@ -795,6 +799,7 @@ class Status(ChoicesEnum):
status = models.IntegerField(
default=Status.PENDING.value, choices=Status.choices())
additional_data = JSONField(default=dict)
slug = models.CharField(max_length=255, null=True, blank=True)


class TodoQA(TodoQAMixin, BaseModel):
Expand Down
34 changes: 21 additions & 13 deletions orchestra/tests/helpers/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,49 +39,54 @@
ITERATION_DURATION = timedelta(hours=1)
PICKUP_DELAY = timedelta(hours=1)

TODO_TEMPLATE_GOOD_CSV_TEXT = """Remove if,Skip if
[],[],the root
[],[],,todo parent 1
[],"[{""prop"": {""value"": true, ""operator"": ""==""}}]",,,todo child 1-1
"[{""prop"": {""value"": true, ""operator"": ""==""}}]",[],,todo parent 2
[],[],,,todo child 2-1
[],[],,,,todo child 2-1-1
[],[],,,todo child 2-2
TODO_TEMPLATE_GOOD_CSV_TEXT = """Slug,Remove if,Skip if
,[],[],the root
todo-parent-slug,[],[],,todo parent 1
,[],"[{""prop"": {""value"": true, ""operator"": ""==""}}]",,,todo child 1-1
,"[{""prop"": {""value"": true, ""operator"": ""==""}}]",[],,todo parent 2
,[],[],,,todo child 2-1
,[],[],,,,todo child 2-1-1
,[],[],,,todo child 2-2
"""

TODO_TEMPLATE_BAD_HEADER_CSV_TEXT = """,Remove if,Skip if
[],[],the root
"""

TODO_TEMPLATE_TWO_ENTRIES_CSV_TEXT = """Remove if,Skip if
[],[],the root,the second root
TODO_TEMPLATE_TWO_ENTRIES_CSV_TEXT = """Slug,Remove if,Skip if
,[],[],the root,the second root
"""

TODO_TEMPLATE_INVALID_PARENT_CSV_TEXT = """Remove if,Skip if
[],[],the root
[],[],,,,,,an impossible child
TODO_TEMPLATE_INVALID_PARENT_CSV_TEXT = """Slug,Remove if,Skip if
,[],[],the root
,[],[],,,,,,an impossible child
"""

TODO_TEMPLATE_NESTED_TODOS = {
'description': 'the root',
'slug': None,
'id': 0,
'items': [
{
'id': 2,
'description': 'todo parent 2',
'slug': None,
'items': [
{
'id': 22,
'description': 'todo child 2-2',
'slug': None,
'items': []
},
{
'id': 21,
'description': 'todo child 2-1',
'slug': None,
'items': [
{
'id': 211,
'description': 'todo child 2-1-1',
'slug': None,
'items': []
}
]
Expand All @@ -97,9 +102,11 @@
{
'id': 1,
'description': 'todo parent 1',
'slug': 'todo-parent-slug',
'items': [{
'id': 11,
'description': 'todo child 1-1',
'slug': None,
'items': [],
'skip_if': [{
'prop': {
Expand Down Expand Up @@ -246,6 +253,7 @@ class TodoFactory(factory.django.DjangoModelFactory):
lambda n: 'Title {}'.format(n))
start_by_datetime = None
due_datetime = None
slug = None

class Meta:
model = 'orchestra.Todo'
Expand Down
5 changes: 5 additions & 0 deletions orchestra/tests/test_project_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def post(url, *args, **kwargs):
return_value.text = json.dumps(return_value.json())
return return_value

todo_child_slug = 'todo-child-slug'
todolist_template = TodoListTemplateFactory(
slug=self.todolist_template_slug,
name=self.todolist_template_name,
Expand All @@ -84,10 +85,12 @@ def post(url, *args, **kwargs):
'description': 'todo parent',
'project': self.project.id,
'step': self.step.slug,
'slug': None,
'items': [{
'id': 2,
'project': self.project.id,
'step': self.step.slug,
'slug': todo_child_slug,
'description': 'todo child',
'items': []
}]
Expand All @@ -105,6 +108,8 @@ def post(url, *args, **kwargs):
additional_data)
self.assertEqual(result['success'], True)
self.assertEqual(len(result['todos']), 3)
self.assertEqual(result['todos'][0]['slug'], todo_child_slug)
self.assertEqual(result['todos'][1]['slug'], None)
for t in result['todos']:
self.assertEqual(t['template'], todolist_template.id)
self.assertEqual(t['section'], None)
Expand Down
14 changes: 12 additions & 2 deletions orchestra/tests/test_todos.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def _todo_data(title, status=Todo.Status.PENDING.value,
start_by=None,
due=None, parent_todo=None, template=None,
activity_log=str({'actions': []}), qa=None,
project=None, step=None, details=None, is_deleted=False):
project=None, step=None, details=None, is_deleted=False,
slug=None):
return {
'title': title,
'template': template,
Expand All @@ -55,6 +56,7 @@ def _todo_data(title, status=Todo.Status.PENDING.value,
'section': None,
'status': status,
'step': step,
'slug': slug,
'details': details,
'is_deleted': is_deleted
}
Expand Down Expand Up @@ -428,6 +430,7 @@ def setUp(self):
self.step = StepFactory(
slug='step-slug',
workflow_version=self.workflow_version)
self.slug = 'todo-item-slug'
self.project = ProjectFactory(
workflow_version=self.workflow_version)
self.project2 = ProjectFactory()
Expand Down Expand Up @@ -538,10 +541,12 @@ def test_update_todos_from_todolist_template_success(self):
'description': 'todo parent',
'project': self.project.id,
'step': self.step.slug,
'slug': None,
'items': [{
'id': 2,
'project': self.project.id,
'step': self.step.slug,
'slug': self.slug,
'description': 'todo child',
'items': []
}]
Expand All @@ -563,7 +568,8 @@ def test_update_todos_from_todolist_template_success(self):
template=todolist_template.id,
parent_todo=todos[1]['id'],
project=self.project.id,
step=self.step.slug),
step=self.step.slug,
slug=self.slug),
_todo_data('todo parent',
template=todolist_template.id,
parent_todo=todos[2]['id'],
Expand Down Expand Up @@ -645,9 +651,11 @@ def test_conditional_skip_remove_todos_from_template(self):
'id': 1,
'description': 'todo parent 1',
'project': self.project.id,
'slug': None,
'items': [{
'id': 2,
'description': 'todo child 1',
'slug': None,
'project': self.project.id,
'items': []
}],
Expand All @@ -661,9 +669,11 @@ def test_conditional_skip_remove_todos_from_template(self):
'id': 3,
'description': 'todo parent 2',
'project': self.project.id,
'slug': None,
'items': [{
'id': 4,
'description': 'todo child 2',
'slug': None,
'project': self.project.id,
'items': [],
'skip_if': [{
Expand Down
1 change: 1 addition & 0 deletions orchestra/todos/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def _add_template_todo(
project=project,
step=step,
title=template_todo['description'],
slug=template_todo.get('slug'),
template=todolist_template,
parent_todo=parent_todo,
status=status,
Expand Down
15 changes: 9 additions & 6 deletions orchestra/todos/import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

REMOVE_IF_HEADER = 'Remove if'
SKIP_IF_HEADER = 'Skip if'
SLUG_HEADER = 'Slug'


def _write_template_rows(writer, todo, depth):
Expand All @@ -25,7 +26,8 @@ def _write_template_rows(writer, todo, depth):
this row's description.
"""
writer.writerow(
[json.dumps(todo.get('remove_if', [])),
[todo.get('slug', ''),
json.dumps(todo.get('remove_if', [])),
json.dumps(todo.get('skip_if', []))] +
([''] * depth) +
[todo.get('description', '')])
Expand Down Expand Up @@ -62,7 +64,7 @@ def export_to_spreadsheet(todo_list_template):
"""
with NamedTemporaryFile(mode='w+', delete=False) as file:
writer = csv.writer(file)
writer.writerow([REMOVE_IF_HEADER, SKIP_IF_HEADER])
writer.writerow([SLUG_HEADER, REMOVE_IF_HEADER, SKIP_IF_HEADER])
_write_template_rows(writer, todo_list_template.todos, 0)
file.flush()
return _upload_csv_to_google(
Expand All @@ -84,7 +86,7 @@ def import_from_spreadsheet(todo_list_template, spreadsheet_url, request):
except ValueError as e:
raise TodoListTemplateValidationError(e)
header = next(reader)
if header[:2] != [REMOVE_IF_HEADER, SKIP_IF_HEADER]:
if header[:3] != [SLUG_HEADER, REMOVE_IF_HEADER, SKIP_IF_HEADER]:
raise TodoListTemplateValidationError(
'Unexpected header: {}'.format(header))
# The `i`'th entry in parent_items is current list of child to-dos
Expand All @@ -96,12 +98,13 @@ def import_from_spreadsheet(todo_list_template, spreadsheet_url, request):
for rowindex, row in enumerate(reader):
item = {
'id': rowindex,
'remove_if': json.loads(row[0] or '[]'),
'skip_if': json.loads(row[1] or '[]'),
'remove_if': json.loads(row[1] or '[]'),
'skip_if': json.loads(row[2] or '[]'),
'slug': row[0] or None,
'items': []
}
nonempty_columns = [(columnindex, text)
for columnindex, text in enumerate(row[2:])
for columnindex, text in enumerate(row[3:])
if text]
if len(nonempty_columns) == 0:
continue
Expand Down
1 change: 1 addition & 0 deletions orchestra/todos/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class Meta:
'section',
'project',
'step',
'slug',
'order',
'status',
'additional_data',
Expand Down

0 comments on commit 7269d35

Please sign in to comment.