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. to your site's urlconf.
""" """
urlpatterns = [ urlpatterns = [path("lists/", include("todo.urls"))]
path('lists/', include('todo.urls')),
]

View file

@ -2,11 +2,7 @@ import os
DEBUG = (True,) DEBUG = (True,)
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DATABASES = { DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
"default": {
"ENGINE": "django.db.backends.sqlite3"
}
}
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
@ -65,28 +61,12 @@ TEMPLATES = [
] ]
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'handlers': { "handlers": {"console": {"class": "logging.StreamHandler"}},
'console': { "loggers": {
'class': 'logging.StreamHandler', "": {"handlers": ["console"], "level": "DEBUG", "propagate": True},
}, "django": {"handlers": ["console"], "level": "WARNING", "propagate": True},
}, "django.request": {"handlers": ["console"], "level": "DEBUG", "propagate": True},
'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. A multi-user, multi-group task management and assignment system for Django.
""" """
__version__ = '2.4.6' __version__ = "2.4.6"
__author__ = 'Scot Hacker' __author__ = "Scot Hacker"
__email__ = 'shacker@birdhouse.org' __email__ = "shacker@birdhouse.org"
__url__ = 'https://github.com/shacker/django-todo' __url__ = "https://github.com/shacker/django-todo"
__license__ = 'BSD License' __license__ = "BSD License"
from . import check from . import check

View file

@ -11,9 +11,7 @@ def dal_check(app_configs, **kwargs):
return [] return []
errors = [] 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: for missing_app in missing_apps:
errors.append( errors.append(Error("{} needs to be in INSTALLED_APPS".format(missing_app)))
Error('{} needs to be in INSTALLED_APPS'.format(missing_app))
)
return errors return errors

View file

@ -11,5 +11,6 @@ except ImportError:
HAS_TASK_MERGE = False HAS_TASK_MERGE = False
if HAS_AUTOCOMPLETE: if HAS_AUTOCOMPLETE:
import dal.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 HAS_TASK_MERGE = True

View file

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

View file

@ -70,14 +70,12 @@ def imap_producer(
try: try:
yield message yield message
except Exception: except Exception:
logger.exception( logger.exception(f"something went wrong while processing {message_uid}")
f"something went wrong while processing {message_uid}"
)
raise raise
if not preserve: if not preserve:
# tag the message for deletion # tag the message for deletion
conn.store(message_uid, '+FLAGS', '\\Deleted') conn.store(message_uid, "+FLAGS", "\\Deleted")
else: else:
logger.debug("did not receive any message") logger.debug("did not receive any message")
finally: 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. # 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 # With arg `tc=True`, Title Cases The Generated Text
fake = Faker() fake = Faker()
thestr = fake.text(max_nb_chars=32).rstrip('.') thestr = fake.text(max_nb_chars=32).rstrip(".")
if tc: if tc:
thestr = titlecase(thestr) thestr = titlecase(thestr)
@ -29,7 +29,7 @@ def gen_content():
# faker provides paragraphs as a list; convert with linebreaks # faker provides paragraphs as a list; convert with linebreaks
fake = Faker() fake = Faker()
grafs = fake.paragraphs() grafs = fake.paragraphs()
thestr = '' thestr = ""
for g in grafs: for g in grafs:
thestr += "{}\n\n".format(g) thestr += "{}\n\n".format(g)
return thestr return thestr
@ -43,11 +43,12 @@ class Command(BaseCommand):
"-d", "-d",
"--delete", "--delete",
help="Wipe out existing content before generating new.", help="Wipe out existing content before generating new.",
action="store_true") action="store_true",
)
def handle(self, *args, **options): def handle(self, *args, **options):
if options.get('delete'): if options.get("delete"):
# Wipe out previous contents? Cascade deletes the Tasks from the TaskLists. # Wipe out previous contents? Cascade deletes the Tasks from the TaskLists.
TaskList.objects.all().delete() TaskList.objects.all().delete()
print("Content from previous run deleted.") print("Content from previous run deleted.")
@ -56,11 +57,11 @@ class Command(BaseCommand):
fake = Faker() # Use to create user's names fake = Faker() # Use to create user's names
# Create users and groups, add different users to different groups. Staff user is in both groups. # 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') sd_group, created = Group.objects.get_or_create(name="Scuba Divers")
bw_group, created = Group.objects.get_or_create(name='Basket Weavers') bw_group, created = Group.objects.get_or_create(name="Basket Weavers")
# Put user1 and user2 in one group, user3 and user4 in another # 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: for username in usernames:
if get_user_model().objects.filter(username=username).exists(): if get_user_model().objects.filter(username=username).exists():
user = get_user_model().objects.get(username=username) user = get_user_model().objects.get(username=username)
@ -70,15 +71,16 @@ class Command(BaseCommand):
first_name=fake.first_name(), first_name=fake.first_name(),
last_name=fake.last_name(), last_name=fake.last_name(),
email="{}@example.com".format(username), email="{}@example.com".format(username),
password="todo") password="todo",
)
if username in ['user1', 'user2']: if username in ["user1", "user2"]:
user.groups.add(bw_group) user.groups.add(bw_group)
if username in ['user3', 'user4']: if username in ["user3", "user4"]:
user.groups.add(sd_group) user.groups.add(sd_group)
if username == 'staffer': if username == "staffer":
user.is_staff = True user.is_staff = True
user.first_name = fake.first_name() user.first_name = fake.first_name()
user.last_name = fake.last_name() user.last_name = fake.last_name()
@ -91,7 +93,9 @@ class Command(BaseCommand):
TaskListFactory.create_batch(5, group=sd_group) TaskListFactory.create_batch(5, group=sd_group)
TaskListFactory.create(name="Public Tickets", slug="tickets", group=bw_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): class TaskListFactory(factory.django.DjangoModelFactory):
@ -120,9 +124,11 @@ class TaskFactory(factory.django.DjangoModelFactory):
task_list = None # Pass this in task_list = None # Pass this in
note = factory.LazyAttribute(lambda o: gen_content()) note = factory.LazyAttribute(lambda o: gen_content())
priority = factory.LazyAttribute(lambda o: random.randint(1, 100)) priority = factory.LazyAttribute(lambda o: random.randint(1, 100))
completed = factory.Faker('boolean', chance_of_getting_true=30) 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_by = factory.LazyAttribute(
created_date = factory.Faker('date_this_year') lambda o: get_user_model().objects.get(username="staffer")
) # Randomized in post
created_date = factory.Faker("date_this_year")
@factory.post_generation @factory.post_generation
def add_details(self, build, extracted, **kwargs): def add_details(self, build, extracted, **kwargs):
@ -130,7 +136,7 @@ class TaskFactory(factory.django.DjangoModelFactory):
fake = Faker() # Use to create user's names fake = Faker() # Use to create user's names
taskgroup = self.task_list.group 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: if self.completed:
self.completed_date = fake.date_this_year() 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 # 1/3 of generated tasks are assigned to someone in this tasks's group
if random.randint(1, 3) == 1: 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() self.save()

View file

@ -26,10 +26,7 @@ class Command(BaseCommand):
worker_name = options["worker_name"] worker_name = options["worker_name"]
tracker = settings.TODO_MAIL_TRACKERS.get(worker_name, None) tracker = settings.TODO_MAIL_TRACKERS.get(worker_name, None)
if tracker is None: if tracker is None:
logger.error( logger.error("couldn't find configuration for %r in TODO_MAIL_TRACKERS", worker_name)
"couldn't find configuration for %r in TODO_MAIL_TRACKERS",
worker_name
)
sys.exit(1) sys.exit(1)
# set the default socket timeout (imaplib doesn't enable configuring it) # 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('auth', '0001_initial'), ("auth", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Comment', name="Comment",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('date', models.DateTimeField(default=datetime.datetime.now)), "id",
('body', models.TextField(blank=True)), models.AutoField(
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), 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,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Item', name="Item",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('title', models.CharField(max_length=140)), "id",
('created_date', models.DateField(auto_now=True, auto_now_add=True)), models.AutoField(
('due_date', models.DateField(null=True, blank=True)), verbose_name="ID", serialize=False, auto_created=True, primary_key=True
('completed', models.BooleanField(default=None)), ),
('completed_date', models.DateField(null=True, blank=True)), ),
('note', models.TextField(null=True, blank=True)), ("title", models.CharField(max_length=140)),
('priority', models.PositiveIntegerField(max_length=3)), ("created_date", models.DateField(auto_now=True, auto_now_add=True)),
('assigned_to', models.ForeignKey(related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ("due_date", models.DateField(null=True, blank=True)),
('created_by', models.ForeignKey(related_name='todo_created_by', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ("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={ options={"ordering": ["priority"]},
'ordering': ['priority'],
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name='List', name="List",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('name', models.CharField(max_length=60)), "id",
('slug', models.SlugField(max_length=60, editable=False)), models.AutoField(
('group', models.ForeignKey(to='auth.Group', on_delete=models.CASCADE)), 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={ options={"ordering": ["name"], "verbose_name_plural": "Lists"},
'ordering': ['name'],
'verbose_name_plural': 'Lists',
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(name="list", unique_together=set([("group", "slug")])),
name='list',
unique_together=set([('group', 'slug')]),
),
migrations.AddField( migrations.AddField(
model_name='item', model_name="item",
name='list', name="list",
field=models.ForeignKey(to='todo.List', on_delete=models.CASCADE), field=models.ForeignKey(to="todo.List", on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='comment', model_name="comment",
name='task', name="task",
field=models.ForeignKey(to='todo.Item', on_delete=models.CASCADE), field=models.ForeignKey(to="todo.Item", on_delete=models.CASCADE),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

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

View file

@ -9,14 +9,18 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("todo", "0002_auto_20150614_2339")]
('todo', '0002_auto_20150614_2339'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='item', model_name="item",
name='assigned_to', 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), 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('auth', '0009_alter_user_last_name_max_length'), ("auth", "0009_alter_user_last_name_max_length"),
('todo', '0003_assignee_optional'), ("todo", "0003_assignee_optional"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='TaskList', name="TaskList",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=60)), "id",
('slug', models.SlugField(default='')), models.AutoField(
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), 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={ options={"verbose_name_plural": "Lists", "ordering": ["name"]},
'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.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( migrations.AddField(
model_name='item', model_name="item",
name='task_list', name="task_list",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='todo.TaskList'), field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.CASCADE, to="todo.TaskList"
), ),
migrations.AlterUniqueTogether(
name='tasklist',
unique_together={('group', 'slug')},
), ),
migrations.AlterUniqueTogether(name="tasklist", unique_together={("group", "slug")}),
] ]

View file

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

View file

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

View file

@ -6,14 +6,12 @@ import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("todo", "0006_rename_item_model")]
('todo', '0006_rename_item_model'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='created_date', name="created_date",
field=models.DateField(blank=True, default=django.utils.timezone.now, null=True), 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [("todo", "0008_mail_tracker")]
('todo', '0008_mail_tracker'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='task', name="task", options={"ordering": ["priority", "created_date"]}
options={'ordering': ['priority', 'created_date']},
), ),
migrations.AlterField( migrations.AlterField(
model_name='task', model_name="task",
name='priority', name="priority",
field=models.PositiveIntegerField(blank=True, null=True), field=models.PositiveIntegerField(blank=True, null=True),
), ),
] ]

View file

@ -11,18 +11,36 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('todo', '0009_priority_optional'), ("todo", "0009_priority_optional"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Attachment', name="Attachment",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('timestamp', models.DateTimeField(default=datetime.datetime.now)), "id",
('file', models.FileField(max_length=255, upload_to=todo.models.get_attachment_upload_dir)), models.AutoField(
('added_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todo.Task')),
],
), ),
),
("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() cursor = get_connection(self.using).cursor()
for model in self.models: for model in self.models:
cursor.execute( cursor.execute(
"LOCK TABLE {table_name}".format( "LOCK TABLE {table_name}".format(table_name=model._meta.db_table)
table_name=model._meta.db_table
)
) )
finally: finally:
if cursor and not cursor.closed: if cursor and not cursor.closed:
@ -159,9 +157,7 @@ class Comment(models.Model):
def snippet(self): def snippet(self):
body_snippet = textwrap.shorten(self.body, width=35, placeholder="...") 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 # Define here rather than in __str__ so we can use it in the admin list_display
return "{author} - {snippet}...".format( return "{author} - {snippet}...".format(author=self.author_text, snippet=body_snippet)
author=self.author_text, snippet=body_snippet
)
def __str__(self): def __str__(self):
return self.snippet return self.snippet
@ -173,9 +169,7 @@ class Attachment(models.Model):
""" """
task = models.ForeignKey(Task, on_delete=models.CASCADE) task = models.ForeignKey(Task, on_delete=models.CASCADE)
added_by = models.ForeignKey( added_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
timestamp = models.DateTimeField(default=datetime.datetime.now) timestamp = models.DateTimeField(default=datetime.datetime.now)
file = models.FileField(upload_to=get_attachment_upload_dir, max_length=255) 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, # 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 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 due_date = newrow.get("Due Date") if newrow.get("Due Date") else None
priority = newrow.get("Priority") if newrow.get("Priority") else None priority = newrow.get("Priority") if newrow.get("Priority") else None
@ -178,7 +182,7 @@ class CSVImporter:
row["Assigned To"] = assignee row["Assigned To"] = assignee
# Set Completed # Set Completed
row["Completed"] = (row["Completed"] == "Yes") row["Completed"] = row["Completed"] == "Yes"
# ####################### # #######################
if row_errors: if row_errors:

View file

@ -9,24 +9,21 @@ from email.message import EmailMessage
def consumer(*args, title_format="[TEST] {subject}", **kwargs): def consumer(*args, title_format="[TEST] {subject}", **kwargs):
return tracker_consumer( return tracker_consumer(
group="Workgroup One", group="Workgroup One", task_list_slug="zip", priority=1, task_title_format=title_format
task_list_slug="zip",
priority=1,
task_title_format=title_format,
)(*args, **kwargs) )(*args, **kwargs)
def make_message(subject, content): def make_message(subject, content):
msg = EmailMessage() msg = EmailMessage()
msg.set_content(content) msg.set_content(content)
msg['Subject'] = subject msg["Subject"] = subject
return msg return msg
def test_tracker_task_creation(todo_setup, django_user_model): def test_tracker_task_creation(todo_setup, django_user_model):
msg = make_message("test1 subject", "test1 content") msg = make_message("test1 subject", "test1 content")
msg['From'] = 'test1@example.com' msg["From"] = "test1@example.com"
msg['Message-ID'] = '<a@example.com>' msg["Message-ID"] = "<a@example.com>"
# test task creation # test task creation
task_count = Task.objects.count() task_count = Task.objects.count()
@ -38,30 +35,26 @@ def test_tracker_task_creation(todo_setup, django_user_model):
# test thread answers # test thread answers
msg = make_message("test2 subject", "test2 content") msg = make_message("test2 subject", "test2 content")
msg['From'] = 'test1@example.com' msg["From"] = "test1@example.com"
msg['Message-ID'] = '<b@example.com>' msg["Message-ID"] = "<b@example.com>"
msg['References'] = '<nope@example.com> <a@example.com>' msg["References"] = "<nope@example.com> <a@example.com>"
task_count = Task.objects.count() task_count = Task.objects.count()
consumer([msg]) consumer([msg])
assert task_count == Task.objects.count(), "comment created another task" assert task_count == Task.objects.count(), "comment created another task"
Comment.objects.get( Comment.objects.get(
task=task, task=task, body__contains="test2 content", email_message_id="<b@example.com>"
body__contains="test2 content",
email_message_id='<b@example.com>'
) )
# test notification answer # test notification answer
msg = make_message("test3 subject", "test3 content") msg = make_message("test3 subject", "test3 content")
msg['From'] = 'test1@example.com' msg["From"] = "test1@example.com"
msg['Message-ID'] = '<c@example.com>' msg["Message-ID"] = "<c@example.com>"
msg['References'] = '<thread-{}@django-todo> <unknown@example.com>'.format(task.pk) msg["References"] = "<thread-{}@django-todo> <unknown@example.com>".format(task.pk)
task_count = Task.objects.count() task_count = Task.objects.count()
consumer([msg]) consumer([msg])
assert task_count == Task.objects.count(), "comment created another task" assert task_count == Task.objects.count(), "comment created another task"
Comment.objects.get( Comment.objects.get(
task=task, task=task, body__contains="test3 content", email_message_id="<c@example.com>"
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 "u3@example.com" in mail.outbox[0].recipients()
assert "u4@example.com" in mail.outbox[0].recipients() assert "u4@example.com" in mail.outbox[0].recipients()
# FIXME: Add tests for: # 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 ### # ### PERMISSIONS ###
def test_view_add_list_nonadmin(todo_setup, client): def test_view_add_list_nonadmin(todo_setup, client):
url = reverse("todo:add_list") url = reverse("todo:add_list")
client.login(username="you", password="password") client.login(username="you", password="password")

View file

@ -4,93 +4,46 @@ from django.urls import path
from todo import views from todo import views
from todo.features import HAS_TASK_MERGE from todo.features import HAS_TASK_MERGE
app_name = 'todo' app_name = "todo"
urlpatterns = [ urlpatterns = [
path( path("", views.list_lists, name="lists"),
'',
views.list_lists,
name="lists"),
# View reorder_tasks is only called by JQuery for drag/drop task ordering. # View reorder_tasks is only called by JQuery for drag/drop task ordering.
path( path("reorder_tasks/", views.reorder_tasks, name="reorder_tasks"),
'reorder_tasks/',
views.reorder_tasks,
name="reorder_tasks"),
# Allow users to post tasks from outside django-todo (e.g. for filing tickets - see docs) # Allow users to post tasks from outside django-todo (e.g. for filing tickets - see docs)
path( path("ticket/add/", views.external_add, name="external_add"),
'ticket/add/',
views.external_add,
name="external_add"),
# Three paths into `list_detail` view # Three paths into `list_detail` view
path("mine/", views.list_detail, {"list_slug": "mine"}, name="mine"),
path( path(
'mine/', "<int:list_id>/<str:list_slug>/completed/",
views.list_detail, views.list_detail,
{'list_slug': 'mine'}, {"view_completed": True},
name="mine"), 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( path(
'<int:list_id>/<str:list_slug>/completed/', "attachment/remove/<int:attachment_id>/", views.remove_attachment, name="remove_attachment"
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'),
] ]
if HAS_TASK_MERGE: if HAS_TASK_MERGE:
# ensure mail tracker autocomplete is optional # ensure mail tracker autocomplete is optional
from todo.views.task_autocomplete import TaskAutocomplete from todo.views.task_autocomplete import TaskAutocomplete
urlpatterns.append( urlpatterns.append(
path( path(
'task/<int:task_id>/autocomplete/', "task/<int:task_id>/autocomplete/", TaskAutocomplete.as_view(), name="task_autocomplete"
TaskAutocomplete.as_view(), )
name='task_autocomplete')
) )
urlpatterns.extend([ urlpatterns.extend(
path( [
'toggle_done/<int:task_id>/', path("toggle_done/<int:task_id>/", views.toggle_done, name="task_toggle_done"),
views.toggle_done, path("delete/<int:task_id>/", views.delete_task, name="delete_task"),
name='task_toggle_done'), path("search/", views.search, name="search"),
path("import_csv/", views.import_csv, name="import_csv"),
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 https://github.com/shacker/django-todo/issues/50
""" """
if defaults('TODO_STAFF_ONLY'): if defaults("TODO_STAFF_ONLY"):
return user.is_staff return user.is_staff
else: else:
# If unset or False, allow all logged in users # 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 = form.save(commit=False)
task.task_list = TaskList.objects.get(slug=settings.TODO_DEFAULT_LIST_SLUG) task.task_list = TaskList.objects.get(slug=settings.TODO_DEFAULT_LIST_SLUG)
task.created_by = request.user 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.assigned_to = User.objects.get(username=settings.TODO_DEFAULT_ASSIGNEE)
task.save() task.save()

View file

@ -18,10 +18,7 @@ def remove_attachment(request, attachment_id: int) -> HttpResponse:
if request.method == "POST": if request.method == "POST":
attachment = get_object_or_404(Attachment, pk=attachment_id) attachment = get_object_or_404(Attachment, pk=attachment_id)
redir_url = reverse( redir_url = reverse("todo:task_detail", kwargs={"task_id": attachment.task.id})
"todo:task_detail",
kwargs={"task_id": attachment.task.id},
)
# Permissions # Permissions
if not ( if not (
@ -33,7 +30,9 @@ def remove_attachment(request, attachment_id: int) -> HttpResponse:
if remove_attachment_file(attachment.id): if remove_attachment_file(attachment.id):
messages.success(request, f"Attachment {attachment.id} removed.") messages.success(request, f"Attachment {attachment.id} removed.")
else: 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) 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"): if request.FILES.get("attachment_file_input"):
file = 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.") messages.error(request, f"File exceeds maximum attachment size.")
return redirect("todo:task_detail", task_id=task.id) return redirect("todo:task_detail", task_id=task.id)
name, extension = os.path.splitext(file.name) 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.") messages.error(request, f"This site does not allow upload of {extension} files.")
return redirect("todo:task_detail", task_id=task.id) 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, "merge_form": merge_form,
"thedate": thedate, "thedate": thedate,
"comment_classes": defaults("TODO_COMMENT_CLASSES"), "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) return render(request, "todo/task_detail.html", context)