Formatting
This commit is contained in:
parent
4a385bde6b
commit
befc7ad2cd
28 changed files with 253 additions and 311 deletions
|
@ -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')),
|
|
||||||
]
|
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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(),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -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")}),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
|
@ -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),
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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>'
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
|
@ -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")
|
||||||
|
|
99
todo/urls.py
99
todo/urls.py
|
@ -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"),
|
|
||||||
])
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue