General test suite for CSV importer
This commit is contained in:
parent
900bc71284
commit
2e72c9f818
6 changed files with 117 additions and 14 deletions
|
@ -80,7 +80,7 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
'django': {
|
'django': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': 'DEBUG',
|
'level': 'WARNING',
|
||||||
'propagate': True,
|
'propagate': True,
|
||||||
},
|
},
|
||||||
'django.request': {
|
'django.request': {
|
||||||
|
|
22
todo/migrations/0009_priority_optional.py
Normal file
22
todo/migrations/0009_priority_optional.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 2.1.7 on 2019-03-18 23:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('todo', '0008_mail_tracker'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='task',
|
||||||
|
options={'ordering': ['priority', 'created_date']},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='task',
|
||||||
|
name='priority',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -82,7 +82,7 @@ class Task(models.Model):
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
note = models.TextField(blank=True, null=True)
|
note = models.TextField(blank=True, null=True)
|
||||||
priority = models.PositiveIntegerField()
|
priority = models.PositiveIntegerField(blank=True, null=True)
|
||||||
|
|
||||||
# Has due date for an instance of this object passed?
|
# Has due date for an instance of this object passed?
|
||||||
def overdue_status(self):
|
def overdue_status(self):
|
||||||
|
@ -115,7 +115,7 @@ class Task(models.Model):
|
||||||
self.delete()
|
self.delete()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["priority"]
|
ordering = ["priority", "created_date"]
|
||||||
|
|
||||||
|
|
||||||
class Comment(models.Model):
|
class Comment(models.Model):
|
||||||
|
|
|
@ -66,21 +66,22 @@ class CSVImporter:
|
||||||
if newrow:
|
if newrow:
|
||||||
# newrow at this point is fully validated, and all FK relations exist,
|
# newrow at this point is fully validated, and all FK relations exist,
|
||||||
# e.g. `newrow.get("Assigned To")`, is a Django User instance.
|
# e.g. `newrow.get("Assigned To")`, is a Django User instance.
|
||||||
|
assignee = newrow.get("Assigned To") if newrow.get("Assigned To") else None
|
||||||
|
created_date = newrow.get("Created Date") if newrow.get("Created Date") else datetime.datetime.today()
|
||||||
|
due_date = newrow.get("Due Date") if newrow.get("Due Date") else None
|
||||||
|
priority = newrow.get("Priority") if newrow.get("Priority") else None
|
||||||
|
|
||||||
obj, created = Task.objects.update_or_create(
|
obj, created = Task.objects.update_or_create(
|
||||||
created_by=newrow.get("Created By"),
|
created_by=newrow.get("Created By"),
|
||||||
task_list=newrow.get("Task List"),
|
task_list=newrow.get("Task List"),
|
||||||
title=newrow.get("Title"),
|
title=newrow.get("Title"),
|
||||||
defaults={
|
defaults={
|
||||||
"assigned_to": newrow.get("Assigned To")
|
"assigned_to": assignee,
|
||||||
if newrow.get("Assigned To")
|
|
||||||
else None,
|
|
||||||
"completed": newrow.get("Completed"),
|
"completed": newrow.get("Completed"),
|
||||||
"created_date": newrow.get("Created Date")
|
"created_date": created_date,
|
||||||
if newrow.get("Created Date")
|
"due_date": due_date,
|
||||||
else None,
|
|
||||||
"due_date": newrow.get("Due Date") if newrow.get("Due Date") else None,
|
|
||||||
"note": newrow.get("Note"),
|
"note": newrow.get("Note"),
|
||||||
"priority": newrow.get("Priority"),
|
"priority": priority,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.upsert_count += 1
|
self.upsert_count += 1
|
||||||
|
@ -90,7 +91,7 @@ class CSVImporter:
|
||||||
)
|
)
|
||||||
self.upserts.append(msg)
|
self.upserts.append(msg)
|
||||||
|
|
||||||
self.summaries.append(f"\nProcessed {self.line_count} CSV rows")
|
self.summaries.append(f"Processed {self.line_count} CSV rows")
|
||||||
self.summaries.append(f"Upserted {self.upsert_count} rows")
|
self.summaries.append(f"Upserted {self.upsert_count} rows")
|
||||||
self.summaries.append(f"Skipped {self.line_count - self.upsert_count} rows")
|
self.summaries.append(f"Skipped {self.line_count - self.upsert_count} rows")
|
||||||
|
|
||||||
|
@ -117,6 +118,7 @@ class CSVImporter:
|
||||||
|
|
||||||
# #######################
|
# #######################
|
||||||
# If specified, Assignee must exist
|
# If specified, Assignee must exist
|
||||||
|
assignee = None # Perfectly valid
|
||||||
if row.get("Assigned To"):
|
if row.get("Assigned To"):
|
||||||
assigned = get_user_model().objects.filter(username=row.get("Assigned To"))
|
assigned = get_user_model().objects.filter(username=row.get("Assigned To"))
|
||||||
if assigned.exists():
|
if assigned.exists():
|
||||||
|
@ -124,8 +126,6 @@ class CSVImporter:
|
||||||
else:
|
else:
|
||||||
msg = f"Missing or invalid task assignee {row.get('Assigned To')}"
|
msg = f"Missing or invalid task assignee {row.get('Assigned To')}"
|
||||||
row_errors.append(msg)
|
row_errors.append(msg)
|
||||||
else:
|
|
||||||
assignee = None # Perfectly valid
|
|
||||||
|
|
||||||
# #######################
|
# #######################
|
||||||
# Group must exist
|
# Group must exist
|
||||||
|
@ -134,6 +134,7 @@ class CSVImporter:
|
||||||
except Group.DoesNotExist:
|
except Group.DoesNotExist:
|
||||||
msg = f"Could not find group {row.get('Group')}."
|
msg = f"Could not find group {row.get('Group')}."
|
||||||
row_errors.append(msg)
|
row_errors.append(msg)
|
||||||
|
target_group = None
|
||||||
|
|
||||||
# #######################
|
# #######################
|
||||||
# Task creator must be in the target group
|
# Task creator must be in the target group
|
||||||
|
|
4
todo/tests/data/csv_import_data.csv
Normal file
4
todo/tests/data/csv_import_data.csv
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Title,Group,Task List,Created By,Created Date,Due Date,Completed,Assigned To,Note,Priority
|
||||||
|
Make dinner,Workgroup One,Zip,u1,,2019-06-14,No,u1,This is note one,3
|
||||||
|
Bake bread,Workgroup One,Zip,u1,2012-03-14,,Yes,,,
|
||||||
|
Bring dessert,Workgroup Two,Zap,u2,2015-06-248,,,,This is note two,77
|
|
76
todo/tests/test_import.py
Normal file
76
todo/tests/test_import.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from todo.models import Task, TaskList
|
||||||
|
from todo.operations.csv_importer import CSVImporter
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Exercise the "Import CSV" feature, which shares a functional module that serves
|
||||||
|
both the `import_csv` management command and the "Import CSV" web interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.fixture
|
||||||
|
def import_setup(todo_setup):
|
||||||
|
app_path = Path(__file__).resolve().parent.parent
|
||||||
|
filepath = Path(app_path, "tests/data/csv_import_data.csv")
|
||||||
|
with filepath.open(mode="r", encoding="utf-8-sig") as fileobj:
|
||||||
|
importer = CSVImporter()
|
||||||
|
results = importer.upsert(fileobj, as_string_obj=True)
|
||||||
|
assert results
|
||||||
|
return {"results": results}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_setup(todo_setup):
|
||||||
|
"""Confirm what we should have from conftest, prior to importing CSV."""
|
||||||
|
assert TaskList.objects.all().count() == 2
|
||||||
|
assert Task.objects.all().count() == 6
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_import(import_setup):
|
||||||
|
"""Confirm that importing the CSV gave us two more rows (one should have been skipped)"""
|
||||||
|
assert Task.objects.all().count() == 8 # 2 out of 3 rows should have imported; one was an error
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_report(import_setup):
|
||||||
|
"""Confirm that importing the CSV returned expected report messaging."""
|
||||||
|
|
||||||
|
results = import_setup["results"]
|
||||||
|
|
||||||
|
assert "Processed 3 CSV rows" in results["summaries"]
|
||||||
|
assert "Upserted 2 rows" in results["summaries"]
|
||||||
|
assert "Skipped 1 rows" in results["summaries"]
|
||||||
|
|
||||||
|
assert isinstance(results["errors"], list)
|
||||||
|
assert len(results["errors"]) == 1
|
||||||
|
assert (
|
||||||
|
results["errors"][0].get(3)[0]
|
||||||
|
== "Could not convert Created Date 2015-06-248 to valid date instance"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'Upserted task 7: "Make dinner" in list "Zip" (group "Workgroup One")' in results["upserts"]
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
'Upserted task 8: "Bake bread" in list "Zip" (group "Workgroup One")' in results["upserts"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_inserted_row(import_setup):
|
||||||
|
"""Confirm that one inserted row is exactly right."""
|
||||||
|
task = Task.objects.get(title="Make dinner", task_list__name="Zip")
|
||||||
|
assert task.created_by == get_user_model().objects.get(username="u1")
|
||||||
|
assert task.assigned_to == get_user_model().objects.get(username="u1")
|
||||||
|
assert not task.completed
|
||||||
|
assert task.note == "This is note one"
|
||||||
|
assert task.priority == 3
|
||||||
|
assert task.created_date == datetime.datetime.today().date()
|
Loading…
Add table
Add a link
Reference in a new issue