Working file upload dialog and receive in view

This commit is contained in:
Scot Hacker 2019-04-07 16:11:19 -07:00
parent 9a5c794c41
commit b6c2227417
3 changed files with 111 additions and 31 deletions

View file

@ -1,10 +1,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
import os
import textwrap import textwrap
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.db import models, DEFAULT_DB_ALIAS from django.db import DEFAULT_DB_ALIAS, models
from django.db.transaction import Atomic, get_connection from django.db.transaction import Atomic, get_connection
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
@ -177,5 +179,12 @@ class Attachment(models.Model):
timestamp = models.DateTimeField(default=datetime.datetime.now) timestamp = models.DateTimeField(default=datetime.datetime.now)
file = models.FileField(upload_to=get_attachment_upload_dir, max_length=255) file = models.FileField(upload_to=get_attachment_upload_dir, max_length=255)
def filename(self):
return os.path.basename(self.file.name)
def extension(self):
name, extension = os.path.splitext(self.file.name)
return extension
def __str__(self): def __str__(self):
return f"{self.task.id} - {self.file.name}" return f"{self.task.id} - {self.file.name}"

View file

@ -115,6 +115,55 @@
{% endif %} {% endif %}
</div> </div>
{% if attachments_enabled %}
<div class="card mt-4">
<h5 class="card-header">
Attachments
</h5>
<div class="card-body pb-0">
{% if task.attachment_set.count %}
<div class="table-responsive">
<table class="table mb-0">
<thead>
<tr>
<th>File</th>
<th>Uploaded</th>
<th>By</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% for attachment in task.attachment_set.all %}
<tr>
<td><a href="{{ attachment.file.url }}">{{ attachment.filename }}</a></td>
<td>{{ attachment.timestamp }}</td>
<td>{{ attachment.added_by.get_full_name }}</td>
<td>{{ attachment.extension.lower }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<form method="POST" action="#" enctype="multipart/form-data">
{% csrf_token %}
<div class="input-group mb-3">
<div class="custom-file">
<input type="file" class="custom-file-input" id="attachment_file_input" name="attachment_file_input" />
<label class="custom-file-label" for="attachment_file_input">Choose file</label>
</div>
<div class="input-group-append">
<button class="btn btn-primary">Upload</button>
</div>
</div>
</form>
</div>
</div>
{% endif %}
<div class="mt-3"> <div class="mt-3">
<h5>Add comment</h5> <h5>Add comment</h5>
<form action="" method="post"> <form action="" method="post">
@ -152,3 +201,16 @@
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %}
{# Support file attachment uploader #}
<script>
$('#attachment_file_input').on('change',function(){
// Get the file name and remove browser-added "fakepath."
// Then replace the "Choose a file" label.
var fileName = $(this).val().replace('C:\\fakepath\\', " ");
$(this).next('.custom-file-label').html(fileName);
})
</script>
{% endblock extra_js %}

View file

@ -1,25 +1,27 @@
import bleach
import datetime import datetime
import bleach
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render, redirect from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import method_decorator
from todo.forms import AddEditTaskForm
from todo.models import Comment, Task
from todo.utils import send_email_to_thread_participants, toggle_task_completed, staff_check, user_can_read_task
from todo.features import HAS_TASK_MERGE from todo.features import HAS_TASK_MERGE
from todo.forms import AddEditTaskForm
from todo.models import Attachment, Comment, Task
from todo.utils import (
send_email_to_thread_participants,
staff_check,
toggle_task_completed,
user_can_read_task,
)
if HAS_TASK_MERGE: if HAS_TASK_MERGE:
from dal import autocomplete from dal import autocomplete
from todo.views.task_autocomplete import TaskAutocomplete
def handle_add_comment(request, task): def handle_add_comment(request, task):
@ -27,9 +29,7 @@ def handle_add_comment(request, task):
return return
Comment.objects.create( Comment.objects.create(
author=request.user, author=request.user, task=task, body=bleach.clean(request.POST["comment-body"], strip=True)
task=task,
body=bleach.clean(request.POST["comment-body"], strip=True),
) )
send_email_to_thread_participants( send_email_to_thread_participants(
@ -39,9 +39,7 @@ def handle_add_comment(request, task):
subject='New comment posted on task "{}"'.format(task.title), subject='New comment posted on task "{}"'.format(task.title),
) )
messages.success( messages.success(request, "Comment posted. Notification email sent to thread participants.")
request, "Comment posted. Notification email sent to thread participants."
)
@login_required @login_required
@ -51,7 +49,7 @@ def task_detail(request, task_id: int) -> HttpResponse:
""" """
task = get_object_or_404(Task, pk=task_id) task = get_object_or_404(Task, pk=task_id)
comment_list = Comment.objects.filter(task=task_id).order_by('-date') comment_list = Comment.objects.filter(task=task_id).order_by("-date")
# Ensure user has permission to view task. Admins can view all tasks. # Ensure user has permission to view task. Admins can view all tasks.
# Get the group this task belongs to, and check whether current user is a member of that group. # Get the group this task belongs to, and check whether current user is a member of that group.
@ -62,6 +60,7 @@ def task_detail(request, task_id: int) -> HttpResponse:
if not HAS_TASK_MERGE: if not HAS_TASK_MERGE:
merge_form = None merge_form = None
else: else:
class MergeForm(forms.Form): class MergeForm(forms.Form):
merge_target = forms.ModelChoiceField( merge_target = forms.ModelChoiceField(
queryset=Task.objects.all(), queryset=Task.objects.all(),
@ -81,25 +80,17 @@ def task_detail(request, task_id: int) -> HttpResponse:
raise PermissionDenied raise PermissionDenied
task.merge_into(merge_target) task.merge_into(merge_target)
return redirect(reverse( return redirect(reverse("todo:task_detail", kwargs={"task_id": merge_target.pk}))
"todo:task_detail",
kwargs={"task_id": merge_target.pk}
))
# Save submitted comments # Save submitted comments
handle_add_comment(request, task) handle_add_comment(request, task)
# Save task edits # Save task edits
if not request.POST.get("add_edit_task"): if not request.POST.get("add_edit_task"):
form = AddEditTaskForm( form = AddEditTaskForm(request.user, instance=task, initial={"task_list": task.task_list})
request.user, instance=task, initial={"task_list": task.task_list}
)
else: else:
form = AddEditTaskForm( form = AddEditTaskForm(
request.user, request.user, request.POST, instance=task, initial={"task_list": task.task_list}
request.POST,
instance=task,
initial={"task_list": task.task_list},
) )
if form.is_valid(): if form.is_valid():
@ -108,9 +99,7 @@ def task_detail(request, task_id: int) -> HttpResponse:
item.save() item.save()
messages.success(request, "The task has been edited.") messages.success(request, "The task has been edited.")
return redirect( return redirect(
"todo:list_detail", "todo:list_detail", list_id=task.task_list.id, list_slug=task.task_list.slug
list_id=task.task_list.id,
list_slug=task.task_list.slug,
) )
# Mark complete # Mark complete
@ -126,13 +115,33 @@ def task_detail(request, task_id: int) -> HttpResponse:
else: else:
thedate = datetime.datetime.now() thedate = datetime.datetime.now()
# Handle uploaded files
if request.FILES.get("attachment_file_input"):
Attachment.objects.create(
task=task,
added_by=request.user,
timestamp=datetime.datetime.now(),
file=request.FILES.get("attachment_file_input"),
)
return redirect("todo:task_detail", task_id=task.id)
# For the context: Settings for file attachments defaults to True
# FIXME: Move settings defaults to a central location?
attachments_enabled = True
if (
hasattr(settings, "TODO_ALLOW_FILE_ATTACHMENTS")
and not settings.TODO_ALLOW_FILE_ATTACHMENTS
):
attachments_enabled = False
context = { context = {
"task": task, "task": task,
"comment_list": comment_list, "comment_list": comment_list,
"form": form, "form": form,
"merge_form": merge_form, "merge_form": merge_form,
"thedate": thedate, "thedate": thedate,
"comment_classes": getattr(settings, 'TODO_COMMENT_CLASSES', []), "comment_classes": getattr(settings, "TODO_COMMENT_CLASSES", []),
"attachments_enabled": attachments_enabled,
} }
return render(request, "todo/task_detail.html", context) return render(request, "todo/task_detail.html", context)