Multiple Models in a single django ModelForm?
You can just show both forms in the template inside of one <form>
html element. Then just process the forms separately in the view. You'll still be able to use form.save()
and not have to process db loading and saving yourself.
In this case you shouldn't need it, but if you're going to be using forms with the same field names, look into the prefix
kwarg for django forms. (I answered a question about it here).
Multiple models in Django form
Given that the categories are fairly static, you don't want your users selecting the categories. The categories themselves should be labels, not fields for your users to select.
You mention in the comment, that the labels will sometimes change. I think there are two questions I would ask before deciding how to procede here:
- Who is going to update the labels moving forwards (do they have basic coding ability, or are they reliant on using something like the admin).
- When the labels change, will their fundamental meaning change or will it just be phrasing
Consideration 1
If the person changing the labels has a basic grasp of django, and the appropriate permissions (or can ask a dev to make the changes for them) then just hard-coding these 5 things is probably the best way forward at first:
class Review(models.Model):
author = models.ForeignKey(User, related_name="%(class)s_author", on_delete=models.CASCADE)
coach = models.ForeignKey(User, related_name="%(class)s_coach", on_delete=models.CASCADE)
comments = models.TextField()
# Categories go here...
damage = models.SmallIntegerField(
help_text="description can go here",
verbose_name="label goes here"
)
style = models.SmallIntegerField()
control = models.SmallIntegerField()
aggression = models.SmallIntegerField()
This has loads of advantages:
- It's one very simple table that easy to understand, instead of 3 tables with joins.
- This will make everything up and down your code-base simpler. It'll make the current situation (managing forms) easier, but it will also make every query, view, template, report, management command, etc. you write easier, moving forwards.
- You can edit the labels and descriptions as and when needed with
verbose_name
andhelp_text
.
If changing the code like this isn't an option though, and the labels have to be set via something like the django admin-app, then a foreign-key is your only way forward.
Again, you don't really want your users to choose the categories, so I would just dynamically add them as fields, rather than using a formset:
class Category(models.Model):
# the field name will need to be a valid field-name, no space etc.
field_name = models.CharField(max_length=40, unique=True)
label = models.CharField(max_length=40)
description = models.TextField()
class ReviewForm.forms(forms.Form):
coach = forms.ModelChoiceField()
def __init__(self, *args, **kwargs):
return_value = super().__init__(*args, **kwargs)
# Here we dynamically add the category fields
categories = Categories.objects.filter(id__in=[1,2,3,4,5])
for category in categories:
self.fields[category.field_name] = forms.IntegerField(
help_text=category.description,
label=category.label,
required=True,
min_value=1,
max_value=5
)
self.fields['comment'] = forms.CharField(widget=forms.Textarea)
return return_value
Since (I'm assuming) the current user will be the review.author
, your going to need access to request.user
and so we should save all your new objects in the view rather than in the form. Your view:
def add_review(request):
if request.method == "POST":
review_form = ReviewForm(request.POST)
if review_form.is_valid():
data = review_form.cleaned_data
# Save the review
review = Review.objects.create(
author=request.user,
coach=data['coach']
comment=data['comment']
)
# Save the ratings
for category in Category.objects.filter(id__in=[1,2,3,4,5]):
Rating.objects.create(
review=review
category=category
rating=data[category.field_name]
)
# potentially return to a confirmation view at this point
if request.method == "GET":
review_form = ReviewForm()
return render(
request,
"add_review.html",
{
"review_form": review_form
}
)
Consideation 2
To see why point 2 (above) is important, imagine the following:
- You start off with 4 categories: Damage, Style, Control and Agression.
- Your site goes live and some reviews come in. Say Coach Tim McCurrach gets a review with scores of 2,1,3,5 respectively.
- Then a few months down the line we realise 'style' isn't a very useful category, so we change the label to 'effectiveness'.
- Now Tim McCurrach has a rating of '1' saved against a category that used to have label 'style' but now has label 'effectiveness' which isn't what the author of the review meant at all.
- All of your old data is meaningless.
If Style is only ever going to change to things very similar to style we don't need to worry so much about that.
If you do need to change the fundamental nature of labels, I would add an active
field to your Category
model:
class Category(models.Model):
field_name = models.CharField(max_length=40, unique=True)
label = models.CharField(max_length=40)
description = models.TextField()
active = models.BooleanField()
Then in the code above, instead of Category.objects.filter(id__in=[1,2,3,4,5])
I would write, Category.objects.filter(active=True)
. To be honest, I think I would do this either way. Hard-coding ids in your code is bad-practice, and very liable to going wrong. This second method is more flexible anyway.
use two models in a single view that you can save and edit in django
create two Models and use it in the ModelForm:
class UserUpdateForm(ModelForm):
class Meta:
model = User
fields = ["username", "email", "phone_number", "bioghraphy", "avatar"]
class AddressForm(ModelForm):
class Meta:
model = Address
fields = "__all__"
in views.py:
class AccountFormView(TemplateView):
...
def post(self, request, *args, **kwargs):
user_form = UserUpdateForm(
request.POST, prefix="user", instance=self.get_object()
)
address_form = AddressForm(
request.POST, prefix="address", instance=self.get_object_address()
)
if user_form.is_valid():
user_form.save()
return HttpResponseRedirect(
reverse_lazy(
"neeko.apps.accounts:accounts-landing",
kwargs={"pk": self.get_object().pk},
)
)
if address_form.is_valid():
address_form.save()
return HttpResponseRedirect(
reverse_lazy(
"neeko.apps.accounts:accounts-landing",
kwargs={"pk": self.get_object().pk},
)
)
return self.render_to_response(
self.get_context_data(user_form=user_form, address_form=address_form)
)
Template:
<form method="post">
{% csrf_token %}
{{ user_form.as_p }}
<button type="submit">form 1</button>
</form>
<h1>form segin</h1>
<form method="post">
{% csrf_token %}
{{ address_form.as_p }}
<button type="submit">form 2 </button>
</form>
example
https://gist.github.com/josuedjh3/7ea29c70a0167ff34a1216edb5dbed05
Edit multiple Models using one form or view in Django
You can put the 2 forms in one template and Django will manage filling forms with the right fields (only exception is the same field name in 2 forms)
def view(request):
if request.method == "GET":
context["userform"]=UserForm()
context["profileform"] =ProfileForm()
else:
userform = UserForm(request.POST)
profileform=ProfileForm(request.POST)
How to save multiple models with multiple modelForms in one django form-tools WizardView
If is_qualified
is not nullable in your Person
model, validation will always fail. What you can do is save both PersonalForm
and IsQualifiedForm
in one go, since they refer to the same model anyways. To do this, set the values of one form in the other. For example:
def done(self, form_list, **kwargs):
person = form_list[0].save(commit=False)
person.is_qualified = form_list[1].cleaned_data['is_qualified']
person.save()
return redirect('home')
Some notes:
- You should probably use named steps instead of relying on form index
- If your case is as simple as the form you provided, you should just make the first two forms a single form
Django Forms, having multiple Models in Meta class?
No. But you don't need to. Instead of instantiating and validating a single form, do it for each type of form you need to support.
# Define your model forms like you normally would
class StudentForm(ModelForm):
...
class TutorForm(ModelForm):
...
class RegistrationForm(Form):
email = ...
...
# Your (simplified) view:
...
context = {
'student_form': StudentForm(),
'tutor_form': TutorForm(),
'registration_form': RegistrationForm()
}
return render(request, 'app/registration.html', context)
# Your template
...
<form action="." method="post">
{{ student_form }}
{{ tutor_form }}
{{ registration_form }}
<input type="submit" value="Register">
</form>
If this means field names are duplicated across forms, use form prefixes to sort that out.
Related Topics
Clicking on Svg Using Selenium Python
How to Get JSON from Webpage into Python Script
Getting Today's Date in Yyyy-Mm-Dd in Python
Python Readlines() Usage and Efficient Practice for Reading
How to Use Raw Socket in Python
Making an Asynchronous Task in Flask
Best Way to Create a "Reversed" List in Python
Why Aren't Superclass _Init_ Methods Automatically Invoked
N-Grams in Python, Four, Five, Six Grams
Login Credentials Not Working with Gmail Smtp
Differencebetween Pylab and Pyplot
How to Uninstall a Package Installed with Pip Install --User