From 90c41d1f2911d2723234c50764e0aa695a5ed706 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 3 Feb 2018 00:09:23 -0800 Subject: [PATCH 001/210] Django 2.0 compatibility (requirement) - URLs to path() style - Misc updates - Namespaces all URL references - Removes dependency on AutoSlugField - Cleaner reversals - Cleaner docstrings --- setup.py | 2 +- todo/migrations/0001_initial.py | 12 +-- todo/models.py | 20 ++--- todo/templates/todo/del_list.html | 2 +- todo/templates/todo/email/assigned_body.txt | 4 +- todo/templates/todo/email/newcomment_body.txt | 4 +- todo/templates/todo/list_lists.html | 7 +- todo/templates/todo/search_results.html | 4 +- todo/templates/todo/view_list.html | 14 ++-- todo/templates/todo/view_task.html | 2 +- todo/urls.py | 35 +++++---- todo/utils.py | 7 +- todo/views.py | 75 ++++++++----------- 13 files changed, 92 insertions(+), 96 deletions(-) diff --git a/setup.py b/setup.py index a708efe..0484232 100755 --- a/setup.py +++ b/setup.py @@ -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, diff --git a/todo/migrations/0001_initial.py b/todo/migrations/0001_initial.py index c0140e6..c29c7a6 100644 --- a/todo/migrations/0001_initial.py +++ b/todo/migrations/0001_initial.py @@ -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, ), ] diff --git a/todo/models.py b/todo/models.py index b7b67db..07332ee 100644 --- a/todo/models.py +++ b/todo/models.py @@ -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) diff --git a/todo/templates/todo/del_list.html b/todo/templates/todo/del_list.html index 78c429c..0946d29 100644 --- a/todo/templates/todo/del_list.html +++ b/todo/templates/todo/del_list.html @@ -23,7 +23,7 @@

- Return to list: {{ list.name }} + Return to list: {{ list.name }} {% else %}

Sorry, you don't have permission to delete lists. Please contact your group administrator.

diff --git a/todo/templates/todo/email/assigned_body.txt b/todo/templates/todo/email/assigned_body.txt index 51d143d..e6e7b33 100644 --- a/todo/templates/todo/email/assigned_body.txt +++ b/todo/templates/todo/email/assigned_body.txt @@ -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 %} diff --git a/todo/templates/todo/email/newcomment_body.txt b/todo/templates/todo/email/newcomment_body.txt index 2a1df3c..8619eb6 100644 --- a/todo/templates/todo/email/newcomment_body.txt +++ b/todo/templates/todo/email/newcomment_body.txt @@ -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 %} diff --git a/todo/templates/todo/list_lists.html b/todo/templates/todo/list_lists.html index 459e842..6e06c51 100644 --- a/todo/templates/todo/list_lists.html +++ b/todo/templates/todo/list_lists.html @@ -14,11 +14,14 @@

{{ group.grouper }}

{% endfor %} -

Create new todo list

+

Create new todo list

{% endblock %} diff --git a/todo/templates/todo/search_results.html b/todo/templates/todo/search_results.html index c1fdbe4..e33061b 100644 --- a/todo/templates/todo/search_results.html +++ b/todo/templates/todo/search_results.html @@ -11,9 +11,9 @@

{{found_items.count}} search results for term: "{{ query_string }}"

{% for f in found_items %} -

{{ f.title }}
+

{{ f.title }}
- On list: {{ f.list.name }}
+ On list: {{ f.list.name }}
Assigned to: {% if f.assigned_to %}{{ f.assigned_to }}{% else %}Anyone{% endif %} (created by: {{ f.created_by }})
Complete: {{ f.completed|yesno:"Yes,No" }}
diff --git a/todo/templates/todo/view_list.html b/todo/templates/todo/view_list.html index 1a27d38..d85b31c 100644 --- a/todo/templates/todo/view_list.html +++ b/todo/templates/todo/view_list.html @@ -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 %} - {{ task.title|truncatewords:20 }} + {{ task.title|truncatewords:20 }} {{ task.created_date|date:"m/d/Y" }} {% if task.overdue_status %}{% endif %} @@ -110,7 +110,7 @@ {% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} {% if list_slug == "mine" %} - {{ task.list }} + {{ task.list }} {% endif %} @@ -118,7 +118,7 @@

-

View completed tasks

+

View completed tasks

{% else %} @@ -141,7 +141,7 @@ {% for task in completed_list %} - {{ task.title|truncatewords:20 }} + {{ task.title|truncatewords:20 }} {{ task.created_date|date:"m/d/Y" }} {{ task.completed_date|date:"m/d/Y" }} {% if task.note %}≈{% endif %} @@ -153,12 +153,12 @@

-

View incomplete tasks

+

View incomplete tasks

{% endif %} {% if user.is_staff %} {% if list_slug != "mine" %} -

Delete this list

+

Delete this list

{% endif %} {% endif %} diff --git a/todo/templates/todo/view_task.html b/todo/templates/todo/view_task.html index aec27ad..1caac6e 100644 --- a/todo/templates/todo/view_task.html +++ b/todo/templates/todo/view_task.html @@ -26,7 +26,7 @@

→ Click to edit details ←

- In list: {{ task.list }}
+ In list: {{ task.list }}
Assigned to: {% if task.assigned_to %}{{ task.assigned_to.get_full_name }}{% else %}Anyone{% endif %}
Created by: {{ task.created_by.first_name }} {{ task.created_by.last_name }}
Due date: {{ task.due_date }}
diff --git a/todo/urls.py b/todo/urls.py index ff52363..0e6e080 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -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\d{1,4})/(?P[\w-]+)/delete$', views.del_list, name="todo-del_list"), - url(r'^task/(?P\d{1,6})$', views.view_task, name='todo-task_detail'), - url(r'^(?P\d{1,4})/(?P[\w-]+)$', views.view_list, name='todo-incomplete_tasks'), - url(r'^(?P\d{1,4})/(?P[\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('//delete$', views.del_list, name="del_list"), + path('task/', views.view_task, name='task_detail'), + path('/', views.view_list, name='incomplete_tasks'), + path('//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"), ] diff --git a/todo/utils.py b/todo/utils.py index cb6db3a..afd6c2c 100644 --- a/todo/utils.py +++ b/todo/utils.py @@ -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", diff --git a/todo/views.py b/todo/views.py index c408ad8..5da286e 100644 --- a/todo/views.py +++ b/todo/views.py @@ -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) From 7ab41ed936a6c0294edd6d9bbdd6bf790ae84cd4 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 3 Feb 2018 00:39:59 -0800 Subject: [PATCH 002/210] Convert readme to markdown format - And remove tox references --- README.md | 15 +++++++++++++++ README.rst | 55 ------------------------------------------------------ 2 files changed, 15 insertions(+), 55 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bbd9d4 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# django-todo + +django-todo is a pluggable, multi-user, multi-group task management and +assignment application for Django, designed to be dropped into an existing site as a reusable app. + +django-todo can serve as anything from a personal to-do tracker to a ticketing system for organizations. + +## Documentation + +For documentation, see the django-todo wiki pages: + +- [Overview and screenshots](http://github.com/shacker/django-todo/wiki/Overview-and-screenshots) +- [Requirements and installation](http://github.com/shacker/django-todo/wiki/Requirements-and-Installation) +- [Version history](http://github.com/shacker/django-todo/wiki/Version-history) + diff --git a/README.rst b/README.rst deleted file mode 100644 index 138b5e8..0000000 --- a/README.rst +++ /dev/null @@ -1,55 +0,0 @@ -============================ -django todo |latest-version| -============================ - -|build-status| |health| |downloads| |license| - -django-todo is a pluggable multi-user, multi-group task management and -assignment application for Django. It can serve as anything from a personal -to-do system to a complete, working ticketing system for organizations. - -Documentation -============= - -For documentation, see the django-todo wiki pages: - -- `Overview and screenshots - `_ - -- `Requirements and installation - `_ - -- `Version history - `_ - -Tests -===== - -Serious tests are missing, but we're checking PEP8 conformity of our syntax on -both Python 2 and 3 using ``tox``. You can run the tests locally via:: - - $ python setup.py test - -No prerequisites are required, all test dependencies will be installed -automatically by ``tox`` in virtual environments created on the fly. -Unfortunately, you'll have to install ``virtualenv`` for this to work, though. - -To remove all build files and folders including Python byte code you can run:: - - $ python setup.py clean - -.. |latest-version| image:: https://img.shields.io/pypi/v/django-todo.svg - :alt: Latest version on PyPI - :target: https://pypi.python.org/pypi/django-todo -.. |build-status| image:: https://travis-ci.org/shacker/django-todo.svg - :alt: Build status - :target: https://travis-ci.org/shacker/django-todo -.. |health| image:: https://landscape.io/github/shacker/django-todo/master/landscape.svg?style=flat - :target: https://landscape.io/github/shacker/django-todo/master - :alt: Code health -.. |downloads| image:: https://img.shields.io/pypi/dm/django-todo.svg - :alt: Monthly downloads from PyPI - :target: https://pypi.python.org/pypi/django-todo -.. |license| image:: https://img.shields.io/pypi/l/django-todo.svg - :alt: Software license - :target: https://github.com/shacker/django-todo/blob/master/LICENSE From 58ac51a260c7e02fb6d05a242aa9a64ed9466928 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 3 Feb 2018 16:24:52 -0800 Subject: [PATCH 003/210] URLs tweak --- todo/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/todo/urls.py b/todo/urls.py index 0e6e080..1e40ded 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -7,10 +7,10 @@ app_name = 'todo' urlpatterns = [ path('', views.list_lists, name="lists"), path('mine/', views.view_list, {'list_slug': 'mine'}, name="mine"), - path('//delete$', views.del_list, name="del_list"), + path('//delete/', views.del_list, name="del_list"), path('task/', views.view_task, name='task_detail'), path('/', views.view_list, name='incomplete_tasks'), - path('//completed$', views.view_list, {'view_completed': True}, name='completed_tasks'), + path('//completed/', views.view_list, {'view_completed': True}, name='completed_tasks'), path('add_list/', views.add_list, name="add_list"), # FIXME need both of these? From f0adb9022408dbcfdbe7e83dc0633d560be4f533 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sun, 4 Feb 2018 00:33:12 -0800 Subject: [PATCH 004/210] Use HTML5 date inputs for due_date --- todo/forms.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/todo/forms.py b/todo/forms.py index f7a015d..59c8f02 100644 --- a/todo/forms.py +++ b/todo/forms.py @@ -29,10 +29,7 @@ class AddItemForm(ModelForm): self.fields['assigned_to'].label_from_instance = \ lambda obj: "%s (%s)" % (obj.get_full_name(), obj.username) - due_date = forms.DateField( - required=False, - widget=forms.DateTimeInput(attrs={'class': 'due_date_picker'}) - ) + due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'})) title = forms.CharField( widget=forms.widgets.TextInput(attrs={'size': 35}) @@ -52,6 +49,8 @@ class EditItemForm(ModelForm): super(EditItemForm, self).__init__(*args, **kwargs) self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=[self.instance.list.group]) + due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'})) + class Meta: model = Item exclude = ('created_date', 'created_by',) From c348ea1179c023f7bbae87f76afd75d170701abe Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sun, 4 Feb 2018 00:34:09 -0800 Subject: [PATCH 005/210] Remove datepicker dependency --- todo/static/todo/css/ui.datepicker.css | 213 ------------------------- todo/templates/todo/base.html | 7 - 2 files changed, 220 deletions(-) delete mode 100644 todo/static/todo/css/ui.datepicker.css diff --git a/todo/static/todo/css/ui.datepicker.css b/todo/static/todo/css/ui.datepicker.css deleted file mode 100644 index 49b00c0..0000000 --- a/todo/static/todo/css/ui.datepicker.css +++ /dev/null @@ -1,213 +0,0 @@ -/* Main Style Sheet for jQuery UI date picker */ -#ui-datepicker-div, .ui-datepicker-inline { - font-family: Arial, Helvetica, sans-serif; - font-size: 14px; - padding: 0; - margin: 0; - background: #ddd; - width: 185px; -} -#ui-datepicker-div { - display: none; - border: 1px solid #777; - z-index: 100; /*must have*/ -} -.ui-datepicker-inline { - float: left; - display: block; - border: 0; -} -.ui-datepicker-rtl { - direction: rtl; -} -.ui-datepicker-dialog { - padding: 5px !important; - border: 4px ridge #ddd !important; -} -.ui-datepicker-disabled { - position: absolute; - z-index: 100; - background-color: white; - opacity: 0.5; -} -button.ui-datepicker-trigger { - width: 25px; -} -img.ui-datepicker-trigger { - margin: 2px; - vertical-align: middle; -} -.ui-datepicker-prompt { - float: left; - padding: 2px; - background: #ddd; - color: #000; -} -* html .ui-datepicker-prompt { - width: 185px; -} -.ui-datepicker-control, .ui-datepicker-links, .ui-datepicker-header, .ui-datepicker { - clear: both; - float: left; - width: 100%; - color: #fff; -} -.ui-datepicker-control { - background: #400; - padding: 2px 0px; -} -.ui-datepicker-links { - background: #000; - padding: 2px 0px; -} -.ui-datepicker-control, .ui-datepicker-links { - font-weight: bold; - font-size: 80%; -} -.ui-datepicker-links label { /* disabled links */ - padding: 2px 5px; - color: #888; -} -.ui-datepicker-clear, .ui-datepicker-prev { - float: left; - width: 34%; -} -.ui-datepicker-rtl .ui-datepicker-clear, .ui-datepicker-rtl .ui-datepicker-prev { - float: right; - text-align: right; -} -.ui-datepicker-current { - float: left; - width: 30%; - text-align: center; -} -.ui-datepicker-close, .ui-datepicker-next { - float: right; - width: 34%; - text-align: right; -} -.ui-datepicker-rtl .ui-datepicker-close, .ui-datepicker-rtl .ui-datepicker-next { - float: left; - text-align: left; -} -.ui-datepicker-header { - padding: 1px 0 3px; - background: #333; - text-align: center; - font-weight: bold; - height: 1.3em; -} -.ui-datepicker-header select { - background: #333; - color: #fff; - border: 0px; - font-weight: bold; -} -.ui-datepicker { - background: #ccc; - text-align: center; - font-size: 100%; -} -.ui-datepicker a { - display: block; - width: 100%; -} -.ui-datepicker-title-row { - background: #777; -} -.ui-datepicker-days-row { - background: #eee; - color: #666; -} -.ui-datepicker-week-col { - background: #777; - color: #fff; -} -.ui-datepicker-days-cell { - color: #000; - border: 1px solid #ddd; -} -.ui-datepicker-days-cell a{ - display: block; -} -.ui-datepicker-week-end-cell { - background: #ddd; -} -.ui-datepicker-title-row .ui-datepicker-week-end-cell { - background: #777; -} -.ui-datepicker-days-cell-over { - background: #fff; - border: 1px solid #777; -} -.ui-datepicker-unselectable { - color: #888; -} -.ui-datepicker-today { - background: #fcc !important; -} -.ui-datepicker-current-day { - background: #999 !important; -} -.ui-datepicker-status { - background: #ddd; - width: 100%; - font-size: 80%; - text-align: center; -} - -/* ________ Datepicker Links _______ - -** Reset link properties and then override them with !important */ -#ui-datepicker-div a, .ui-datepicker-inline a { - cursor: pointer; - margin: 0; - padding: 0; - background: none; - color: #000; -} -.ui-datepicker-inline .ui-datepicker-links a { - padding: 0 5px !important; -} -.ui-datepicker-control a, .ui-datepicker-links a { - padding: 2px 5px !important; - color: #eee !important; -} -.ui-datepicker-title-row a { - color: #eee !important; -} -.ui-datepicker-control a:hover { - background: #fdd !important; - color: #333 !important; -} -.ui-datepicker-links a:hover, .ui-datepicker-title-row a:hover { - background: #ddd !important; - color: #333 !important; -} - -/* ___________ MULTIPLE MONTHS _________*/ - -.ui-datepicker-multi .ui-datepicker { - border: 1px solid #777; -} -.ui-datepicker-one-month { - float: left; - width: 185px; -} -.ui-datepicker-new-row { - clear: left; -} - -/* ___________ IE6 IFRAME FIX ________ */ - -.ui-datepicker-cover { - display: none; /*sorry for IE5*/ - display/**/: block; /*sorry for IE5*/ - position: absolute; /*must have*/ - z-index: -1; /*must have*/ - filter: mask(); /*must have*/ - top: -4px; /*must have*/ - left: -4px; /*must have*/ - width: 200px; /*must have*/ - height: 200px; /*must have*/ -} diff --git a/todo/templates/todo/base.html b/todo/templates/todo/base.html index 3e128f6..76527da 100644 --- a/todo/templates/todo/base.html +++ b/todo/templates/todo/base.html @@ -5,11 +5,4 @@ - {% endblock extrahead %} From 0c42de2ffb2ae41e1177887517a55e0a1e178541 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sun, 4 Feb 2018 00:35:04 -0800 Subject: [PATCH 006/210] Rename some views and URLs more logically --- todo/models.py | 4 ++-- todo/templates/todo/del_list.html | 2 +- todo/templates/todo/email/assigned_body.txt | 2 +- todo/templates/todo/email/newcomment_body.txt | 2 +- .../todo/{view_list.html => list_detail.html} | 11 +++++------ todo/templates/todo/list_lists.html | 4 ++-- todo/templates/todo/search_results.html | 2 +- todo/templates/todo/view_task.html | 2 +- todo/urls.py | 16 ++++++++++------ todo/views.py | 10 +++++----- 10 files changed, 29 insertions(+), 26 deletions(-) rename todo/templates/todo/{view_list.html => list_detail.html} (92%) diff --git a/todo/models.py b/todo/models.py index 07332ee..f7fb0ed 100644 --- a/todo/models.py +++ b/todo/models.py @@ -17,7 +17,7 @@ class List(models.Model): def __str__(self): return self.name - def incomplete_tasks(self): + def list_detail(self): # Count all incomplete tasks on the current list instance return Item.objects.filter(list=self, completed=0) @@ -47,7 +47,7 @@ class Item(models.Model): def overdue_status(self): "Returns whether the item's due date has passed or not." if self.due_date and datetime.date.today() > self.due_date: - return 1 + return True def __str__(self): return self.title diff --git a/todo/templates/todo/del_list.html b/todo/templates/todo/del_list.html index 0946d29..428953a 100644 --- a/todo/templates/todo/del_list.html +++ b/todo/templates/todo/del_list.html @@ -23,7 +23,7 @@

- Return to list: {{ list.name }} + Return to list: {{ list.name }} {% else %}

Sorry, you don't have permission to delete lists. Please contact your group administrator.

diff --git a/todo/templates/todo/email/assigned_body.txt b/todo/templates/todo/email/assigned_body.txt index e6e7b33..ae3b7f0 100644 --- a/todo/templates/todo/email/assigned_body.txt +++ b/todo/templates/todo/email/assigned_body.txt @@ -17,4 +17,4 @@ Task details/comments: 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:list_detail' task.list.id task.list.slug %} diff --git a/todo/templates/todo/email/newcomment_body.txt b/todo/templates/todo/email/newcomment_body.txt index 8619eb6..c5eec34 100644 --- a/todo/templates/todo/email/newcomment_body.txt +++ b/todo/templates/todo/email/newcomment_body.txt @@ -12,5 +12,5 @@ Task details/comments: 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:list_detail' task.list.id task.list.slug %} diff --git a/todo/templates/todo/view_list.html b/todo/templates/todo/list_detail.html similarity index 92% rename from todo/templates/todo/view_list.html rename to todo/templates/todo/list_detail.html index d85b31c..0f8ba8a 100644 --- a/todo/templates/todo/view_list.html +++ b/todo/templates/todo/list_detail.html @@ -43,8 +43,7 @@ {% if auth_ok %}
{% csrf_token %} - - {# Only show task adder if viewing a proper list #} + {# Only show task adder if viewing a list is not "mine" #} {% if list_slug != "mine" %}

→ Click to add task ←

@@ -110,15 +109,15 @@ {% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} {% if list_slug == "mine" %} - {{ task.list }} + {{ task.list }} {% endif %} {% endfor %} -

-

View completed tasks

+

+

View completed tasks

{% else %} @@ -153,7 +152,7 @@

-

View incomplete tasks

+

View incomplete tasks

{% endif %} {% if user.is_staff %} diff --git a/todo/templates/todo/list_lists.html b/todo/templates/todo/list_lists.html index 6e06c51..c951932 100644 --- a/todo/templates/todo/list_lists.html +++ b/todo/templates/todo/list_lists.html @@ -15,8 +15,8 @@ diff --git a/todo/templates/todo/search_results.html b/todo/templates/todo/search_results.html index e33061b..4928992 100644 --- a/todo/templates/todo/search_results.html +++ b/todo/templates/todo/search_results.html @@ -13,7 +13,7 @@ {% for f in found_items %}

{{ f.title }}
- On list: {{ f.list.name }}
+ On list: {{ f.list.name }}
Assigned to: {% if f.assigned_to %}{{ f.assigned_to }}{% else %}Anyone{% endif %} (created by: {{ f.created_by }})
Complete: {{ f.completed|yesno:"Yes,No" }}
diff --git a/todo/templates/todo/view_task.html b/todo/templates/todo/view_task.html index 1caac6e..9fc0bef 100644 --- a/todo/templates/todo/view_task.html +++ b/todo/templates/todo/view_task.html @@ -26,7 +26,7 @@

→ Click to edit details ←

- In list: {{ task.list }}
+ In list: {{ task.list }}
Assigned to: {% if task.assigned_to %}{{ task.assigned_to.get_full_name }}{% else %}Anyone{% endif %}
Created by: {{ task.created_by.first_name }} {{ task.created_by.last_name }}
Due date: {{ task.due_date }}
diff --git a/todo/urls.py b/todo/urls.py index 1e40ded..2b771f3 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -6,13 +6,17 @@ app_name = 'todo' urlpatterns = [ path('', views.list_lists, name="lists"), - path('mine/', views.view_list, {'list_slug': 'mine'}, name="mine"), + + # Three paths into `list_detail` view + path('//', views.list_detail, name='list_detail'), + path('mine/', views.list_detail, {'list_slug': 'mine'}, name="mine"), + path('//completed/', views.list_detail, {'view_completed': True}, name='completed_tasks'), + path('//delete/', views.del_list, name="del_list"), - path('task/', views.view_task, name='task_detail'), - path('/', views.view_list, name='incomplete_tasks'), - path('//completed/', views.view_list, {'view_completed': True}, name='completed_tasks'), path('add_list/', views.add_list, name="add_list"), + path('task//', views.task_detail, name='task_detail'), + # FIXME need both of these? path('search-post/', views.search_post, name="search-post"), path('search/', views.search, name="search"), @@ -21,6 +25,6 @@ urlpatterns = [ path('reorder_tasks/', views.reorder_tasks, name="reorder_tasks"), 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"), + path('recent/added/', views.list_detail, {'list_slug': 'recent-add'}, name="recently_added"), + path('recent/completed/', views.list_detail, {'list_slug': 'recent-complete'}, name="recently_completed"), ] diff --git a/todo/views.py b/todo/views.py index 5da286e..61955c7 100644 --- a/todo/views.py +++ b/todo/views.py @@ -77,7 +77,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): +def list_detail(request, list_id=None, list_slug=None, view_completed=False): """Display and manage items in a list. """ @@ -145,11 +145,11 @@ def view_list(request, list_id=0, list_slug=None, view_completed=False): 'priority': 999, }) - return render(request, 'todo/view_list.html', locals()) + return render(request, 'todo/list_detail.html', locals()) @user_passes_test(check_user_allowed) -def view_task(request, task_id): +def task_detail(request, task_id): """View task details. Allow task details to be edited. """ task = get_object_or_404(Item, pk=task_id) @@ -201,7 +201,7 @@ def view_task(request, task_id): messages.success(request, "The task has been edited.") - return redirect('todo:lists', args=[task.list.id, task.list.slug]) + return redirect('todo:list_detail', list_id=task.list.id, list_slug=task.list.slug) else: form = EditItemForm(instance=task) if task.due_date: @@ -217,7 +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 list_detail.html """ newtasklist = request.POST.getlist('tasktable[]') # First item in received list is always empty - remove it From cd1678da4707b4044145be9bba970e4b0b301858 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sun, 4 Feb 2018 00:40:00 -0800 Subject: [PATCH 007/210] Remove remaining tox references --- .gitignore | 1 - setup.py | 75 ------------------------------------------------------ tox.ini | 23 ----------------- 3 files changed, 99 deletions(-) delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 9ae827f..4f3f8fa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /.coverage/ /.eggs/ /.idea/ -/.tox/ /build/ /dist/ /docs/build/ diff --git a/setup.py b/setup.py index 0484232..694a69d 100755 --- a/setup.py +++ b/setup.py @@ -1,83 +1,13 @@ #!/usr/bin/env python -from glob import glob -from os import remove -from os.path import abspath, dirname, join from setuptools import setup, find_packages -from setuptools.command.test import test as TestCommand -from shlex import split -from shutil import rmtree -from sys import exit import todo as package - -class Tox(TestCommand): - user_options = [('tox-args=', 'a', "Arguments to pass to tox")] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.tox_args = None - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self): - import tox - args = self.tox_args - if args: - args = split(self.tox_args) - errno = tox.cmdline(args=args) - exit(errno) - - -class Clean(TestCommand): - def run(self): - delete_in_root = [ - 'build', - 'dist', - '.eggs', - '*.egg-info', - '.tox', - ] - delete_everywhere = [ - '__pycache__', - '*.pyc', - ] - for candidate in delete_in_root: - rmtree_glob(candidate) - for visible_dir in glob('[A-Za-z0-9]*'): - for candidate in delete_everywhere: - rmtree_glob(join(visible_dir, candidate)) - rmtree_glob(join(visible_dir, '*', candidate)) - rmtree_glob(join(visible_dir, '*', '*', candidate)) - - -def rmtree_glob(file_glob): - for fobj in glob(file_glob): - try: - rmtree(fobj) - print('%s/ removed ...' % fobj) - except OSError: - try: - remove(fobj) - print('%s removed ...' % fobj) - except OSError: - pass - - -def read_file(*pathname): - with open(join(dirname(abspath(__file__)), *pathname)) as f: - return f.read() - - setup( name='django-todo', version=package.__version__, description=package.__doc__.strip(), - long_description=read_file('README.rst'), author=package.__author__, author_email=package.__email__, url=package.__url__, @@ -98,10 +28,5 @@ setup( ], include_package_data=True, zip_safe=False, - tests_require=['tox'], install_requires=['unidecode', ], - cmdclass={ - 'clean': Clean, - 'test': Tox, - }, ) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 090c1fa..0000000 --- a/tox.ini +++ /dev/null @@ -1,23 +0,0 @@ -[tox] -envlist = - flake8py{2,3} - -[testenv] -# deps = pytest -commands = - # py.test - -[testenv:flake8py2] -basepython = python2.7 -deps = flake8 -commands = flake8 . - -[testenv:flake8py3] -basepython = python3.5 -deps = flake8 -commands = flake8 . - -[flake8] -max-line-length = 120 -max-complexity = 10 -exclude = [build, lib, bin, dist, docs/conf.py, */migrations, .eggs, *.egg-info] From f2b87979f256f9eb6cd6cab44c8828b28063cb67 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sun, 4 Feb 2018 00:44:15 -0800 Subject: [PATCH 008/210] due_date optional in forms --- todo/forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/todo/forms.py b/todo/forms.py index 59c8f02..e6bd958 100644 --- a/todo/forms.py +++ b/todo/forms.py @@ -29,7 +29,7 @@ class AddItemForm(ModelForm): self.fields['assigned_to'].label_from_instance = \ lambda obj: "%s (%s)" % (obj.get_full_name(), obj.username) - due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'})) + due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}), required=False) title = forms.CharField( widget=forms.widgets.TextInput(attrs={'size': 35}) @@ -49,7 +49,7 @@ class EditItemForm(ModelForm): super(EditItemForm, self).__init__(*args, **kwargs) self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=[self.instance.list.group]) - due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'})) + due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}), required=False) class Meta: model = Item From 1fff35c613c6ee57fca447be8c7a50bf41e0587d Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sun, 4 Feb 2018 01:01:48 -0800 Subject: [PATCH 009/210] Indentation fix in list_detail template --- todo/templates/todo/list_detail.html | 222 +++++++++++++-------------- 1 file changed, 109 insertions(+), 113 deletions(-) diff --git a/todo/templates/todo/list_detail.html b/todo/templates/todo/list_detail.html index 0f8ba8a..5e4286a 100644 --- a/todo/templates/todo/list_detail.html +++ b/todo/templates/todo/list_detail.html @@ -4,33 +4,32 @@ {% block content %} {% if list_slug == "mine" %} @@ -45,34 +44,34 @@ {% csrf_token %} {# Only show task adder if viewing a list is not "mine" #} {% if list_slug != "mine" %} -

→ Click to add task ←

+

→ Click to add task ←

+
+ + + + + + + + + + + + + + +
{{ form.title.errors }}{{ form.due_date.errors }}
{{ form.title }} {{ form.due_date }} {{ form.assigned_to }}
+ + {{ form.note }} +

*Email notifications will only be sent if task is assigned to someone besides yourself.

+
-
- - - - - - - - - - - - - - -
{{ form.title.errors }}{{ form.due_date.errors }}
{{ form.title }} {{ form.due_date }} {{ form.assigned_to }}
- {{ form.note }} -

*Email notifications will only be sent if task is assigned to someone besides yourself.

-
- - - - - -

-
+ + + + +

+
{% endif %} {% if not view_completed %} @@ -80,85 +79,82 @@

Incomplete tasks :: Drag rows to set priorities

- - - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - + + + + + + + + + + {% if list_slug == "mine" %} + + {% endif %} + + {% for task in task_list %} - - - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - + + + + + + + + + + {% if list_slug == "mine" %} + + {% endif %} + + {% endfor %}
DoneTaskCreatedDue onOwnerAssignedNoteCommListDel
DoneTaskCreatedDue onOwnerAssignedNoteCommListDel
{{ task.title|truncatewords:20 }}{{ task.created_date|date:"m/d/Y" }} - {% if task.overdue_status %}{% endif %} - {{ task.due_date|date:"m/d/Y" }} - {% if task.overdue_status %}{% endif %} - {{ task.created_by }}{% if task.assigned_to %}{{ task.assigned_to }}{% else %}Anyone{% endif %}{% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %}{{ task.list }}
{{ task.title|truncatewords:20 }}{{ task.created_date|date:"m/d/Y" }} + {% if task.overdue_status %}{% endif %} + {{ task.due_date|date:"m/d/Y" }} + {% if task.overdue_status %}{% endif %} + {{ task.created_by }}{% if task.assigned_to %}{{ task.assigned_to }}{% else %}Anyone{% endif %}{% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %}{{ task.list }}

-

View completed tasks

+

View completed tasks

{% else %}

Completed tasks

- - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - + + + + + + + + {% if list_slug == "mine" %} {% endif %} + + {% for task in completed_list %} - - - - - - - - - {% endfor %} + + + + + + + + + {% endfor %} -
UndoTaskCreatedCompleted onNoteCommListDel
UndoTaskCreatedCompleted onNoteCommListDel
{{ task.title|truncatewords:20 }}{{ task.created_date|date:"m/d/Y" }}{{ task.completed_date|date:"m/d/Y" }}{% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} -
{{ task.title|truncatewords:20 }}{{ task.created_date|date:"m/d/Y" }}{{ task.completed_date|date:"m/d/Y" }}{% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} +
-

+ +

-

View incomplete tasks

+

View incomplete tasks

{% endif %} {% if user.is_staff %} - {% if list_slug != "mine" %} -

Delete this list

- {% endif %} + {% if list_slug != "mine" %} +

Delete this list

+ {% endif %} {% endif %} {% endif %} From 774cd3d05765efd566924e659c9fa55dfa47482b Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sun, 4 Feb 2018 23:24:27 -0800 Subject: [PATCH 010/210] list_detail indentation --- todo/templates/todo/list_detail.html | 48 +++++++++++++++++++--------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/todo/templates/todo/list_detail.html b/todo/templates/todo/list_detail.html index 5e4286a..ccd2c53 100644 --- a/todo/templates/todo/list_detail.html +++ b/todo/templates/todo/list_detail.html @@ -95,24 +95,42 @@ {% for task in task_list %} - - {{ task.title|truncatewords:20 }} - {{ task.created_date|date:"m/d/Y" }} - {% if task.overdue_status %}{% endif %} - {{ task.due_date|date:"m/d/Y" }} - {% if task.overdue_status %}{% endif %} + + + + {{ task.title|truncatewords:20 }} + + + {{ task.created_date|date:"m/d/Y" }} + + + {% if task.overdue_status %}{% endif %} + {{ task.due_date|date:"m/d/Y" }} + {% if task.overdue_status %}{% endif %} - {{ task.created_by }} - {% if task.assigned_to %}{{ task.assigned_to }}{% else %}Anyone{% endif %} - {% if task.note %}≈{% endif %} - {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} + + {{ task.created_by }} + + + {% if task.assigned_to %}{{ task.assigned_to }}{% else %}Anyone{% endif %} + + + {% if task.note %}≈{% endif %} + + + {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} + {% if list_slug == "mine" %} - {{ task.list }} - {% endif %} - - - {% endfor %} + + {{ task.list }} + + {% endif %} + + + + + {% endfor %}

From efc2dbe11ad17a6013ff2fc028eb7fc8cd1917c4 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 10 Feb 2018 00:25:28 -0800 Subject: [PATCH 011/210] Rename List model to TaskList --- todo/admin.py | 8 +- todo/forms.py | 24 ++--- todo/migrations/0004_rename_list_tasklist.py | 52 ++++++++++ todo/models.py | 8 +- todo/templates/todo/del_list.html | 8 +- todo/templates/todo/email/assigned_body.txt | 6 +- todo/templates/todo/email/newcomment_body.txt | 4 +- todo/templates/todo/search_results.html | 11 ++- todo/templates/todo/view_task.html | 4 +- todo/views.py | 95 ++++++++----------- 10 files changed, 130 insertions(+), 90 deletions(-) create mode 100644 todo/migrations/0004_rename_list_tasklist.py diff --git a/todo/admin.py b/todo/admin.py index 9e28467..d598552 100644 --- a/todo/admin.py +++ b/todo/admin.py @@ -1,10 +1,10 @@ from django.contrib import admin -from todo.models import Item, List, Comment +from todo.models import Item, TaskList, Comment class ItemAdmin(admin.ModelAdmin): - list_display = ('title', 'list', 'priority', 'due_date') - list_filter = ('list',) + list_display = ('title', 'task_list', 'completed', 'priority', 'due_date') + list_filter = ('task_list',) ordering = ('priority',) search_fields = ('name',) @@ -13,6 +13,6 @@ class CommentAdmin(admin.ModelAdmin): list_display = ('author', 'date', 'snippet') -admin.site.register(List) +admin.site.register(TaskList) admin.site.register(Comment, CommentAdmin) admin.site.register(Item, ItemAdmin) diff --git a/todo/forms.py b/todo/forms.py index e6bd958..946b286 100644 --- a/todo/forms.py +++ b/todo/forms.py @@ -1,20 +1,20 @@ from django import forms from django.forms import ModelForm from django.contrib.auth.models import Group -from todo.models import Item, List +from todo.models import Item, TaskList from django.contrib.auth import get_user_model -class AddListForm(ModelForm): +class AddTaskListForm(ModelForm): # The picklist showing allowable groups to which a new list can be added # determines which groups the user belongs to. This queries the form object # to derive that list. def __init__(self, user, *args, **kwargs): - super(AddListForm, self).__init__(*args, **kwargs) + super(AddTaskListForm, self).__init__(*args, **kwargs) self.fields['group'].queryset = Group.objects.filter(user=user) class Meta: - model = List + model = TaskList exclude = [] @@ -23,19 +23,21 @@ class AddItemForm(ModelForm): # must find other members of the groups the current list belongs to. def __init__(self, task_list, *args, **kwargs): super(AddItemForm, self).__init__(*args, **kwargs) - # print dir(self.fields['list']) - # print self.fields['list'].initial + # debug: + # print(dir(self.fields['list'])) + # print(self.fields['list'].initial) self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=[task_list.group]) self.fields['assigned_to'].label_from_instance = \ lambda obj: "%s (%s)" % (obj.get_full_name(), obj.username) - 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(attrs={'size': 35}) - ) + widget=forms.widgets.TextInput(attrs={'size': 35})) - note = forms.CharField(widget=forms.Textarea(), required=False) + note = forms.CharField( + widget=forms.Textarea(), required=False) class Meta: model = Item @@ -47,7 +49,7 @@ class EditItemForm(ModelForm): # must find other members of the groups the current list belongs to. def __init__(self, *args, **kwargs): super(EditItemForm, self).__init__(*args, **kwargs) - self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=[self.instance.list.group]) + self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=[self.instance.task_list.group]) due_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}), required=False) diff --git a/todo/migrations/0004_rename_list_tasklist.py b/todo/migrations/0004_rename_list_tasklist.py new file mode 100644 index 0000000..65e9b04 --- /dev/null +++ b/todo/migrations/0004_rename_list_tasklist.py @@ -0,0 +1,52 @@ +# Generated by Django 2.0.2 on 2018-02-09 23:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0009_alter_user_last_name_max_length'), + ('todo', '0003_assignee_optional'), + ] + + operations = [ + migrations.CreateModel( + name='TaskList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=60)), + ('slug', models.SlugField(default='')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), + ], + options={ + 'verbose_name_plural': 'Lists', + 'ordering': ['name'], + }, + ), + migrations.AlterUniqueTogether( + name='list', + unique_together=set(), + ), + migrations.RemoveField( + model_name='list', + name='group', + ), + migrations.RemoveField( + model_name='item', + name='list', + ), + migrations.DeleteModel( + name='List', + ), + migrations.AddField( + model_name='item', + name='task_list', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='todo.TaskList'), + ), + migrations.AlterUniqueTogether( + name='tasklist', + unique_together={('group', 'slug')}, + ), + ] diff --git a/todo/models.py b/todo/models.py index f7fb0ed..c4277b3 100644 --- a/todo/models.py +++ b/todo/models.py @@ -9,7 +9,7 @@ from django.conf import settings @python_2_unicode_compatible -class List(models.Model): +class TaskList(models.Model): name = models.CharField(max_length=60) slug = models.SlugField(default='',) group = models.ForeignKey(Group, on_delete=models.CASCADE) @@ -19,11 +19,11 @@ class List(models.Model): def list_detail(self): # Count all incomplete tasks on the current list instance - return Item.objects.filter(list=self, completed=0) + return Item.objects.filter(task_list=self, completed=0) class Meta: ordering = ["name"] - verbose_name_plural = "Lists" + verbose_name_plural = "Task Lists" # Prevents (at the database level) creation of two lists with the same name in the same group unique_together = ("group", "slug") @@ -32,7 +32,7 @@ class List(models.Model): @python_2_unicode_compatible class Item(models.Model): title = models.CharField(max_length=140) - list = models.ForeignKey(List, on_delete=models.CASCADE) + task_list = models.ForeignKey(TaskList, on_delete=models.CASCADE, null=True) created_date = models.DateField(auto_now=True) due_date = models.DateField(blank=True, null=True, ) completed = models.BooleanField(default=None) diff --git a/todo/templates/todo/del_list.html b/todo/templates/todo/del_list.html index 428953a..29f2f4b 100644 --- a/todo/templates/todo/del_list.html +++ b/todo/templates/todo/del_list.html @@ -1,11 +1,11 @@ {% extends "todo/base.html" %} -{% block title %}{{ list_title }} to-do items{% endblock %} +{% block title %}Delete list{% endblock %} {% block content %} {% if user.is_staff %} -

Delete entire list: {{ list.name }} ?

+

Delete entire list: {{ task_list.name }} ?

Category tally:

@@ -19,11 +19,11 @@
{% csrf_token %} - +

- Return to list: {{ list.name }} + Return to list: {{ task_list.name }} {% else %}

Sorry, you don't have permission to delete lists. Please contact your group administrator.

diff --git a/todo/templates/todo/email/assigned_body.txt b/todo/templates/todo/email/assigned_body.txt index ae3b7f0..b56c1f8 100644 --- a/todo/templates/todo/email/assigned_body.txt +++ b/todo/templates/todo/email/assigned_body.txt @@ -1,6 +1,6 @@ Dear {{ task.assigned_to.first_name }} - -A new task on the list {{ task.list.name }} has been assigned to you by {{ task.created_by.first_name }} {{ task.created_by.last_name }}: +A new task on the list {{ task.task_list.name }} has been assigned to you by {{ task.created_by.first_name }} {{ task.created_by.last_name }}: {{ task.title }} @@ -16,5 +16,5 @@ Note: {{ task.note }} Task details/comments: http://{{ site }}{% url 'todo:task_detail' task.id %} -List {{ task.list.name }}: -http://{{ site }}{% url 'todo:list_detail' task.list.id task.list.slug %} +List {{ task.task_list.name }}: +http://{{ site }}{% url 'todo:list_detail' task.task_list.id task.task_list.slug %} diff --git a/todo/templates/todo/email/newcomment_body.txt b/todo/templates/todo/email/newcomment_body.txt index c5eec34..81947ff 100644 --- a/todo/templates/todo/email/newcomment_body.txt +++ b/todo/templates/todo/email/newcomment_body.txt @@ -11,6 +11,6 @@ Comment: Task details/comments: https://{{ site }}{% url 'todo:task_detail' task.id %} -List {{ task.list.name }}: -https://{{ site }}{% url 'todo:list_detail' task.list.id task.list.slug %} +List {{ task.task_list.name }}: +https://{{ site }}{% url 'todo:list_detail' task.task_list.id task.task_list.slug %} diff --git a/todo/templates/todo/search_results.html b/todo/templates/todo/search_results.html index 4928992..d50759a 100644 --- a/todo/templates/todo/search_results.html +++ b/todo/templates/todo/search_results.html @@ -13,8 +13,15 @@ {% for f in found_items %}

{{ f.title }}
- On list: {{ f.list.name }}
- Assigned to: {% if f.assigned_to %}{{ f.assigned_to }}{% else %}Anyone{% endif %} (created by: {{ f.created_by }})
+ In list: + + {{ f.task_list.name }} + +
+ Assigned to: + {% if f.assigned_to %}{{ f.assigned_to }}{% else %}Anyone{% endif %} + (created by: {{ f.created_by }}) +
Complete: {{ f.completed|yesno:"Yes,No" }}

diff --git a/todo/templates/todo/view_task.html b/todo/templates/todo/view_task.html index 9fc0bef..47f627f 100644 --- a/todo/templates/todo/view_task.html +++ b/todo/templates/todo/view_task.html @@ -26,7 +26,7 @@

→ Click to edit details ←

- In list: {{ task.list }}
+ In list: {{ task.list }}
Assigned to: {% if task.assigned_to %}{{ task.assigned_to.get_full_name }}{% else %}Anyone{% endif %}
Created by: {{ task.created_by.first_name }} {{ task.created_by.last_name }}
Due date: {{ task.due_date }}
@@ -48,7 +48,7 @@ List: - {{ form.list }} + {{ form.task_list }} diff --git a/todo/views.py b/todo/views.py index 61955c7..67bbc7a 100644 --- a/todo/views.py +++ b/todo/views.py @@ -14,9 +14,9 @@ from django.urls import reverse from django.views.decorators.csrf import csrf_exempt 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 +from todo.forms import AddTaskListForm, AddItemForm, EditItemForm, AddExternalItemForm, SearchForm +from todo.models import Item, TaskList, Comment +from todo.utils import toggle_done, toggle_deleted, send_notify_mail def check_user_allowed(user): @@ -43,9 +43,9 @@ def list_lists(request): # Superusers see all lists if request.user.is_superuser: - list_list = List.objects.all().order_by('group', 'name') + list_list = TaskList.objects.all().order_by('group', 'name') else: - list_list = List.objects.filter(group__in=request.user.groups.all()).order_by('group', 'name') + list_list = TaskList.objects.filter(group__in=request.user.groups.all()).order_by('group', 'name') list_count = list_list.count() @@ -53,7 +53,7 @@ def list_lists(request): if request.user.is_superuser: item_count = Item.objects.filter(completed=0).count() else: - item_count = Item.objects.filter(completed=0).filter(list__group__in=request.user.groups.all()).count() + item_count = Item.objects.filter(completed=0).filter(task_list__group__in=request.user.groups.all()).count() return render(request, 'todo/list_lists.html', locals()) @@ -62,68 +62,47 @@ def list_lists(request): 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. """ - list = get_object_or_404(List, slug=list_slug) + task_list = get_object_or_404(TaskList, 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)) + 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') 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() - item_count_total = Item.objects.filter(list=list.id).count() + item_count_done = Item.objects.filter(task_list=task_list.id, completed=True).count() + item_count_undone = Item.objects.filter(task_list=task_list.id, completed=False).count() + item_count_total = Item.objects.filter(task_list=task_list.id).count() return render(request, 'todo/del_list.html', locals()) -@user_passes_test(check_user_allowed) def list_detail(request, list_id=None, list_slug=None, view_completed=False): - """Display and manage items in a list. + """Display and manage items in a todo list. """ - # Make sure the accessing user has permission to view this list. - # Always authorize the "mine" view. Admins can view/edit all lists. - if list_slug == "mine" or list_slug == "recent-add" or list_slug == "recent-complete": - auth_ok = True - else: - list = get_object_or_404(List, id=list_id) - if list.group in request.user.groups.all() or request.user.is_staff or list_slug == "mine": - auth_ok = True - else: # User does not belong to the group this list is attached to - messages.error(request, "You do not have permission to view/edit this list.") + if request.POST: + # Process completed and deleted requests on each POST + toggle_done(request, request.POST.getlist('toggle_done_tasks')) + toggle_deleted(request, request.POST.getlist('toggle_deleted_tasks')) - # Process all possible list interactions on each submit - mark_done(request, request.POST.getlist('mark_done')) - del_tasks(request, request.POST.getlist('del_tasks')) - undo_completed_task(request, request.POST.getlist('undo_completed_task')) - - thedate = datetime.datetime.now() - created_date = "%s-%s-%s" % (thedate.year, thedate.month, thedate.day) - - # Get set of items with this list ID, or filter on items assigned to me, or recently added/completed if list_slug == "mine": - task_list = Item.objects.filter(assigned_to=request.user, completed=False) - completed_list = Item.objects.filter(assigned_to=request.user, completed=True) - - elif list_slug == "recent-add": - # Only show items in lists that are in groups that the current user is also in. - # Assume this only includes uncompleted items. - task_list = Item.objects.filter( - list__group__in=(request.user.groups.all()), - completed=False).order_by('-created_date')[:50] - - elif list_slug == "recent-complete": - # Only show items in lists that are in groups that the current user is also in. - task_list = Item.objects.filter( - list__group__in=request.user.groups.all(), - completed=True).order_by('-completed_date')[:50] - + items = Item.objects.filter(assigned_to=request.user) else: - task_list = Item.objects.filter(list=list.id, completed=0) - completed_list = Item.objects.filter(list=list.id, completed=1) + task_list = get_object_or_404(TaskList, id=list_id) + items = Item.objects.filter(task_list=task_list.id) + + # Apply filters to base queryset + if view_completed: + items = items.filter(completed=True) + else: + items = items.filter(completed=False) + + # ###################### + # Add New Task Form + # ###################### if request.POST.getlist('add_task'): - form = AddItemForm(list, request.POST, initial={ + form = AddItemForm(task_list, request.POST, initial={ 'assigned_to': request.user.id, 'priority': 999, }) @@ -140,7 +119,7 @@ def list_detail(request, list_id=None, list_slug=None, view_completed=False): else: # Don't allow adding new tasks on some views if list_slug != "mine" and list_slug != "recent-add" and list_slug != "recent-complete": - form = AddItemForm(list, initial={ + form = AddItemForm(task_list=task_list, initial={ 'assigned_to': request.user.id, 'priority': 999, }) @@ -159,7 +138,7 @@ def task_detail(request, task_id): # Get the group this task belongs to, and check whether current user is a member of that group. # Admins can edit all tasks. - if task.list.group in request.user.groups.all() or request.user.is_staff: + if task.task_list.group in request.user.groups.all() or request.user.is_staff: auth_ok = True if request.POST: @@ -201,7 +180,7 @@ def task_detail(request, task_id): messages.success(request, "The task has been edited.") - return redirect('todo:list_detail', list_id=task.list.id, list_slug=task.list.slug) + return redirect('todo:list_detail', list_id=task.task_list.id, list_slug=task.task_list.slug) else: form = EditItemForm(instance=task) if task.due_date: @@ -276,7 +255,7 @@ def add_list(request): """Allow users to add a new todo list to the group they're in. """ if request.POST: - form = AddListForm(request.user, request.POST) + form = AddTaskListForm(request.user, request.POST) if form.is_valid(): try: form.save() @@ -290,9 +269,9 @@ def add_list(request): "Most likely a list with the same name in the same group already exists.") else: if request.user.groups.all().count() == 1: - form = AddListForm(request.user, initial={"group": request.user.groups.all()[0]}) + form = AddTaskListForm(request.user, initial={"group": request.user.groups.all()[0]}) else: - form = AddListForm(request.user) + form = AddTaskListForm(request.user) return render(request, 'todo/add_list.html', locals()) From fc4fa7b7f3db503be629ec73b369b2ae306a7113 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 10 Feb 2018 00:25:56 -0800 Subject: [PATCH 012/210] Move JS to bottom of list_detail --- todo/templates/todo/list_detail.html | 295 ++++++++++++--------------- 1 file changed, 129 insertions(+), 166 deletions(-) diff --git a/todo/templates/todo/list_detail.html b/todo/templates/todo/list_detail.html index ccd2c53..1d8ac3f 100644 --- a/todo/templates/todo/list_detail.html +++ b/todo/templates/todo/list_detail.html @@ -1,179 +1,142 @@ {% extends "todo/base.html" %} -{% block title %}Todo List: {{ list.name }}{% endblock %} +{% block title %}Todo List: {{ task_list.name }}{% endblock %} {% block content %} - - - {% if list_slug == "mine" %} -

Tasks assigned to {{ request.user }}

- {% elif auth_ok %} -

Tasks filed under "{{ list.name }}"

-

This list belongs to group {{ list.group }}

+ {# fixme: convert to bs buttons #} + {% if list_slug != "mine" %} + {% if view_completed %} +

View incomplete tasks

+ {% else %} +

View completed tasks

{% endif %} - {% if auth_ok %} -
- {% csrf_token %} - {# Only show task adder if viewing a list is not "mine" #} - {% if list_slug != "mine" %} -

→ Click to add task ←

-
- - - - - - - - - - - - - - -
{{ form.title.errors }}{{ form.due_date.errors }}
{{ form.title }} {{ form.due_date }} {{ form.assigned_to }}
- - {{ form.note }} -

*Email notifications will only be sent if task is assigned to someone besides yourself.

-
+

Delete this list

- - - - -

-
- {% endif %} + {% endif %} + - {% if not view_completed %} - -

Incomplete tasks :: Drag rows to set priorities

- - - - - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - - {% for task in task_list %} - - - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - - {% endfor %} -
DoneTaskCreatedDue onOwnerAssignedNoteCommListDel
- - - {{ task.title|truncatewords:20 }} - - {{ task.created_date|date:"m/d/Y" }} - - {% if task.overdue_status %}{% endif %} - {{ task.due_date|date:"m/d/Y" }} - {% if task.overdue_status %}{% endif %} - - {{ task.created_by }} - - {% if task.assigned_to %}{{ task.assigned_to }}{% else %}Anyone{% endif %} - - {% if task.note %}≈{% endif %} - - {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} - - {{ task.list }} - - -
- -

-

View completed tasks

- - {% else %} - -

Completed tasks

- - - - - - - - - {% if list_slug == "mine" %} {% endif %} - - - - {% for task in completed_list %} - - - - - - - - - {% endfor %} - -
UndoTaskCreatedCompleted onNoteCommListDel
{{ task.title|truncatewords:20 }}{{ task.created_date|date:"m/d/Y" }}{{ task.completed_date|date:"m/d/Y" }}{% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} -
-

-
-

View incomplete tasks

- {% endif %} - - {% if user.is_staff %} - {% if list_slug != "mine" %} -

Delete this list

- {% endif %} - {% endif %} - - {% endif %} {% endblock %} + +{% block extra_js %} + +{% endblock extra_js %} \ No newline at end of file From 0c8d31b6af33ca1a3994a1a2979d196f69234f6e Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 10 Feb 2018 00:26:19 -0800 Subject: [PATCH 013/210] Clean up URLs --- todo/urls.py | 46 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/todo/urls.py b/todo/urls.py index 2b771f3..6ea26e3 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -8,18 +8,48 @@ urlpatterns = [ path('', views.list_lists, name="lists"), # Three paths into `list_detail` view - path('//', views.list_detail, name='list_detail'), - path('mine/', views.list_detail, {'list_slug': 'mine'}, name="mine"), - path('//completed/', views.list_detail, {'view_completed': True}, name='completed_tasks'), + path( + 'mine/', + views.list_detail, + {'list_slug': 'mine'}, + name="mine"), - path('//delete/', views.del_list, name="del_list"), - path('add_list/', views.add_list, name="add_list"), + path( + '//', + views.list_detail, + name='list_detail'), - path('task//', views.task_detail, name='task_detail'), + path( + '//completed/', + views.list_detail, + {'view_completed': True}, + name='list_detail_completed'), + + path( + '//delete/', + views.del_list, + name="del_list"), + + path( + 'add_list/', + views.add_list, + name="add_list"), + + path( + 'task//', + views.task_detail, + name='task_detail'), # FIXME need both of these? - path('search-post/', views.search_post, name="search-post"), - path('search/', views.search, name="search"), + 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 path('reorder_tasks/', views.reorder_tasks, name="reorder_tasks"), From 03c125ca50dae2838e0b5625ea3008bd26e49a32 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 10 Feb 2018 00:27:00 -0800 Subject: [PATCH 014/210] Refactor do/undo ops into single toggle function --- todo/utils.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/todo/utils.py b/todo/utils.py index afd6c2c..b73f822 100644 --- a/todo/utils.py +++ b/todo/utils.py @@ -8,26 +8,19 @@ from django.template.loader import render_to_string from todo.models import Item -def mark_done(request, done_items): +def toggle_done(request, items): # Check for items in the mark_done POST array. If present, change status to complete. - for item in done_items: + for item in items: i = Item.objects.get(id=item) - i.completed = True + old_state = "completed" if i.completed else "incomplete" + i.completed = not i.completed # Invert the done state, either way + new_state = "completed" if i.completed else "incomplete" i.completed_date = datetime.datetime.now() i.save() - messages.success(request, "Item \"{i}\" marked complete.".format(i=i.title)) + messages.success(request, "Item \"{i}\" changed from {o} to {n}.".format(i=i.title, o=old_state, n=new_state)) -def undo_completed_task(request, undone_items): - # Undo: Set completed items back to incomplete - for item in undone_items: - i = Item.objects.get(id=item) - i.completed = False - i.save() - messages.success(request, "Previously completed task \"{i}\" marked incomplete.".format(i=i.title)) - - -def del_tasks(request, deleted_items): +def toggle_deleted(request, deleted_items): # Delete selected items for item_id in deleted_items: i = Item.objects.get(id=item_id) From 8cba92b57101bfe234b1205f8164b3af41d7cde0 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Sat, 10 Feb 2018 23:28:35 -0800 Subject: [PATCH 015/210] Better template filenames - Also removes some extraneous CSS classes --- todo/static/todo/css/styles.css | 9 -------- ...ernal_task.html => add_task_external.html} | 0 todo/templates/todo/list_lists.html | 2 +- .../todo/{view_task.html => task_detail.html} | 21 ++++++++++++++----- todo/views.py | 4 ++-- 5 files changed, 19 insertions(+), 17 deletions(-) rename todo/templates/todo/{add_external_task.html => add_task_external.html} (100%) rename todo/templates/todo/{view_task.html => task_detail.html} (80%) diff --git a/todo/static/todo/css/styles.css b/todo/static/todo/css/styles.css index a20c4b8..3200eaf 100644 --- a/todo/static/todo/css/styles.css +++ b/todo/static/todo/css/styles.css @@ -15,15 +15,6 @@ ul.messages li { color: gray; } -a.todo { - text-decoration: none; - color: #474747; -} - -a.showlink { - text-decoration: underline; -} - label { display: block; diff --git a/todo/templates/todo/add_external_task.html b/todo/templates/todo/add_task_external.html similarity index 100% rename from todo/templates/todo/add_external_task.html rename to todo/templates/todo/add_task_external.html diff --git a/todo/templates/todo/list_lists.html b/todo/templates/todo/list_lists.html index c951932..979e3f1 100644 --- a/todo/templates/todo/list_lists.html +++ b/todo/templates/todo/list_lists.html @@ -15,7 +15,7 @@