Django File Upload Progress - django

I have used Django File Upload Progress process for getting progress but getting error pliks.upload" does not define a "UploadProgressCachedHandler" upload handler backend.
Steps i followed is
1) Create a folder upload and add file upload handler(UploadProgressCachedHandler.py) to it.
from django.core.files.uploadhandler import FileUploadHandler
from django.core.cache import cache
class UploadProgressCachedHandler(FileUploadHandler):
"""
Tracks progress for file uploads.
The http post request must contain a header or query parameter, 'X-Progress-ID'
which should contain a unique string to identify the upload to be tracked.
Copied from:
http://djangosnippets.org/snippets/678/
See views.py for upload_progress function...
"""
def __init__(self, request=None):
super(UploadProgressCachedHandler, self).__init__(request)
self.progress_id = None
self.cache_key = None
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
self.content_length = content_length
if 'X-Progress-ID' in self.request.GET :
self.progress_id = self.request.GET['X-Progress-ID']
elif 'X-Progress-ID' in self.request.META:
self.progress_id = self.request.META['X-Progress-ID']
if self.progress_id:
self.cache_key = "%s_%s" % (self.request.META['REMOTE_ADDR'], self.progress_id )
cache.set(self.cache_key, {
'length': self.content_length,
'uploaded' : 0
})
def new_file(self, field_name, file_name, content_type, content_length, charset=None):
pass
def receive_data_chunk(self, raw_data, start):
if self.cache_key:
data = cache.get(self.cache_key)
data['uploaded'] += self.chunk_size
cache.set(self.cache_key, data)
return raw_data
def file_complete(self, file_size):
pass
def upload_complete(self):
if self.cache_key:
cache.delete(self.cache_key)
2)Add the UploadProgressCachedHandler to your settings.py file:
from django.conf import global_settings
FILE_UPLOAD_HANDLERS = ('pliks.upload.UploadProgressCachedHandler', ) + \
global_settings.FILE_UPLOAD_HANDLERS
3)Add code to view.py
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseServerError
def upload_progress(request):
"""
A view to report back on upload progress.
Return JSON object with information about the progress of an upload.
Copied from:
http://djangosnippets.org/snippets/678/
See upload.py for file upload handler.
"""
#import ipdb
#ipdb.set_trace()
progress_id = ''
if 'X-Progress-ID' in request.GET:
progress_id = request.GET['X-Progress-ID']
elif 'X-Progress-ID' in request.META:
progress_id = request.META['X-Progress-ID']
if progress_id:
from django.utils import simplejson
cache_key = "%s_%s" % (request.META['REMOTE_ADDR'], progress_id)
data = cache.get(cache_key)
return HttpResponse(simplejson.dumps(data))
else:
return HttpResponseServerError(
'Server Error: You must provide X-Progress-ID header or query param.')
4) Add url
url(r'^admin/upload_progress/$',
'utils.views.upload_progress',
name="admin-upload-progress"),
5) In template
{% block extra-head %}
<script type="text/javascript" >
// Generate 32 char random uuid
function gen_uuid() {
var uuid = ""
for (var i=0; i < 32; i++) {
uuid += Math.floor(Math.random() * 16).toString(16);
}
return uuid
}
// Add upload progress for multipart forms.
$(function() {
/*
This throws a syntax error...
$('form[#enctype=multipart/form-data]').submit(function(){
*/
$('#upload').submit(function(){
// Prevent multiple submits
if ($.data(this, 'submitted')) return false;
var freq = 1000; // freqency of update in ms
var uuid = gen_uuid(); // id for this upload so we can fetch progress info.
var progress_url = '/feature/admin/upload_progress/'; // ajax view serving progress info
// Append X-Progress-ID uuid form action
this.action += (this.action.indexOf('?') == -1 ? '?' : '&') + 'X-Progress-ID=' + uuid;
var $progress = $('<div id="upload-progress" class="upload-progress"></div>').
appendTo(document.body).append('<div class="progress-container"><span class="progress-info">uploading 0%</span><div class="progress-bar"></div></div>');
// progress bar position
$progress.css({
position: ($.browser.msie && $.browser.version < 7 )? 'absolute' : 'fixed',
left: '50%', marginLeft: 0-($progress.width()/2), bottom: '20%'
}).show();
// Update progress bar
function update_progress_info() {
$progress.show();
$.getJSON(progress_url, {'X-Progress-ID': uuid}, function(data, status){
if (data) {
var progress = parseInt(data.uploaded) / parseInt(data.length);
var width = $progress.find('.progress-container').width()
var progress_width = width * progress;
$progress.find('.progress-bar').width(progress_width);
$progress.find('.progress-info').text('uploading ' + parseInt(progress*100) + '%');
}
window.setTimeout(update_progress_info, freq);
});
};
window.setTimeout(update_progress_info, freq);
$.data(this, 'submitted', true); // mark form as submitted.
});
});
</script>
{% endblock %}
{% block content %}
<form id='upload' action="." enctype="multipart/form-data" method="POST">
<table>
{{ form.as_table }}
</table>
<p><input type="submit" value="Submit"></p>
</form>
{% endblock %}
Somewhere i got wrong....

You need to provide the class name of the handler, if your file is called UploadProgressCachedHandler.py you are providing the module name here. you would need to use
FILE_UPLOAD_HANDLERS = ('pliks.upload.UploadProgressCachedHandler.UploadProgressCachedHandler', ) + \
global_settings.FILE_UPLOAD_HANDLERS
Python convention would be to call the module handlers.py rather than name it after a class it contains.

Related

How to show related object popup when the user clicks on selected autocomplete field options in Django admin?

I want to show the standard related object popup when the user clicks on a selected option in a Django admin autocomplete multi-select field, like it works when clicking the ForeignKey field pencil icon 🖉.
The models are as follows:
class Author(models.Model):
name = models.CharField(_('name'), max_length=160)
class Book(models.Model):
authors = models.ManyToManyField(Author, verbose_name=_('authors'), blank=True)
...
Is it possible to do this by extending Django admin?
I found that adding the required custom HTML was easier using the ModelSelect2Multiple widget from django-autocomplete-light (DAL).
The admin configuration is as follows:
from dal import autocomplete
#admin.register(Book)
class BookAdmin(admin.ModelAdmin):
class Media:
js = [
'/static/books/js/book-admin.js',
# other required JS files, see https://github.com/yourlabs/django-autocomplete-light/issues/1143#issuecomment-632755326
]
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'authors':
return forms.ModelMultipleChoiceField(
required=False,
label=Author._meta.verbose_name_plural.title(),
queryset=Author.objects.all(),
widget=autocomplete.ModelSelect2Multiple(
url='author-autocomplete',
attrs={'data-html': True}))
return super().formfield_for_foreignkey(db_field, request, **kwargs)
The DAL view is as follows:
from django.utils.safestring import mark_safe
from dal import autocomplete
from .models import Author
from django.urls import reverse
class AuthorAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_staff:
return Author.objects.none()
qs = Author.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
return qs
def get_selected_result_label(self, item):
change_url = reverse('admin:books_author_change', kwargs={'object_id': item.id})
return mark_safe('<span onclick="event.stopPropagation(); showRelatedObjectPopup({'
f"href: '{change_url}?_popup=1', id: 'change_id_author'"
f'}})">{item.name}</span>')
When selecting a new author in the Authors field in the Book change view in admin, the HTML element is managed by ModelSelect2Multiple, so the custom HTML is present and clicking on the newly selected author opens the popup as intended. But the custom HTML will not be present in existing selections, so the click handlers have to be added with jQuery in book-admin.js:
'use strict';
window.addEventListener("load", function () {
/**
* Show related object popup when user clicks on selected author name.
*/
(function ($) {
var $authorsSelect2Selections = $('div.form-row.field-authors .select2-selection__choice > span:nth-child(2)');
var $authorOptions = $('#id_authors > option');
$authorsSelect2Selections.click(function ($event) {
$event.stopPropagation();
var self = this;
// Find corresponding option by text comparison, assuming that author name is unique
var $result = $authorOptions.filter(function() {
return $(this).text() === self.textContent;
});
showRelatedObjectPopup({
href: '/admin/books/author/' + $result.val() + '/change/?_popup=1',
id: 'change_id_other_authors'
});
});
})(django.jQuery);
});
event.stopPropagation() prevents the Select2 dropdown from opening.
You also need to override dismissChangeRelatedObjectPopup and dismissAddRelatedObjectPopup in book-admin.js to avoid problems, here is an incomplete version:
/**
* Override Django related object popup dismissal functions with DAL amendments.
* Incomplete.
*/
(function ($) {
function dismissChangeRelatedObjectPopupForDAL(win, objId, newRepr, newId) {
var elem = document.getElementById(win.name);
if (elem && elem.options && elem.dataset.autocompleteLightUrl) { // this is a DAL element
$(elem.options).each(function () {
if (this.value === objId) {
this.textContent = newRepr;
// this.value = newId;
}
});
// FIXME: trigger('change') does not update the element as it should and removes popup code
// $(elem).trigger('change');
win.close();
} else {
dismissChangeRelatedObjectPopupOriginal(win, objId, newRepr, newId);
}
}
window.dismissChangeRelatedObjectPopupOriginal = window.dismissChangeRelatedObjectPopup;
window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopupForDAL;
function dismissAddRelatedObjectPopupForDAL(win, newId, newRepr) {
var elem = document.getElementById(win.name);
if (elem && elem.options && elem.dataset.autocompleteLightUrl) { // this is a DAL element
elem.options[elem.options.length] = new Option(newRepr, newId, true, true);
// FIXME: trigger('change') adds the new element, but removes popup code
$(elem).trigger('change');
win.close();
} else {
dismissAddRelatedObjectPopupOriginal(win, newId, newRepr);
}
}
window.dismissAddRelatedObjectPopupOriginal = window.dismissAddRelatedObjectPopup
window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopupForDAL
})(django.jQuery);

Pass javascript variable to views.py

I really need some help on this subject, which seems easy, but i cant get myself to figure out the problem.
I understand that using ajax is the best way to pass a variable from a template to a view.
Is what i have done good, or would you recommend i try something different ?
function updateArea(e) {
var data = draw.getAll();
var answer = document.getElementById('calculated-area');
if (data.features.length > 0) {
var area = turf.area(data);
// restrict to area to 2 decimal points
var rounded_area = Math.round(area*100)/100;
answer.innerHTML = '<p><strong>' + rounded_area + '</strong></p><p>square meters</p>';
var convertedData = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(data));
document.getElementById('export').setAttribute('href', 'data:' + convertedData);
document.getElementById('export').setAttribute('download','data.geojson');
} else {
answer.innerHTML = '';
if (e.type !== 'draw.delete') alert("Use the draw tools to draw a polygon!");
}
}
I want to take the area variable and convertedData variable and pass them to a view.
So after some searching and trial and error, here is what I tried to do using Ajax.
When I run this i get no errors, but i get nothing in my database.
template.html
<form id="areaform" method="POST">
{% csrf_token %}
<input type="submit" name="area" value=""/>
</form>
$(document).on('submit', '#areaform',function(e) {
e.preventDefault();
$.ajax({
type: 'POST',
url:'fertisatmap/area',
data:{
name: $('#area').val(),
},
sucess:function(){}
});
});
urls.py
path('fertisatmap/area', fertisat_views.fertisatareaview, name='fertisat-area'),
views.py
def fertisatareaview(request):
if request.method == "POST":
area = request.POST['area']
Fertidb.objects.create(
area = area)
return HttpResponse('')

How to Post csv file in simple Html into Django api?

I tried post FormData using "angular Js" and then using Directly post html to django api But It Will be failure , Anyone Tried it and Known It Please Tell me your answer it is the full code i tried out :
html:
<form action="http://localhost:55555/collaboratoradmin/test/upload_driver" method="post" enctype="multipart/form-data">
<input type="file" id="file" name="csv_file" accept=".csv"
onchange="angular.element(this).scope().getFileDetails(this)" />
<button type="submit" ng-click="uploadFiles()">upload</button>
Angular Js:
$scope.getFileDetails = function (e) {
$scope.files = [];
$scope.$apply(function () {
var file = e.files[0];
filename = '';
if ('name' in file)
filename = file.name;
else
filename = file.fileName;
});
};
$scope.uploadFiles = function () {
//FILL FormData WITH FILE DETAILS.
// ADD LISTENERS.
var objXhr = new XMLHttpRequest();
objXhr.addEventListener("progress", updateProgress, false);
objXhr.addEventListener("load", transferComplete, false);
// SEND FILE DETAILS TO THE API.
objXhr.open('POST', endpoint + '/collaboratoradmin/test/upload_driver', true);
var fd = new FormData();
fd.append("filename", filename);
fd.append("csv_file", file);
objXhr.send(fd);
}
// UPDATE PROGRESS BAR.
function updateProgress(e) {
if (e.lengthComputable) {
document.getElementById('pro').setAttribute('value', e.loaded);
document.getElementById('pro').setAttribute('max', e.total);
}
}
// CONFIRMATION.
function transferComplete(e) {
alert("Files uploaded successfully.");
}
Django api:
#api_view(['POST'])
def upload_driver(request):
if request.POST and request.FILES:
csvfile = request.FILES['csv_file']
dialect = csv.Sniffer().sniff(codecs.EncodedFile(csvfile, "utf-8").read(1024))
csvfile.open()
reader = csv.reader(codecs.EncodedFile(csvfile, "utf-8"), delimiter=',', dialect=dialect)
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('myproject.myapp.views.list'))
print(list1)

View didn't return a response

I am working on a python django web app in which I want to implement internationalization and auto translate the whole app into french or chinese.
I took reference from this site https://www.metod.io/en/blog/2015/05/05/django-i18n-part-1/
But whenever I try to run the app it shows this error:
500: ValueError at /en/get_dashboard_data/ The view
dashboard.views.getDashboardData didn't return an HttpResponse object.
It returned None instead.
And url get_dashboard_data is fetching data through ajax.
url(r'^get_dashboard_data/$', views.getDashboardData, name='getDashboardData'),
view
#login_required(login_url='/')
def getDashboardData(request):
dbname = request.user.username
if request.method == 'POST' and request.is_ajax():
if request.POST.get('action') == 'sale_chart_data':
data = DashboardData(dbname).getSaleChartData()
channel_list = data[0]
data_list = data[1]
print 123, data_list, channel_list
return HttpResponse(json.dumps({'channel_list':channel_list, 'data_list':data_list}), content_type='application/json')
if request.POST.get('action') == 'get_sale_numbers':
sale_data = DashboardData(dbname).getSaleNumbers()
return HttpResponse(json.dumps({'sale_number_data':sale_data}), content_type='application/json')
if request.POST.get('action') == 'get_inventory_numbers':
inventory_data = DashboardData(dbname).getInventoryData()
return HttpResponse(json.dumps({'inventory_data':inventory_data}), content_type='application/json')
if request.POST.get('action') == 'get_order_numbers':
order_data = DashboardData(dbname).getOrderData()
return HttpResponse(json.dumps({'order_data':order_data}), content_type='application/json')
if request.POST.get('action') == 'get_hourly_data':
order_data = DashboardData(dbname).getHourlyData()
sale_data = order_data[1]
count_data = order_data[0]
return HttpResponse(json.dumps({'sale_data':sale_data, 'count_data':count_data}), content_type='application/json')
if request.POST.get('action') == 'top_performers':
data = DashboardData(dbname).getTopPerformers()
inventory_count_dict = data[0]
current_month_dict = data[1]
last_month_dict = data[2]
current_quarter_dict = data[3]
current_year_dict = data[4]
channel_list = data[5]
return HttpResponse(json.dumps({'inventory_count_dict':inventory_count_dict,'current_month_dict':current_month_dict,'last_month_dict':last_month_dict,'current_quarter_dict':current_quarter_dict,'current_year_dict':current_year_dict,'channel_list':channel_list}), content_type='application/json')
if request.POST.get('action') == 'top_products':
product_data = DashboardData(dbname).getTopProducts()
return HttpResponse(json.dumps({'product_data':product_data}), content_type='application/json')
javascript
function getSaleChart(){
$.ajax({
url : "/get_dashboard_data/",
type : "POST",
data : {action:'sale_chart_data'},
success : function(response) {
channel_list = response.channel_list;
data_list = response.data_list;
c3.generate({
bindto: '#sale-chart-30-days',
data:{
x: 'dates',
xFormat: '%b %d',
columns: data_list,
colors:{
Flipkart: '#1AB394',
Paytm: '#BABABA'
},
type: 'bar',
groups: [ channel_list ]
},
axis: {
x: {
type: 'timeseries'
}
}
});
},
error : function(xhr,errmsg,err) {
toastr["error"]("Something Broke.", "Oops !!!.");
console.log(xhr.status + ": " + xhr.responseText);
}
});
}
This is why you should really practice more defensive programming. Though you insist that the request method is POST and it is ajax and the action is sale_chart_data one of the three isn't what you expect it to be.
Your function really should be like follows. It's plain old good practice.
def getDashboardData(request):
dbname = request.user.username
if request.method == 'POST' and request.is_ajax():
action = request.POST.get('action')
if action == 'sale_chart_data':
data = DashboardData(dbname).getSaleChartData()
....
...
# other if conditions here
else :
return HttpResponse(json.dumps({'message':'Unknown action {0}'.format(action)}), content_type='application/json')
else :
return HttpResponse(json.dumps({'message':'Only ajax post supported'}), content_type='application/json')
And then you ought to set break points and evaluate the request to figure out what exactly is happening in this particular request.
My guess would be that your JavaScript does indeed make a POST request to /get_dashboard_data/ but it receives a redirect response (HTTP 301 or 302) to /en/get_dashboard_data/ due to some kind of i18n middleware.
The browser follows the redirect, but the new request to /en/get_dashboard_data/ is a GET request.
Edit:
When following a redirect, the browser will always perform the second request as GET, there is no way to prevent that. You have several options to solve this:
make the initial request to the right application. This means you have to pass your i18n URL into your JavaScript instead of hardcoding it. You can add something like this to your template:
<script>var dashBoardDataUrl = "{% url "name-of-dashboard-url" %}"</script>
as your "actions" just get code, you could just accept a GET request and read the action from query paramters
Split that view into several smaller views that accept GET request so you have something that resembles a REST API.

getting a user-inputed variable in django template wihtout using forms

I have a django template which also has a div element that takes in a user_inputed value.
When the value is entered, I call a javascript function say onSubmit(user_input)
<input type="text" class= "inputtext" onKeyPress="return onSubmit(this.value)">
Now in this onSubmit() function which now has the user-inputted value user_input, I want to be able to use url patterns to a direct to a view, like
function onSubmit(user_input) {window.location = "{% url myview user_input %}";}
The problem here is that since user_input is empty when the template is loaded, the url-view reverse lookup gives an error. Is there a way to trigger this lookup only when the onSubmit function is called.
I know form is an alternative, but it just feels like it'll be an overkill for this situation.
You can get the URL via AJAX:
views.py:
def get_url(request):
name = request.GET.get('name')
args = reguest.GET.get('args', [])
kwargs = request.GET.get('kwargs', {})
try:
url = django.core.urlresolvers.reverse(name, args=args, kwargs=kwargs)
except NoReverseMatch:
url = None
return django.http.HttpResponse(url)
urls.py
#...
('^url$', get_url)
#...
js:
function onSubmit(user_input) {
var args = [user_input];
jQuery.get('/url', {'args': args}, function(data) {
var url = data;
if (url) {
window.location = url;
} else {
alert('fail');
}
});
}
Alternatively, if your URL rule is simple enough, you can use some placeholder when resolving and URL, and before submitting the form you should replace it with real input:
var fakeUrl = '{% url myview "%s%" %}';
function onSubmit(user_input) {
window.location = fakeUrl.replace('%s%', user_input);
}