I have the following Django form:
class PageForm(forms.Form):
title = forms.CharField(max_length=50)
image = forms.ImageField(required=False)
content = forms.CharField(widget=forms.Textarea(attrs={'cols':20, 'rows':10}))
I'm using this to create new pages from a given template. But now I want to also add edit possibility, so I would like to use the form to render the same template only with some default values which are retrived from a page id. This is what I have:
page_to_edit = Page.objects.filter(id=page_id)[0] // Get page from model
title = page_to_edit.title
content = page_to_edit.content
picture = page_to_edit.picture.order_by('?')[0].file // Here file is a models.ImageField
initial_data = {'title' : title, 'content' : content, 'image' : picture}
form = PageForm(initial_data)
// Finally return this form to template
Now this works as I want for title and content, and those are properly rendered in template with initial values, but the ImageField is just empty. I've also tried passing picture.url instead of picture but no change.
Any help would be appreciated.
All the best
I would approach this the following way:
You can use .get() to retrieve a single record, Page.objects.get(pk=page_id), instead of .filter() which returns a queryset. There is also a shortcut get_object_or_404 that takes a common pattern of wrapping a .get() into a try..except block for you. If the model is not found, an HTTP 404 page is returned instead.
Use a ModelForm instead
A ModelForm accepts an instance parameter of an existing model so you don't need to set initial values.
With these things in mind your code would end up looking something like this:
# forms.py
from django import forms
class PageForm(forms.ModelForm):
class Meta:
model = Page
fields = ('title', 'image', 'content')
# views.py
from django.shortcuts import render, get_object_or_404
def edit_page(request, page_id):
page = get_object_or_404(Page, pk=page_id)
if request.method == 'POST':
form = PageForm(instance=page, request.POST)
if form.is_valid():
form.save()
else:
form = PageForm(instance=page)
return render(request, 'some_template.html', {'form': form})
def create_page(request):
if request.method == 'POST':
form = PageForm(request.POST)
if form.is_valid():
form.save()
else:
form = PageForm()
return render(request, 'some_template.html', {'form': form})
# urls.py
(r'^page/create/$', views.create_page, 'create_page'),
(r'^page/edit/(?P<page_id>\d+)/$', views.edit_page, 'edit_page'),
Related
I come from DRF background so please already assume that I might be getting something wildly wrong here. I am trying to use Django Form as a sort of proxy for DRF serializers. As in, when I fetch an object, I can quickly render it back, and I can of course accept POST requests and store them. What I can't seem to find is how do I use my object instances to process them with forms.
Here's my form:
class ProfileForm(ModelForm):
class Meta:
model = UserProfile
fields = ('name', 'profile_pic')
The actual model:
class UserProfile(models.Model):
user = models.OneToOneField(CustomUser, null=True, on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True)
profile_pic = models.ImageField(upload_to='profile_image', null=True, blank=True)
def __str__(self):
return str(self.user)
My view:
def get_profile(request):
if request.user.is_authenticated:
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
# update the model
profile = request.user.userprofile
form = ProfileForm(model_to_dict(profile))
if not form.is_valid():
form = ProfileForm()
return render(request, 'profile-edit.html', {'form':form})
else:
return render(request, 'index.html')
So, in my view (which is a GET view), I'm doing something like this:
from django.forms import model_to_dict
profile = request.user.userprofile
form = ProfileForm(model_to_dict(profile))
return render(..., form)
Is this even the right approach? Besides, the problem is that my form doesn't seem to handle the profile_pic field properly (i.e. the img src field in the from html is just __).
Ideally I would want to build the form from the object instance itself, but that also doesn't work.
form = ProfileForm(instance = profile) is bound, but not valid
form = ProfileForm(UserProfile.objects.all()[0]) is bound, and I can access data items through form.data.<fields>, but as soon as do form.is_valid(), I get:
AttributeError: 'UserProfile' object has no attribute 'get'
UPDATE: form = ProfileForm(request.<GET/POST>, instance=profile) is actually bound but not valid with the error that field name is required, even though profile.name is indeed valid CharField.
So how can I take my object, then bind it to a form, and then return it to be rendered properly. Imgine this is a user profile that I render, but also let the user update it whenever they want. What is the Django way of doing it? (I come from DRF background.)
Most of the SO answers that I have found almost always initialize the form with request.POST which I don't need.
EDIT: My use case, I have an endpoint /profile which, when requested as GET, should return a profile (with all the info that their UserProfile object holds but the user can also edit any of the fields that they want to. Say they have edited a field, they can simply hit update, which would then submit the form. On the backend, there profile would get updated, and they would get served the latest profile details. Of course, I can do it by rendering a from using a template, but I instead wanted to use the form which is already there. As I mentioned model_to_dict approach kind of serves this point to some extent, except it breaks for images... so I thought there might be a solution.
Looking at your view you make some mistakes which simply make using the forms difficult. Firstly if you want to update a form you should instantiate the form with the model instance so instead of form = ProfileForm(model_to_dict(profile)) it should be form = ProfileForm(instance=profile).
Also right after this line you write:
if not form.is_valid():
form = ProfileForm()
Why check is_valid on a form that is not bound?
Your view should ideally look something like:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
#login_required
def get_profile(request):
profile = request.user.userprofile
form = ProfileForm(instance=profile)
if request.method == 'POST':
form = ProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
return redirect('some-view-name')
return render(request, 'profile-edit.html', {'form':form})
I have this view in my app:
def contact(request):
form_class = ContactForm
if request.method == 'POST':
form = form_class(data=request.POST)
if form.is_valid():
contact_name = form.cleaned_data['contact_name']
contact_email = form.cleaned_data['contact_email']
contact_website = form.cleaned_data['contact_website']
contact_subject = form.cleaned_data['contact_subject']
form_content = form.cleaned_data['content']
template = get_template('contact/contact_template.txt')
context = Context({'contact_name': contact_name,
'contact_email': contact_email,
'contact_website': contact_website,
'contact_subject': contact_subject,
'form_content': form_content, })
content = template.render(context)
email = EmailMessage(
"New contact form submission",
content,
"www.inexistente.com" + '<support#inexistente.com>',
['mymail#gmail.com'],
headers={'Reply-To': contact_email}
)
email.send()
return redirect('/')
return render(request, 'contact/contact.html', {'form': form_class, })
I want to transform this to a class based view, I believe that is "more organized" for me...
What generic Django view to use?
Do you believe that is recommended to do this?
In the view, Is recommended use this code at the end to renew the form in case of not passing?
"else:
form = form_class()"
i was trying without that code and my form renews itself perfectly.
apologizeme in advance if I overlook something, any contribution is wellcome, Thanks for evaluate!
Since you have a view that displays a form and which redisplays the form with validation errors on error and redirects to a new URL on success, you can use FormView generic view.
Your FBV code converted to CBV:
from django.views.generic import FormView
class Contact(FormView):
form_class = ContactForm # Form class to be used
template_name = 'contact/contact.html' # Template to be used
success_url = '/' # Redirect to this url when form is valid
def form_valid(self, form):
template = get_template('contact/contact_template.txt')
context_dict = form.cleaned_data
context_dict['form_content'] = form.cleaned_data['content']
context = Context(context_dict)
content = template.render(context)
email = EmailMessage(
"New contact form submission",
content,
"www.inexistente.com" + '<support#inexistente.com>',
['mymail#gmail.com'],
headers={'Reply-To': contact_email}
)
email.send()
return super(Contact, self).form_valid(form)
Here,
form_class: defines the form class to be used.
template_name: defines the template to be used to display the form.
success_url: defines the url to be used when the form is valid.
You can put all the logic for the code to be executed when form is valid inside the form_valid() function. After performing all the operations, call the super() which redirects to the success_url defined in your class.
Also, when you are building context to be passed to the email template, you can use the form.cleaned_data dictionary. All the keys you have used to build the context dictionary is same as in the form's cleaned_data dictionary except the form_content key. So, i have just used the form's cleaned_data dictionary and added an extra key form_content in a context_dict dictionary which will be then used in the email template for rendering.
I'm new here and also new to Django and any kind of web development.
Right now, I am working on a project that allows users to submit academic papers to be edited by other users.
The problem I am having is that I would like to allow users to upload a file and with that file, upload some data about the file (meta data?) such as the title of the paper (listed as titleField) prompt, etc.
I have been able to find answers on how to only upload the file, but not on how to upload the data and the file as a whole package.
If anyone can shed any light on this, that would help me a lot!
Here is the models.py:
from django.db import models
class Document(models.Model):
docfile = models.FileField(upload_to='documents/%Y/%m/%d')
titleField = models.CharField(max_length=100, default="")
dueDateField = models.IntegerField(default=10)
classNumField = models.IntegerField(default=0)
promptField = models.CharField(max_length=300, default="")
And below is the function that uploads the file. I understand how this section works, however it is uploading the additonal data from the forms that confuses me:
views.py
def list(request):
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
newdoc = Document(docfile = request.FILES['docfile'])
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('ReadMyPaper.myapp.views.list'))
else:
form = DocumentForm() # A empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'myapp/list.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
Are you using a ModelForm ? If no, thats how to create one:
# forms.py
from django import forms
from .models import Document
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
And in both cases, how to properly use it:
# views.py
from .forms import DocumentForm
def document_list(request):
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
new_doc = form.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('ReadMyPaper.myapp.views.list'))
else:
form = DocumentForm() # A empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'myapp/list.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
First point : Django forms (the non-ModelForm ones) do two things:
they display their fields, and
they sanitize and validate their data.
So when using Djago forms, you shouldn't have to access request.POST (or request.GET in the rare cases you have a GET form) directly, only form.cleaned_data.
Second point: ModelForms also know
how to create their own fields based on the associated model (but you can restrict / override these fields manually) and
create or update a model instance based on their (validated) data.
So when using a ModelForm, you shouldn't even have to do anything with the model itself in your view (for the most common use cases at least).
When i try to upload file it gives me template error, that its required to fill in. Code:
models:
class ahoja(models.Model):
image = models.ImageField(upload_to='smayat')
forms:
class ahojaForm(ModelForm):
class Meta:
model = ahoja
exclude = ()
view:
def testview(request):
if request.method == 'POST': # pokud form byl odeslan
form = ahojaForm(request.POST, request.FILES) # formular s daty
if form.is_valid():
form.save() #vytvoří událost
return HttpResponseRedirect('/hlavni_stranka/kalendar/')
else:
form = ahojaForm() # prázdný formulář
return render(request, 'hlavni_stranka/test.html', {'form': form,})
The first thing to check is the enctype attribute in your template. From the docs:
Note that request.FILES will only contain data if the request method was POST and the <form> that posted the request has the attribute enctype="multipart/form-data". Otherwise, request.FILES will be empty.
How can I upload images with imagefield? The following is giving me a 'NoneType' object has no attribute 'chunks'.
I believe I am doing this wrong, can someone show me the correct way of doing this?
This is what I have so far for saving the uploaded image.
def add_employee(request):
if request.method == 'POST':
form_input = AddEmployee(request.POST, request.FILES)
if form_input.is_valid():
cd = form_input.cleaned_data
new_emp = Employees(
first_name = cd['first_name']
.....
)
new_emp.save()
photo_file = cd['photo_file']
new_emp.photo.save('filename', photo_file)
return HttpResponseRedirect('/thanks/')
forms.py and models.py
class AddEmployee(forms.Form):
...
photo_file = forms.ImageField(required=False)
class Employees(models.Model):
...
photo = models.ImageField(upload_to='employee_photos', blank=True, null=True)
Okay after some digging around I found out what the problem was.
request.FILES is getting nothing, hence the NoneType i needed to add enctype=multipart/form-data in my form in order for the request to work.
You should probably be using modelforms. With a modelform, your code would look something like this:
in forms.py:
from django import forms
from .models import Employees
class EmployeeForm(forms.ModelForm):
class Meta:
model = Employees
in views.py:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import EmployeeForm
def add_employee(request):
form = EmployeeForm(request.POST or None, request.FILES or None)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thanks/')
return render(request, 'your_template.html', {'form': form})
This is the standard/best practice way to deal with forms that are related to models. The request.POST or None is a trick to avoid having to check request.method == 'POST'.
This is the simple case, you can easily add select fields to be included/excluded from your model, add extra fields for a specific form, add extra logic to be run before your model is saved, or add custom validation.
See the documentation for ModelForms: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/