How to upload multiple files in Django using Dropzone js? - django

Info: I want to upload multiple files using Dropzone js in Django project. I have two models. One for the Post and the other would be for the File. My files model would have a foreignkey to the Post model. About my Views.py when the user is filling out the form post he has to complete the Files form too for the post.
Fine: When i submit the Ajax_file_uploads form instead of using Dropzone.js multiple selected files are attached with single Post instance.
Problem: If i try to upload multiple files using Dropzone.js Multiple articles are created along with multiple files when I submit the form.
Any help would be much appreciated!
models.py
class Post(models.Model):
title = models.CharField(max_length=100, blank=True)
content = models.TextField(blank=True)
class FileAttach(models.Model):
file = models.FileField(upload_to='uploads/')
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='file_attach', null=True)
views.py
def Ajax_file_uploads(request):
if request.method == "POST":
p_form = PostForm(request.POST or None)
form = AttachForm(request.POST or None, request.FILES)
if p_form.is_valid():
art = p_form.save(commit=False)
art.user = request.user
art.save()
files = request.FILES.getlist('file')
if form.is_valid():
for f in files:
file_instance = FileAttach(file=f, post=art)
file_instance.save()
return JsonResponse({"error": False, "message": "Uploaded Successfully"})
else:
return JsonResponse({"error": True, "errors": p_form.errors})
else:
p_form = PostForm()
form = AttachForm()
return render(request, "upload/api.html", {"p_form": p_form, "form": form})
create.html
<script>
Dropzone.autoDiscover = false;
// Dropzone.options.myDropzone = false;
// set the dropzone container id
var id = "#kt_dropzonejs_example_2";
// set the preview element template
var previewNode = $(id + " .dropzone-item");
previewNode.id = "";
var previewTemplate = previewNode.parent(".dropzone-items").html();
previewNode.remove();
var myDropzone = new Dropzone(id, { // Make the whole body a dropzone
// url: "/uploader/files/", // Set the url for your upload script location
url: '/uploader/multi/',
headers: { 'X-CSRFToken': '{{ csrf_token }}' },
timeout: 2000000,
parallelUploads: 100,
previewTemplate: previewTemplate,
// uploadMultiple: true,
maxFilesize: 200, // Max filesize in MB
maxFiles: 5, // Max filesize in MB
autoQueue: false, // Make sure the files aren't queued until manually added
previewsContainer: id + " .dropzone-items", // Define the container to display the previews
clickable: id + " .dropzone-select", // Define the element that should be used as click trigger to select files.
uploadprogress: function (file, progress, bytesSent) {
if (file.previewElement) {
var progressElement = file.previewElement.querySelector("[data-dz-uploadprogress]");
progressElement.style.width = progress + "%";
progressElement.querySelector(".dropzone-progress-total").textContent = progress.toFixed(0) + "%";
}
},
sending: function (file, xhr, formData) {
// Show the total progress bar when upload starts
$(id + " .progress-bar").css("opacity", "1");
// Add other form data
var data = $('#form-data').serializeArray();
$.each(data, function (key, el) {
formData.append(el.name, el.value);
});
},
// success: function (file) {
// file.previewElement.remove()
// }
init: function () {
$("#form-data").validate({
submitHandler: function (form) {
myDropzone.enqueueFiles(myDropzone.getFilesWithStatus(Dropzone.ADDED));
}
});
},
});
myDropzone.on("addedfile", function (file) {
// Hookup the start button
$(document).find(id + " .dropzone-item").css("display", "");
$(id + " .dropzone-remove-all").css("display", "inline-block");
});
// Hide the total progress bar when nothing's uploading anymore
myDropzone.on("complete", function (progress) {
var thisProgressBar = id + " .dz-complete";
setTimeout(function () {
$(thisProgressBar + " .progress-bar, " + thisProgressBar + " .progress").css("opacity", "0");
}, 300)
});
// Setup the button for remove all files
document.querySelector(id + " .dropzone-remove-all").onclick = function () {
$(id + " .dropzone-remove-all").css("display", "none");
myDropzone.removeAllFiles(true);
};
// On all files removed
myDropzone.on("removedfile", function (file) {
if (myDropzone.files.length < 1) {
$(id + " .dropzone-remove-all").css("display", "none");
}
});
</script>

Dropzone submits each file separately via AJAX calls and when it does that it submits the file and all form inputs with each AJAX call. Based on the way your views.py file is written, this will cause a Post instance to be created and then a FileAttach instance to be created and associated with the Post instance.
I see three ways you could fix this:
Modify your views.py file to check for an existing Post instance before creating a new one. Because each file is uploaded asynchronously there is still a chance that the first Post instance would not be created before the second file upload looks for it and thus two Post instances would still be created.
Add some additional JavaScript to first make an AJAX call that creates the Post instance and returns its id value then allow Dropzone to make its AJAX calls that include the Post.id value. This answer outlines the opposite approach (upload files, then submit form); the concept just needs to be reversed.
Set the uploadMultiple option to true on the Dropzone object so that all the files (and the form) are submitted in one submission to the backend. I have not worked with this so not sure how you would handle that in views.py. My guess is that request.FILES would contain multiple file entries, but again I am not sure how your AttachForm would deal with that.
If it were me, I would go with option 2.

Related

How to access "context" from http request between two django apps

So i'm fairly new to django, html, and javascript.
In my first app, I have a button that when I click it, it invokes an $.ajax type "GET" function. In the script of the html template of app1, I have the following:
$('#btn_to_app2').click(function () {
if(Object.keys(name).length > 0){
$.ajax(
{
type:"GET",
url:"to_app2",
data:{
'name': JSON.stringify(name),
'place': JSON.stringify(place),
},
success: function (data) {
if(data["noted"] == 1){
window.location.href = "http://" + window.location.host + "/app2/";
}
}
}
)
}
The accessed url refers to a view function (defined in the url.py and views.py files of the corresponding app1).
def to_app2(request):
if request.is_ajax() and request.method == 'GET':
name = json.loads(request.GET['name'])
request.session['name'] = name
place = json.loads(request.GET['place'])
request.session['place'] = place
return JsonResponse({
'noted': 1,
})
The data obtained is send to the next app using a http request. In the views.py file of app2, I have the following bit of code:
def app2(request):
context = {
'name': request.session['name'],
'place': request.session['place']
}
return render(request, 'app2.html', context)
How can I now access the information contained in the "context" variable within the script of the html-file of app2?
I have tried the let name = {{ name | safe }}; framework, with quotes and without, but that doesn't seem to work.

Save Django Model when list_editable fields are changed in Django Admin change_list view

I have a Django Model which I'm trying to update from the Admin change_list view when a field that is listed in list_editable has a change event fired. Right now the only way to save the updates is by clicking a "Save" button and then the page reloads. I don't want the page to reload at all. I just want it to update the model asynchronously.
For example I have the following model.
class Example(models.Model):
name = models.CharField()
hide = models.BooleanField(default=False)
In the Admin Form I have
class ExampleAdmin(admin.ModelAdmin):
list_display = [
"name",
"hide"
]
list_editable = [
"name",
"hide"
]
When viewing the change list for Examples now I will see the values listed out with an editable text input for name and a checkbox for hide.
I would like to listen for events on those inputs and when they are fired send an async request to update the specific model.
UPDATE
This is what I have right now.
window.addEventListener("load", function() {
(function($) {
$(".field-name .vTextField").on("change",function (event) {
$('#changelist-form').submit()
})
$('#changelist-form').submit(function(event) { // On form submit event
$.ajax({ // create an AJAX call...
data: $(this).serialize(), // get the form data
type: "POST", // GET or POST
url: $(this).attr('action'), // the file to call
success: function(response) { // on success..
console.log(response)
},
error: function(e, x, r) { // on error..
console.log(e)
}
});
event.preventDefault()
return false;
});
})(django.jQuery);
})
I can see this firing in the network requests and can verify that the serialized data being sent is updated. The response is a 200, but when I refresh the page the data has not been updated.
UPDATE
At this point I'm giving up on sending this data to Django and will be sending the serialized data to a custom endpoint and parse through and save the data in a custom save function.
Within your Admin Model add the following
class ExampleAdmin(admin.ModelAdmin):
class Media:
js = ("js/admin/example.js")
list_display = [
"name",
"hide"
]
list_editable = [
"name",
"hide"
]
example.js
window.addEventListener("load", function() {
(function($) {
$(".field-name .vTextField").on("change",function (event) {
submit()
})
function getFormData($form){
var unindexed_array = $form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i){
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
function submit(){
$.ajax({
data: getFormData($('#changelist-form')),
type: "POST",
url: '/api/save-example',
success: function(response) {
console.log(response)
},
error: function(e, x, r) {
console.log(e)
}
});
}
})(django.jQuery);
})
I'm using Django Rest Framework so I created a POST endpoint, but you could also do this in a Django View that is listening for a POST.
When you execute the post Django will send the data categorized by the row number and field name, like so.
form-0-id : 1
form-0-name: example-name
form-0-hide: false
form-0-example_parent: 100
form-1-id : 2
form-1-name: example-name-two
form-1-hide: true
form-1-example_parent: 200
#api_view(["POST"])
def save_example(request):
try:
total_rows = int(request.POST.get("form-TOTAL_FORMS"))
prefix = "form-"
form_fields = {
"id": "id",
"name": "name",
"hide": "hide",
"example_parent": "example_parent_id",
}
for x in range(total_rows):
item = {}
for key, val in form_fields.items():
post_field = prefix + str(x) + "-" + key
field = val
if key == "id":
id = request.POST.get(post_field)
else:
item[field] = request.POST.get(post_field)
Example.objects.filter(id=id).update(**item)
return Response(status=status.HTTP_200_OK)
except Exception as e:
return Response(status=status.HTTP_404_NOT_FOUND)
Note
total_rows = int(request.POST.get("form-TOTAL_FORMS")) which lets you know how to loop through all the form data that is passed back.
This doesn't take different values into consideration. (i.e. Checkboxes will return an on value but no value if not checked.
You will need to update your save_example function accordingly.

Tabulator PUT via Ajax to Django REST Endpoint - Reduces Table to Last Edited Record

I am using Tabulator with Django to edit a model. After any change to a cell, I use setData to make an Ajax call to a REST endpoint created using Django REST Framework. The database updates ok. The problem is that the response from the server contains only the single record that was updated, and this is making the Tabulator data reduce to only that record.
My question is, how can I get Tabulator to disregard the response, or otherwise have the data be left alone following the edit?
I am pretty new at this stuff (both Django and especially JavaScript) so apologies if I've missed something basic.
My tabulator code is below.
The function getCookie is to generate a CSRF_TOKEN as per the instructions in the Django documentation here. This is then included in the header as 'X-CSRFTOKEN': CSRF_TOKEN.
The variable ajaxConfigPut is used to set the method to PUT and to include the CSRF_TOKEN as noted above. This is then used in the table.setData call later on (table.setData(updateurl, updateData, ajaxConfigPut);).
The function ajaxResponse at the end just checks if the response is an array or not (because Tabulator expects an array which is fine for GET, but the PUT response was only a single {} object. So this function forces the PUT response into an array consisting of one object [{}].
<div id="example-table"></div>
<script type="text/javascript">
// get CSRF token
// https://docs.djangoproject.com/en/dev/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-and-csrf-cookie-httponly-are-false
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var CSRF_TOKEN = getCookie('csrftoken');
// set variable to customise ajaxConfig for use in the setData call
var ajaxConfigPut = {
method:"PUT", //set request type to Position
headers: {
// "Content-type": 'application/json; charset=utf-8', //set specific content type
'X-CSRFTOKEN': CSRF_TOKEN,
},
};
//create Tabulator on DOM element with id "example-table"
var table = new Tabulator("#example-table", {
ajaxURL:"{% url 'cust_listapi' %}", // reverse pick up the url since in a django template (?)
height:205, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
layout:"fitColumns", //fit columns to width of table (optional)
columns:[ //Define Table Columns
{title:"Name", field:"name", width:150, editor:true},
{title:"Age", field:"age", hozAlign:"center",editor:true},
{title:"Age_Bar", field:"age", hozAlign:"left", formatter:"progress"},
{title:"Customer Status", field:"is_customer", hozAlign:"left"},
// {title:"Favourite Color", field:"col"},
// {title:"Date Of Birth", field:"dob", sorter:"date", hozAlign:"center"},
],
// see http://tabulator.info/docs/4.6/components#component-cell
cellEdited:function(cell){ //trigger an alert message when the row is clicked
console.log("Cell edited in row " + cell.getData().id
+ " and column " + cell.getField()
+ " from " + cell.getOldValue() + " to "
+ cell.getValue()
+ ". The row pk=" + cell.getData().id
);
console.log(cell.getData());
var updateurl = "{% url 'cust_listapi' %}" + cell.getData().id + "/"
console.log('URL is: ' + updateurl)
// Create variable from full row data but drop the id;
console.log('About to create updateData')
var updateData = {};
updateData[cell.getField()] = cell.getValue();
console.log(updateData);
console.log('About to setData');
table.setData(updateurl, updateData, ajaxConfigPut);
console.log('Finished setData');
//cell.restoreOldValue();
},
ajaxResponse:function(url, params, response){
console.log('Beginning ajaxResponse')
console.log('The type is:', typeof(response));
console.log(Array.isArray(response))
console.log(response)
result = response;
if(Array.isArray(response) === false){
result = [response];
};
return result;
}
});
</script>
Here's a screenshot of the table before editing:
Table Before Editing
And here's a screenshot after editing the top row (changing 'Mabel' to 'Jemima'):
Screenshot after editing
And here's the console log:
Console Log
I tried amending the response from the endpoint so that all records from the database are returned, but the problem with that is it doesn't include the edit, so the Tabulator table data is overwritten. Here's the code I used in the Django views.py. Maybe there's a way to return the data that has been changed?
views.py
from rest_framework import generics, mixins
from apps.app_mymodel.models import Customer
from .serializers import CustomerSerializer
class CustomerListAPIView(generics.ListAPIView):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
class CustomerUpdateAPIView(generics.GenericAPIView,
mixins.ListModelMixin,
mixins.UpdateModelMixin):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
# Override the put function here to return all records
def put(self, request, *args, **kwargs):
# return self.update(request, *args, **kwargs)
return self.list(request, *args, **kwargs)
Here's the serializer:
serializers.py
from rest_framework import serializers
from apps.app_mymodel.models import Customer
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = '__all__'
Can someone please point me in the right direction?
None of the Mixins used by your CustomerUpdateAPIView have a method called put. I don't think that function is called. Instead, you can try to override the update method of your viewset. It could look like this:
def update(self, request, *args, **kwargs):
obj = super().update(request, *args, **kwargs) # performs the update operation
return self.list(request, *args, **kwargs)
You can check out this URL to understand the classes you are using: http://www.cdrf.co/
There you will see all the methods of the classes you are using to better understand the flow of your request.

Updating only part of an HTML page using Django

I have an HTML (annotator.html) page where the top half contains a table. I have JavaScript code that pulls data out of the data model when the user selects a row in the table and displays that data in a div below the table.
My Javascript looks like this:
$('#radiological tbody').on( 'click', 'tr', function () {
if ( $(this).hasClass('selected') ) {
$(this).removeClass('selected');
}
else {
//first unselect any other selected row
dt_table.$('tr.selected').removeClass('selected');
//then select row of interest
$(this).addClass('selected');
var rowIndex = dt_table.row('.selected')[0];
var row = dt_table.rows(rowIndex);
var id = row.data()[0]['id'];
var url = '/report/' + id + '/';
//window.location = url;
$.ajax({
url: url,
type: 'get', // This is the default though, you don't actually need to always mention it
success: function(data) {
},
failure: function(data) {
alert('Got an error dude');
}
});
And correctly populates the bottom half of my page but also updates the top half. I Just want the bottom half refreshed.
My report view looks like this:
def report_page(request, id):
template_name = "annotator.html"
data = Radiology2.objects.get(id=id)
context = {"data": data}
context["title"] = title
context["version"] = version
context["id"] = id
if (request.is_ajax()):
# return render(request, template_name, context)
return JsonResponse(context)
else:
return render(request, template_name, context)
I added support for AJAX. I'm just not sure how to return from view properly when AJAX is used. Obviously, I'm not understanding something.
Instead of Ajax, I replaced window.location=url; with $('#divIdToRefresh').load(url + ' #divIdToRefresh');
You can easily use Ajax - jQuery or native JS would be better for that

Unable to get POST or GET form data while using AJAX

Have been trying to filter data using django-filters. The code is working when I send a separate POST or GET request from the template. I want to avoid that extra reload that's taking place to filter the table of information.
Here's the view:
def search(request):
dynamic_filter = [f.name for f in Controlpanel._meta.get_fields()]
class UserFilter(django_filters.FilterSet):
class Meta:
model = Controlpanel
fields = dynamic_filter
user_list = Controlpanel.objects.all()
user_filter = UserFilter(request.GET.get("filters[]"),
queryset=user_list)
chart = list(user_filter.qs.values())
return JsonResponse(chart, safe=False)
Here's the AJAX code that calls this above view:
$('#filter-data').on('submit', function (event) {
event.preventDefault();
var dynamic = $('#filter-data').serialize();
console.log($('#filter-data').serializeArray())
$.ajax({
url: '/search/',
type: 'GET',
data: {
filters : dynamic
},
dataType: 'json',
success : function(json) {
console.log(json); // log the returned json to the console
console.log("success"); // another sanity check
},
// handle a non-successful response
error : function(xhr,errmsg,err) {
console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console
}
});
The request.GET(or POST) currently stays empty even if I add a CSRF token and make it a POST request.
I came across some question on SO stating that use of request.body solves the issue but even that was a fail.
The issue was that the POST request was being passed as a string.
This solved the issue:
user_filters = request.POST.get('filters', '')
user_filters = user_filters.split("&")
user_filters = {item.split("=")[0]: item.split("=")[1].replace("%20", " ")
for item in user_filters}
user_filters.pop('csrfmiddlewaretoken')