Working file upload dialog and receive in view
This commit is contained in:
parent
9a5c794c41
commit
b6c2227417
3 changed files with 111 additions and 31 deletions
|
@ -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}"
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue