Dynamic Forms with Foreign Keys in Django - django

I have the following models
class Make(models.Model):
make_name = models.CharField(max_length=32)
def __unicode__(self):
return self.book_title
class MakeModel(models.Model):
model_number = models.IntegerField()
model_capacity = models.IntegerField()
model_capacity_unit = models.CharField(max_length=4)
Make = models.ForeignKey(Make)
def __unicode__(self):
return self.model_number
Basically, I want my form to have all the Make details at the top, and all the MakeModel details in a grid below, and to be able to add more MakeModels with ease. Such as in this mockup
https://www.dropbox.com/s/e7rg3zxe6y8q9fi/invoicedetails.gif
Any ideas what needs doing? People mention things like inlineformset_factory, but I'm not convinced either how to use it, or if it will do the thing I expect.
Any help would be appreciated

You need a form and a formset.
A form: one form
A formset: multiple copies of one form
Your view would look something like this in the POST cycle:
Update: oh right, foreignkey. The easiest way to be honest is just to commit=False the formset and manually assign the FK. You can use an inlineformset as well. https://docs.djangoproject.com/en/dev/ref/forms/models/#django.forms.models.inlineformset_factory
FormSet = modelformset_factory(MakeModel)
Form = modelform_factory(Make)
def myview(request):
formset = FormSet(request.POST or None)
form = Form(request.POST or None)
if request.method == 'POST':
if formset.is_valid() and form.is_valid():
main_instance = form.save()
formset.save(commit=False)
for instance in formset:
instance.fk = main_instance
instance.save()
https://docs.djangoproject.com/en/dev/topics/forms/formsets/

Related

Django get_or_create model function

In one of my recent project, I created a website for users to submit their information in a multi-stage form, in each form I use get_or_create to see if the user submit information previously or not, for example, consider user education model as follows,
class UserEducation(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE)
university_name = models.CharField(max_length=100)
in the view, I have the following code,
def education_view(request):
if request.method == "POST":
uedu, created = UserEducation.objects.get_or_create(user=request.user)
uedu.university_name = request.POST['university_name']
uedu.save()
return HttpResponse("success")
I didn't set uploading for the submit button and the problem is some users have multiple education object!
Does anyone know why this happened and whats is wrong with get_or_create?
Insted you can use update_or_create
uedu, created = UserEducation.objects.update_or_create(
user=request.user,uedu.university_name = request.POST['university_name'],
defaults={'user': 'default_value'},
)
I think the reason is that, as every time you moved on to the next step, Django will think that you tell it to create a new object because, as every time you submit a form, a new Model will be created.
What you should do is to halt the process until everything is finished. Something like:
class Person(models.Model):
fn = models.CharField(max_length=40)
class Pet(models.Model):
owner = models.ForeignKey(Person)
name = models.CharField(max_length=40)
class PersonForm(forms.ModelForm):
class Meta:
model = Person
class PetForm(forms.ModelForm):
class Meta:
model = Pet
exclude = ('owner',)
#views
def step1(request):
initial={'fn': request.session.get('fn', None)}
form = PersonForm(request.POST or None, initial=initial)
if request.method == 'POST':
if form.is_valid():
request.session['fn'] = form.cleaned_data['fn']
return HttpResponseRedirect(reverse('step2'))
return render(request, 'step1.html', {'form': form})
def step2(request):
form = PetForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
pet = form.save(commit=False)
person = Person.objects.create(fn=request.session['fn'])
pet.owner = person
pet.save()
return HttpResponseRedirect(reverse('finished'))
return render(request, 'step2.html', {'form': form})
Reference
I find the following note in the documentation,
Warning
This method is atomic assuming that the database enforces uniqueness
of the keyword arguments (see unique or unique_together). If the
fields used in the keyword arguments do not have a uniqueness
constraint, concurrent calls to this method may result in multiple
rows with the same parameters being inserted.
the university does not have a unique constraint on the user foreign key and as the result, multiple objects will be saved in the concurrent calls.

Hiding foreign key fields from ModelForms in Django

I am using django ModelForms to generate my input forms.
I specify in my form model to only use a set of fields:
class <Model>Form(ModelForm):
class Meta:
model = <Model>
fields = ('date', 'comment_1')
My model is defined as:
class <Model>(models.Model):
fk_id_1 = models.ForeignKey(<ExternalModel1>, null=False, blank=False)
fk_id_2 = models.ForeignKey(<ExternalModel2>, null=False, blank=False)
date = models.DateField()
comment_1 = models.CharField(max_length=100)
comment_2 = models.CharField(max_length=100)
However, the ForeignKey boxes show.
How is it possible for me to hide them from the form? Also, how can I set the values for those dropboxes from within the view and not sure, say JQuery externally to do it? Ideally, after the ''is_valid()'' check, I would like to set the IDs of my Foreign Keys and then do save. Maybe I should look into solving this using another way?
This is the View:
def <Model>_add(request, trainee_id):
<Model>FormSet = modelformset_factory(<Model>)
if request.method == 'POST':
formset = <Model>FormSet(request.POST, request.FILES)
if formset.is_valid() and formset.has_changed():
formset.save()
# do something.
else:
formset = <Model>FormSet(queryset=<Model>.objects.none())
return render_to_response("<Model>_add.html", {
"formset": formset, "fk_id_1": fk_id_1,
}, context_instance=RequestContext(request))
I can solve this issue using JQuery but I would like a more elegant approach.
Note: I tried posting this earlier but I think it was not as clear as it is here: Presetting values on a foreign entity relationship in a ModelForm ... I didn't understand exactly what was said about QuerySet.
You need to be a bit more explicit in how you define the form:
class <Model>Form(ModelForm):
class Meta:
model = <Model>
fields = ['date', 'comment_1']
exclude = ['fk_id_1', 'fk_id_2']
Then in your view:
from django.shortcuts import render, redirect
def <Model>_add(request, trainee_id):
<Model>FormSet = modelformset_factory(<Model>)
if request.method == 'POST':
formset = <Model>FormSet(request.POST, request.FILES)
if formset.is_valid() and formset.has_changed():
forms = formset.save(commit=False)
for form in forms:
form.fk_id_1 = SomeOtherModel.objects.get(pk=1)
form.fk_id_2 = SomeOtherModel.objects.get(pk=2)
form.save()
# add your success redirect here, for example:
return redirect('/')
else:
formset = <Model>FormSet(queryset=<Model>.objects.none())
return render(request, "<Model>_add.html", {"formset": formset})
Every ModelForm also has a save() method. doc
or:
in views.py
form.instance.fk_id_1 = ...
form.instance.fk_id_2 = ...
form.save()

What's the best way to include a ModelForm from a different Model in a Template?

I have the following models:
class Picture(models.Model):
title = models.CharField(max_length=255)
(etc)
And
class Report(models.Model):
complaint = models.TextField()
picture = models.ForeignKey('Picture')
What i'd like to be able to do is
a) Include the report ModelForm in the 'Picture' ModelView template
b) Prepopulate the picture field in the report Modelform with the relevant picture
I've been messing around with contextprocessors, inclusiontags and trying to pass the variable through a querystring to a new page, but they all seem quite complex and to not work correctly.
What I'd like help with is understanding the most 'django' way to do this, and any pointers to getting this done.
Solution
With Scott's help, the working solution is:
views.py
def picture(request, slug):
picture = Picture.objects.get(slug=str(slug))
d = dict(picture=picture, form=ReportPicture())
d.update(csrf(request))
if request.method == 'POST':
form = ReportPicture(request.POST)
if form.is_valid():
report = form.save(commit=False)
report.picture = picture
report.save()
return redirect(picture.get_absolute_url())
return render_to_response("picture_detail.html", d)
I believe you are wanting inline formsets (docs).
# models.py
class Picture(models.Model):
title = models.CharField(max_length=255)
class Report(models.Model):
complaint = models.TextField()
picture = models.ForeignKey('Picture')
# views.py
from django.forms.models import inlineformset_factory
from .models import Picture, Report
def manage_picture(request, pk):
picture = Picture.objects.get(pk=pk)
ReportInlineFormSet = inlineformset_factory(Picture, Report)
if request.method == "POST":
formset = ReportInlineFormSet(request.POST, request.FILES, instance=picture)
if formset.is_valid():
formset.save()
return HttpResponseRedirect(picture.get_absolute_url())
else:
formset = ReportInlineFormSet(instance=picture)
return render_to_response("manage_picture.html", {"formset": formset})
If you're using Class Based Views Django doesn't natively offer any inline formset support but there is an excellent app that handles most of this for you called django-extra_views.

Changing django form values

I have a form that gets values from a database created by a model. Say my the table has 2 columns, city and code, and I display just the city in my form using a ModelChoiceField.
When the use submits the form and I am going through the validation process, I would like to change the value of the city the user has selected with it's code.
models.py
class Location(models.Model):
city = models.CharField(max_length=200)
code = models.CharField(max_length=10)
def __unicode__(self):
return self.city
forms.py
city = forms.ModelChoiceField(queryset=Location.objects.all(),label='City')
views.py
def profile(request):
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
???????
How could I do this?
Thanks - Oli
You can do this:
def profile(request):
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
#Retrieve the city's code and add it to the profile
location = Location.objects.get(pk=form.cleaned_data['city'])
profile.city = location.code
profile.save()
However you should be able to have the form setting the code directly in the ModelChoiceField. Check here and the django docs
I would overwrite the save method of the form. And change the field there. That way you still would have a clean view where all logic related to the form stays contained within the form.

Django Form problems with UserProfile

I'd like to create a "update user's profile" page to let users modify their profiles, so I come up with the following models:
class Profile(models.Model):
user = models.OneToOneField(User)
nick_name = models.CharField(blank=True,max_length=100)
school = models.CharField(blank=True,max_length=100)
motto = models.CharField(blank=True,max_length=100)
class ProfileForm(ModelForm):
class Meta:
model = Profile
And my view is designed as:
#login_required
def update_profile_view(request):
if request.method == 'POST':
user = request.user
try:
profile = user.get_profile()
except Exception:
profile = Profile.objects.create(user=user)
form = ProfileForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
profile.nick_name = cd['nick_name']
profile.school = cd['school']
profile.motto = cd['motto']
profile.save()
return HttpResponseRedirect('/main_page/')
else:
form = ProfileForm()
return render(request, 'update_profile.html', {'form':form})
The relationship between an user and a profile is apparently 1to1, and with request I can determine the current user. So the form's user field needn't to be filled. Unfortunately, this couldn't pass "the form.is_valid()" test. And it seems hard to modify a form before "is_valid" invoked. For simplicity, I don't want to create my own Form Class, neither do I want to write customized form validation. Is there any other way to solve the problem?
Your view can be greatly simplified:
#login_required
def update_profile_view(request):
try:
profile = Profile.objects.get(user=request.user)
except Profile.DoesNotExist:
profile = None
form = ProfileForm(request.POST or None, instance=profile)
if request.method == 'POST':
if form.is_valid():
form.save()
return HttpResponseRedirect('/main_page/')
return render(request, 'update_profile.html', {'form':form})
There's no need to manually assign the fields like you're doing. Django ORM knows how to do an insert versus an update automatically. So if you simply pass the ProfileForm an instance of a Profile, it knows to do an update. If there's no instance of a profile, it's going to do an insert.
Now, if you want to make the assignment of the user transparent in the UI, you'll need to exclude the user field from the form and assign it yourself. There are a couple of different ways to do that.
I would also recommend leveraging reverse in your redirect so you don't have a hard-coded path.
You have basicly two choices:
1 Modification of ProfileForm:
class ProfileForm(ModelForm):
class Meta:
model = Profileclass
exclude = ('user',)
2 Change this lines as follows:
form = ProfileForm(request.POST, instance=profile)
if form.is_valid():
updated_profile = form.save()
You can either set the user field's value to not required in the init method (self.fields['user'].required = False) of the form or set the user not editable in the model (editable=False).
In your view method, call profile = form.save(commit=False), then do profile.user = your_user and profile.save()
You don't have to apply the cleaned data manually to the profile since the ModelForm does this.