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
Related
So I have this simple Django application where users can post diary as well as images. Here, uploading image is optional, so I have left the field as not required. The problem is when I leave the ImageField empty. Saving the post w/o image works fine, but when I try to GET the post, I run into the error. First let me show you the code. models.py
class Diary(models.Model):
objects = models.Manager()
# some other stuff here
img = models.ImageField(null=True, blank=True, upload_to="everyday_img")
forms.py
class DiaryInputForm(forms.ModelForm):
# some other stuff here.
img = forms.ImageField(required=False)
views.py
def InputDiary(request):
form = DiaryInputForm(initial={'authuser':request.user})
if request.method == 'POST':
form = DiaryInputForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.authuser = request.user
# some other stuff here
user_img = request.FILES.get('img', None)
instance.save()
return redirect('/diary/diarymain')
return render(request,'diary/diaryinput.html', {'form':form})
def ViewDiary(request, authuser_id, slug):
today = Diary.objects.get(slug=slug)
return render(request, 'diary/diaryview.html', {'today' : today})
diaryview.html
...
<div class="row justify-content-center">
<div class="col col-12">
<div class="detail-contents">
<img src="{{today.img.url}}" class="user-img"><br>
{{today.what|safe}}
</div>
</div>
</div>
...
So when diaryview.html is loaded, the error occurs. I know it's obviously because the img column has no data, but I have no idea on how I should deal with it. Thank you very much in advance. :)
you need to parse the files too from the form
if request.FILES.get('img'):
instance.img= request.FILES.get('img')
also make sure you have MEDIA_URL and MEDIA_ROOT defined in the settings.py file and also url in urls.py file to serve media and static files.
also do this in the html
<img src="{{today.img.url|default_if_none:'#' }}" class="user-img"><br>
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.
New to Django and having problem seeing form fields displayed. What I see is just the submit button. If pressed, the form is finally presented, but with the format for a form that had bad data (typical 'this field is required' error for each box, red box, etc).
The form works fine after entering data and again pressing submit (stores entries in my db). I have a number of forms on the same page that have the same behavior.
Example of one form:
#model
class dbPara(models.Model): #parameters
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
username = models.CharField(max_length=10)
turns = models.FloatField(default=27)
units = models.FloatField(default=5)
rise = models.FloatField(default=2.9)
rescutL = models.FloatField(default=0.0833333333)
rescutH = models.FloatField(default=0.333333333)
LorR = models.CharField(max_length=1, default='R')
def __str__(self):
return self.timestamp, self.username, self.turns, self.units, self.rise, self.rescutL, self.rescutH, self.LorR
#form
class ParaForm(ModelForm):
class Meta:
model = dbPara
widgets = {'username': forms.HiddenInput()}
fields =['username', 'turns', 'units', 'rise', 'rescutL', 'rescutH', 'LorR']
#view
def importParameters(request):
if request.method == 'GET':
form = ParaForm()
else:
form = ParaForm(request.POST)
if form.is_valid():
entry=dbPara(username = request.POST.get('username'),
turns = request.POST.get('turns'),
units = request.POST.get('units'),
rise = request.POST.get('rise'),
rescutL = request.POST.get('rescutL'),
rescutH = request.POST.get('rescutH'),
LorR = request.POST.get('LorR')
)
entry.save()
return render(request, 'main.html',
{'ParaHTML' : form })
#url
urlpatterns = patterns('Inputs.views',
url(r'^importParameters/$', 'importParameters', name='urlParameters'),
)
#main.html
<div class='col-lg-3'>
<h4>Set Rosetta Parameters</h4>
<action="{% url "urlParameters" %}" method="post">{% csrf_token %}
{{ ParaHTML|crispy }}
<input type="hidden" name = "username" value = "{{ user.get_username }}">
<input type="submit" class="btn btn-primary" value="Set">
</form>
</div>
Appreciate any advice (better simple than 'most correct but complicated')
Could it be due to using default in the model? Would that not 'fill in the form' and result in 'POST' at the initial visit to the page, resulting in just the button? Thoughts?
One Suggesestion here ....
if Using request.POST.get('anything') simply then it Will raise error if particular string not find as in example('anything') string...
Because request.POST.get('anything') will return None if 'anything' is not in request.POST.
Additionally, .get allows you to provide an additional parameter of a default value which is returned if the key is not in the dictionary.
e.g: Corrected will be request.POST.get('anything', 'mydefaultvalue')
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.
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.