Formatting

This commit is contained in:
Scot Hacker 2019-04-12 00:09:01 -07:00
parent 4a385bde6b
commit befc7ad2cd
28 changed files with 253 additions and 311 deletions

View file

@ -10,6 +10,4 @@ For your project, ignore this file and add
to your site's urlconf.
"""
urlpatterns = [
path('lists/', include('todo.urls')),
]
urlpatterns = [path("lists/", include("todo.urls"))]

View file

@ -2,11 +2,7 @@ import os
DEBUG = (True,)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3"
}
}
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
@ -65,28 +61,12 @@ TEMPLATES = [
]
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
'django': {
'handlers': ['console'],
'level': 'WARNING',
'propagate': True,
},
'django.request': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
"version": 1,
"disable_existing_loggers": False,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"loggers": {
"": {"handlers": ["console"], "level": "DEBUG", "propagate": True},
"django": {"handlers": ["console"], "level": "WARNING", "propagate": True},
"django.request": {"handlers": ["console"], "level": "DEBUG", "propagate": True},
},
}

View file

@ -1,12 +1,12 @@
"""
A multi-user, multi-group task management and assignment system for Django.
"""
__version__ = '2.4.6'
__version__ = "2.4.6"
__author__ = 'Scot Hacker'
__email__ = 'shacker@birdhouse.org'
__author__ = "Scot Hacker"
__email__ = "shacker@birdhouse.org"
__url__ = 'https://github.com/shacker/django-todo'
__license__ = 'BSD License'
__url__ = "https://github.com/shacker/django-todo"
__license__ = "BSD License"
from . import check

View file

@ -11,9 +11,7 @@ def dal_check(app_configs, **kwargs):
return []
errors = []
missing_apps = {'dal', 'dal_select2'} - set(settings.INSTALLED_APPS)
missing_apps = {"dal", "dal_select2"} - set(settings.INSTALLED_APPS)
for missing_app in missing_apps:
errors.append(
Error('{} needs to be in INSTALLED_APPS'.format(missing_app))
)
errors.append(Error("{} needs to be in INSTALLED_APPS".format(missing_app)))
return errors

View file

@ -11,5 +11,6 @@ except ImportError:
HAS_TASK_MERGE = False
if HAS_AUTOCOMPLETE:
import dal.autocomplete
if getattr(dal.autocomplete, 'Select2QuerySetView', None) is not None:
if getattr(dal.autocomplete, "Select2QuerySetView", None) is not None:
HAS_TASK_MERGE = True

View file

@ -1,8 +1,9 @@
import importlib
def _declare_backend(backend_path):
backend_path = backend_path.split('.')
backend_module_name = '.'.join(backend_path[:-1])
backend_path = backend_path.split(".")
backend_module_name = ".".join(backend_path[:-1])
class_name = backend_path[-1]
def backend(*args, headers={}, from_address=None, **kwargs):
@ -17,9 +18,10 @@ def _declare_backend(backend_path):
_backend.from_address = from_address
_backend.headers = headers
return _backend
return backend
smtp_backend = _declare_backend('django.core.mail.backends.smtp.EmailBackend')
console_backend = _declare_backend('django.core.mail.backends.console.EmailBackend')
locmem_backend = _declare_backend('django.core.mail.backends.locmem.EmailBackend')
smtp_backend = _declare_backend("django.core.mail.backends.smtp.EmailBackend")
console_backend = _declare_backend("django.core.mail.backends.console.EmailBackend")
locmem_backend = _declare_backend("django.core.mail.backends.locmem.EmailBackend")

View file

@ -70,14 +70,12 @@ def imap_producer(
try:
yield message
except Exception:
logger.exception(
f"something went wrong while processing {message_uid}"
)
logger.exception(f"something went wrong while processing {message_uid}")
raise
if not preserve:
# tag the message for deletion
conn.store(message_uid, '+FLAGS', '\\Deleted')
conn.store(message_uid, "+FLAGS", "\\Deleted")
else:
logger.debug("did not receive any message")
finally:

View file

@ -18,7 +18,7 @@ def gen_title(tc=True):
# faker doesn't provide a way to generate headlines in Title Case, without periods, so make our own.
# With arg `tc=True`, Title Cases The Generated Text
fake = Faker()
thestr = fake.text(max_nb_chars=32).rstrip('.')
thestr = fake.text(max_nb_chars=32).rstrip(".")
if tc:
thestr = titlecase(thestr)
@ -29,7 +29,7 @@ def gen_content():
# faker provides paragraphs as a list; convert with linebreaks
fake = Faker()
grafs = fake.paragraphs()
thestr = ''
thestr = ""
for g in grafs:
thestr += "{}\n\n".format(g)
return thestr
@ -43,11 +43,12 @@ class Command(BaseCommand):
"-d",
"--delete",
help="Wipe out existing content before generating new.",
action="store_true")
action="store_true",
)
def handle(self, *args, **options):
if options.get('delete'):
if options.get("delete"):
# Wipe out previous contents? Cascade deletes the Tasks from the TaskLists.
TaskList.objects.all().delete()
print("Content from previous run deleted.")
@ -56,11 +57,11 @@ class Command(BaseCommand):
fake = Faker() # Use to create user's names
# Create users and groups, add different users to different groups. Staff user is in both groups.
sd_group, created = Group.objects.get_or_create(name='Scuba Divers')
bw_group, created = Group.objects.get_or_create(name='Basket Weavers')
sd_group, created = Group.objects.get_or_create(name="Scuba Divers")
bw_group, created = Group.objects.get_or_create(name="Basket Weavers")
# Put user1 and user2 in one group, user3 and user4 in another
usernames = ['user1', 'user2', 'user3', 'user4', 'staffer']
usernames = ["user1", "user2", "user3", "user4", "staffer"]
for username in usernames:
if get_user_model().objects.filter(username=username).exists():
user = get_user_model().objects.get(username=username)
@ -70,15 +71,16 @@ class Command(BaseCommand):
first_name=fake.first_name(),
last_name=fake.last_name(),
email="{}@example.com".format(username),
password="todo")
password="todo",
)
if username in ['user1', 'user2']:
if username in ["user1", "user2"]:
user.groups.add(bw_group)
if username in ['user3', 'user4']:
if username in ["user3", "user4"]:
user.groups.add(sd_group)
if username == 'staffer':
if username == "staffer":
user.is_staff = True
user.first_name = fake.first_name()
user.last_name = fake.last_name()
@ -91,7 +93,9 @@ class Command(BaseCommand):
TaskListFactory.create_batch(5, group=sd_group)
TaskListFactory.create(name="Public Tickets", slug="tickets", group=bw_group)
print("For each of two groups, created fake tasks in each of {} fake lists.".format(num_lists))
print(
"For each of two groups, created fake tasks in each of {} fake lists.".format(num_lists)
)
class TaskListFactory(factory.django.DjangoModelFactory):
@ -120,9 +124,11 @@ class TaskFactory(factory.django.DjangoModelFactory):
task_list = None # Pass this in
note = factory.LazyAttribute(lambda o: gen_content())
priority = factory.LazyAttribute(lambda o: random.randint(1, 100))
completed = factory.Faker('boolean', chance_of_getting_true=30)
created_by = factory.LazyAttribute(lambda o: get_user_model().objects.get(username='staffer')) # Randomized in post
created_date = factory.Faker('date_this_year')
completed = factory.Faker("boolean", chance_of_getting_true=30)
created_by = factory.LazyAttribute(
lambda o: get_user_model().objects.get(username="staffer")
) # Randomized in post
created_date = factory.Faker("date_this_year")
@factory.post_generation
def add_details(self, build, extracted, **kwargs):
@ -130,7 +136,7 @@ class TaskFactory(factory.django.DjangoModelFactory):
fake = Faker() # Use to create user's names
taskgroup = self.task_list.group
self.created_by = taskgroup.user_set.all().order_by('?').first()
self.created_by = taskgroup.user_set.all().order_by("?").first()
if self.completed:
self.completed_date = fake.date_this_year()
@ -141,6 +147,6 @@ class TaskFactory(factory.django.DjangoModelFactory):
# 1/3 of generated tasks are assigned to someone in this tasks's group
if random.randint(1, 3) == 1:
self.assigned_to = taskgroup.user_set.all().order_by('?').first()
self.assigned_to = taskgroup.user_set.all().order_by("?").first()
self.save()

View file

@ -26,10 +26,7 @@ class Command(BaseCommand):
worker_name = options["worker_name"]
tracker = settings.TODO_MAIL_TRACKERS.get(worker_name, None)
if tracker is None:
logger.error(
"couldn't find configuration for %r in TODO_MAIL_TRACKERS",
worker_name
)
logger.error("couldn't find configuration for %r in TODO_MAIL_TRACKERS", worker_name)
sys.exit(1)
# set the default socket timeout (imaplib doesn't enable configuring it)

View file

@ -9,70 +9,93 @@ from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
("auth", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Comment',
name="Comment",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('date', models.DateTimeField(default=datetime.datetime.now)),
('body', models.TextField(blank=True)),
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID", serialize=False, auto_created=True, primary_key=True
),
),
("date", models.DateTimeField(default=datetime.datetime.now)),
("body", models.TextField(blank=True)),
(
"author",
models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
],
options={
},
options={},
bases=(models.Model,),
),
migrations.CreateModel(
name='Item',
name="Item",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=140)),
('created_date', models.DateField(auto_now=True, auto_now_add=True)),
('due_date', models.DateField(null=True, blank=True)),
('completed', models.BooleanField(default=None)),
('completed_date', models.DateField(null=True, blank=True)),
('note', models.TextField(null=True, blank=True)),
('priority', models.PositiveIntegerField(max_length=3)),
('assigned_to', models.ForeignKey(related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
('created_by', models.ForeignKey(related_name='todo_created_by', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID", serialize=False, auto_created=True, primary_key=True
),
),
("title", models.CharField(max_length=140)),
("created_date", models.DateField(auto_now=True, auto_now_add=True)),
("due_date", models.DateField(null=True, blank=True)),
("completed", models.BooleanField(default=None)),
("completed_date", models.DateField(null=True, blank=True)),
("note", models.TextField(null=True, blank=True)),
("priority", models.PositiveIntegerField(max_length=3)),
(
"assigned_to",
models.ForeignKey(
related_name="todo_assigned_to",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
(
"created_by",
models.ForeignKey(
related_name="todo_created_by",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
],
options={
'ordering': ['priority'],
},
options={"ordering": ["priority"]},
bases=(models.Model,),
),
migrations.CreateModel(
name='List',
name="List",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=60)),
('slug', models.SlugField(max_length=60, editable=False)),
('group', models.ForeignKey(to='auth.Group', on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID", serialize=False, auto_created=True, primary_key=True
),
),
("name", models.CharField(max_length=60)),
("slug", models.SlugField(max_length=60, editable=False)),
("group", models.ForeignKey(to="auth.Group", on_delete=models.CASCADE)),
],
options={
'ordering': ['name'],
'verbose_name_plural': 'Lists',
},
options={"ordering": ["name"], "verbose_name_plural": "Lists"},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='list',
unique_together=set([('group', 'slug')]),
),
migrations.AlterUniqueTogether(name="list", unique_together=set([("group", "slug")])),
migrations.AddField(
model_name='item',
name='list',
field=models.ForeignKey(to='todo.List', on_delete=models.CASCADE),
model_name="item",
name="list",
field=models.ForeignKey(to="todo.List", on_delete=models.CASCADE),
preserve_default=True,
),
migrations.AddField(
model_name='comment',
name='task',
field=models.ForeignKey(to='todo.Item', on_delete=models.CASCADE),
model_name="comment",
name="task",
field=models.ForeignKey(to="todo.Item", on_delete=models.CASCADE),
preserve_default=True,
),
]

View file

@ -6,19 +6,13 @@ from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('todo', '0001_initial'),
]
dependencies = [("todo", "0001_initial")]
operations = [
migrations.AlterField(
model_name='item',
name='created_date',
field=models.DateField(auto_now=True),
model_name="item", name="created_date", field=models.DateField(auto_now=True)
),
migrations.AlterField(
model_name='item',
name='priority',
field=models.PositiveIntegerField(),
model_name="item", name="priority", field=models.PositiveIntegerField()
),
]

View file

@ -9,14 +9,18 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('todo', '0002_auto_20150614_2339'),
]
dependencies = [("todo", "0002_auto_20150614_2339")]
operations = [
migrations.AlterField(
model_name='item',
name='assigned_to',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL),
),
model_name="item",
name="assigned_to",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="todo_assigned_to",
to=settings.AUTH_USER_MODEL,
),
)
]

View file

@ -7,46 +7,39 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
('todo', '0003_assignee_optional'),
("auth", "0009_alter_user_last_name_max_length"),
("todo", "0003_assignee_optional"),
]
operations = [
migrations.CreateModel(
name='TaskList',
name="TaskList",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=60)),
('slug', models.SlugField(default='')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("name", models.CharField(max_length=60)),
("slug", models.SlugField(default="")),
(
"group",
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="auth.Group"),
),
],
options={
'verbose_name_plural': 'Lists',
'ordering': ['name'],
},
),
migrations.AlterUniqueTogether(
name='list',
unique_together=set(),
),
migrations.RemoveField(
model_name='list',
name='group',
),
migrations.RemoveField(
model_name='item',
name='list',
),
migrations.DeleteModel(
name='List',
options={"verbose_name_plural": "Lists", "ordering": ["name"]},
),
migrations.AlterUniqueTogether(name="list", unique_together=set()),
migrations.RemoveField(model_name="list", name="group"),
migrations.RemoveField(model_name="item", name="list"),
migrations.DeleteModel(name="List"),
migrations.AddField(
model_name='item',
name='task_list',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='todo.TaskList'),
),
migrations.AlterUniqueTogether(
name='tasklist',
unique_together={('group', 'slug')},
model_name="item",
name="task_list",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.CASCADE, to="todo.TaskList"
),
),
migrations.AlterUniqueTogether(name="tasklist", unique_together={("group", "slug")}),
]

View file

@ -5,18 +5,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('todo', '0004_rename_list_tasklist'),
]
dependencies = [("todo", "0004_rename_list_tasklist")]
operations = [
migrations.AlterModelOptions(
name='tasklist',
options={'ordering': ['name'], 'verbose_name_plural': 'Task Lists'},
name="tasklist", options={"ordering": ["name"], "verbose_name_plural": "Task Lists"}
),
migrations.AlterField(
model_name='item',
name='completed',
field=models.BooleanField(default=False),
model_name="item", name="completed", field=models.BooleanField(default=False)
),
]

View file

@ -8,12 +8,7 @@ class Migration(migrations.Migration):
atomic = False
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('todo', '0005_auto_20180212_2325'),
("todo", "0005_auto_20180212_2325"),
]
operations = [
migrations.RenameModel(
old_name='Item',
new_name='Task',
),
]
operations = [migrations.RenameModel(old_name="Item", new_name="Task")]

View file

@ -6,14 +6,12 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('todo', '0006_rename_item_model'),
]
dependencies = [("todo", "0006_rename_item_model")]
operations = [
migrations.AlterField(
model_name='task',
name='created_date',
model_name="task",
name="created_date",
field=models.DateField(blank=True, default=django.utils.timezone.now, null=True),
),
)
]

View file

@ -5,18 +5,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('todo', '0008_mail_tracker'),
]
dependencies = [("todo", "0008_mail_tracker")]
operations = [
migrations.AlterModelOptions(
name='task',
options={'ordering': ['priority', 'created_date']},
name="task", options={"ordering": ["priority", "created_date"]}
),
migrations.AlterField(
model_name='task',
name='priority',
model_name="task",
name="priority",
field=models.PositiveIntegerField(blank=True, null=True),
),
]

View file

@ -11,18 +11,36 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('todo', '0009_priority_optional'),
("todo", "0009_priority_optional"),
]
operations = [
migrations.CreateModel(
name='Attachment',
name="Attachment",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('timestamp', models.DateTimeField(default=datetime.datetime.now)),
('file', models.FileField(max_length=255, upload_to=todo.models.get_attachment_upload_dir)),
('added_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todo.Task')),
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("timestamp", models.DateTimeField(default=datetime.datetime.now)),
(
"file",
models.FileField(
max_length=255, upload_to=todo.models.get_attachment_upload_dir
),
),
(
"added_by",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
(
"task",
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="todo.Task"),
),
],
),
)
]

View file

@ -45,9 +45,7 @@ class LockedAtomicTransaction(Atomic):
cursor = get_connection(self.using).cursor()
for model in self.models:
cursor.execute(
"LOCK TABLE {table_name}".format(
table_name=model._meta.db_table
)
"LOCK TABLE {table_name}".format(table_name=model._meta.db_table)
)
finally:
if cursor and not cursor.closed:
@ -159,9 +157,7 @@ class Comment(models.Model):
def snippet(self):
body_snippet = textwrap.shorten(self.body, width=35, placeholder="...")
# Define here rather than in __str__ so we can use it in the admin list_display
return "{author} - {snippet}...".format(
author=self.author_text, snippet=body_snippet
)
return "{author} - {snippet}...".format(author=self.author_text, snippet=body_snippet)
def __str__(self):
return self.snippet
@ -173,9 +169,7 @@ class Attachment(models.Model):
"""
task = models.ForeignKey(Task, on_delete=models.CASCADE)
added_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
added_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
timestamp = models.DateTimeField(default=datetime.datetime.now)
file = models.FileField(upload_to=get_attachment_upload_dir, max_length=255)

View file

@ -67,7 +67,11 @@ class CSVImporter:
# newrow at this point is fully validated, and all FK relations exist,
# 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()
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
@ -178,7 +182,7 @@ class CSVImporter:
row["Assigned To"] = assignee
# Set Completed
row["Completed"] = (row["Completed"] == "Yes")
row["Completed"] = row["Completed"] == "Yes"
# #######################
if row_errors:

View file

@ -9,24 +9,21 @@ from email.message import EmailMessage
def consumer(*args, title_format="[TEST] {subject}", **kwargs):
return tracker_consumer(
group="Workgroup One",
task_list_slug="zip",
priority=1,
task_title_format=title_format,
group="Workgroup One", task_list_slug="zip", priority=1, task_title_format=title_format
)(*args, **kwargs)
def make_message(subject, content):
msg = EmailMessage()
msg.set_content(content)
msg['Subject'] = subject
msg["Subject"] = subject
return msg
def test_tracker_task_creation(todo_setup, django_user_model):
msg = make_message("test1 subject", "test1 content")
msg['From'] = 'test1@example.com'
msg['Message-ID'] = '<a@example.com>'
msg["From"] = "test1@example.com"
msg["Message-ID"] = "<a@example.com>"
# test task creation
task_count = Task.objects.count()
@ -38,30 +35,26 @@ def test_tracker_task_creation(todo_setup, django_user_model):
# test thread answers
msg = make_message("test2 subject", "test2 content")
msg['From'] = 'test1@example.com'
msg['Message-ID'] = '<b@example.com>'
msg['References'] = '<nope@example.com> <a@example.com>'
msg["From"] = "test1@example.com"
msg["Message-ID"] = "<b@example.com>"
msg["References"] = "<nope@example.com> <a@example.com>"
task_count = Task.objects.count()
consumer([msg])
assert task_count == Task.objects.count(), "comment created another task"
Comment.objects.get(
task=task,
body__contains="test2 content",
email_message_id='<b@example.com>'
task=task, body__contains="test2 content", email_message_id="<b@example.com>"
)
# test notification answer
msg = make_message("test3 subject", "test3 content")
msg['From'] = 'test1@example.com'
msg['Message-ID'] = '<c@example.com>'
msg['References'] = '<thread-{}@django-todo> <unknown@example.com>'.format(task.pk)
msg["From"] = "test1@example.com"
msg["Message-ID"] = "<c@example.com>"
msg["References"] = "<thread-{}@django-todo> <unknown@example.com>".format(task.pk)
task_count = Task.objects.count()
consumer([msg])
assert task_count == Task.objects.count(), "comment created another task"
Comment.objects.get(
task=task,
body__contains="test3 content",
email_message_id='<c@example.com>'
task=task, body__contains="test3 content", email_message_id="<c@example.com>"
)

View file

@ -55,5 +55,6 @@ def test_send_email_to_thread_participants(todo_setup, django_user_model, email_
assert "u3@example.com" in mail.outbox[0].recipients()
assert "u4@example.com" in mail.outbox[0].recipients()
# FIXME: Add tests for:
# Attachments: Test whether allowed, test multiple, test extensions
# Attachments: Test whether allowed, test multiple, test extensions

View file

@ -166,6 +166,7 @@ def test_no_javascript_in_comments(todo_setup, client):
# ### PERMISSIONS ###
def test_view_add_list_nonadmin(todo_setup, client):
url = reverse("todo:add_list")
client.login(username="you", password="password")

View file

@ -4,93 +4,46 @@ from django.urls import path
from todo import views
from todo.features import HAS_TASK_MERGE
app_name = 'todo'
app_name = "todo"
urlpatterns = [
path(
'',
views.list_lists,
name="lists"),
path("", views.list_lists, name="lists"),
# View reorder_tasks is only called by JQuery for drag/drop task ordering.
path(
'reorder_tasks/',
views.reorder_tasks,
name="reorder_tasks"),
path("reorder_tasks/", views.reorder_tasks, name="reorder_tasks"),
# Allow users to post tasks from outside django-todo (e.g. for filing tickets - see docs)
path(
'ticket/add/',
views.external_add,
name="external_add"),
path("ticket/add/", views.external_add, name="external_add"),
# Three paths into `list_detail` view
path("mine/", views.list_detail, {"list_slug": "mine"}, name="mine"),
path(
'mine/',
"<int:list_id>/<str:list_slug>/completed/",
views.list_detail,
{'list_slug': 'mine'},
name="mine"),
{"view_completed": True},
name="list_detail_completed",
),
path("<int:list_id>/<str:list_slug>/", views.list_detail, name="list_detail"),
path("<int:list_id>/<str:list_slug>/delete/", views.del_list, name="del_list"),
path("add_list/", views.add_list, name="add_list"),
path("task/<int:task_id>/", views.task_detail, name="task_detail"),
path(
'<int:list_id>/<str:list_slug>/completed/',
views.list_detail,
{'view_completed': True},
name='list_detail_completed'),
path(
'<int:list_id>/<str:list_slug>/',
views.list_detail,
name='list_detail'),
path(
'<int:list_id>/<str:list_slug>/delete/',
views.del_list,
name="del_list"),
path(
'add_list/',
views.add_list,
name="add_list"),
path(
'task/<int:task_id>/',
views.task_detail,
name='task_detail'),
path(
'attachment/remove/<int:attachment_id>/',
views.remove_attachment,
name='remove_attachment'),
"attachment/remove/<int:attachment_id>/", views.remove_attachment, name="remove_attachment"
),
]
if HAS_TASK_MERGE:
# ensure mail tracker autocomplete is optional
from todo.views.task_autocomplete import TaskAutocomplete
urlpatterns.append(
path(
'task/<int:task_id>/autocomplete/',
TaskAutocomplete.as_view(),
name='task_autocomplete')
"task/<int:task_id>/autocomplete/", TaskAutocomplete.as_view(), name="task_autocomplete"
)
)
urlpatterns.extend([
path(
'toggle_done/<int:task_id>/',
views.toggle_done,
name='task_toggle_done'),
path(
'delete/<int:task_id>/',
views.delete_task,
name='delete_task'),
path(
'search/',
views.search,
name="search"),
path(
'import_csv/',
views.import_csv,
name="import_csv"),
])
urlpatterns.extend(
[
path("toggle_done/<int:task_id>/", views.toggle_done, name="task_toggle_done"),
path("delete/<int:task_id>/", views.delete_task, name="delete_task"),
path("search/", views.search, name="search"),
path("import_csv/", views.import_csv, name="import_csv"),
]
)

View file

@ -20,7 +20,7 @@ def staff_check(user):
https://github.com/shacker/django-todo/issues/50
"""
if defaults('TODO_STAFF_ONLY'):
if defaults("TODO_STAFF_ONLY"):
return user.is_staff
else:
# If unset or False, allow all logged in users

View file

@ -43,7 +43,7 @@ def external_add(request) -> HttpResponse:
task = form.save(commit=False)
task.task_list = TaskList.objects.get(slug=settings.TODO_DEFAULT_LIST_SLUG)
task.created_by = request.user
if defaults('TODO_DEFAULT_ASSIGNEE'):
if defaults("TODO_DEFAULT_ASSIGNEE"):
task.assigned_to = User.objects.get(username=settings.TODO_DEFAULT_ASSIGNEE)
task.save()

View file

@ -18,10 +18,7 @@ def remove_attachment(request, attachment_id: int) -> HttpResponse:
if request.method == "POST":
attachment = get_object_or_404(Attachment, pk=attachment_id)
redir_url = reverse(
"todo:task_detail",
kwargs={"task_id": attachment.task.id},
)
redir_url = reverse("todo:task_detail", kwargs={"task_id": attachment.task.id})
# Permissions
if not (
@ -33,7 +30,9 @@ def remove_attachment(request, attachment_id: int) -> HttpResponse:
if remove_attachment_file(attachment.id):
messages.success(request, f"Attachment {attachment.id} removed.")
else:
messages.error(request, f"Sorry, there was a problem deleting attachment {attachment.id}.")
messages.error(
request, f"Sorry, there was a problem deleting attachment {attachment.id}."
)
return redirect(redir_url)

View file

@ -121,13 +121,13 @@ def task_detail(request, task_id: int) -> HttpResponse:
if request.FILES.get("attachment_file_input"):
file = request.FILES.get("attachment_file_input")
if file.size > defaults('TODO_MAXIMUM_ATTACHMENT_SIZE'):
if file.size > defaults("TODO_MAXIMUM_ATTACHMENT_SIZE"):
messages.error(request, f"File exceeds maximum attachment size.")
return redirect("todo:task_detail", task_id=task.id)
name, extension = os.path.splitext(file.name)
if extension not in defaults('TODO_LIMIT_FILE_ATTACHMENTS'):
if extension not in defaults("TODO_LIMIT_FILE_ATTACHMENTS"):
messages.error(request, f"This site does not allow upload of {extension} files.")
return redirect("todo:task_detail", task_id=task.id)
@ -144,7 +144,7 @@ def task_detail(request, task_id: int) -> HttpResponse:
"merge_form": merge_form,
"thedate": thedate,
"comment_classes": defaults("TODO_COMMENT_CLASSES"),
"attachments_enabled": defaults('TODO_ALLOW_FILE_ATTACHMENTS'),
"attachments_enabled": defaults("TODO_ALLOW_FILE_ATTACHMENTS"),
}
return render(request, "todo/task_detail.html", context)