Django - how to correctly upload and handle a file? - django

I have the following model of an apk, the package_name and sdk_version will be taken by parsing the apk file which the user will upload. I also need to save the path of the uploaded file in my model, that's why I used FilePathField, however I'm not sure it's the correct way to handle the task. I saw some examples where FileField was used, and it got me confused, when do I use which?
Another point to make, since a path is just a string, I can save it as Charfield, can't I?
class Apk(models.Model):
package_name = models.CharField(max_length=45, unique=True)
sdk_version = models.CharField(max_length=45, unique=True)
apk_file = models.FilePathField()
To upload the file I used this guide.
views.py:
def upload_apk(request):
handle_uploaded_file(request.FILES['file'], str(request.FILES['file']))
return HttpResponse("Upload Successful")
def handle_uploaded_file(file, filename):
if not os.path.exists('policies/upload/'):
os.mkdir('policies/upload/')
with open('policies/upload/' + filename, 'wb+') as destination:
for chunk in file.chunks():
destination.write(chunk)
apk_path = "/policies/upload/" + filename
apkf = APK(apk_path)
package_name = apkf.get_package()
sdk_version = apkf.get_androidversion_name()
template.html:
<form id="uploadApkForm" action="{{ request.build_absolute_uri }}uploadApk/" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="input-element" style="border:1px solid black; background:white; padding:2px">
<input type="file" name="file" style="width:100%" required>
</div>
<div style="width:100%;">
<div style="position: absolute;
left: 50%;
bottom: 0px;
transform: translate(-50%, -50%);
margin: 0 auto;">
<input id="uploadBtn" type="submit" value="Ok" class="btn btn-primary" style="width:75px; margin-right:10px" />
<input id="clsBtn" type="button" class="btn btn-primary" value="Cancel" style="width:75px; "/>
</div>
</div>
</form>
I saw different examples where ModelForm was used, and I'm not sure if my way to upload the file is good.
Can you please point out what is the best way to upload a file and save it's path in data base?

In my opinion, it is probably easiest to use a FileField. Using a filefield, it will actually save the file at a specific location, as well as allow you to use the file as an object rather just a simple path. With the filefield, it will also give you the ability to access the path.

Related

Send JSON from Places Autocomplete to Flask

I'm working on my very first web app utilizing the Google Places Autocomplete functionality in the frontend and Flask in the backend.
Current situation:
Whenever an address is selected from the autocomplete suggestions, a variable called 'address' is populated in the background containing the API response as JSON. Using a window alert I can confirm that this part works fine.
To-Do/ issue:
The address variable should be sent over to Flask so that I can do use it going forward.
Using AJAX to post the data however it never seems to reach Flask. The output is always None.
My best guess is that the submit button implemented after the Autocomplete stuff somehow overrides the JSON POST data in order to keep only the actual text which is in the form while submitting*.
Does that make sense? If yes, how can I still send the JSON data successfully? Or is the issue somewhere else?
I would appreciate any help.
Here is my code:
home.html
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
{% from "_formhelpers.html" import render_field %}
<div class="container">
<form class="form form-horizontal" action="" method="post" role="form" novalidate>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=key&libraries=places&language=en"></script>
<script type="text/javascript">
google.maps.event.addDomListener(window, 'load', function () {
var autocomplete = new google.maps.places.Autocomplete(document.getElementById('autocomplete'),{
types: ['geocode']
});
// autocomplete.setFields('address_components');
google.maps.event.addListener(autocomplete, 'place_changed', function () {
var place = autocomplete.getPlace();
var address = place.address_components;
window.alert(JSON.stringify(address));
}
)})
$.ajax({
type: "POST",
url: "/",
data: address,
success: function(){},
dataType: "json",
contentType : "application/json"
});
</script>
<input type="text" id="autocomplete" size=50 style="width: 250px" placeholder="Enter your location" name=inputkiez>
<a href=# id=autocomplete><button class='btn btn-default'>Submit</button></a>
</form>
<div class="row">
or check out <a href='/result'> the latest reviews from others </a>
<div>
</div>
{% endblock %}
routes.py
#app.route('/', methods=['GET','POST'])
def search():
if request.method == 'POST':
jsdata = request.get_json()
flash('Data is: {}'.format(jsdata))
return redirect('/review')
return render_template('home.html')
#app.route('/review', methods=['GET', 'POST'])
def review():
reviewform = ReviewForm()
if reviewform.validate_on_submit():
userreview = Reviews(
reviewcriteria1= reviewform.reviewcriteria1.data,
reviewcriteria2= reviewform.reviewcriteria2.data,
reviewcriteria3= reviewform.reviewcriteria3.data,
)
db.session.add(userreview)
db.session.commit()
return redirect('/result')
return render_template('review.html', form=reviewform)
*The text in the form would include the address selected from Autocomplete but without any additional data obviously. I even managed to pass this text to the next page with request.form.to_dict() but this is not good enough for my use case since I also want at least the postal code to be sent over.
This is not the exact answer to my question but I found a way to send over the data to flask without having to bring in JSON/AJAX at all.
The trick is to send the data from the Autoplaces response as a hidden input of the form:
<form method="post" action="">
<input id="userinput" placeholder="Enter a location" type="text" name="name" class="form-control"><br>
<div id="map" style="height: 300px;width: 300px; float: none; margin: 0 auto;"></div><br>
<input type="hidden" name="address" id="address">
<input type="submit" name="submit" value="Submit" class="form-control btn btn-primary">
<div>or check out <a href='/result'> the latest reviews from others </a></div>
</form>
Then in routes.py you can easily get the data like this:
#app.route('/', methods=['GET','POST'])
def search():
if request.method == 'POST':
address = request.form['address']
# do something
This is basically a slightly modified version of the solution posted here (YT video).

Django: how to retrieve a form search parameters in a django generic listView

how to retrieve a form search parameters in a django generic listView. My url is:
url(r'postsearch$', views.PostsList.as_view(), name='postsearch'),
My generic listview is:
class PostsList(generic.ListView):
model = Post
template_name = 'posts/post_list.html'
def get_queryset(self):
localisation = #how to get location
discipline = #how to get discipline
return Post.objects.filter(.......)
and my form is:
<form class="form-inline text-center" action="{% url 'posts:postsearch' %}" id="form-searchLessons" method="get">
<div class="form-group">
<input type="text" class="form-control" id="typeCours" list="matieres" placeholder="Matieres: e.g. Math, Physique,.." name="discipline">
<datalist id="matieres">
<option value="value1">
<option value="value2">
</datalist>
</div>
<div class="form-group">
<input type="text" class="form-control" id="Localisation" placeholder="Lieu: Bousaada, Douaouda,.."
name="localisation" onFocus="geolocate()">
</div>
<button type="submit" class="btn btn-default" id="btn-getLessons">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span> Trouver !
</button>
</form>
I want to get the Posts by applying a filter according to the lacalisation and matieres introduced in the search fields (in the form)
You can add the search terms to your url regular expression.
url(r'postsearch/(?P<localisation>\w+)/(?P<descipline>\w+)/$', views.PostsList.as_view(), name='postsearch'),
(Note, mind the trailing slash)
In your get_queryset method you can use those given url parameters
def get_queryset(self):
localisation = self.kwargs['localisation'] or None
discipline = self.kwargs['discipline'] or None
filters = {}
if localisation:
filters.update(localisation: localisation)
if discipline:
filters.update(discipline: discipline)
return Post.objects.filter(**filters)
Eventually you should relocate getting the parameters outside your get_queryset, but that is up to you.
I'm not sure about the security risks doing it this way. Anyone having more information about the security risks during this operation, please share.
I build a library that can help you to solve this problem, you just have to put in the searchable_fields the attributes you want to filter and it will take care of the rest.
https://github.com/SchroterQuentin/django-search-listview

How to use GeoDjango Pointfield in Form?

I wanted to know to to use the PointField widget that is automatically generated from a Django form.
I am using the generic views for this (CreateView)
This is what my model looks like.
from django.contrib.gis.db import models
class Post(models.Model):
title = models.CharField(max_length=60)
text = models.CharField(max_length=255)
location = models.PointField(geography=True, null=True, blank=True)
objects = models.GeoManager()
The form is then automatically generated for me and I just call it in my view. As such:
{{ form.as_p }}
This is the output of that piece of code.
<form method="post">
<input type='hidden' name='csrfmiddlewaretoken' value='wVZJIf7098cyREWe3n3jiZinPdbl8nEe' />
<p><label for="id_title">Title:</label> <input id="id_title" maxlength="60" name="title" type="text" /></p>
<p><label for="id_text">Text:</label> <input id="id_text" maxlength="255" name="text" type="text" /></p>
<p><label for="id_location">Location:</label> <style type="text/css">
#id_location_map { width: 600px; height: 400px; }
#id_location_map .aligned label { float: inherit; }
#id_location_div_map { position: relative; vertical-align: top; float: left; }
#id_location { display: none; }
.olControlEditingToolbar .olControlModifyFeatureItemActive {
background-image: url("/static/admin/img/gis/move_vertex_on.png");
background-repeat: no-repeat;
}
.olControlEditingToolbar .olControlModifyFeatureItemInactive {
background-image: url("/static/admin/img/gis/move_vertex_off.png");
background-repeat: no-repeat;
}
</style>
<div id="id_location_div_map">
<div id="id_location_map"></div>
<span class="clear_features">Delete all Features</span>
<textarea id="id_location" class="vSerializedField required" cols="150" rows="10" name="location"></textarea>
<script type="text/javascript">
var map_options = {};
var options = {
geom_name: 'Point',
id: 'id_location',
map_id: 'id_location_map',
map_options: map_options,
map_srid: 4326,
name: 'location'
};
var geodjango_location = new MapWidget(options);
</script>
</div>
</p>
<input type="submit" value="Create" />
</form>
In the head tags I import an OpenLayers script from
http://cdnjs.cloudflare.com/ajax/libs/openlayers/2.13.1/OpenLayers.js
However, the page will not show anything for the pointfield. (The other fields work just fine).
In chromedevtools it shows this error
Uncaught ReferenceError: MapWidget is not defined
For this line of code
var geodjango_location = new MapWidget(options)
Basically I want to know if there is someother javascript library I should be linking to or am I missing something else?
I've looked through the documentation on GeoDjango forms, but don't know what else to try
https://docs.djangoproject.com/en/dev/ref/contrib/gis/forms-api/
Add this to the head section:
<head>
{{ form.media }}
</head>
I have a related problem with my Admin UI. My solution is just a reference for your problem. Firefox browser was blocking the loading of mix http/https http://openlayers.org/api/2.13/OpenLayers.js because my geodjango site forces https.
One solution is to download the OpenLayer.js into my geodjango project static directory, and add the following line to my CustomGeoModelAdmin:
class MyCustomGeoModelAdmin(....):
openlayers_url = '/static/mygeodjangoproject/OpenLayers.js'
#property
def media(self):
"Injects OpenLayers JavaScript into the admin."
media = super(MyCustomGeoModelAdmin, self).media
media.add_js([self.openlayers_url])
return media
and voilĂ , my admin site now shows a Geographical Map for the Point Field.

Django : Read multiple file from html form

I am trying to upload multiple files using Django. Using following code select multiple files in html form.
index.html
IMAGE Files:<input type="file" name="image" multiple /><br/>
Views.py
image=request.FILES.get('image')
models.py
image=models.ImageField(upload_to=get_upload_pathimage)
Now I get only last file (if I select 3 files then get 3rd file). How to get all images ?
My solution for getting multiple files from html and upload that files in django
index.html
<div id="fine-uploader-manual-trigger">
<input class="custom-file" type="file" name="imgname" id="productImgUpload" accept=".xlsx,.xls,image/*,.doc,audio/*,.docx,video/*,.ppt,.pptx,.txt,.pdf" multiple>
</div>
views.py
filelist = request.FILES.getlist('imgname')
for i in range(len(filepath)):
filename = filelist[i]
request.FILES is a MultiValueDict and doing get will return only the last value as you noted. If you want all of the values you should use images = request.FILES.getlist('image').
i just try for getting multiple images from static folder to html page using for loop in Django.
{% for i in lst %}
<td style="margin-left: 10px;">
<img src="{% static '/images2/' %}{{i}}.png" style="width: 300px; height: 200px;">
{% endfor %}

Django Upload From Template

I am looking into uploading a file from the html template. I've seen a fair amount of documentation including FileFields, ImageFields etc. However, ideally I do not want to rewrite my code.
Currently, I have a simple form on my template and I would like to have an upload function there, where, an image will be uploaded and stored into my applications media folder and if possible added to a database.
I do know that I've probably taken a long and complex route but if anyone can help it'll be great!
html.py:
<div class="row"> <div class="span1 offset5"> </bR>
<form class="form-horizontal" method="get" action="/add/submit" value="add">
<fieldset> <div class="input">
<div class="inline-inputs">
<label>Ride Name:</label><input type="text" name="name">
<label>Type:</label><input type="text" name="type">
<label>Theme:</label><input type="text" name="theme">
<label>Description:</label><textarea rows="5" name ="description"></textarea>
<label>Author:</label><input type="text" name="author">
<label>Date Released:</label>
<div id="datetimepicker" class="input-append date">
<input type="text" name="date"></input>
<span class="add-on">
<i data-time-icon="icon-time" data-date-icon="icon-calendar"></i>
</span>
</div>
<label>Thread:</label><input type="text" name="thread">
<label>Video</label><textarea rows="2" name ="video"></textarea>
<br><br>
<input class="btn btn-primary" type="submit" value="Add" />
</div> </div>
</fieldset>
</form>
</div> </div>
Currently my Views.py just takes the entered data and inserts it into a database. I want to add the ability for a file to be uploaded:
def Ride_Add_Submit(request):
name = request.GET['name']
type = request.GET['type']
theme = request.GET['theme']
description = request.GET['description']
author = request.GET['author']
releasedate=request.GET['date']
video=request.GET['video']
thread=request.GET['thread']
entry = RollerCoaster(name=name, type=type, theme=theme, description=description, releasedate=releasedate, author=author, video=video, thread=thread)
entry.save()
return TemplateResponse(request, 'Ride_Add.html')
I don't understand why you keep talking about the template here, the template has nothing whatsoever to do with anything. The handling of the upload, like all logic, is done in the view.
The file upload overview still has all the information you need. You can ignore the parts about the Django form and checking if it's valid, and simply pass the file object to your upload handling function, which that page also explains.
However you will need to change your template so that the form element uses POST instead of GET (which is almost certainly a good idea anyway), and use enctype="multipart/form-data" as also described on that page.
Finally, I really would advise you to rewrite your code to use ModelForms. Not only would it make your code much simpler, it would also do things like validate the entry to make sure all the required fields are present and are of the right types, and so on - as well as output valid HTML (for instance, you're missing for attributes in your label tags).