Django: How to prepopulate file upload path with current file path? - django

I have a modelform that my views generate an HTML form for editing content. Currently, it's able to pull in the current stored text content, like this:
#login_required
def edit_person(request, s_id, p_id):
p = get_object_or_404(Person, id=p_id)
if request.method == 'POST':
form = PersonForm(request.POST, request.FILES)
if form.is_valid():
p.name = request.POST['name']
p.title = request.POST['title']
handle_uploaded_file(request.FILES['photo'], request.FILES['photo'].name, 'media/images/people/')
p.photo = request.FILES['photo']
p.order = request.POST['order']
p.save()
return HttpResponseRedirect('/section/'+s_id+'/')
else:
return HttpResponse("error")
else:
form = PersonForm({ 'name': p.name, 'title': p.title, 'photo': p.photo, 'order': p.order })
return render_to_response('auth/edit-form.html', { 'form': form }, context_instance=RequestContext(request))
return HttpResponseRedirect('/section/'+s_id+'/')
However, the photo file path is blank. I don't want the user to have to upload a new file every time they edit something if they don't want to change the image. How do I get the file upload field to appear pre-populated and not overwrite itself if they don't change it? Thanks.

Believe it or not, it can be done! However, it requires the use of a custom django app called django-file-resubmit
Note that app as given only works for the widgets in admin and requires sorl-thumbnail.
You may prefer to use my fork:
https://github.com/JordanReiter/django-file-resubmit
It's a general-purpose version for use everywhere a ModelForm is used that doesn't have any other prerequisites.
It's pretty cool in that it automagically stores the file on submission (even if there is a validation error) and retrieves it from the cache when the widget is rendered in the form.
This is literally all you have to do to implement it:
import file_resubmit.widgets
class PersonForm:
""" existing code here """
photo = forms.ImageField(required=False, widget=file_resubmit.widgets.ResubmitImageWidget())

Related

Suggestions on improving a form view

I'm building an app where the user enters data and then gets redirected to a page that shows results based on their input with some simple equations. However, every time I refresh the results page, a new model instance is saved on the database.
Is there another (more efficient and effective) way of passing the data from this view to another view where I have access to the instance of that model submitted through the form view? What's the Django way of passing form data to a view?
The only limitation is I don't want user authentication so using self.request.user is not an option unless it can be implemented in a way that doesn't require users to sign up and sign in.
I'm still somewhat new to Django so any pointers to obvious solutions that I'm overlooking would be greatly appreciated.
This is the view that processes the model form:
def createcalculation(request):
form = CalcForm()
if request.method == 'POST':
form = CalcForm(request.POST)
if form.is_valid():
item = form.save()
m_data = get_object_or_404(Calculate, id=item.id)
context = {'c_data': form.cleaned_data, 'm_data': m_data}
return render(request, 'calc/res_ca.html', context)
context = {'c_form': form}
return render(request, 'calc/calc.html', context)
It is advisable to always do a redirect after a successful POST. Your code should look something like this:
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse
...
def createcalculation(request):
form = CalcForm()
if request.method == 'POST':
form = CalcForm(request.POST)
if form.is_valid():
item = form.save()
m_data = get_object_or_404(Calculate, id=item.id)
context = {
'c_data': form.cleaned_data,
'm_data': m_data
}
return redirect(reverse('app_name:view_name', kwargs=context))
context = {'c_form': form}
return render(request, 'calc/calc.html', context)
You can pass the newly created item object in the context as well. Also, you should change app_name and view_name text to match your situation.

Do I really need to accommodate both GET and POST request formats in a django form?

I'm new to django but something I don't understand is the need for accommodating both the GET and POST request types when developing a form. Please refer to code below from django docs:
from .forms import NameForm
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
The reason this confuses me is because I have developed a GET based form and it is working and I have no need for the POST portion above? See below:
# views.py
def simplifier_form(request):
form = LabelForm()
return render(request, "simplifier.html", {"form": form})
def process_simplifier(request):
label_name = request.GET.get('labels')
results = Disturbance.objects.filter(labeldisturbances__label_name=label_name)
painsresults = Pain.objects.filter(disturbances__pk__in=results).distinct()
total_disturbances = Disturbance.objects.filter(pain__pk__in=painsresults)
total_labels = Label.objects.filter(disturbances__pk__in=total_disturbances).distinct()
context = {'results': results, 'painsresults': painsresults, 'total_disturbances': total_disturbances, 'total_labels': total_labels}
return render(request,'process_simplifier.html', context)
# forms.py
class LabelForm(ModelForm):
labels = forms.ModelChoiceField(
queryset=Label.objects.all(),
to_field_name='label_name',
widget=forms.Select(attrs={'class': 'labels'}),
)
class Meta:
model = Label
fields = ['labels']
So why do the django docs and most examples include code for both methods when you only really use one like in my example above?
The question is... How do you want to process your from when submitted on POST?
Remember, your form can also be submitted on GET... You can decide what you would want to do if submitted on GET.

Is there a method to post form data from one url to another url, and then render the template based on the form data

I am trying to create a simple one item product store, in which customers would go to a product page and choose the quantity they would like to purchase in a form. After completing the form, I would then like for it to redirect to the checkout page and render the quantity they chose. Is there a simple way to do this? At the moment, I am posting the form data to the product page url and then redirecting the user to the checkout page, however I am unsure how to access that data.
def proxy_detail(request, proxy_slug):
if request.method == 'POST':
form = forms.AddProxyAmountForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
number_of_proxies = int(cd['number_of_proxies'])
return redirect('payment:checkout')
else:
add_proxy_form = forms.AddProxyAmountForm()
proxy_product = get_object_or_404(models.ProxyProduct, slug = proxy_slug)
return render(request, 'shop/product/proxy_detail.html', {'proxy_product' : proxy_product, 'add_proxy_form' : add_proxy_form })
Its not an exact answer but I am new here so I can't really comment :p
Anyways, I think in your view, what you can do is, get the data in a variable and pass it as a context to render another page.
def viewname(request):
if request.method == "POST":
#your logic to save here
context = {variableOfInterest : request.POST.valueOfInterest} #considering you are getting it from the form
return render(request, "redirected.html", context)

Getting 'builtin_function_or_method object is not iterable' on file upload

I am unable to upload the file. I am getting
Type error builtin_function_or_method' object is not iterable
models.py
class seeker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
class Upload(models.Model):
user = models.ForeignKey(Seekers)
resume = models.FileField(upload_to ='resume', blank = True, null = True)
forms.py
class SeekersForm(forms.Form):
resume = forms.FileField(label = 'Select a file',help_text = 'max.3 MB')
views.py
def List(request):
# Handle file upload
if request.method == 'POST':
form = SeekersForm(request.POST, request.FILES)
if form.is_valid():
#id = User.object.get(id)
newdoc = Seekers.objects.get(user_id)
newdoc.resume =Upload(resume = request.FILES['resume'])
newdoc.save()
#seekers_edit = Seekers.objects.get(id)
#seekers_edit.resume = Seekers(resume = request.FILES['resume'])
#seekers_edit.save()
#Redirect to the document list after POST
return HttpResponseRedirect('/profile/')
else:
form = SeekersForm() # A empty, unbound form
#Load documents for the list page
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html',{'seekers':seekers,'form':form},context_instance=RequestContext(request))
It's hard to say where your problem is, but I think the following line of code is the main problem:
newdoc.resume =Upload(resume = request.FILES['resume'])
You have to save a file in a FileField explicitly before you save the entire model instance. Also, if you have a ForeignKey field in one of your models and you want to assign it an instance of another model, please save that instance first before you do the assignment. Without knowing your Seekers model, all I can do is guessing what might help you. Something like the following might get you started:
your_file = request.FILES['resume']
upload_instance = Upload()
upload_instance.resume.save(name=your_file.name, content=your_file, save=False)
upload_instance.user = ... # Here goes an instance of your Seekers model
upload_instance.save() # Here you save the whole instance of your Upload model
Also, please note the following:
Your model Seekers should rather be named Seeker using the singular, not the plural. This should generally be like that with all your models.
Python functions should always start with a lowercase letter, i.e. list instead of List. However, this name is a bad choice here anyway, because a function called list is already present in Python's standard library.
Please take a closer look at Django's documentation. It's all in there what you need to know. I recommend you to read especially these sections:
https://docs.djangoproject.com/en/1.4/ref/models/fields/#filefield
https://docs.djangoproject.com/en/1.4/ref/files/file/
Problems in your code:
Your form definition duplicates information from your model — just use forms.ModelForm (with exclude so as not to display the user field)
As currently pasted, newdoc = Seekers.objects.get(user_id) will raise a TypeError ('foo' object is not iterable); .get() accepts keyword parameter filters, not anything else.
Accessing request.FILES['resume'] manually isn't necessary or recommended
So, in short, you're almost there; just let Django forms do more of the work for you:
# forms.py
class SeekerForm(forms.ModelForm)
class Meta:
model = Seeker
# views.py
def seeker_list(request):
# Opinions are divided as to whether it's ever appropriate to
# modify the database like this on a GET request, but it seems
# to make sense here
seeker = Seekers.objects.get_or_create(user=request.user)
if request.method == 'POST':
form = SeekerForm(request.POST, request.FILES, instance=seeker)
if form.is_valid():
form.save()
return HttpResponseRedirect('/profile/')
else:
form = SeekerForm(instance=seeker)
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html', {
'seekers':seekers,
'form':form
}, context_instance=RequestContext(request))
It's not clear what the significance (if any) of the commented-out sections of your code is — I've assumed you always want to modify the current user's Seeker, but if not then adapt as appropriate.

How to override filefield in views.py?

I have view to document edit:
#login_required
def document_edit(request, doc_id):
try:
doc = Document.objects.get(id=doc_id)
except Document.DoesNotExist:
raise Http404
form = DocumentForm(instance=doc)
if request.method == "POST":
form = DocumentForm(request.POST, request.FILES, instance=doc)
if form.is_valid():
if request.POST.get('cancel'):
return HttpResponseRedirect('/')
if request.POST.get('delete'):
document = Document.objects.get(id=doc_id)
document.file.delete()
document.delete()
return HttpResponseRedirect('/')
else:
form.save(author=request.user)
text = "Document edited!"
return render_to_response('message.html', {'text' : text}, context_instance = RequestContext(request))
else:
return render_to_response('document_edit.html', {'form':form,}, context_instance = RequestContext(request))
else:
form = DocumentForm(instance=Document.objects.get(id=doc_id))
return render_to_response('document_edit.html', {'form':form, 'doc':doc,}, context_instance = RequestContext(request))
and this form in forms.py:
class DocumentForm(ModelForm):
file = forms.FileField(label = 'file', required=True, error_messages={'required' : 'required!','empty': "this file is empty!"})
title = forms.CharField(label = 'title', widget = forms.TextInput(attrs={'size': 93,}), error_messages={'required': 'required!'})
description = forms.CharField(label = 'description', widget = forms.Textarea(attrs={'rows': 10, 'cols': 120,}), error_messages={'required': 'required!'})
editable = forms.BooleanField(label='Document editable?', widget=forms.RadioSelect(choices=YES_OR_NO), required=False, initial=True)
class Meta:
model = Document
exclude = ('author',)
def save(self, author, commit=True):
document=ModelForm.save(self,commit=False)
document.author = author
if commit:
document.save()
return document
Now i want possibility to override existed file (and automatically deleting previous file). How can i do that?
If I understand correctly, you are looking to replace any previous file in the 'file' field (a FileField datatype) of a Document Model. That will happen automatically if you upload a new file using the form. Are you seeing different behavior?
Automatically deleting the previous files depends on the Django version you are using. As of 1.3 (currently the most recent) FileField will not delete any files (changelog). In 1.2 and prior, the FileField would remove the file from the filesystem on change and on delete. So if you change or delete the Model instance in 1.2, the file goes away. Under 1.3, the file stays.
You are doing the right thing by calling doc.file.delete(). However, if you do any sort of bulk operation on model instances, this won't be enough. To test in 1.3, use the AdminSite to select multiple records and select the delete dropdown. While the records will be gone, the files will still exist. These bulk QuerySet operations and any delete operation that skips your custom document_edit() function will leave behind the files. Your best bet will be to write a removal function for all FileField datatypes and attach it with a pre/post_delete signal. If you need help with this, I suggest a new SO Question.
Other things: You can tidy your code with this shortcut function
from django.shortcuts import get_object_or_404
doc = get_object_or_404(Document, id=doc_id)
You use Document.objects.get() three times in your posted code. Just use the above shortcut once and then use the 'doc' variable in all other locations