Django 2.0 compatibility (requirement)

- URLs to path() style
- Misc updates
- Namespaces all URL references
- Removes dependency on AutoSlugField
- Cleaner reversals
- Cleaner docstrings
This commit is contained in:
Scot Hacker 2018-02-03 00:09:23 -08:00
parent 238e39085d
commit 90c41d1f29
13 changed files with 92 additions and 96 deletions

View file

@ -99,7 +99,7 @@ setup(
include_package_data=True,
zip_safe=False,
tests_require=['tox'],
install_requires=['django-autoslug', 'unidecode', ],
install_requires=['unidecode', ],
cmdclass={
'clean': Clean,
'test': Tox,

View file

@ -20,7 +20,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('date', models.DateTimeField(default=datetime.datetime.now)),
('body', models.TextField(blank=True)),
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
options={
},
@ -37,8 +37,8 @@ class Migration(migrations.Migration):
('completed_date', models.DateField(null=True, blank=True)),
('note', models.TextField(null=True, blank=True)),
('priority', models.PositiveIntegerField(max_length=3)),
('assigned_to', models.ForeignKey(related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL)),
('created_by', models.ForeignKey(related_name='todo_created_by', to=settings.AUTH_USER_MODEL)),
('assigned_to', models.ForeignKey(related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
('created_by', models.ForeignKey(related_name='todo_created_by', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
options={
'ordering': ['priority'],
@ -51,7 +51,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=60)),
('slug', models.SlugField(max_length=60, editable=False)),
('group', models.ForeignKey(to='auth.Group')),
('group', models.ForeignKey(to='auth.Group', on_delete=models.CASCADE)),
],
options={
'ordering': ['name'],
@ -66,13 +66,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='item',
name='list',
field=models.ForeignKey(to='todo.List'),
field=models.ForeignKey(to='todo.List', on_delete=models.CASCADE),
preserve_default=True,
),
migrations.AddField(
model_name='comment',
name='task',
field=models.ForeignKey(to='todo.Item'),
field=models.ForeignKey(to='todo.Item', on_delete=models.CASCADE),
preserve_default=True,
),
]

View file

@ -3,17 +3,16 @@ import datetime
from django.db import models
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.conf import settings
from autoslug import AutoSlugField
@python_2_unicode_compatible
class List(models.Model):
name = models.CharField(max_length=60)
slug = AutoSlugField(populate_from='name', editable=False, always_update=True)
group = models.ForeignKey(Group)
slug = models.SlugField(default='',)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
def __str__(self):
return self.name
@ -33,13 +32,14 @@ class List(models.Model):
@python_2_unicode_compatible
class Item(models.Model):
title = models.CharField(max_length=140)
list = models.ForeignKey(List)
list = models.ForeignKey(List, on_delete=models.CASCADE)
created_date = models.DateField(auto_now=True)
due_date = models.DateField(blank=True, null=True, )
completed = models.BooleanField(default=None)
completed_date = models.DateField(blank=True, null=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='todo_created_by')
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='todo_assigned_to')
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)
note = models.TextField(blank=True, null=True)
priority = models.PositiveIntegerField()
@ -53,7 +53,7 @@ class Item(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 item creation / completed date
def save(self):
@ -72,8 +72,8 @@ 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)
task = models.ForeignKey(Item)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
task = models.ForeignKey(Item, on_delete=models.CASCADE)
date = models.DateTimeField(default=datetime.datetime.now)
body = models.TextField(blank=True)

View file

@ -23,7 +23,7 @@
<p><input type="submit" name="delete-confirm" value="Do it! &rarr;" class="todo-button"> </p>
</form>
<a href="{% url 'todo-incomplete_tasks' list.id list_slug %}">Return to list: {{ list.name }}</a>
<a href="{% url 'todo:incomplete_tasks' list.id list_slug %}">Return to list: {{ list.name }}</a>
{% else %}
<p>Sorry, you don't have permission to delete lists. Please contact your group administrator.</p>

View file

@ -14,7 +14,7 @@ Note: {{ task.note }}
Task details/comments:
http://{{ site }}{% url 'todo-task_detail' task.id %}
http://{{ site }}{% url 'todo:task_detail' task.id %}
List {{ task.list.name }}:
http://{{ site }}{% url 'todo-incomplete_tasks' task.list.id task.list.slug %}
http://{{ site }}{% url 'todo:incomplete_tasks' task.list.id task.list.slug %}

View file

@ -9,8 +9,8 @@ Comment:
{% endautoescape %}
Task details/comments:
https://{{ site }}{% url 'todo-task_detail' task.id %}
https://{{ site }}{% url 'todo:task_detail' task.id %}
List {{ task.list.name }}:
https://{{ site }}{% url 'todo-incomplete_tasks' task.list.id task.list.slug %}
https://{{ site }}{% url 'todo:incomplete_tasks' task.list.id task.list.slug %}

View file

@ -14,11 +14,14 @@
<h3>{{ group.grouper }}</h3>
<ul>
{% for item in group.list %}
<li><a class="todo" href="{% url 'todo-incomplete_tasks' item.id item.slug %}">{{ item.name }} </a> ({{ item.incomplete_tasks.count }}/{{ item.item_set.count }})</li>
<li>
<a class="todo" href="{% url 'todo:incomplete_tasks' item.id item.slug %}">{{ item.name }}</a>
({{ item.incomplete_tasks.count }}/{{ item.item_set.count }})
</li>
{% endfor %}
</ul>
{% endfor %}
<p><a href="{% url 'todo-add_list' %}">Create new todo list</a></p>
<p><a href="{% url 'todo:add_list' %}">Create new todo list</a></p>
{% endblock %}

View file

@ -11,9 +11,9 @@
<h2>{{found_items.count}} search results for term: "{{ query_string }}"</h2>
<div class="post_list">
{% for f in found_items %}
<p><strong><a href="{% url 'todo-task_detail' f.id %}">{{ f.title }}</a></strong><br />
<p><strong><a href="{% url 'todo:task_detail' f.id %}">{{ f.title }}</a></strong><br />
<span class="minor">
On list: <a href="{% url 'todo-incomplete_tasks' f.list.id f.list.slug %}">{{ f.list.name }}</a><br />
On list: <a href="{% url 'todo:incomplete_tasks' f.list.id f.list.slug %}">{{ f.list.name }}</a><br />
Assigned to: {% if f.assigned_to %}{{ f.assigned_to }}{% else %}Anyone{% endif %} (created by: {{ f.created_by }})<br />
Complete: {{ f.completed|yesno:"Yes,No" }}
</span>

View file

@ -9,7 +9,7 @@
// data in a list. We pass that list as an object called "data" to a Django view
// to save the re-ordered data into the database.
$.post("{% url 'todo-reorder_tasks' %}", data, "json");
$.post("{% url 'todo:reorder_tasks' %}", data, "json");
return false;
};
@ -98,7 +98,7 @@
{% for task in task_list %}
<tr id="{{ task.id }}">
<td><input type="checkbox" name="mark_done" value="{{ task.id }}" id="mark_done_{{ task.id }}"> </td>
<td><a href="{% url 'todo-task_detail' task.id %}">{{ task.title|truncatewords:20 }}</a></td>
<td><a href="{% url 'todo:task_detail' task.id %}">{{ task.title|truncatewords:20 }}</a></td>
<td>{{ task.created_date|date:"m/d/Y" }}</td>
<td>
{% if task.overdue_status %}<span class="overdue">{% endif %}
@ -110,7 +110,7 @@
<td style="text-align:center;">{% if task.note %}&asymp;{% endif %} </td>
<td style="text-align:center;">{% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %}</td>
{% if list_slug == "mine" %}
<td><a href="{% url 'todo-incomplete_tasks' task.list.id task.list.slug %}">{{ task.list }}</a></td>
<td><a href="{% url 'todo:incomplete_tasks' task.list.id task.list.slug %}">{{ task.list }}</a></td>
{% endif %}
<td><input type="checkbox" name="del_tasks" value="{{ task.id }}" id="del_task_{{ task.id }}"> </td>
</tr>
@ -118,7 +118,7 @@
</table>
<p><input type="submit" name="mark_tasks_done" value="Continue..." class="todo-button"></p>
<p><a class="todo" href="{% url 'todo-completed_tasks' list_id list_slug %}">View completed tasks</a></p>
<p><a class="todo" href="{% url 'todo:completed_tasks' list_id list_slug %}">View completed tasks</a></p>
{% else %}
@ -141,7 +141,7 @@
{% for task in completed_list %}
<tr>
<td><input type="checkbox" name="undo_completed_task" value="{{ task.id }}" id="id_undo_completed_task{{ task.id }}"> </td>
<td><a href="{% url 'todo-task_detail' task.id %}">{{ task.title|truncatewords:20 }}</a></td>
<td><a href="{% url 'todo:task_detail' task.id %}">{{ task.title|truncatewords:20 }}</a></td>
<td>{{ task.created_date|date:"m/d/Y" }}</td>
<td>{{ task.completed_date|date:"m/d/Y" }}</td>
<td style="text-align:center;">{% if task.note %}&asymp;{% endif %} </td>
@ -153,12 +153,12 @@
</table>
<p><input type="submit" name="deldonetasks" value="Continue..." class="todo-button"></p>
</form>
<p><a class="todo" href="{% url 'todo-incomplete_tasks' list_id list_slug %}">View incomplete tasks</a></p>
<p><a class="todo" href="{% url 'todo:incomplete_tasks' list_id list_slug %}">View incomplete tasks</a></p>
{% endif %}
{% if user.is_staff %}
{% if list_slug != "mine" %}
<p><a class="todo" href="{% url 'todo-del_list' list.id list_slug %}">Delete this list</a></p>
<p><a class="todo" href="{% url 'todo:del_list' list.id list_slug %}">Delete this list</a></p>
{% endif %}
{% endif %}

View file

@ -26,7 +26,7 @@
<p id="slideToggle" ><strong>&rarr; Click to edit details &larr;</strong></p>
<p>
<strong>In list:</strong> <a href="{% url 'todo-incomplete_tasks' task.list.id task.list.slug %}" class="showlink">{{ task.list }}</a><br />
<strong>In list:</strong> <a href="{% url 'todo:incomplete_tasks' task.list.id task.list.slug %}" class="showlink">{{ task.list }}</a><br />
<strong>Assigned to:</strong> {% if task.assigned_to %}{{ task.assigned_to.get_full_name }}{% else %}Anyone{% endif %}<br />
<strong>Created by:</strong> {{ task.created_by.first_name }} {{ task.created_by.last_name }}<br />
<strong>Due date:</strong> {{ task.due_date }}<br />

View file

@ -1,23 +1,26 @@
from django.conf.urls import url
from django.urls import path
from todo import views
app_name = 'todo'
urlpatterns = [
url(r'^$', views.list_lists, name="todo-lists"),
url(r'^mine/$', views.view_list, {'list_slug': 'mine'}, name="todo-mine"),
url(r'^(?P<list_id>\d{1,4})/(?P<list_slug>[\w-]+)/delete$', views.del_list, name="todo-del_list"),
url(r'^task/(?P<task_id>\d{1,6})$', views.view_task, name='todo-task_detail'),
url(r'^(?P<list_id>\d{1,4})/(?P<list_slug>[\w-]+)$', views.view_list, name='todo-incomplete_tasks'),
url(r'^(?P<list_id>\d{1,4})/(?P<list_slug>[\w-]+)/completed$', views.view_list, {'view_completed': True},
name='todo-completed_tasks'),
url(r'^add_list/$', views.add_list, name="todo-add_list"),
url(r'^search-post/$', views.search_post, name="todo-search-post"),
url(r'^search/$', views.search, name="todo-search"),
path('', views.list_lists, name="lists"),
path('mine/', views.view_list, {'list_slug': 'mine'}, name="mine"),
path('<int:list_id>/<str:list_slug>/delete$', views.del_list, name="del_list"),
path('task/<int:task_id>', views.view_task, name='task_detail'),
path('<int:list_id>/<str:list_slug>', views.view_list, name='incomplete_tasks'),
path('<int:list_id>/<str:list_slug>/completed$', views.view_list, {'view_completed': True}, name='completed_tasks'),
path('add_list/', views.add_list, name="add_list"),
# FIXME need both of these?
path('search-post/', views.search_post, name="search-post"),
path('search/', views.search, name="search"),
# View reorder_tasks is only called by JQuery for drag/drop task ordering
url(r'^reorder_tasks/$', views.reorder_tasks, name="todo-reorder_tasks"),
path('reorder_tasks/', views.reorder_tasks, name="reorder_tasks"),
url(r'^ticket/add/$', views.external_add, name="todo-external-add"),
url(r'^recent/added/$', views.view_list, {'list_slug': 'recent-add'}, name="todo-recently_added"),
url(r'^recent/completed/$', views.view_list, {'list_slug': 'recent-complete'},
name="todo-recently_completed"),
path('ticket/add/', views.external_add, name="external-add"),
path('recent/added/', views.view_list, {'list_slug': 'recent-add'}, name="recently_added"),
path('recent/completed/', views.view_list, {'list_slug': 'recent-complete'}, name="recently_completed"),
]

View file

@ -1,14 +1,12 @@
import datetime
from django.contrib import messages
from django.template.loader import render_to_string
from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.template.loader import render_to_string
from todo.models import Item
# Need for links in email templates
current_site = Site.objects.get_current()
def mark_done(request, done_items):
# Check for items in the mark_done POST array. If present, change status to complete.
@ -39,6 +37,7 @@ def del_tasks(request, deleted_items):
def send_notify_mail(request, new_task):
# Send email
current_site = Site.objects.get_current()
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': new_task})
email_body = render_to_string(
"todo/email/assigned_body.txt",

View file

@ -1,43 +1,39 @@
import datetime
from django.contrib import messages
from django.contrib.auth.decorators import user_passes_test, login_required
from django.contrib.auth.models import User
from django.contrib import messages
from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.db import IntegrityError
from django.db.models import Q
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.template import RequestContext
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.template.loader import render_to_string
from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.sites.models import Site
from todo import settings
from todo.forms import AddListForm, AddItemForm, EditItemForm, AddExternalItemForm, SearchForm
from todo.models import Item, List, Comment
from todo.utils import mark_done, undo_completed_task, del_tasks, send_notify_mail
# Need for links in email templates
current_site = Site.objects.get_current()
def check_user_allowed(user):
"""
Conditions for user_passes_test decorator.
"""
if settings.STAFF_ONLY:
return user.is_authenticated() and user.is_staff
return user.is_authenticated and user.is_staff
else:
return user.is_authenticated()
return user.is_authenticated
@user_passes_test(check_user_allowed)
def list_lists(request):
"""Homepage view - list of lists a user can view, and ability to add a list.
"""
Homepage view - list of lists a user can view, and ability to add a list.
"""
thedate = datetime.datetime.now()
searchform = SearchForm(auto_id=False)
@ -64,15 +60,14 @@ def list_lists(request):
@user_passes_test(check_user_allowed)
def del_list(request, list_id, list_slug):
"""
Delete an entire list. Danger Will Robinson! Only staff members should be allowed to access this view.
"""Delete an entire list. Danger Will Robinson! Only staff members should be allowed to access this view.
"""
list = get_object_or_404(List, slug=list_slug)
if request.method == 'POST':
List.objects.get(id=list.id).delete()
messages.success(request, "{list_name} is gone.".format(list_name=list.name))
return HttpResponseRedirect(reverse('todo-lists'))
return redirect('todo:lists')
else:
item_count_done = Item.objects.filter(list=list.id, completed=1).count()
item_count_undone = Item.objects.filter(list=list.id, completed=0).count()
@ -83,8 +78,7 @@ def del_list(request, list_id, list_slug):
@user_passes_test(check_user_allowed)
def view_list(request, list_id=0, list_slug=None, view_completed=False):
"""
Display and manage items in a list.
"""Display and manage items in a list.
"""
# Make sure the accessing user has permission to view this list.
@ -142,7 +136,7 @@ def view_list(request, list_id=0, list_slug=None, view_completed=False):
send_notify_mail(request, new_task)
messages.success(request, "New task \"{t}\" has been added.".format(t=new_task.title))
return HttpResponseRedirect(request.path)
return redirect(request.path)
else:
# Don't allow adding new tasks on some views
if list_slug != "mine" and list_slug != "recent-add" and list_slug != "recent-complete":
@ -156,8 +150,7 @@ def view_list(request, list_id=0, list_slug=None, view_completed=False):
@user_passes_test(check_user_allowed)
def view_task(request, task_id):
"""
View task details. Allow task details to be edited.
"""View task details. Allow task details to be edited.
"""
task = get_object_or_404(Item, pk=task_id)
comment_list = Comment.objects.filter(task=task_id)
@ -185,6 +178,7 @@ def view_task(request, task_id):
c.save()
# And email comment to all people who have participated in this thread.
current_site = Site.objects.get_current()
email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task})
email_body = render_to_string(
"todo/email/newcomment_body.txt",
@ -207,7 +201,7 @@ def view_task(request, task_id):
messages.success(request, "The task has been edited.")
return HttpResponseRedirect(reverse('todo-incomplete_tasks', args=[task.list.id, task.list.slug]))
return redirect('todo:lists', args=[task.list.id, task.list.slug])
else:
form = EditItemForm(instance=task)
if task.due_date:
@ -223,8 +217,7 @@ def view_task(request, task_id):
@csrf_exempt
@user_passes_test(check_user_allowed)
def reorder_tasks(request):
"""
Handle task re-ordering (priorities) from JQuery drag/drop in view_list.html
"""Handle task re-ordering (priorities) from JQuery drag/drop in view_list.html
"""
newtasklist = request.POST.getlist('tasktable[]')
# First item in received list is always empty - remove it
@ -245,14 +238,14 @@ def reorder_tasks(request):
@login_required
def external_add(request):
"""
Allow users who don't have access to the rest of the ticket system to file a ticket in a specific list.
"""Allow users who don't have access to the rest of the ticket system to file a ticket in a specific list.
Public tickets are unassigned unless settings.DEFAULT_ASSIGNEE exists.
"""
if request.POST:
form = AddExternalItemForm(request.POST)
if form.is_valid():
current_site = Site.objects.get_current()
item = form.save(commit=False)
item.list_id = settings.DEFAULT_LIST_ID
item.created_by = request.user
@ -270,7 +263,8 @@ def external_add(request):
messages.success(request, "Your trouble ticket has been submitted. We'll get back to you soon.")
return HttpResponseRedirect(settings.PUBLIC_SUBMIT_REDIRECT)
return redirect(settings.PUBLIC_SUBMIT_REDIRECT)
else:
form = AddExternalItemForm()
@ -279,8 +273,7 @@ def external_add(request):
@user_passes_test(check_user_allowed)
def add_list(request):
"""
Allow users to add a new todo list to the group they're in.
"""Allow users to add a new todo list to the group they're in.
"""
if request.POST:
form = AddListForm(request.user, request.POST)
@ -288,7 +281,8 @@ def add_list(request):
try:
form.save()
messages.success(request, "A new list has been added.")
return HttpResponseRedirect(request.path)
return redirect('todo:lists')
except IntegrityError:
messages.error(
request,
@ -305,19 +299,17 @@ def add_list(request):
@user_passes_test(check_user_allowed)
def search_post(request):
"""
Redirect POST'd search param to query GET string
"""Redirect POST'd search param to query GET string
"""
if request.POST:
q = request.POST.get('q')
url = reverse('todo-search') + "?q=" + q
return HttpResponseRedirect(url)
url = reverse('todo:search') + "?q=" + q
return redirect(url)
@user_passes_test(check_user_allowed)
def search(request):
"""
Search for tasks
"""Search for tasks
"""
if request.GET:
@ -343,9 +335,8 @@ def search(request):
query_string = None
found_items = None
return render(
request,
'todo/search_results.html', {
'query_string': query_string,
'found_items': found_items
}, context_instance=RequestContext(request))
context = {
'query_string': query_string,
'found_items': found_items
}
return render(request, 'todo/search_results.html', context)