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
|
||||
|
||||
setup(
|
||||
name='django-todo',
|
||||
name="django-todo",
|
||||
version=package.__version__,
|
||||
description=package.__doc__.strip(),
|
||||
author=package.__author__,
|
||||
|
@ -14,18 +14,18 @@ setup(
|
|||
license=package.__license__,
|
||||
packages=find_packages(),
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: Office/Business :: Groupware',
|
||||
'Topic :: Software Development :: Bug Tracking',
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Django",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Office/Business :: Groupware",
|
||||
"Topic :: Software Development :: Bug Tracking",
|
||||
],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=['unidecode', ],
|
||||
install_requires=["unidecode"],
|
||||
)
|
||||
|
|
|
@ -1,64 +1,63 @@
|
|||
import os
|
||||
|
||||
DEBUG = True,
|
||||
DEBUG = (True,)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
print("bd ", BASE_DIR)
|
||||
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"
|
||||
|
||||
# Document
|
||||
TODO_STAFF_ONLY = False
|
||||
TODO_DEFAULT_LIST_SLUG = 'tickets'
|
||||
TODO_DEFAULT_LIST_SLUG = "tickets"
|
||||
TODO_DEFAULT_ASSIGNEE = None
|
||||
TODO_PUBLIC_SUBMIT_REDIRECT = '/'
|
||||
TODO_PUBLIC_SUBMIT_REDIRECT = "/"
|
||||
|
||||
SECRET_KEY = "LKFSD8sdl.,8&sdf--"
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.staticfiles',
|
||||
'todo',
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.staticfiles",
|
||||
"todo",
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'base_urls'
|
||||
ROOT_URLCONF = "base_urls"
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'todo', 'templates'), ],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join(BASE_DIR, "todo", "templates")],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
# Your stuff: custom template context processors go here
|
||||
],
|
||||
]
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -3,14 +3,14 @@ from todo.models import Task, TaskList, Comment
|
|||
|
||||
|
||||
class TaskAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'task_list', 'completed', 'priority', 'due_date')
|
||||
list_filter = ('task_list',)
|
||||
ordering = ('priority',)
|
||||
search_fields = ('name',)
|
||||
list_display = ("title", "task_list", "completed", "priority", "due_date")
|
||||
list_filter = ("task_list",)
|
||||
ordering = ("priority",)
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_display = ('author', 'date', 'snippet')
|
||||
list_display = ("author", "date", "snippet")
|
||||
|
||||
|
||||
admin.site.register(TaskList)
|
||||
|
|
|
@ -11,13 +11,16 @@ class AddTaskListForm(ModelForm):
|
|||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(AddTaskListForm, self).__init__(*args, **kwargs)
|
||||
self.fields['group'].queryset = Group.objects.filter(user=user)
|
||||
self.fields['group'].widget.attrs = {
|
||||
'id': 'id_group', 'class': "custom-select mb-3", 'name': 'group'}
|
||||
self.fields["group"].queryset = Group.objects.filter(user=user)
|
||||
self.fields["group"].widget.attrs = {
|
||||
"id": "id_group",
|
||||
"class": "custom-select mb-3",
|
||||
"name": "group",
|
||||
}
|
||||
|
||||
class Meta:
|
||||
model = TaskList
|
||||
exclude = ['created_date', 'slug', ]
|
||||
exclude = ["created_date", "slug"]
|
||||
|
||||
|
||||
class AddEditTaskForm(ModelForm):
|
||||
|
@ -26,22 +29,25 @@ class AddEditTaskForm(ModelForm):
|
|||
|
||||
def __init__(self, user, *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()
|
||||
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'].widget.attrs = {
|
||||
'id': 'id_assigned_to', 'class': "custom-select mb-3", 'name': 'assigned_to'}
|
||||
self.fields['task_list'].value = kwargs['initial']['task_list'].id
|
||||
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"].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(
|
||||
widget=forms.DateInput(attrs={'type': 'date'}), required=False)
|
||||
due_date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}), required=False)
|
||||
|
||||
title = forms.CharField(
|
||||
widget=forms.widgets.TextInput())
|
||||
title = forms.CharField(widget=forms.widgets.TextInput())
|
||||
|
||||
note = forms.CharField(
|
||||
widget=forms.Textarea(), required=False)
|
||||
note = forms.CharField(widget=forms.Textarea(), required=False)
|
||||
|
||||
class Meta:
|
||||
model = Task
|
||||
|
@ -51,27 +57,24 @@ class AddEditTaskForm(ModelForm):
|
|||
class AddExternalTaskForm(ModelForm):
|
||||
"""Form to allow users who are not part of the GTD system to file a ticket."""
|
||||
|
||||
title = forms.CharField(
|
||||
widget=forms.widgets.TextInput(attrs={'size': 35}),
|
||||
label="Summary"
|
||||
)
|
||||
note = forms.CharField(
|
||||
widget=forms.widgets.Textarea(),
|
||||
label='Problem Description',
|
||||
)
|
||||
priority = forms.IntegerField(
|
||||
widget=forms.HiddenInput(),
|
||||
)
|
||||
title = forms.CharField(widget=forms.widgets.TextInput(attrs={"size": 35}), label="Summary")
|
||||
note = forms.CharField(widget=forms.widgets.Textarea(), label="Problem Description")
|
||||
priority = forms.IntegerField(widget=forms.HiddenInput())
|
||||
|
||||
class Meta:
|
||||
model = Task
|
||||
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):
|
||||
"""Search."""
|
||||
|
||||
q = forms.CharField(
|
||||
widget=forms.widgets.TextInput(attrs={'size': 35})
|
||||
)
|
||||
q = forms.CharField(widget=forms.widgets.TextInput(attrs={"size": 35}))
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.utils import timezone
|
|||
|
||||
class TaskList(models.Model):
|
||||
name = models.CharField(max_length=60)
|
||||
slug = models.SlugField(default='',)
|
||||
slug = models.SlugField(default="")
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -28,12 +28,19 @@ class Task(models.Model):
|
|||
title = models.CharField(max_length=140)
|
||||
task_list = models.ForeignKey(TaskList, on_delete=models.CASCADE, 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_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(
|
||||
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)
|
||||
priority = models.PositiveIntegerField()
|
||||
|
||||
|
@ -47,7 +54,7 @@ class Task(models.Model):
|
|||
return self.title
|
||||
|
||||
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
|
||||
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
|
||||
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)
|
||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
||||
date = models.DateTimeField(default=datetime.datetime.now)
|
||||
|
|
|
@ -5,13 +5,14 @@ from django.contrib.auth.models import Group
|
|||
from todo.models import Task, TaskList
|
||||
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def todo_setup(django_user_model):
|
||||
# Two groups with different users, two sets of tasks.
|
||||
|
||||
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)
|
||||
tlist1 = TaskList.objects.create(group=g1, name="Zip", slug="zip")
|
||||
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)
|
||||
|
||||
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)
|
||||
tlist2 = TaskList.objects.create(group=g2, name="Zap", slug="zap")
|
||||
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()
|
||||
# Set up an in-memory mail server to receive test emails
|
||||
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):
|
||||
|
@ -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")
|
||||
task = Task.objects.filter(created_by=u1).first()
|
||||
|
||||
u3 = django_user_model.objects.create_user(username="u3", password="zzz", email="u3@example.com")
|
||||
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", )
|
||||
u3 = django_user_model.objects.create_user(
|
||||
username="u3", password="zzz", email="u3@example.com"
|
||||
)
|
||||
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)
|
||||
assert len(mail.outbox) == 1 # One message to multiple recipients
|
||||
assert 'u1@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 "u1@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()
|
||||
|
|
|
@ -16,19 +16,20 @@ After that, view contents and behaviors.
|
|||
|
||||
# ### SMOKETESTS ###
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_todo_setup(todo_setup):
|
||||
assert Task.objects.all().count() == 6
|
||||
|
||||
|
||||
def test_view_list_lists(todo_setup, admin_client):
|
||||
url = reverse('todo:lists')
|
||||
url = reverse("todo:lists")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_view_reorder(todo_setup, admin_client):
|
||||
url = reverse('todo:reorder_tasks')
|
||||
url = reverse("todo:reorder_tasks")
|
||||
response = admin_client.get(url)
|
||||
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()
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_view_mine(todo_setup, admin_client):
|
||||
url = reverse('todo:mine')
|
||||
url = reverse("todo:mine")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_view_list_completed(todo_setup, admin_client):
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_view_list(todo_setup, admin_client):
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_del_list(todo_setup, admin_client):
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_view_add_list(todo_setup, admin_client):
|
||||
url = reverse('todo:add_list')
|
||||
url = reverse("todo:add_list")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_view_task_detail(todo_setup, admin_client):
|
||||
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)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_view_search(todo_setup, admin_client):
|
||||
url = reverse('todo:search')
|
||||
url = reverse("todo:search")
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
@ -100,11 +103,11 @@ def test_no_javascript_in_task_note(todo_setup, client):
|
|||
"priority": 10,
|
||||
"title": title,
|
||||
"note": note,
|
||||
'add_edit_task': 'Submit'
|
||||
"add_edit_task": "Submit",
|
||||
}
|
||||
|
||||
client.login(username='u2', password="password")
|
||||
url = reverse('todo:list_detail', kwargs={"list_id": task_list.id, "list_slug": task_list.slug})
|
||||
client.login(username="u2", password="password")
|
||||
url = reverse("todo:list_detail", kwargs={"list_id": task_list.id, "list_slug": task_list.slug})
|
||||
|
||||
response = client.post(url, data)
|
||||
assert response.status_code == 302
|
||||
|
@ -118,7 +121,7 @@ def test_no_javascript_in_task_note(todo_setup, client):
|
|||
@pytest.mark.django_db
|
||||
def test_no_javascript_in_comments(todo_setup, client):
|
||||
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.created_by = user
|
||||
|
@ -127,11 +130,8 @@ def test_no_javascript_in_comments(todo_setup, client):
|
|||
user.groups.add(task.task_list.group)
|
||||
|
||||
comment = "foo <script>alert('oh noez');</script> bar"
|
||||
data = {
|
||||
"comment-body": comment,
|
||||
"add_comment": 'Submit'
|
||||
}
|
||||
url = reverse('todo:task_detail', kwargs={"task_id": task.id})
|
||||
data = {"comment-body": comment, "add_comment": "Submit"}
|
||||
url = reverse("todo:task_detail", kwargs={"task_id": task.id})
|
||||
|
||||
response = client.post(url, data)
|
||||
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):
|
||||
url = reverse('todo:add_list')
|
||||
url = reverse("todo:add_list")
|
||||
client.login(username="you", password="password")
|
||||
response = client.get(url)
|
||||
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):
|
||||
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")
|
||||
response = client.get(url)
|
||||
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.
|
||||
"""
|
||||
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")
|
||||
response = client.get(url)
|
||||
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.
|
||||
"""
|
||||
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")
|
||||
response = client.get(url)
|
||||
assert response.status_code == 403
|
||||
|
@ -190,7 +190,7 @@ def test_view_task_mine(todo_setup, client):
|
|||
# Users can always view their own tasks
|
||||
task = Task.objects.filter(created_by__username="u1").first()
|
||||
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)
|
||||
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.
|
||||
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")
|
||||
response = client.get(url)
|
||||
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.
|
||||
# For this we can use the fixture data as-is.
|
||||
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")
|
||||
response = client.get(url)
|
||||
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:
|
||||
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(
|
||||
"todo/email/assigned_body.txt",
|
||||
{'task': new_task, 'site': current_site, })
|
||||
"todo/email/assigned_body.txt", {"task": new_task, "site": current_site}
|
||||
)
|
||||
|
||||
send_mail(
|
||||
email_subject, email_body, new_task.created_by.email,
|
||||
[new_task.assigned_to.email], fail_silently=False)
|
||||
email_subject,
|
||||
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):
|
||||
# Notify all previous commentors on a Task about a new comment.
|
||||
|
||||
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(
|
||||
"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.
|
||||
|
|
211
todo/views.py
211
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.models import Task, TaskList, Comment
|
||||
from todo.utils import (
|
||||
send_notify_mail,
|
||||
send_email_to_thread_participants,
|
||||
)
|
||||
from todo.utils import send_notify_mail, send_email_to_thread_participants
|
||||
|
||||
|
||||
def staff_only(function):
|
||||
|
@ -31,6 +28,7 @@ def staff_only(function):
|
|||
Custom view decorator allows us to raise 403 on insufficient permissions,
|
||||
rather than redirect user to login view.
|
||||
"""
|
||||
|
||||
def wrap(request, *args, **kwargs):
|
||||
if request.user.is_staff:
|
||||
return function(request, *args, **kwargs)
|
||||
|
@ -52,13 +50,18 @@ def list_lists(request) -> HttpResponse:
|
|||
|
||||
# Make sure user belongs to at least one group.
|
||||
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
|
||||
if request.user.is_superuser:
|
||||
lists = TaskList.objects.all().order_by('group', 'name')
|
||||
lists = TaskList.objects.all().order_by("group", "name")
|
||||
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()
|
||||
|
||||
|
@ -66,17 +69,21 @@ def list_lists(request) -> HttpResponse:
|
|||
if request.user.is_superuser:
|
||||
task_count = Task.objects.filter(completed=0).count()
|
||||
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 = {
|
||||
"lists": lists,
|
||||
"thedate": thedate,
|
||||
"searchform": searchform,
|
||||
"list_count": list_count,
|
||||
"task_count": task_count,
|
||||
"lists": lists,
|
||||
"thedate": thedate,
|
||||
"searchform": searchform,
|
||||
"list_count": list_count,
|
||||
"task_count": task_count,
|
||||
}
|
||||
|
||||
return render(request, 'todo/list_lists.html', context)
|
||||
return render(request, "todo/list_lists.html", context)
|
||||
|
||||
|
||||
@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:
|
||||
raise PermissionDenied
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
TaskList.objects.get(id=task_list.id).delete()
|
||||
messages.success(request, "{list_name} is gone.".format(list_name=task_list.name))
|
||||
return redirect('todo:lists')
|
||||
return redirect("todo:lists")
|
||||
else:
|
||||
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()
|
||||
|
@ -108,7 +115,7 @@ def del_list(request, list_id: int, list_slug: str) -> HttpResponse:
|
|||
"task_count_total": task_count_total,
|
||||
}
|
||||
|
||||
return render(request, 'todo/del_list.html', context)
|
||||
return render(request, "todo/del_list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -141,33 +148,36 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False):
|
|||
# Add New Task Form
|
||||
# ######################
|
||||
|
||||
if request.POST.getlist('add_edit_task'):
|
||||
form = AddEditTaskForm(request.user, request.POST, initial={
|
||||
'assigned_to': request.user.id,
|
||||
'priority': 999,
|
||||
'task_list': task_list
|
||||
})
|
||||
if request.POST.getlist("add_edit_task"):
|
||||
form = AddEditTaskForm(
|
||||
request.user,
|
||||
request.POST,
|
||||
initial={"assigned_to": request.user.id, "priority": 999, "task_list": task_list},
|
||||
)
|
||||
|
||||
if form.is_valid():
|
||||
new_task = form.save(commit=False)
|
||||
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()
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
else:
|
||||
# Don't allow adding new tasks on some views
|
||||
if list_slug not in ["mine", "recent-add", "recent-complete", ]:
|
||||
form = AddEditTaskForm(request.user, initial={
|
||||
'assigned_to': request.user.id,
|
||||
'priority': 999,
|
||||
'task_list': task_list,
|
||||
})
|
||||
if list_slug not in ["mine", "recent-add", "recent-complete"]:
|
||||
form = AddEditTaskForm(
|
||||
request.user,
|
||||
initial={"assigned_to": request.user.id, "priority": 999, "task_list": task_list},
|
||||
)
|
||||
|
||||
context = {
|
||||
"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,
|
||||
}
|
||||
|
||||
return render(request, 'todo/list_detail.html', context)
|
||||
return render(request, "todo/list_detail.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -195,52 +205,54 @@ def task_detail(request, task_id: int) -> HttpResponse:
|
|||
raise PermissionDenied
|
||||
|
||||
# Save submitted comments
|
||||
if request.POST.get('add_comment'):
|
||||
if request.POST.get("add_comment"):
|
||||
Comment.objects.create(
|
||||
author=request.user,
|
||||
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(
|
||||
task, request.POST['comment-body'], request.user,
|
||||
subject='New comment posted on task "{}"'.format(task.title))
|
||||
task,
|
||||
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.")
|
||||
|
||||
# Save task edits
|
||||
if request.POST.get('add_edit_task'):
|
||||
form = AddEditTaskForm(request.user, request.POST, instance=task, initial={'task_list': task.task_list})
|
||||
if request.POST.get("add_edit_task"):
|
||||
form = AddEditTaskForm(
|
||||
request.user, request.POST, instance=task, initial={"task_list": task.task_list}
|
||||
)
|
||||
|
||||
if form.is_valid():
|
||||
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()
|
||||
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:
|
||||
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
|
||||
if request.POST.get('toggle_done'):
|
||||
results_changed = toggle_done([task.id, ])
|
||||
if request.POST.get("toggle_done"):
|
||||
results_changed = toggle_done([task.id])
|
||||
for res in results_changed:
|
||||
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:
|
||||
thedate = task.due_date
|
||||
else:
|
||||
thedate = datetime.datetime.now()
|
||||
|
||||
context = {
|
||||
"task": task,
|
||||
"comment_list": comment_list,
|
||||
"form": form,
|
||||
"thedate": thedate,
|
||||
}
|
||||
context = {"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
|
||||
|
@ -248,7 +260,7 @@ def task_detail(request, task_id: int) -> HttpResponse:
|
|||
def reorder_tasks(request) -> HttpResponse:
|
||||
"""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:
|
||||
# First task in received list is always empty - remove it
|
||||
del newtasklist[0]
|
||||
|
@ -280,24 +292,23 @@ def add_list(request) -> HttpResponse:
|
|||
newlist.slug = slugify(newlist.name)
|
||||
newlist.save()
|
||||
messages.success(request, "A new list has been added.")
|
||||
return redirect('todo:lists')
|
||||
return redirect("todo:lists")
|
||||
|
||||
except IntegrityError:
|
||||
messages.warning(
|
||||
request,
|
||||
"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:
|
||||
if request.user.groups.all().count() == 1:
|
||||
form = AddTaskListForm(request.user, initial={"group": request.user.groups.all()[0]})
|
||||
else:
|
||||
form = AddTaskListForm(request.user)
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = {"form": form}
|
||||
|
||||
return render(request, 'todo/add_list.html', context)
|
||||
return render(request, "todo/add_list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -306,36 +317,32 @@ def search(request) -> HttpResponse:
|
|||
"""
|
||||
if request.GET:
|
||||
|
||||
query_string = ''
|
||||
query_string = ""
|
||||
found_tasks = None
|
||||
if ('q' in request.GET) and request.GET['q'].strip():
|
||||
query_string = request.GET['q']
|
||||
if ("q" in request.GET) and request.GET["q"].strip():
|
||||
query_string = request.GET["q"]
|
||||
|
||||
found_tasks = Task.objects.filter(
|
||||
Q(title__icontains=query_string) |
|
||||
Q(note__icontains=query_string)
|
||||
Q(title__icontains=query_string) | Q(note__icontains=query_string)
|
||||
)
|
||||
else:
|
||||
# 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.
|
||||
found_tasks = Task.objects.all()
|
||||
|
||||
if 'inc_complete' in request.GET:
|
||||
if "inc_complete" in request.GET:
|
||||
found_tasks = found_tasks.exclude(completed=True)
|
||||
|
||||
else:
|
||||
query_string = None
|
||||
found_tasks =None
|
||||
found_tasks = None
|
||||
|
||||
# Only include tasks that are in groups of which this user is a member:
|
||||
if not request.user.is_superuser:
|
||||
found_tasks = found_tasks.filter(task_list__group__in=request.user.groups.all())
|
||||
|
||||
context = {
|
||||
'query_string': query_string,
|
||||
'found_tasks': found_tasks
|
||||
}
|
||||
return render(request, 'todo/search_results.html', context)
|
||||
context = {"query_string": query_string, "found_tasks": found_tasks}
|
||||
return render(request, "todo/search_results.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -348,10 +355,14 @@ def external_add(request) -> HttpResponse:
|
|||
"""
|
||||
|
||||
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():
|
||||
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:
|
||||
form = AddExternalTaskForm(request.POST)
|
||||
|
@ -367,26 +378,36 @@ def external_add(request) -> HttpResponse:
|
|||
|
||||
# Send email to assignee if we have one
|
||||
if task.assigned_to:
|
||||
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task.title})
|
||||
email_body = render_to_string("todo/email/assigned_body.txt", {'task': task, 'site': current_site, })
|
||||
email_subject = render_to_string(
|
||||
"todo/email/assigned_subject.txt", {"task": task.title}
|
||||
)
|
||||
email_body = render_to_string(
|
||||
"todo/email/assigned_body.txt", {"task": task, "site": current_site}
|
||||
)
|
||||
try:
|
||||
send_mail(
|
||||
email_subject, email_body, task.created_by.email,
|
||||
[task.assigned_to.email, ], fail_silently=False)
|
||||
email_subject,
|
||||
email_body,
|
||||
task.created_by.email,
|
||||
[task.assigned_to.email],
|
||||
fail_silently=False,
|
||||
)
|
||||
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)
|
||||
|
||||
else:
|
||||
form = AddExternalTaskForm(initial={'priority': 999})
|
||||
form = AddExternalTaskForm(initial={"priority": 999})
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
}
|
||||
context = {"form": form}
|
||||
|
||||
return render(request, 'todo/add_task_external.html', context)
|
||||
return render(request, "todo/add_task_external.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -399,9 +420,9 @@ def toggle_done(request, task_id: int) -> HttpResponse:
|
|||
|
||||
# Permissions
|
||||
if not (
|
||||
(task.created_by == request.user) or
|
||||
(task.assigned_to == request.user) or
|
||||
(task.task_list.group in request.user.groups.all())
|
||||
(task.created_by == request.user)
|
||||
or (task.assigned_to == request.user)
|
||||
or (task.task_list.group in request.user.groups.all())
|
||||
):
|
||||
raise PermissionDenied
|
||||
|
||||
|
@ -410,8 +431,9 @@ def toggle_done(request, task_id: int) -> HttpResponse:
|
|||
task.save()
|
||||
|
||||
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
|
||||
|
@ -424,9 +446,9 @@ def delete_task(request, task_id: int) -> HttpResponse:
|
|||
|
||||
# Permissions
|
||||
if not (
|
||||
(task.created_by == request.user) or
|
||||
(task.assigned_to == request.user) or
|
||||
(task.task_list.group in request.user.groups.all())
|
||||
(task.created_by == request.user)
|
||||
or (task.assigned_to == request.user)
|
||||
or (task.task_list.group in request.user.groups.all())
|
||||
):
|
||||
raise PermissionDenied
|
||||
|
||||
|
@ -434,4 +456,7 @@ def delete_task(request, task_id: int) -> HttpResponse:
|
|||
task.delete()
|
||||
|
||||
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