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).
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})
After logging in, my users complete a four forms and a pdf is generated containing the data from the forms. I can successfully do this and save the pdf, but dont know how to attach the generated pdf to the user. I usually save form data in an if request.method == 'POST': statement in the view.py file, but the pdf isnt being sent through POST, it is instead generated and saved within a function called after if request.method == 'POST': so I guess i need a way to attach it to the users pdf model within that function?
Also, the questionnaire is quite large so I broke it into four separate forms and four seperate models each with a one-to-one relationship with the user. Is this good practice or should I create one large model and break it up using four separate forms?
utils.py
def generate_questionnaire_pdf(request):
# get user
user = request.user
# create pdf
pdf = SimpleDocTemplate(settings.MEDIA_ROOT+'\pdfs\\questionnaire_pdfs\\user_%s.pdf' %user.id)
[..cont..]
pdf.build(elems, onFirstPage=partial(pageSetup, title=title, user=user), onLaterPages=pageSetup)
form.py
class QuestionnairePdfForm(forms.ModelForm):
class Meta:
model = QuestionnairePdf
fields = ['questionnaire_pdf']
model.py
class QuestionnairePdf(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
questionnaire_pdf = models.FileField(upload_to='pdfs\\questionnaire_pdfs')
view.py
def final_question(request):
if request.method == 'POST':
form = FinalQuestionForm(request.POST, request.FILES, instance=request.user.finalquestion)
if form.is_valid():
form.save()
# generate pdf containing all answers to questionnaire
utils.generate_questionnaire_pdf(request)
Thank you.
So I'm working on a portfolio site, and I've got a form called Work that represents the works in the portfolio. A Work has an M2M field for normal images, and one ImageField for the main image that is to be used for thumbnails etc.
My problem is the following, when I go to the update view I created for Work, the old Image (that is already in the database) is listed in the form, but no in the field itself. It says current: [name of the image], and then the regular filefield with label edit.
I don't want the user to only be able to update a work if they upload the image again. How do I pass the current image to the form?
#models.py
class Work(models.Model):
name = models.CharField(max_length=255)
image = models.ImageField( upload_to="images" )
#forms.py
class Meta:
model = Work
exclude = ('slug',)
#views.py
def workEdit(request, pk):
if request.method == "POST":
form = WorkForm(request.POST, request.FILES)
if form.is_valid():
new_work = form.save(commit=True)
return redirect("/portfolio/beheer/werk")
else:
print(form)
print(form.errors)
else:
work = get_object_or_404(Work, pk=pk)
form = WorkForm(request.POST ,request.FILES, instance=work)
context = {
'form': form,
}
return render(request, 'submit.html', context)
I'm looking to get the number of objects a user uploaded into the database. For example:
Let's say i have a model called "Link" (which adds a url with its title and description) into my models.py:
class Link(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=200)
link = models.URLField(max_length=200)
description = models.TextField()
With its form:
class UrlForm(ModelForm):
class Meta:
model = Link
And its view:
def linkurl(request):
form = UrlForm()
if request.method == 'POST':
form = UrlForm(data=request.POST, instance=Link())
if form.is_valid():
form.instance.user = request.user
form.save()
return redirect("home")
else:
form = UrlForm()
return render(request, "addurl.html", {'form': form})
So, knowing that the user can add urls to my app, i'm looking to print the number of urls the user uploaded into my app. Is that actually possible?
Thank you, in advance.
Use filter and count:
# change request.user to user id or user instance you need
user_links_count = Link.objects.filter(user=request.user).count()
There are multiple ways to achieve this.
Use a query and pass it into your template:
ndpu has already pointed it out here:
https://stackoverflow.com/a/22288960/659900
user_links_count = Link.objects.filter(user=request.user).count()
render(request, "addurl.html", {'form': form, linkscount: user_links_count})
Use a model property which you can use to access your model over the form directly, without touching the render method in your view.
add the following method to your link model: (warning: untested!)
#property
def user_links_count(self):
try:
return self.objects.filter(user=self.user).count()
except:
return 0 #or any error message you want
now in your template, you can access your link model directly via the form you are using. assuming you are not using an empty model:
{{ form._meta.model.user_links_count }}
However I would recommend ndpu's solution for a one off solution. Consider an enhancement to your model if you need this functionality more often
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'),