I have integrated the blobstore example into my own code and found that I cannot get it to work properly. The image gets uploaded (it's in the database) but the line upload = self.get_uploads()[0] doesn't work since self.get_uploads() is empty
Here's my code:
class ImageCreate(Resource):
def get(self):
form = ImageCreateForm()
return render_template('images/create.html', form=form, upload_url=blobstore.create_upload_url('/admin/upload')))
class PhotoUploadHandler(Resource, BlobstoreUploadHandler):
def post(self):
try:
upload = self.get_uploads()[0]
form = ImageCreateForm(data=request.get_json())
image =Image(title=form.title.data, blob_key=upload.key(), notes=form.notes.data)
image.put()
redirect('/admin/image/list')
except():
redirect('/admin/image/list')
api.add_resource(ImageCreate, '/admin/images/create', endpoint='image_create')
api.add_resource(PhotoUploadHandler, '/admin/upload', endpoint='image_upload')
class Image(ndb.Model):
title = ndb.StringProperty()
blob_key = ndb.BlobKeyProperty()
notes = ndb.StringProperty()
date_added = ndb.DateTimeProperty(auto_now_add=True)
class ImageCreateForm(Form):
title = StringField('Title', validators=[DataRequired()])
notes = TextAreaField('Notes')
<form action="{{ upload_url }}" method="post" name="create-image" enctype=multipart/form-data>
{{ form.hidden_tag() }}
<div> {{ form.title }}</div>
<input type="file" name="file">
<div> {{ form.notes }}</div>
<div><input type="submit" name="submit" value="Upload"></div>
</form>
So, after searching and trying a LOT. And visiting a bunch of obscure website that promise answers (most do not) I finally found why this doesn't work... Apparently the self.get_uploads() is webapp2 specific. If one uses flask, another approach is necessary.
To get the current uploaded file, the easiest way is to access the request directly and extract the blob-key from the headers, like so:
class PhotoUploadHandler(Resource, BlobstoreUploadHandler):
def post(self):
try:
f = request.files['file']
header = f.headers['Content-Type']
parsed_header = parse_options_header(header)
blob_key = parsed_header[1]['blob-key']
This effectively does the same as (and replaces) the self.get_uploads() function and can be used with flask. Notice that I extend from both Resource and BlobstoreUploadHandler since I'm using flask-restfull. Using only flask one only needs to extend from BlobstoreUploadHandler.
Related
I trying to propose to the users of my site to download a document in either pdf or odt version through radio buttons. How can I get and use the value of the radio button chosen by the user to serve the appropriate file. So far, I can only serve one at a time.
My current work:
models.py
class File(models.Model):
name = models.CharField(max_length=200)
pdf_version = models.FileField()
odt_version = models.FileField()
def __str__(self):
'''String name represents class File'''
return self.name
urls.py
path('files_page/', views.files_page, name='files_page'),
path('download_file/<int:file_id>/', views.download_file, name='download_file'),
views.py
def files_page(request):
files = File.objects.all()
context = {'files':files}
return render (request, 'walk/files_page.html', context)
def download_file(request, file_id):
#No post request; do nothing
if request.method != 'POST':
pass
else:
#fetch the file to download
#file = File.objects.get(id=file_id)
response = FileResponse(open('/home/me/Desktop/super/media_cdn/tog.pdf', 'rb'))
response['Content-Disposition'] = 'attachment; filename="tog.pdf"'
return response
template
{%block content%}
{%for file in files %}
<p>{{file.name}}</p>
<p>{{file.pdf_version}}</p>
<p>{{file.csv_version}}</p>
<form action="{%url 'walk:download_file' file.id%}" method="POST">
{%csrf_token%}
<input type="radio" name="format" value="pdf" checked> pdf
<input type="radio" name="format" value="csv"> csv
<button name="submit">download</button>
</form>
{%endfor%}
{%endblock content%}
Let's start with using forms. Yes, you use django forms in django instead re-implementing everything yourself.
Create forms.py:
from django import forms
FILE_FORMAT_CHOICES = [("csv", "Download PDF"), ("csv", "Download CSV")]
class FileFormatForm(forms.Form):
file_format = forms.ChoiceField(choices=FILE_FORMAT_CHOICES, widget=forms.RadioSelect())
Inside of the template used by files_page (just let django render the fields, don't do it yourself):
<form action="{%url 'walk:download_file' file.id%}" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="Download">
</form>
And finally adjust the views.py:
def files_page(request):
...
context = {
'files': files,
'form': FileFormatForm() # empty / without POST
}
...
def download_file(request, file_id):
assert request.method == 'POST', "users should only come here with POST now"
form = FileFormatForm(request.POST) # populate from POST
if form.data['file_format'] == 'pdf':
return "return PDF file response here"
else:
return "return CSV file response here"
Note: you don't use tab in Python. Use 4x whitespaces instead.
Another Note: Class Based Views to further reduce the amount of boilerplate.
I am trying to make a form to upload a file, but the file data is not being sent with the request. I'm manually navigating to my file and hitting submit. My FileRequired validator fails. (And if I don't include it the data field on form.scan_file is empty.)
Here's my form:
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired
class ScanForm(FlaskForm):
scan_file = FileField(validators=[FileAllowed(['nii', 'nii.gz', 'zip']), FileRequired()])
Here's my views.py:
from flask import Blueprint, render_template, request, flash, redirect, url_for, session
from .models import Scan
from .forms import ScanForm
from .service import ScanService
from cookiecutter_mbam.utils import flash_errors
blueprint = Blueprint('scan', __name__, url_prefix='/scans', static_folder='../static')
#blueprint.route('/add', methods=['GET', 'POST'])
def add():
"""Add a scan."""
form = ScanForm(request.form)
if form.validate_on_submit():
f = form.scan_file.data
service = ScanService()
xnat_uri = service.upload(session['user_id'], session['curr_experiment'], f)
Scan.create(xnat_uri=xnat_uri)
flash('You successfully added a new scan.', 'success')
return redirect(url_for('experiment.experiments'))
else:
flash_errors(form)
return render_template('scans/upload.html',scan_form=form)
Here's my upload.html:
{% extends "layout.html" %}
{% block content %}
<form method="POST" action="{{ url_for('scan.add') }}" enctype="multipart/form-data">
{{ scan_form.csrf_token }}
<input type="file" name="file">
<input class="btn btn-primary" type="submit" value="Submit">
</form>
{% endblock %}
It doesn't look like I'm making the same mistake as this person. What am I doing wrong?
EDIT: Since posting, I have found this question, but on working through the offered solutions, none seem relevant to my situation.
EDIT 2: At one point, I printed request.files in the Werkzeug debugger and it was an empty dict. I can't reconstruct exactly what I did to get that result. Since then, I've inserted some print statements and in fact, request.files has my file object. So I have a way to retrieve my file. But I am supposed to be able to retrieve my file object at form.scan_file.data (see here). Right now this evaluates to None. More specifically, form.scan_file.has_file() evaluates to False. form.data evaluates to {'scan_file': None, 'csrf_token': <long-random-string> }
Even if I have another way of retrieving my file object, a consequence of this problem is that validation isn't working. My form doesn't pass the FileRequired() validation.
EDIT 3: With my new understanding of my problem, I see that it's similar to this question. However, it's at least apparently not duplicative because none of form = ScanForm(request.form), form = ScanForm(), or form = ScanForm(CombinedMultiDict((request.files, request.form))) make any difference to the behavior outlined in Edit 2.
First of all, check if your data gets posted on that route. Second, I think you don't need to pass request.form to ScanForm, you just need to instantiate it like:
def add():
"""Add a scan."""
form = ScanForm()
...
To check what gets posted with form, instead of
if form.validate_on_submit():
you can use, and print form.scan_file.data:
if form.is_submitted():
print(form.scan_file.data)
Lastly, you can render input file with
{{scan_form.scan_file }} or <input type="file" name="scan_file">
(name attribute of input element should be equal to "scan_file")
Here is my example:
Form:
class ArticleForm(FlaskForm):
article_image = FileField('Article_image', validators=[FileRequired()])
Form in template:
<form action="" method="post" enctype="multipart/form-data">
{{ article_form.csrf_token }}
{{ article_form.article_image }}
<input type="submit" value="submit"/>
</form>
Controller (saving file):
article_form = ArticleForm()
if article_form.validate_on_submit():
f = article_form.article_image.data
name = current_user.username + "__" + f.filename
name = secure_filename(name)
f.save(os.path.join("./static/article_images/", name))
I was stupid enough to forget to add enctype="multipart/form-data" to the form tag which caused the form.file.data to contain only the name of the file and not the whole life.
I'm trying to build an application that asks users to upload 2 different pdf files, each from a separate input button/area. I'm new to Django and through reading the documentation a bunch of times I've gotten a few things to work.
Using <form method="post" enctype="multipart/form-data"> I was able to get 2 fields for input which look something like this: App Screenshot of 2 input areas.
However, when I click the select button in either case and select the file and click 'Upload', The same 2 files show up in both input areas. I've experimented with this alot and can't seem to find any resources that attempt to solve my problems.
Quick note: I know there are ways to upload multiple files in a single input area, but I don't want to do that. I want to specifically have 2 different input areas as a design decision.
I'm not sure if this even close to the right approach or if there are better tools for handling this type of situation. Below are chunks of the code I've written. If anyone could give me advice on how to better approach this problem or tell me how to fix my code it would be much appreciated.
Thanks!
forms.py
class FilePDFForm(forms.ModelForm):
class Meta:
model = FilePDF
fields = ('identifier', 'pdf', 'pdf2' )
models.py
class FilePDF(models.Model):
identifier = models.CharField(max_length=50, blank = True)
pub_date = models.DateTimeField(auto_now_add=True)
pdf = models.FileField(upload_to='documents/')
pdf2 = models.FileField(upload_to='documents/')
def __str__(self):
return self.pdf.name + ',' + self.pdf2.name
views.py
def index(request):
if request.method == 'POST' and request.FILES['myfile'] and request.FILES['myfile2']:
genfile = request.FILES['myfile']
genfile2 = request.FILES['myfile2']
fs = FileSystemStorage()
filename = fs.save(genfile.name, genfile)
filename2 = fs.save(genfile2.name, genfile2)
uploaded_file_url = fs.url(filename)
uploaded_file2_url = fs.url(filename2)
file1_uploaded = True
return render(request, '...index.html', {
'uploaded_file_url': uploaded_file_url,
'uploaded_file2_url': uploaded_file2_url,
'file1_name': filename,
'file2_name': filename2
})
return render(request, '...index.html')
index.html
<div class="col-xs-3">
<div class="form-group">
<label class="control-label">Please select the first PDF file from your
computer.</label> <input type="file" class="filestyle" name="myfile"
data-buttontext=" Select" data-buttonname="btn btn-primary" /><br />
<label class="control-label">Please select the second PDF file from your
computer.</label> <input type="file" class="filestyle" name="myfile2"
data-buttontext=" Select" data-buttonname="btn btn-primary" /> <button type=
"submit" class="btn btn-primary">Upload</button>
</div>
</div>
Change your view like this form,
def index(request):
if request.method == 'POST':
f = FilePDFForm(request.POST, request.FILES)
if f.is_valid():
new_object = f.save()
# do remaining thing here
-----------------
return render(request, '...index.html')
for more refer this https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#modelform
I'm trying to use django-jfu to multiupload images, but I have a problem. I want to handle a foreign key dynamically (via url or something), but I can't think of anything.
I have the following models:
class Event(models.Model):
name = models.CharField(max_length=128)
class Picture(models.Model):
event = models.ForeignKey(Event)
image = models.ImageField(upload_to='media')
According to django-jfu, you have to specify a "upload" view to call from the template via template tag. This is my upload view:
#require_POST
def upload(request):
event = Event.objects.get(id=26)
file = upload_receive(request)
instance = Picture(image = file, event = event)
print instance
instance.save()
basename = os.path.basename(instance.image.path)
file_dict = {
'name' : basename,
'size' : file.size,
'url': settings.MEDIA_URL + basename,
'thumbnailUrl': settings.MEDIA_URL + basename,
'deleteUrl': reverse('jfu_delete', kwargs = { 'pk': instance.pk }),
'deleteType': 'POST',
}
return UploadResponse(request, file_dict)
Right now, as a test, it only saves pictures to event with id=26, but how can I handle it dynamically? This is the view and template where I'm calling the template tag:
view
def add_pictures_to_event(request, event_id):
return render(request, 'add_pictures_to_event.html')
template
{% extends 'base.html' %}
{% load staticfiles %}
{% load jfutags %}
{% block body %}
<div class="container">
<h2>Photo upload</h2>
{% jfu %}
</div>
{% endblock %}
As you can see, the view add_pictures_to_event, gets the request and the id of the event, but I cant seem to pass it to the upload view.
Any help would be appreciated.
I had the same question. I looked at different django versions of jQuery File Upload but stuck with Alem's jfu but with the changes from Thomas Willson to make it work in 1.9. My solution might not be the best but I could not find an other way.
I assume you already created an event and then add images to it.
media_upload_form.html is in my projects static directory. I used the UPLOAD_FORM_EXTRA block to add a hidden formfield with the current event_id:
{% block UPLOAD_FORM_EXTRA %}
<input type="hidden" name="currentevent" value="{{instance.pk}}">
{% endblock %}
I assume you have the view from the docs. I changed in the beginning of the uploadview:
file = upload_receive( request )
event_instance = get_object_or_404(Event, id=request.POST['currentevent'])
instance = Picture( file = file, event=event_instance)
instance.save()
It is probably against all django rules but it works. If anyone has a better solution I like to know too. FormSets maybe?
I am willing to use Django for a school project but I'm encountering several issues.
The one I need help for is described in the title. Basically, I have a todo application in which I could add tasks. Now that I added a form in a my view to let the user add a task, I can't access the tasks in the Django admin.
I can still delete them with the admin but each time I try to add or to edit a task through the admin it throws me this error :
TypeError at /admin/todo/task/12/`
render_option() argument after * must be a sequence, not int
But, the form I added for the user works well.
My guess is that the 12 we can see the url is making the error but I don't know why. I point out that I'm still kinda new to Django, I didn't find any similar problem (found this but it didn't help me) so I thought it could be a good idea to ask here :). Here are my files :
todo/models.py
PRIORITY_TYPES = (
(1, 'Normal'),
(2, 'High'),
)
class Task(models.Model):
application = models.CharField(max_length=120, default='esportbets')
title = models.CharField(max_length=120)
author = models.CharField(max_length=60, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
completed = models.DateTimeField(blank=True, null=True)
priority = models.IntegerField(choices=[PRIORITY_TYPES], default=1)
done = models.BooleanField(default=False)
def __unicode__(self):
return self.title
todo/forms.py
class AddTaskForm(forms.Form):
application = forms.CharField(max_length=120, initial='esportbets', help_text='the application it is about')
title = forms.CharField(max_length=120, help_text='the task to do')
priority = forms.ChoiceField(choices=PRIORITY_TYPES, initial=1)
todo/views.py
def index(request):
if request.method == 'POST':
form = AddTaskForm(request.POST)
if form.is_valid():
new_task = Task.objects.create(application=form.cleaned_data['application'],
title=form.cleaned_data['title'],
priority=form.cleaned_data['priority'])
request.POST = None
redirect('/todo/', RequestContext(request))
else:
form = AddTaskForm()
tasks = Task.objects.all().order_by('-created')
tasks_high = tasks.filter(priority=2)
tasks_normal = tasks.filter(priority=1)
template_datas = {'form':form, 'tasks_high':tasks_high, 'tasks_normal':tasks_normal, 'user':request.user}
return render_to_response('todo/base.html', template_datas, RequestContext(request))
todo/base.html
{% if user.is_authenticated %}
<hr /><h3>ADD A TASK</h3><br />
<form method="post" action=".">
{% csrf_token %}
{{ form.as_p }}
<br />
<input type="submit" value="add" />
<input type="reset" value="reset" />
</form>
{% endif %}
todo/models.py: remove the [] around PRIORITY_TYPES.
todo/forms.py: replace the forms.ChoiceField(...) by forms.TypedChoiceField(choices=PRIORITY_TYPES, initial=1, coerce=int)
Since you are essentially copying data 1:1 from the form to a model, I'd recommend using django.forms.ModelForm.
If you want to minimize your code further you could use the generic CreateView. I recently wrote an answer to "Best practices on saving in a view, based on example code" which includes some example code.