Django ModelForm ManyToManyField isn't able to update selected values - python

I am building my first project in Django 1.8 with python 3.4. I have the following model called Lid in models.py:
class Lid(models.Model):
...
vereniging = models.ManyToManyField(Vereniging, blank=True)
I use the following ModelForm, forms.py
class LidForm(forms.ModelForm):
class Meta:
model = Lid
exclude = []
When I use this ModelForm to create a form to make a new object, a multiple select box appears and I am able to select multipe Vereniging objects. This is my view in views.py:
def add_lid(request):
if request.method == 'POST':
form = LidForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, 'Succes.')
return HttpResponseRedirect(reverse('leden:home'))
else:
form = LidForm()
return render(request, 'leden/lid/addlid.html', {'formset': form})
When I want to edit my objects however, I am not able to change the selected selected Vereniging objects.
def edit_lid(request, lid_id):
lid = get_object_or_404(Lid, pk=lid_id)
if request.method == 'POST':
form = LidForm(request.POST, request.FILES, instance=lid)
if form.is_valid():
nieuwlid = form.save(commit=False)
nieuwlid.inschrijving_oras = lid.inschrijving_oras
nieuwlid.save()
messages.success(request, 'Success.')
return HttpResponseRedirect(reverse('leden:lid', kwargs={'lid_id': lid_id}))
else:
form = LidForm(instance=lid)
return render(request, 'leden/lid/editlid.html', {'formset': form, 'lid': lid})
So this is basically my problem: when using a ModelForm, I am only able to set ManyToMany relationships when creating an object. I am not able to update these m2m relationships. Do you know what I am doing wrong?

Use save_m2m(). From the docs:
Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you’ve manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.

Related

How to configure Django generic view for both GET and POST using model form

I want to create a view function + template that displays a simple form (derived from a user model) and also captures the form submission. How do I do this using generic views in Django?
My user model is:
class User(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
I only need to capture the email field in the form.
I think there must be a simple way to do this using generic forms, however I'm not sure which one to use nor how to do it. The only other ways I know how to do it are:
1) Create UserForm explicitly and a single view function separating POST and GET requests. E.g., :
def contact(request):
if request.method == 'GET':
# display UserForm
....
elif request.method == 'POST':
# process form submission
....
2) Create two views (with seperate URLs) - one using generic view to display form and another view to receive form submission e.g.,:
class contact(generic.DetailView):
# display form from User model
model = User
....
def submit(request):
# process form submission
....
So, my two questions are:
can and how should this be implemented using ONLY a generic view?
which generic view should be used?
First part of the answer: use a single view. If you use a function view (which is by far the simplest solution), the canonical form-handling edit view looks like:
def myview(request, instance_id, ...):
instance = get_object_or_404(pk=instance_id)
if request.method == "POST":
form = MyForm(request.POST, ..., instance=instance)
if form.is_valid():
# assuming a ModelForm
form.save()
return redirect(somewhere)
# invalid forms will be re-rendered with the error messages
else:
form = MyForm(instance=instance)
return render(request, "myapp/mytemplate.html", {"form": form})
For a create view, you just remove all the instance_xxx parts. Or you can use the same view for both create and update making the instance_id optional:
def myview(request, instance_id=None, ...):
if instance_id is not None:
instance = get_object_or_404(pk=instance_id)
else:
instance = None
if request.method == "POST":
form = MyForm(request.POST, ..., instance=instance)
if form.is_valid():
# assuming a ModelForm
form.save()
return redirect(somewhere)
# invalid forms will be re-rendered with the error messages
else:
form = MyForm(instance=instance)
return render(request, "myapp/mytemplate.html", {"form": form})
If you want a class-based generic view they are documented here. I personally don't think there's much to gain from generic class-based views (except eventually headaches when you try to grasp the execution flow scattered amongst half a dozen base classes and mixins) but YMMV.
update
if I want to do some processing on the data (including adding in extra fields) before saving an instance to the DB, where would I do this?
Preferably in the form itself unless you need some other data that you don't want to pass to the form. For all forms you can process data at the validation stage. With a ModelForm you can also override the save() method itself:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('whatever', 'something')
def save(self, commit=True):
""" Save user and create a pro account """
instance = super(MyModelForm, self).save(commit=False)
instance.something_else = 42
if commit:
instance.save()
return instance
CreateView can work perfectly according to your requirements, You only need to create a form of contact models and success_url where user will redirect after form submission. It'll save automatic user data into models
class ContactView(generic.CreateView):
form_class = ContactForm
template_name = 'contact/index.html'
success_url = '/homepage/' . #Mention URL here
This can also be done using only CreateView.Specify email in fields as you need only the email field in the form.You can also process submitted form in form_valid method.
class UserCreate(CreateView):
model = User
fields = ['email']
success_url = '/your_success_url/'
#transaction.atomic
def form_valid(self, form):
new_user = form.save(commit=False)
# process your submitted form here.
# eg. add any extra fields as:
# new_user.something = something
new_user.save()
return super().form_valid(form)

How do I `save()` a Profile model if it has a OneToOne relationship to my AbstractBaseUser model without running into UNIQUE constraint errors?

I have two separate models:
1. A MyUser model which inherits from AbstractBaseUser, and has a field of profile_page = models.OneToOneField(Profile, null=True)
2. A Profile model with a user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True) relationship to the User model.
I am attempting to allow users of the site to edit their own Profile information by providing them a ProfileForm ModelForm.
In my user_profile/views.py I have this profile_edit FBV:
def profile_edit(request):
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
form.instance.user = request.user
form.instance.save()
return redirect('/profile/edit')
else:
form = ProfileForm(instance=request.user)
print(request.user)
print('hello get req here')
context = {
'form': form,
}
return render(request, 'profile_edit.html', context)
When I attempt to update profile information in profile_edit.html, the POST data will go through the first time, but not get saved to the DB. On a second attempt, I receive a UNIQUE constraint failed: user_profile_profile.user_id error.
form.instance.save() is pointed to as the direct cause of the exception.
In my estimation the error has something to do with the fact that upon creation of a new user, an initial unique ID is created for the user. So when I try to save() the Profile object, I think it is attempting to save() a new User, thereby causing the Unique Constraint failure.
How can I configure the two models in such a way that upon creation of a new User, the User has the ability to update their own Profile information? What should change in my views.py?
You're doing two things wrong, both to do with the instance argument.
Firstly, in the GET block, you're passing request.user as the instance. But this is a Profile form; you need to pass the user profile, not the user itself:
form = ProfileForm(instance=request.user.profile)
Secondly, in the POST block, you're not passing an instance at all. This means that Django won't know to update an existing item, but will try and create a new one. Again you need to pass the profile:
form = ProfileForm(request.POST, instance=request.user.profile)
Note also though, you should consider whether you need a separate Profile model at all. Initially that was the recommended way to provide custom user information, but for several versions Django has given you a way to use a customised User model - which you are in fact doing. You probably want to put the profile data directly in MyUser, rather than in a separate model with a one-to-one relationship.
Edit after comment It sounds like you're not automatically creating a profile when you create the user. You could do something like this in the view:
def profile_edit(request):
try:
profile = request.user.profile
except Profile.DoesNotExist:
profile = Profile(user=request.user)
if request.method == 'POST':
form = ProfileForm(request.POST, instance=profile)
...
else:
form = ProfileForm(instance=profile)

Two models in django

I am learning Django so I don´t know about this.
What is happening is that I have two tables.
Table BlogPost : Save all post.
Table Categoria : Save the ID of the category of register post.
My model.py
class BlogPost(models.Model):
title=models.CharField(max_length=150)
author = models.ForeignKey(User)
categorias_post = models.ManyToManyField(Categoria)
body = RichTextField(('Content of post'))
creada_en = models.DateTimeField(auto_now_add=True)
actualizada_al = models.DateTimeField(auto_now=True)
My forms.py
class FormularioPost(forms.ModelForm):
class Meta:
model = BlogPost
fields = ('title', 'author', 'categorias_post', 'body')
My views.py
def postregistrado(request):
if request.method == "POST":
form = FormularioPost(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save
messages.success(request, 'Su post ha sido registrado con éxito.')
else:
form = FormularioPost()
return render_to_response(
"postRegistrado.html",
locals(),
context_instance=RequestContext(request),
)
I want to insert in two different tables from the same views.py. Can anyone help me with that?
When you are using commit=False, you have to explicitly call save_m2m() to save the many to many fields.
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save() #Note that it is a function call.
post.save_m2m()
You can read more on this in the documentation here
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn’t possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you’ve manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
Another thing is, make sure you add the login_required decorator on this view, so that you don't run into weird issues when post.author = request.user evaluates to anonymous users

How can I change form field values after calling the is_valid() method?

How can I change form field values after calling the is_valid() method?
I am trying to alter the field u_id after I validate the data with form.is_valid (this is required). I can alter the data, even display it in the HttpResponse, but I cannot write it into my Postgresql DB. Any ideas?
class ProductForm(forms.ModelForm):
class Meta:
model = Product
class Product(models.Model):
p_name = models.CharField(max_length=30)
u_id = models.CharField(max_length=80)
def uploadImage(request):
if request.method == 'POST':
form1 = ProductForm(request.POST, prefix="product")
if form.is_valid() and form1.is_valid():
form1.cleaned_data['uid']='12134324231'
form1.save()
return HttpResponse(form1.cleaned_data['p_name'])
return render_to_response('upload.html', {'form': form, 'form1': form1}, RequestContext(request))
Save the model form with commit=False, then modify the instance before saving to the database.
if form.is_valid() and form1.is_valid():
instance = form1.save(commit=False)
instance.uid = '12134324231'
instance.save()
If form1 had any many-to-many relationships, you would have to call the save_m2m method to save the many-to-many form data. See the docs for full details.
From Overriding clean() on a ModelFormSet.
Also note that by the time you reach this step, individual model instances have already been created for each Form. Modifying a value in form.cleaned_data is not sufficient to affect the saved value. If you wish to modify a value in ModelFormSet.clean() you must modify form.instance:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super(MyModelFormSet, self).clean()
for form in self.forms:
name = form.cleaned_data['name'].upper()
form.cleaned_data['name'] = name
# update the instance value.
form.instance.name = name
So what you should do is:
if form.is_valid() and form1.is_valid():
form1.instance.uid ='12134324231'
form1.save()

Difference between Django ModelForm and Model instance save methods

I'm trying to understand the difference between Django's ModelForm save method and saving the Model instance directly.
Personally, I find saving directly more intuitive and more clearly shows when the data is saved. Plus, if I need to modify the instance before saving, then I have to use the Model save method as the Django documentation explains here.
So, once the form is validated, what is the difference? Would there be a difference if the form used multiple models or some other more complex use case?
I'm using Django version 1.4 if that matters. And below is some code showing how I tend to save validated form data.
Thanks in advance!
# models.py
class Project(models.Model):
project_name = models.CharField(unique=True, null=False, blank=False)
# views.py
def add_project(request):
if request.method == 'POST':
project = Project()
form = ProjectForm(request.POST, instance=project)
if form.is_valid():
project.save() ### <-- project.save() vs form.save() ###
return HttpResponseRedirect(reverse('view_project', args=(project.id,)))
else:
form = ProjectForm()
return render_to_response(
'add_project.html',
{
'form': form,
},
context_instance=RequestContext(request)
)
# forms.py
class ProjectForm(ModelForm):
class Meta:
model = Project
In the commented line you have, project.save() simply won't do anything. The instance has not been updated with the form data, it is simply the empty instance you created two lines before. The only way to update an existing instance is by saving its form.
ModelForm.save() returns an object saved from the data that was put into the form, Model.save() returns an object from the data that the object was initialized with or values that were set after it was created. So when it comes to getting the data from what the user inputted on the form to a persisted object, it makes more sense to call ModelForm.save() rather than going through the work of validating the data yourself, initializing the object and then saving it because all that work is handled by the ModelForm.

Categories