How to bind a django form to an object instance - python

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})

Related

Cannot add model instance to OneToManyField in django - "None has no attribute add"

I already have seen this bug in other post, but still in trouble.
I'm trying to create a social network like instagram where users will be able to publish posts (photos).
I have User class which herit from AbstractUser, and got a OneToMany field of posts: each user can publish many posts.
After successfully pulling my photo from: PostForm(request.POST, request.FILES) and saving it correctly, I cannot add this photo to the current user's publications/posts and got error:
'NoneType' object has no attribute 'add'
def blog_and_photo_upload(request):
form = PostForm()
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
user = get_user(request) # user instance is correct with good pk
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.save()
user.posts.add(post) # row doesnt work
redirect('home')
return render(request, 'base/upload_post.html', {'form': form})
models.py
class Post(models.Model):
...
image = ResizedImageField(size=[300, 300], blank=True, upload_to='posts')
class User(AbstractUser):
...
posts = models.ForeignKey(Post, on_delete=models.Cascade, null=True)
You can simply update the form like this:
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.posts = post
user.save()
return redirect('home')
But, I think the design of the model is wrong, User to Post relation should be like this:
Class User(...):
posts = models.ManyToManyField(Post)
In that way, your original implementation should work. (Probably you don't need user.save() call in your view).
At first there should be return redirect(...) not only redirect() and secondly try to use the following view:
def blog_and_photo_upload(request):
form = PostForm()
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
user = get_user(request) # user instance is correct with good pk
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.posts.add(post) # add post to user's posts field
user.save()
return redirect('home')
return render(request, 'base/upload_post.html', {'form': form})
You need to bind first Post with User model like add a ForeignKey or a ManyToManyFields to relate them
posts = models.ForeignKey(User)
then you will be able to call it like you did
user.posts # this won't return None
Check this many to many field docs: https://docs.djangoproject.com/en/4.1/topics/db/examples/many_to_many/

Django 1.6: How to print the number of objects a user uploaded?

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

django imagefield save NoneType Object error

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/

django newbie. Having trouble with ModelForm

i'm trying to write a very simple django app. I cant get it to show my form inside my template.
<form ...>
{{form.as_p}}
</form>
it shows absolutely nothing. If I add a submit button, it only shows that.
Do I have to declare a form object that inherits from forms.Form ? Cant it be done with ModelForms?
[UPDATE]Solved! (apologize for wasting your time)
In my urls file I had:
(r'login/$',direct_to_template, {'template':'register.html'}
Switched to:
(r'login/$','portal.views.register')
And yes, I feel terrible.
Background:
I have a Student model, and I have a registration page. When it is accessed, it should display a textfield asking for students name. If the student completes that field, then it saves it.
#models.py
class Student(models.Model):
name = models.CharField(max_length =50)
#forms.py
class StudentForm (forms.ModelForm):
class Meta:
model = Student
So, here is my view:
def register(request):
if request.method == 'POST':
form = StudentForm(request.POST)
if form.is_valid():
form.save()
return render_to_response('/thanks/')
else:
student = Student()
form = StudentForm(instance =student)
return render_to_response('register.html',{'form':form})
The problem is in your view. You will have no existing student object to retrieve from the database. The following code sample will help you implement an "create" view.
As a side note, you might like using the direct_to_template generic view function to make your life a bit easier.
def add_student(request):
if request.method == 'POST':
form = StudentForm(request.POST)
if form.is_valid():
new_student = form.save()
return HttpResponseRedirect('/back/to/somewhere/on/success/')
else:
form = StudentForm()
return direct_to_template(request,
'register.html',
{'form':form})

Django form fails validation on a unique field

I have a simple model that is defined as:
class Article(models.Model):
slug = models.SlugField(max_length=50, unique=True)
title = models.CharField(max_length=100, unique=False)
and the form:
class ArticleForm(ModelForm):
class Meta:
model = Article
The validation here fails when I try to update an existing row:
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid(): # POOF
form.save()
Creating a new entry is fine, however, when I try to update any of these fields, the validation no longer passes.
The "errors" property had nothing, but I dropped into the debugger and deep within the Django guts I saw this:
slug: "Article with this None already exists"
So it looks like is_valid() fails on a unique value check, but all I want to do is update the row.
I can't just do:
form.save(force_update=True)
... because the form will fail on validation.
This looks like something very simple, but I just can't figure it out.
I am running Django 1.0.2
What croaks is BaseModelForm.validate_unique() which is called on form initialization.
I don't think you are actually updating an existing article, but instead creating a new one, presumably with more or less the same content, especially the slug, and thus you will get an error. It is a bit strange that you don't get better error reporting, but also I do not know what the rest of your view looks like.
What if you where to try something along these lines (I have included a bit more of a possible view function, change it to fit your needs); I haven't actually tested my code, so I am sure I've made at least one mistake, but you should at least get the general idea:
def article_update(request, id):
article = get_objects_or_404(Article, pk=id)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return HttpResponseRedirect(to-some-suitable-url)
else:
form = ArticleForm(instance=article)
return render_to_response('article_update.html', { 'form': form })
The thing is, as taurean noted, you should instantiate your model form with the object you wish to update, otherwise you will get a new one.
I was also searching for a way to update an existing record, even tried form.save(force_update=True) but received errors??
Finally by trial & error managed to update existing record. Below codes tested working. Hope this helps...
models.py from djangobook
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField(blank=True, verbose_name='e-mail')
objects = models.Manager()
sel_objects=AuthorManager()
def __unicode__(self):
return self.first_name+' '+ self.last_name
class AuthorForm(ModelForm):
class Meta:
model = Author
# views.py
# add new record
def authorcontact(request):
if request.method == 'POST':
form = AuthorForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
form = AuthorForm()
return render_to_response('author_form.html', {'form': form})
update existing record
def authorcontactupd(request,id):
if request.method == 'POST':
a=Author.objects.get(pk=int(id))
form = AuthorForm(request.POST, instance=a)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
a=Author.objects.get(pk=int(id))
form = AuthorForm(instance=a)
return render_to_response('author_form.html', {'form': form})
All i can guess is that you are getting an object to fill a form, and trying to save it again.
Try using a ModelForm, and intantiate it with desired object.
It appears that your SlugField is returning None and because a null/blank slug already exists somewhere in the database, its giving an 'already exists' error. It seems like your slug field isn't saving correctly at all.

Categories

Resources