Black formatting
This commit is contained in:
parent
f526ed5166
commit
21ec87cee4
10 changed files with 276 additions and 227 deletions
24
setup.py
24
setup.py
|
@ -5,7 +5,7 @@ from setuptools import setup, find_packages
|
||||||
import todo as package
|
import todo as package
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-todo',
|
name="django-todo",
|
||||||
version=package.__version__,
|
version=package.__version__,
|
||||||
description=package.__doc__.strip(),
|
description=package.__doc__.strip(),
|
||||||
author=package.__author__,
|
author=package.__author__,
|
||||||
|
@ -14,18 +14,18 @@ setup(
|
||||||
license=package.__license__,
|
license=package.__license__,
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
"Development Status :: 5 - Production/Stable",
|
||||||
'Environment :: Web Environment',
|
"Environment :: Web Environment",
|
||||||
'Framework :: Django',
|
"Framework :: Django",
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
'License :: OSI Approved :: BSD License',
|
"License :: OSI Approved :: BSD License",
|
||||||
'Operating System :: OS Independent',
|
"Operating System :: OS Independent",
|
||||||
'Programming Language :: Python',
|
"Programming Language :: Python",
|
||||||
'Programming Language :: Python :: 3',
|
"Programming Language :: Python :: 3",
|
||||||
'Topic :: Office/Business :: Groupware',
|
"Topic :: Office/Business :: Groupware",
|
||||||
'Topic :: Software Development :: Bug Tracking',
|
"Topic :: Software Development :: Bug Tracking",
|
||||||
],
|
],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=['unidecode', ],
|
install_requires=["unidecode"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,64 +1,63 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
DEBUG = True,
|
DEBUG = (True,)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
print("bd ", BASE_DIR)
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
"ENGINE": "django.db.backends.sqlite3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
# Document
|
# Document
|
||||||
TODO_STAFF_ONLY = False
|
TODO_STAFF_ONLY = False
|
||||||
TODO_DEFAULT_LIST_SLUG = 'tickets'
|
TODO_DEFAULT_LIST_SLUG = "tickets"
|
||||||
TODO_DEFAULT_ASSIGNEE = None
|
TODO_DEFAULT_ASSIGNEE = None
|
||||||
TODO_PUBLIC_SUBMIT_REDIRECT = '/'
|
TODO_PUBLIC_SUBMIT_REDIRECT = "/"
|
||||||
|
|
||||||
SECRET_KEY = "LKFSD8sdl.,8&sdf--"
|
SECRET_KEY = "LKFSD8sdl.,8&sdf--"
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.sites',
|
"django.contrib.sites",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
'todo',
|
"todo",
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = 'base_urls'
|
ROOT_URLCONF = "base_urls"
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [os.path.join(BASE_DIR, 'todo', 'templates'), ],
|
"DIRS": [os.path.join(BASE_DIR, "todo", "templates")],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.template.context_processors.media',
|
"django.template.context_processors.media",
|
||||||
'django.template.context_processors.static',
|
"django.template.context_processors.static",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
# Your stuff: custom template context processors go here
|
# Your stuff: custom template context processors go here
|
||||||
],
|
]
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
]
|
]
|
|
@ -3,14 +3,14 @@ from todo.models import Task, TaskList, Comment
|
||||||
|
|
||||||
|
|
||||||
class TaskAdmin(admin.ModelAdmin):
|
class TaskAdmin(admin.ModelAdmin):
|
||||||
list_display = ('title', 'task_list', 'completed', 'priority', 'due_date')
|
list_display = ("title", "task_list", "completed", "priority", "due_date")
|
||||||
list_filter = ('task_list',)
|
list_filter = ("task_list",)
|
||||||
ordering = ('priority',)
|
ordering = ("priority",)
|
||||||
search_fields = ('name',)
|
search_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
class CommentAdmin(admin.ModelAdmin):
|
class CommentAdmin(admin.ModelAdmin):
|
||||||
list_display = ('author', 'date', 'snippet')
|
list_display = ("author", "date", "snippet")
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(TaskList)
|
admin.site.register(TaskList)
|
||||||
|
|
|
@ -11,13 +11,16 @@ class AddTaskListForm(ModelForm):
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
super(AddTaskListForm, self).__init__(*args, **kwargs)
|
super(AddTaskListForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['group'].queryset = Group.objects.filter(user=user)
|
self.fields["group"].queryset = Group.objects.filter(user=user)
|
||||||
self.fields['group'].widget.attrs = {
|
self.fields["group"].widget.attrs = {
|
||||||
'id': 'id_group', 'class': "custom-select mb-3", 'name': 'group'}
|
"id": "id_group",
|
||||||
|
"class": "custom-select mb-3",
|
||||||
|
"name": "group",
|
||||||
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TaskList
|
model = TaskList
|
||||||
exclude = ['created_date', 'slug', ]
|
exclude = ["created_date", "slug"]
|
||||||
|
|
||||||
|
|
||||||
class AddEditTaskForm(ModelForm):
|
class AddEditTaskForm(ModelForm):
|
||||||
|
@ -26,22 +29,25 @@ class AddEditTaskForm(ModelForm):
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
task_list = kwargs.get('initial').get('task_list')
|
task_list = kwargs.get("initial").get("task_list")
|
||||||
members = task_list.group.user_set.all()
|
members = task_list.group.user_set.all()
|
||||||
self.fields['assigned_to'].queryset = members
|
self.fields["assigned_to"].queryset = members
|
||||||
self.fields['assigned_to'].label_from_instance = lambda obj: "%s (%s)" % (obj.get_full_name(), obj.username)
|
self.fields["assigned_to"].label_from_instance = lambda obj: "%s (%s)" % (
|
||||||
self.fields['assigned_to'].widget.attrs = {
|
obj.get_full_name(),
|
||||||
'id': 'id_assigned_to', 'class': "custom-select mb-3", 'name': 'assigned_to'}
|
obj.username,
|
||||||
self.fields['task_list'].value = kwargs['initial']['task_list'].id
|
)
|
||||||
|
self.fields["assigned_to"].widget.attrs = {
|
||||||
|
"id": "id_assigned_to",
|
||||||
|
"class": "custom-select mb-3",
|
||||||
|
"name": "assigned_to",
|
||||||
|
}
|
||||||
|
self.fields["task_list"].value = kwargs["initial"]["task_list"].id
|
||||||
|
|
||||||
due_date = forms.DateField(
|
due_date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}), required=False)
|
||||||
widget=forms.DateInput(attrs={'type': 'date'}), required=False)
|
|
||||||
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(widget=forms.widgets.TextInput())
|
||||||
widget=forms.widgets.TextInput())
|
|
||||||
|
|
||||||
note = forms.CharField(
|
note = forms.CharField(widget=forms.Textarea(), required=False)
|
||||||
widget=forms.Textarea(), required=False)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Task
|
model = Task
|
||||||
|
@ -51,27 +57,24 @@ class AddEditTaskForm(ModelForm):
|
||||||
class AddExternalTaskForm(ModelForm):
|
class AddExternalTaskForm(ModelForm):
|
||||||
"""Form to allow users who are not part of the GTD system to file a ticket."""
|
"""Form to allow users who are not part of the GTD system to file a ticket."""
|
||||||
|
|
||||||
title = forms.CharField(
|
title = forms.CharField(widget=forms.widgets.TextInput(attrs={"size": 35}), label="Summary")
|
||||||
widget=forms.widgets.TextInput(attrs={'size': 35}),
|
note = forms.CharField(widget=forms.widgets.Textarea(), label="Problem Description")
|
||||||
label="Summary"
|
priority = forms.IntegerField(widget=forms.HiddenInput())
|
||||||
)
|
|
||||||
note = forms.CharField(
|
|
||||||
widget=forms.widgets.Textarea(),
|
|
||||||
label='Problem Description',
|
|
||||||
)
|
|
||||||
priority = forms.IntegerField(
|
|
||||||
widget=forms.HiddenInput(),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Task
|
model = Task
|
||||||
exclude = (
|
exclude = (
|
||||||
'task_list', 'created_date', 'due_date', 'created_by', 'assigned_to', 'completed', 'completed_date', )
|
"task_list",
|
||||||
|
"created_date",
|
||||||
|
"due_date",
|
||||||
|
"created_by",
|
||||||
|
"assigned_to",
|
||||||
|
"completed",
|
||||||
|
"completed_date",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchForm(forms.Form):
|
class SearchForm(forms.Form):
|
||||||
"""Search."""
|
"""Search."""
|
||||||
|
|
||||||
q = forms.CharField(
|
q = forms.CharField(widget=forms.widgets.TextInput(attrs={"size": 35}))
|
||||||
widget=forms.widgets.TextInput(attrs={'size': 35})
|
|
||||||
)
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.utils import timezone
|
||||||
|
|
||||||
class TaskList(models.Model):
|
class TaskList(models.Model):
|
||||||
name = models.CharField(max_length=60)
|
name = models.CharField(max_length=60)
|
||||||
slug = models.SlugField(default='',)
|
slug = models.SlugField(default="")
|
||||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -28,12 +28,19 @@ class Task(models.Model):
|
||||||
title = models.CharField(max_length=140)
|
title = models.CharField(max_length=140)
|
||||||
task_list = models.ForeignKey(TaskList, on_delete=models.CASCADE, null=True)
|
task_list = models.ForeignKey(TaskList, on_delete=models.CASCADE, null=True)
|
||||||
created_date = models.DateField(default=timezone.now, blank=True, null=True)
|
created_date = models.DateField(default=timezone.now, blank=True, null=True)
|
||||||
due_date = models.DateField(blank=True, null=True, )
|
due_date = models.DateField(blank=True, null=True)
|
||||||
completed = models.BooleanField(default=False)
|
completed = models.BooleanField(default=False)
|
||||||
completed_date = models.DateField(blank=True, null=True)
|
completed_date = models.DateField(blank=True, null=True)
|
||||||
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='todo_created_by', on_delete=models.CASCADE)
|
created_by = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL, related_name="todo_created_by", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
assigned_to = models.ForeignKey(
|
assigned_to = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, blank=True, null=True, related_name='todo_assigned_to', on_delete=models.CASCADE)
|
settings.AUTH_USER_MODEL,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
related_name="todo_assigned_to",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
note = models.TextField(blank=True, null=True)
|
note = models.TextField(blank=True, null=True)
|
||||||
priority = models.PositiveIntegerField()
|
priority = models.PositiveIntegerField()
|
||||||
|
|
||||||
|
@ -47,7 +54,7 @@ class Task(models.Model):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('todo:task_detail', kwargs={'task_id': self.id, })
|
return reverse("todo:task_detail", kwargs={"task_id": self.id})
|
||||||
|
|
||||||
# Auto-set the Task creation / completed date
|
# Auto-set the Task creation / completed date
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
|
@ -65,6 +72,7 @@ class Comment(models.Model):
|
||||||
Not using Django's built-in comments because we want to be able to save
|
Not using Django's built-in comments because we want to be able to save
|
||||||
a comment and change task details at the same time. Rolling our own since it's easy.
|
a comment and change task details at the same time. Rolling our own since it's easy.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||||
date = models.DateTimeField(default=datetime.datetime.now)
|
date = models.DateTimeField(default=datetime.datetime.now)
|
||||||
|
|
|
@ -5,13 +5,14 @@ from django.contrib.auth.models import Group
|
||||||
from todo.models import Task, TaskList
|
from todo.models import Task, TaskList
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def todo_setup(django_user_model):
|
def todo_setup(django_user_model):
|
||||||
# Two groups with different users, two sets of tasks.
|
# Two groups with different users, two sets of tasks.
|
||||||
|
|
||||||
g1 = Group.objects.create(name="Workgroup One")
|
g1 = Group.objects.create(name="Workgroup One")
|
||||||
u1 = django_user_model.objects.create_user(username="u1", password="password", email="u1@example.com")
|
u1 = django_user_model.objects.create_user(
|
||||||
|
username="u1", password="password", email="u1@example.com"
|
||||||
|
)
|
||||||
u1.groups.add(g1)
|
u1.groups.add(g1)
|
||||||
tlist1 = TaskList.objects.create(group=g1, name="Zip", slug="zip")
|
tlist1 = TaskList.objects.create(group=g1, name="Zip", slug="zip")
|
||||||
Task.objects.create(created_by=u1, title="Task 1", task_list=tlist1, priority=1)
|
Task.objects.create(created_by=u1, title="Task 1", task_list=tlist1, priority=1)
|
||||||
|
@ -19,7 +20,9 @@ def todo_setup(django_user_model):
|
||||||
Task.objects.create(created_by=u1, title="Task 3", task_list=tlist1, priority=3)
|
Task.objects.create(created_by=u1, title="Task 3", task_list=tlist1, priority=3)
|
||||||
|
|
||||||
g2 = Group.objects.create(name="Workgroup Two")
|
g2 = Group.objects.create(name="Workgroup Two")
|
||||||
u2 = django_user_model.objects.create_user(username="u2", password="password", email="u2@example.com")
|
u2 = django_user_model.objects.create_user(
|
||||||
|
username="u2", password="password", email="u2@example.com"
|
||||||
|
)
|
||||||
u2.groups.add(g2)
|
u2.groups.add(g2)
|
||||||
tlist2 = TaskList.objects.create(group=g2, name="Zap", slug="zap")
|
tlist2 = TaskList.objects.create(group=g2, name="Zap", slug="zap")
|
||||||
Task.objects.create(created_by=u2, title="Task 1", task_list=tlist2, priority=1)
|
Task.objects.create(created_by=u2, title="Task 1", task_list=tlist2, priority=1)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from todo.utils import send_notify_mail, send_email_to_thread_participants
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
# Set up an in-memory mail server to receive test emails
|
# Set up an in-memory mail server to receive test emails
|
||||||
def email_backend_setup(settings):
|
def email_backend_setup(settings):
|
||||||
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
|
settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
||||||
|
|
||||||
|
|
||||||
def test_send_notify_mail_not_me(todo_setup, django_user_model, email_backend_setup):
|
def test_send_notify_mail_not_me(todo_setup, django_user_model, email_backend_setup):
|
||||||
|
@ -46,13 +46,17 @@ def test_send_email_to_thread_participants(todo_setup, django_user_model, email_
|
||||||
u1 = django_user_model.objects.get(username="u1")
|
u1 = django_user_model.objects.get(username="u1")
|
||||||
task = Task.objects.filter(created_by=u1).first()
|
task = Task.objects.filter(created_by=u1).first()
|
||||||
|
|
||||||
u3 = django_user_model.objects.create_user(username="u3", password="zzz", email="u3@example.com")
|
u3 = django_user_model.objects.create_user(
|
||||||
u4 = django_user_model.objects.create_user(username="u4", password="zzz", email="u4@example.com")
|
username="u3", password="zzz", email="u3@example.com"
|
||||||
Comment.objects.create(author=u3, task=task, body="Hello", )
|
)
|
||||||
Comment.objects.create(author=u4, task=task, body="Hello", )
|
u4 = django_user_model.objects.create_user(
|
||||||
|
username="u4", password="zzz", email="u4@example.com"
|
||||||
|
)
|
||||||
|
Comment.objects.create(author=u3, task=task, body="Hello")
|
||||||
|
Comment.objects.create(author=u4, task=task, body="Hello")
|
||||||
|
|
||||||
send_email_to_thread_participants(task, "test body", u1)
|
send_email_to_thread_participants(task, "test body", u1)
|
||||||
assert len(mail.outbox) == 1 # One message to multiple recipients
|
assert len(mail.outbox) == 1 # One message to multiple recipients
|
||||||
assert 'u1@example.com' in mail.outbox[0].recipients()
|
assert "u1@example.com" in mail.outbox[0].recipients()
|
||||||
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()
|
||||||
|
|
|
@ -16,19 +16,20 @@ After that, view contents and behaviors.
|
||||||
|
|
||||||
# ### SMOKETESTS ###
|
# ### SMOKETESTS ###
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_todo_setup(todo_setup):
|
def test_todo_setup(todo_setup):
|
||||||
assert Task.objects.all().count() == 6
|
assert Task.objects.all().count() == 6
|
||||||
|
|
||||||
|
|
||||||
def test_view_list_lists(todo_setup, admin_client):
|
def test_view_list_lists(todo_setup, admin_client):
|
||||||
url = reverse('todo:lists')
|
url = reverse("todo:lists")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_view_reorder(todo_setup, admin_client):
|
def test_view_reorder(todo_setup, admin_client):
|
||||||
url = reverse('todo:reorder_tasks')
|
url = reverse("todo:reorder_tasks")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 201 # Special case return value expected
|
assert response.status_code == 201 # Special case return value expected
|
||||||
|
|
||||||
|
@ -37,53 +38,55 @@ def test_view_external_add(todo_setup, admin_client, settings):
|
||||||
default_list = TaskList.objects.first()
|
default_list = TaskList.objects.first()
|
||||||
settings.TODO_DEFAULT_LIST_SLUG = default_list.slug
|
settings.TODO_DEFAULT_LIST_SLUG = default_list.slug
|
||||||
assert settings.TODO_DEFAULT_LIST_SLUG == default_list.slug
|
assert settings.TODO_DEFAULT_LIST_SLUG == default_list.slug
|
||||||
url = reverse('todo:external_add')
|
url = reverse("todo:external_add")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_view_mine(todo_setup, admin_client):
|
def test_view_mine(todo_setup, admin_client):
|
||||||
url = reverse('todo:mine')
|
url = reverse("todo:mine")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_view_list_completed(todo_setup, admin_client):
|
def test_view_list_completed(todo_setup, admin_client):
|
||||||
tlist = TaskList.objects.get(slug="zip")
|
tlist = TaskList.objects.get(slug="zip")
|
||||||
url = reverse('todo:list_detail_completed', kwargs={'list_id': tlist.id, 'list_slug': tlist.slug})
|
url = reverse(
|
||||||
|
"todo:list_detail_completed", kwargs={"list_id": tlist.id, "list_slug": tlist.slug}
|
||||||
|
)
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_view_list(todo_setup, admin_client):
|
def test_view_list(todo_setup, admin_client):
|
||||||
tlist = TaskList.objects.get(slug="zip")
|
tlist = TaskList.objects.get(slug="zip")
|
||||||
url = reverse('todo:list_detail', kwargs={'list_id': tlist.id, 'list_slug': tlist.slug})
|
url = reverse("todo:list_detail", kwargs={"list_id": tlist.id, "list_slug": tlist.slug})
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_del_list(todo_setup, admin_client):
|
def test_del_list(todo_setup, admin_client):
|
||||||
tlist = TaskList.objects.get(slug="zip")
|
tlist = TaskList.objects.get(slug="zip")
|
||||||
url = reverse('todo:del_list', kwargs={'list_id': tlist.id, 'list_slug': tlist.slug})
|
url = reverse("todo:del_list", kwargs={"list_id": tlist.id, "list_slug": tlist.slug})
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_view_add_list(todo_setup, admin_client):
|
def test_view_add_list(todo_setup, admin_client):
|
||||||
url = reverse('todo:add_list')
|
url = reverse("todo:add_list")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_view_task_detail(todo_setup, admin_client):
|
def test_view_task_detail(todo_setup, admin_client):
|
||||||
task = Task.objects.first()
|
task = Task.objects.first()
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse("todo:task_detail", kwargs={"task_id": task.id})
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_view_search(todo_setup, admin_client):
|
def test_view_search(todo_setup, admin_client):
|
||||||
url = reverse('todo:search')
|
url = reverse("todo:search")
|
||||||
response = admin_client.get(url)
|
response = admin_client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@ -100,11 +103,11 @@ def test_no_javascript_in_task_note(todo_setup, client):
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
"title": title,
|
"title": title,
|
||||||
"note": note,
|
"note": note,
|
||||||
'add_edit_task': 'Submit'
|
"add_edit_task": "Submit",
|
||||||
}
|
}
|
||||||
|
|
||||||
client.login(username='u2', password="password")
|
client.login(username="u2", password="password")
|
||||||
url = reverse('todo:list_detail', kwargs={"list_id": task_list.id, "list_slug": task_list.slug})
|
url = reverse("todo:list_detail", kwargs={"list_id": task_list.id, "list_slug": task_list.slug})
|
||||||
|
|
||||||
response = client.post(url, data)
|
response = client.post(url, data)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
|
@ -118,7 +121,7 @@ def test_no_javascript_in_task_note(todo_setup, client):
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_no_javascript_in_comments(todo_setup, client):
|
def test_no_javascript_in_comments(todo_setup, client):
|
||||||
user = get_user_model().objects.get(username="u2")
|
user = get_user_model().objects.get(username="u2")
|
||||||
client.login(username='u2', password="password")
|
client.login(username="u2", password="password")
|
||||||
|
|
||||||
task = Task.objects.first()
|
task = Task.objects.first()
|
||||||
task.created_by = user
|
task.created_by = user
|
||||||
|
@ -127,11 +130,8 @@ def test_no_javascript_in_comments(todo_setup, client):
|
||||||
user.groups.add(task.task_list.group)
|
user.groups.add(task.task_list.group)
|
||||||
|
|
||||||
comment = "foo <script>alert('oh noez');</script> bar"
|
comment = "foo <script>alert('oh noez');</script> bar"
|
||||||
data = {
|
data = {"comment-body": comment, "add_comment": "Submit"}
|
||||||
"comment-body": comment,
|
url = reverse("todo:task_detail", kwargs={"task_id": task.id})
|
||||||
"add_comment": 'Submit'
|
|
||||||
}
|
|
||||||
url = reverse('todo:task_detail', kwargs={"task_id": task.id})
|
|
||||||
|
|
||||||
response = client.post(url, data)
|
response = client.post(url, data)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -152,7 +152,7 @@ These exercise our custom @staff_only decorator without calling that function ex
|
||||||
|
|
||||||
|
|
||||||
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")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
@ -160,7 +160,7 @@ def test_view_add_list_nonadmin(todo_setup, client):
|
||||||
|
|
||||||
def test_view_del_list_nonadmin(todo_setup, client):
|
def test_view_del_list_nonadmin(todo_setup, client):
|
||||||
tlist = TaskList.objects.get(slug="zip")
|
tlist = TaskList.objects.get(slug="zip")
|
||||||
url = reverse('todo:del_list', kwargs={'list_id': tlist.id, 'list_slug': tlist.slug})
|
url = reverse("todo:del_list", kwargs={"list_id": tlist.id, "list_slug": tlist.slug})
|
||||||
client.login(username="you", password="password")
|
client.login(username="you", password="password")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
@ -170,7 +170,7 @@ def test_view_list_mine(todo_setup, client):
|
||||||
"""View a list in a group I belong to.
|
"""View a list in a group I belong to.
|
||||||
"""
|
"""
|
||||||
tlist = TaskList.objects.get(slug="zip") # User u1 is in this group's list
|
tlist = TaskList.objects.get(slug="zip") # User u1 is in this group's list
|
||||||
url = reverse('todo:list_detail', kwargs={'list_id': tlist.id, 'list_slug': tlist.slug})
|
url = reverse("todo:list_detail", kwargs={"list_id": tlist.id, "list_slug": tlist.slug})
|
||||||
client.login(username="u1", password="password")
|
client.login(username="u1", password="password")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -180,7 +180,7 @@ def test_view_list_not_mine(todo_setup, client):
|
||||||
"""View a list in a group I don't belong to.
|
"""View a list in a group I don't belong to.
|
||||||
"""
|
"""
|
||||||
tlist = TaskList.objects.get(slug="zip") # User u1 is in this group, user u2 is not.
|
tlist = TaskList.objects.get(slug="zip") # User u1 is in this group, user u2 is not.
|
||||||
url = reverse('todo:list_detail', kwargs={'list_id': tlist.id, 'list_slug': tlist.slug})
|
url = reverse("todo:list_detail", kwargs={"list_id": tlist.id, "list_slug": tlist.slug})
|
||||||
client.login(username="u2", password="password")
|
client.login(username="u2", password="password")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
@ -190,7 +190,7 @@ def test_view_task_mine(todo_setup, client):
|
||||||
# Users can always view their own tasks
|
# Users can always view their own tasks
|
||||||
task = Task.objects.filter(created_by__username="u1").first()
|
task = Task.objects.filter(created_by__username="u1").first()
|
||||||
client.login(username="u1", password="password")
|
client.login(username="u1", password="password")
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse("todo:task_detail", kwargs={"task_id": task.id})
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ def test_view_task_my_group(todo_setup, client, django_user_model):
|
||||||
|
|
||||||
# Now u2 should be able to view one of u1's tasks.
|
# Now u2 should be able to view one of u1's tasks.
|
||||||
task = Task.objects.filter(created_by__username="u1").first()
|
task = Task.objects.filter(created_by__username="u1").first()
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse("todo:task_detail", kwargs={"task_id": task.id})
|
||||||
client.login(username="u2", password="password")
|
client.login(username="u2", password="password")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -215,7 +215,8 @@ def test_view_task_not_in_my_group(todo_setup, client):
|
||||||
# User canNOT view a task that isn't theirs if the two users are not in a shared group.
|
# User canNOT view a task that isn't theirs if the two users are not in a shared group.
|
||||||
# For this we can use the fixture data as-is.
|
# For this we can use the fixture data as-is.
|
||||||
task = Task.objects.filter(created_by__username="u1").first()
|
task = Task.objects.filter(created_by__username="u1").first()
|
||||||
url = reverse('todo:task_detail', kwargs={'task_id': task.id})
|
url = reverse("todo:task_detail", kwargs={"task_id": task.id})
|
||||||
client.login(username="u2", password="password")
|
client.login(username="u2", password="password")
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
|
@ -11,24 +11,30 @@ def send_notify_mail(new_task):
|
||||||
|
|
||||||
if not new_task.assigned_to == new_task.created_by:
|
if not new_task.assigned_to == new_task.created_by:
|
||||||
current_site = Site.objects.get_current()
|
current_site = Site.objects.get_current()
|
||||||
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': new_task})
|
email_subject = render_to_string("todo/email/assigned_subject.txt", {"task": new_task})
|
||||||
email_body = render_to_string(
|
email_body = render_to_string(
|
||||||
"todo/email/assigned_body.txt",
|
"todo/email/assigned_body.txt", {"task": new_task, "site": current_site}
|
||||||
{'task': new_task, 'site': current_site, })
|
)
|
||||||
|
|
||||||
send_mail(
|
send_mail(
|
||||||
email_subject, email_body, new_task.created_by.email,
|
email_subject,
|
||||||
[new_task.assigned_to.email], fail_silently=False)
|
email_body,
|
||||||
|
new_task.created_by.email,
|
||||||
|
[new_task.assigned_to.email],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def send_email_to_thread_participants(task, msg_body, user, subject=None):
|
def send_email_to_thread_participants(task, msg_body, user, subject=None):
|
||||||
# Notify all previous commentors on a Task about a new comment.
|
# Notify all previous commentors on a Task about a new comment.
|
||||||
|
|
||||||
current_site = Site.objects.get_current()
|
current_site = Site.objects.get_current()
|
||||||
email_subject = subject if subject else render_to_string("todo/email/assigned_subject.txt", {'task': task})
|
email_subject = (
|
||||||
|
subject if subject else render_to_string("todo/email/assigned_subject.txt", {"task": task})
|
||||||
|
)
|
||||||
email_body = render_to_string(
|
email_body = render_to_string(
|
||||||
"todo/email/newcomment_body.txt",
|
"todo/email/newcomment_body.txt",
|
||||||
{'task': task, 'body': msg_body, 'site': current_site, 'user': user}
|
{"task": task, "body": msg_body, "site": current_site, "user": user},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get list of all thread participants - everyone who has commented, plus task creator.
|
# Get list of all thread participants - everyone who has commented, plus task creator.
|
||||||
|
|
201
todo/views.py
201
todo/views.py
|
@ -20,10 +20,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from todo.forms import AddTaskListForm, AddEditTaskForm, AddExternalTaskForm, SearchForm
|
from todo.forms import AddTaskListForm, AddEditTaskForm, AddExternalTaskForm, SearchForm
|
||||||
from todo.models import Task, TaskList, Comment
|
from todo.models import Task, TaskList, Comment
|
||||||
from todo.utils import (
|
from todo.utils import send_notify_mail, send_email_to_thread_participants
|
||||||
send_notify_mail,
|
|
||||||
send_email_to_thread_participants,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def staff_only(function):
|
def staff_only(function):
|
||||||
|
@ -31,6 +28,7 @@ def staff_only(function):
|
||||||
Custom view decorator allows us to raise 403 on insufficient permissions,
|
Custom view decorator allows us to raise 403 on insufficient permissions,
|
||||||
rather than redirect user to login view.
|
rather than redirect user to login view.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrap(request, *args, **kwargs):
|
def wrap(request, *args, **kwargs):
|
||||||
if request.user.is_staff:
|
if request.user.is_staff:
|
||||||
return function(request, *args, **kwargs)
|
return function(request, *args, **kwargs)
|
||||||
|
@ -52,13 +50,18 @@ def list_lists(request) -> HttpResponse:
|
||||||
|
|
||||||
# Make sure user belongs to at least one group.
|
# Make sure user belongs to at least one group.
|
||||||
if request.user.groups.all().count() == 0:
|
if request.user.groups.all().count() == 0:
|
||||||
messages.warning(request, "You do not yet belong to any groups. Ask your administrator to add you to one.")
|
messages.warning(
|
||||||
|
request,
|
||||||
|
"You do not yet belong to any groups. Ask your administrator to add you to one.",
|
||||||
|
)
|
||||||
|
|
||||||
# Superusers see all lists
|
# Superusers see all lists
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
lists = TaskList.objects.all().order_by('group', 'name')
|
lists = TaskList.objects.all().order_by("group", "name")
|
||||||
else:
|
else:
|
||||||
lists = TaskList.objects.filter(group__in=request.user.groups.all()).order_by('group', 'name')
|
lists = TaskList.objects.filter(group__in=request.user.groups.all()).order_by(
|
||||||
|
"group", "name"
|
||||||
|
)
|
||||||
|
|
||||||
list_count = lists.count()
|
list_count = lists.count()
|
||||||
|
|
||||||
|
@ -66,7 +69,11 @@ def list_lists(request) -> HttpResponse:
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
task_count = Task.objects.filter(completed=0).count()
|
task_count = Task.objects.filter(completed=0).count()
|
||||||
else:
|
else:
|
||||||
task_count = Task.objects.filter(completed=0).filter(task_list__group__in=request.user.groups.all()).count()
|
task_count = (
|
||||||
|
Task.objects.filter(completed=0)
|
||||||
|
.filter(task_list__group__in=request.user.groups.all())
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"lists": lists,
|
"lists": lists,
|
||||||
|
@ -76,7 +83,7 @@ def list_lists(request) -> HttpResponse:
|
||||||
"task_count": task_count,
|
"task_count": task_count,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'todo/list_lists.html', context)
|
return render(request, "todo/list_lists.html", context)
|
||||||
|
|
||||||
|
|
||||||
@staff_only
|
@staff_only
|
||||||
|
@ -92,10 +99,10 @@ def del_list(request, list_id: int, list_slug: str) -> HttpResponse:
|
||||||
if task_list.group not in request.user.groups.all() and not request.user.is_staff:
|
if task_list.group not in request.user.groups.all() and not request.user.is_staff:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
TaskList.objects.get(id=task_list.id).delete()
|
TaskList.objects.get(id=task_list.id).delete()
|
||||||
messages.success(request, "{list_name} is gone.".format(list_name=task_list.name))
|
messages.success(request, "{list_name} is gone.".format(list_name=task_list.name))
|
||||||
return redirect('todo:lists')
|
return redirect("todo:lists")
|
||||||
else:
|
else:
|
||||||
task_count_done = Task.objects.filter(task_list=task_list.id, completed=True).count()
|
task_count_done = Task.objects.filter(task_list=task_list.id, completed=True).count()
|
||||||
task_count_undone = Task.objects.filter(task_list=task_list.id, completed=False).count()
|
task_count_undone = Task.objects.filter(task_list=task_list.id, completed=False).count()
|
||||||
|
@ -108,7 +115,7 @@ def del_list(request, list_id: int, list_slug: str) -> HttpResponse:
|
||||||
"task_count_total": task_count_total,
|
"task_count_total": task_count_total,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'todo/del_list.html', context)
|
return render(request, "todo/del_list.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -141,33 +148,36 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
||||||
# Add New Task Form
|
# Add New Task Form
|
||||||
# ######################
|
# ######################
|
||||||
|
|
||||||
if request.POST.getlist('add_edit_task'):
|
if request.POST.getlist("add_edit_task"):
|
||||||
form = AddEditTaskForm(request.user, request.POST, initial={
|
form = AddEditTaskForm(
|
||||||
'assigned_to': request.user.id,
|
request.user,
|
||||||
'priority': 999,
|
request.POST,
|
||||||
'task_list': task_list
|
initial={"assigned_to": request.user.id, "priority": 999, "task_list": task_list},
|
||||||
})
|
)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
new_task = form.save(commit=False)
|
new_task = form.save(commit=False)
|
||||||
new_task.created_date = timezone.now()
|
new_task.created_date = timezone.now()
|
||||||
new_task.note = bleach.clean(form.cleaned_data['note'], strip=True)
|
new_task.note = bleach.clean(form.cleaned_data["note"], strip=True)
|
||||||
form.save()
|
form.save()
|
||||||
|
|
||||||
# Send email alert only if Notify checkbox is checked AND assignee is not same as the submitter
|
# Send email alert only if Notify checkbox is checked AND assignee is not same as the submitter
|
||||||
if "notify" in request.POST and new_task.assigned_to and new_task.assigned_to != request.user:
|
if (
|
||||||
|
"notify" in request.POST
|
||||||
|
and new_task.assigned_to
|
||||||
|
and new_task.assigned_to != request.user
|
||||||
|
):
|
||||||
send_notify_mail(new_task)
|
send_notify_mail(new_task)
|
||||||
|
|
||||||
messages.success(request, "New task \"{t}\" has been added.".format(t=new_task.title))
|
messages.success(request, 'New task "{t}" has been added.'.format(t=new_task.title))
|
||||||
return redirect(request.path)
|
return redirect(request.path)
|
||||||
else:
|
else:
|
||||||
# Don't allow adding new tasks on some views
|
# Don't allow adding new tasks on some views
|
||||||
if list_slug not in ["mine", "recent-add", "recent-complete", ]:
|
if list_slug not in ["mine", "recent-add", "recent-complete"]:
|
||||||
form = AddEditTaskForm(request.user, initial={
|
form = AddEditTaskForm(
|
||||||
'assigned_to': request.user.id,
|
request.user,
|
||||||
'priority': 999,
|
initial={"assigned_to": request.user.id, "priority": 999, "task_list": task_list},
|
||||||
'task_list': task_list,
|
)
|
||||||
})
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"list_id": list_id,
|
"list_id": list_id,
|
||||||
|
@ -178,7 +188,7 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
||||||
"view_completed": view_completed,
|
"view_completed": view_completed,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'todo/list_detail.html', context)
|
return render(request, "todo/list_detail.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -195,52 +205,54 @@ def task_detail(request, task_id: int) -> HttpResponse:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
# Save submitted comments
|
# Save submitted comments
|
||||||
if request.POST.get('add_comment'):
|
if request.POST.get("add_comment"):
|
||||||
Comment.objects.create(
|
Comment.objects.create(
|
||||||
author=request.user,
|
author=request.user,
|
||||||
task=task,
|
task=task,
|
||||||
body=bleach.clean(request.POST['comment-body'], strip=True),
|
body=bleach.clean(request.POST["comment-body"], strip=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
send_email_to_thread_participants(
|
send_email_to_thread_participants(
|
||||||
task, request.POST['comment-body'], request.user,
|
task,
|
||||||
subject='New comment posted on task "{}"'.format(task.title))
|
request.POST["comment-body"],
|
||||||
|
request.user,
|
||||||
|
subject='New comment posted on task "{}"'.format(task.title),
|
||||||
|
)
|
||||||
messages.success(request, "Comment posted. Notification email sent to thread participants.")
|
messages.success(request, "Comment posted. Notification email sent to thread participants.")
|
||||||
|
|
||||||
# Save task edits
|
# Save task edits
|
||||||
if request.POST.get('add_edit_task'):
|
if request.POST.get("add_edit_task"):
|
||||||
form = AddEditTaskForm(request.user, request.POST, instance=task, initial={'task_list': task.task_list})
|
form = AddEditTaskForm(
|
||||||
|
request.user, request.POST, instance=task, initial={"task_list": task.task_list}
|
||||||
|
)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
item = form.save(commit=False)
|
item = form.save(commit=False)
|
||||||
item.note = bleach.clean(form.cleaned_data['note'], strip=True)
|
item.note = bleach.clean(form.cleaned_data["note"], strip=True)
|
||||||
item.save()
|
item.save()
|
||||||
messages.success(request, "The task has been edited.")
|
messages.success(request, "The task has been edited.")
|
||||||
return redirect('todo:list_detail', list_id=task.task_list.id, list_slug=task.task_list.slug)
|
return redirect(
|
||||||
|
"todo:list_detail", list_id=task.task_list.id, list_slug=task.task_list.slug
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
form = AddEditTaskForm(request.user, instance=task, initial={'task_list': task.task_list})
|
form = AddEditTaskForm(request.user, instance=task, initial={"task_list": task.task_list})
|
||||||
|
|
||||||
# Mark complete
|
# Mark complete
|
||||||
if request.POST.get('toggle_done'):
|
if request.POST.get("toggle_done"):
|
||||||
results_changed = toggle_done([task.id, ])
|
results_changed = toggle_done([task.id])
|
||||||
for res in results_changed:
|
for res in results_changed:
|
||||||
messages.success(request, res)
|
messages.success(request, res)
|
||||||
|
|
||||||
return redirect('todo:task_detail', task_id=task.id,)
|
return redirect("todo:task_detail", task_id=task.id)
|
||||||
|
|
||||||
if task.due_date:
|
if task.due_date:
|
||||||
thedate = task.due_date
|
thedate = task.due_date
|
||||||
else:
|
else:
|
||||||
thedate = datetime.datetime.now()
|
thedate = datetime.datetime.now()
|
||||||
|
|
||||||
context = {
|
context = {"task": task, "comment_list": comment_list, "form": form, "thedate": thedate}
|
||||||
"task": task,
|
|
||||||
"comment_list": comment_list,
|
|
||||||
"form": form,
|
|
||||||
"thedate": thedate,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'todo/task_detail.html', context)
|
return render(request, "todo/task_detail.html", context)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
|
@ -248,7 +260,7 @@ def task_detail(request, task_id: int) -> HttpResponse:
|
||||||
def reorder_tasks(request) -> HttpResponse:
|
def reorder_tasks(request) -> HttpResponse:
|
||||||
"""Handle task re-ordering (priorities) from JQuery drag/drop in list_detail.html
|
"""Handle task re-ordering (priorities) from JQuery drag/drop in list_detail.html
|
||||||
"""
|
"""
|
||||||
newtasklist = request.POST.getlist('tasktable[]')
|
newtasklist = request.POST.getlist("tasktable[]")
|
||||||
if newtasklist:
|
if newtasklist:
|
||||||
# First task in received list is always empty - remove it
|
# First task in received list is always empty - remove it
|
||||||
del newtasklist[0]
|
del newtasklist[0]
|
||||||
|
@ -280,24 +292,23 @@ def add_list(request) -> HttpResponse:
|
||||||
newlist.slug = slugify(newlist.name)
|
newlist.slug = slugify(newlist.name)
|
||||||
newlist.save()
|
newlist.save()
|
||||||
messages.success(request, "A new list has been added.")
|
messages.success(request, "A new list has been added.")
|
||||||
return redirect('todo:lists')
|
return redirect("todo:lists")
|
||||||
|
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
messages.warning(
|
messages.warning(
|
||||||
request,
|
request,
|
||||||
"There was a problem saving the new list. "
|
"There was a problem saving the new list. "
|
||||||
"Most likely a list with the same name in the same group already exists.")
|
"Most likely a list with the same name in the same group already exists.",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if request.user.groups.all().count() == 1:
|
if request.user.groups.all().count() == 1:
|
||||||
form = AddTaskListForm(request.user, initial={"group": request.user.groups.all()[0]})
|
form = AddTaskListForm(request.user, initial={"group": request.user.groups.all()[0]})
|
||||||
else:
|
else:
|
||||||
form = AddTaskListForm(request.user)
|
form = AddTaskListForm(request.user)
|
||||||
|
|
||||||
context = {
|
context = {"form": form}
|
||||||
"form": form,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'todo/add_list.html', context)
|
return render(request, "todo/add_list.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -306,36 +317,32 @@ def search(request) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
if request.GET:
|
if request.GET:
|
||||||
|
|
||||||
query_string = ''
|
query_string = ""
|
||||||
found_tasks = None
|
found_tasks = None
|
||||||
if ('q' in request.GET) and request.GET['q'].strip():
|
if ("q" in request.GET) and request.GET["q"].strip():
|
||||||
query_string = request.GET['q']
|
query_string = request.GET["q"]
|
||||||
|
|
||||||
found_tasks = Task.objects.filter(
|
found_tasks = Task.objects.filter(
|
||||||
Q(title__icontains=query_string) |
|
Q(title__icontains=query_string) | Q(note__icontains=query_string)
|
||||||
Q(note__icontains=query_string)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# What if they selected the "completed" toggle but didn't enter a query string?
|
# What if they selected the "completed" toggle but didn't enter a query string?
|
||||||
# We still need found_tasks in a queryset so it can be "excluded" below.
|
# We still need found_tasks in a queryset so it can be "excluded" below.
|
||||||
found_tasks = Task.objects.all()
|
found_tasks = Task.objects.all()
|
||||||
|
|
||||||
if 'inc_complete' in request.GET:
|
if "inc_complete" in request.GET:
|
||||||
found_tasks = found_tasks.exclude(completed=True)
|
found_tasks = found_tasks.exclude(completed=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
query_string = None
|
query_string = None
|
||||||
found_tasks =None
|
found_tasks = None
|
||||||
|
|
||||||
# Only include tasks that are in groups of which this user is a member:
|
# Only include tasks that are in groups of which this user is a member:
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
found_tasks = found_tasks.filter(task_list__group__in=request.user.groups.all())
|
found_tasks = found_tasks.filter(task_list__group__in=request.user.groups.all())
|
||||||
|
|
||||||
context = {
|
context = {"query_string": query_string, "found_tasks": found_tasks}
|
||||||
'query_string': query_string,
|
return render(request, "todo/search_results.html", context)
|
||||||
'found_tasks': found_tasks
|
|
||||||
}
|
|
||||||
return render(request, 'todo/search_results.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -348,10 +355,14 @@ def external_add(request) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not settings.TODO_DEFAULT_LIST_SLUG:
|
if not settings.TODO_DEFAULT_LIST_SLUG:
|
||||||
raise RuntimeError("This feature requires TODO_DEFAULT_LIST_SLUG: in settings. See documentation.")
|
raise RuntimeError(
|
||||||
|
"This feature requires TODO_DEFAULT_LIST_SLUG: in settings. See documentation."
|
||||||
|
)
|
||||||
|
|
||||||
if not TaskList.objects.filter(slug=settings.TODO_DEFAULT_LIST_SLUG).exists():
|
if not TaskList.objects.filter(slug=settings.TODO_DEFAULT_LIST_SLUG).exists():
|
||||||
raise RuntimeError("There is no TaskList with slug specified for TODO_DEFAULT_LIST_SLUG in settings.")
|
raise RuntimeError(
|
||||||
|
"There is no TaskList with slug specified for TODO_DEFAULT_LIST_SLUG in settings."
|
||||||
|
)
|
||||||
|
|
||||||
if request.POST:
|
if request.POST:
|
||||||
form = AddExternalTaskForm(request.POST)
|
form = AddExternalTaskForm(request.POST)
|
||||||
|
@ -367,26 +378,36 @@ def external_add(request) -> HttpResponse:
|
||||||
|
|
||||||
# Send email to assignee if we have one
|
# Send email to assignee if we have one
|
||||||
if task.assigned_to:
|
if task.assigned_to:
|
||||||
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task.title})
|
email_subject = render_to_string(
|
||||||
email_body = render_to_string("todo/email/assigned_body.txt", {'task': task, 'site': current_site, })
|
"todo/email/assigned_subject.txt", {"task": task.title}
|
||||||
|
)
|
||||||
|
email_body = render_to_string(
|
||||||
|
"todo/email/assigned_body.txt", {"task": task, "site": current_site}
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
send_mail(
|
send_mail(
|
||||||
email_subject, email_body, task.created_by.email,
|
email_subject,
|
||||||
[task.assigned_to.email, ], fail_silently=False)
|
email_body,
|
||||||
|
task.created_by.email,
|
||||||
|
[task.assigned_to.email],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
messages.warning(request, "Task saved but mail not sent. Contact your administrator.")
|
messages.warning(
|
||||||
|
request, "Task saved but mail not sent. Contact your administrator."
|
||||||
|
)
|
||||||
|
|
||||||
messages.success(request, "Your trouble ticket has been submitted. We'll get back to you soon.")
|
messages.success(
|
||||||
|
request, "Your trouble ticket has been submitted. We'll get back to you soon."
|
||||||
|
)
|
||||||
return redirect(settings.TODO_PUBLIC_SUBMIT_REDIRECT)
|
return redirect(settings.TODO_PUBLIC_SUBMIT_REDIRECT)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = AddExternalTaskForm(initial={'priority': 999})
|
form = AddExternalTaskForm(initial={"priority": 999})
|
||||||
|
|
||||||
context = {
|
context = {"form": form}
|
||||||
"form": form,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'todo/add_task_external.html', context)
|
return render(request, "todo/add_task_external.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -399,9 +420,9 @@ def toggle_done(request, task_id: int) -> HttpResponse:
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
if not (
|
if not (
|
||||||
(task.created_by == request.user) or
|
(task.created_by == request.user)
|
||||||
(task.assigned_to == request.user) or
|
or (task.assigned_to == request.user)
|
||||||
(task.task_list.group in request.user.groups.all())
|
or (task.task_list.group in request.user.groups.all())
|
||||||
):
|
):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
|
@ -410,8 +431,9 @@ def toggle_done(request, task_id: int) -> HttpResponse:
|
||||||
task.save()
|
task.save()
|
||||||
|
|
||||||
messages.success(request, "Task status changed for '{}'".format(task.title))
|
messages.success(request, "Task status changed for '{}'".format(task.title))
|
||||||
return redirect(reverse('todo:list_detail', kwargs={"list_id": tlist.id, "list_slug": tlist.slug}))
|
return redirect(
|
||||||
|
reverse("todo:list_detail", kwargs={"list_id": tlist.id, "list_slug": tlist.slug})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -424,9 +446,9 @@ def delete_task(request, task_id: int) -> HttpResponse:
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
if not (
|
if not (
|
||||||
(task.created_by == request.user) or
|
(task.created_by == request.user)
|
||||||
(task.assigned_to == request.user) or
|
or (task.assigned_to == request.user)
|
||||||
(task.task_list.group in request.user.groups.all())
|
or (task.task_list.group in request.user.groups.all())
|
||||||
):
|
):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
|
@ -434,4 +456,7 @@ def delete_task(request, task_id: int) -> HttpResponse:
|
||||||
task.delete()
|
task.delete()
|
||||||
|
|
||||||
messages.success(request, "Task '{}' has been deleted".format(task.title))
|
messages.success(request, "Task '{}' has been deleted".format(task.title))
|
||||||
return redirect(reverse('todo:list_detail', kwargs={"list_id": tlist.id, "list_slug": tlist.slug}))
|
return redirect(
|
||||||
|
reverse("todo:list_detail", kwargs={"list_id": tlist.id, "list_slug": tlist.slug})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue