After a ModelForm has been submitted, how can I add a foreign key relation so that it validates?
models.py
class Comment(models.Model):
id = models.AutoField(primary_key=True)
activity = models.ForeignKey(Activity)
submitter = models.ForeignKey(User)
creation_date = models.DateTimeField(auto_now_add=True)
content = models.TextField()
forms.py
class CommentForm(forms.ModelForm):
content = forms.CharField(widget=forms.Textarea)
class Meta:
model = Comment
views.py
def index(request, id=None):
activity_instance = Activity.objects.get(pk=1)
submitter_instance = User.objects.get(id=1)
newComment = CommentForm(request.POST)
newComment.activity = activity_instance
newComment.submitter = submitter_instance
if newComment.is_valid(): # <-- false, which is the problem
I think you are mixing up form instance with model instance. your newComment is a form, assigning other objects as a form attribute will not make the form saving the foreign key(not sure where did you find this usage), because all form data is saved in form.data, which is a dict like data structure.
I'm not sure what does your form look like because you didn't exclude the foreign keys so they should be rendered as dropdowns and you could select them. If you don't want the user to select the foreign key but choose to assign the values as you currently do, you should exclude them in the form so form.is_valid() would pass:
class CommentForm(forms.ModelForm):
content = forms.CharField(widget=forms.Textarea)
class Meta:
model = Comment
exclude = ('activity', 'submitter')
views.py
def index(request, id=None):
activity_instance = Activity.objects.get(pk=1)
submitter_instance = User.objects.get(id=1)
comment_form = CommentForm(request.POST)
if comment_form.is_valid():
new_comment = comment_form.save(commit=False)
new_comment.activity = activity_instance
new_comment.submitter = submitter_instance
new_comment.save()
Django doc about save() method.
Related
So this is the scenario. I allow my user to first input their vehicle brand with one form and then use another form to list down the models available for that vehicle brand. The information on the vehicles and the brands is stored in my database.
Refer to this image to get a better idea:
And this is my views.py:
def driver_dashboard_trip_brand (request, brand):
if request.method == "POST":
form = AddVehicleForm(request.POST)
else:
form = AddVehicleForm()
brands = VehicleBrand.objects.all()
context = {
"form":form,
"brands":brands,
"chosen_brand":brand
}
return render (request, "app/driver_dashboard.html", context)
And my forms.py:
class AddVehicleForm(forms.ModelForm):
model = forms.ModelChoiceField(queryset=VehicleModel.objects.all())
vehicle_colour = forms.ChoiceField(choices=COLOURS)
vehicle_number = forms.CharField(max_length=8, widget=forms.TextInput(attrs={'placeholder': 'eg: CAB-1234'}))
class Meta:
model = Vehicle
fields = ['model', 'vehicle_colour', 'vehicle_number']
So in order to set a query in the forms.py, I would first need to send the data from views.py to forms.py, and then I also need to do a query.
So my question is how can I query for all the car models from the VehicleModel database and create choices attribute for the form, once the user chooses the car brand.
My models.py...
class VehicleModel (models.Model):
brand = models.ForeignKey(VehicleBrand, on_delete=models.CASCADE)
model = models.CharField(max_length=30)
def __str__ (self):
return f"{self.brand} - {self.model}"
Its honestly not so hard, i kinda figured it out...
So this is my forms.py...
class AddVehicleForm(forms.ModelForm):
def __init__(self, brand=None, *args, **kwargs):
super(AddVehicleForm, self).__init__(*args, **kwargs)
self.fields['model'].queryset = VehicleModel.objects.filter(brand=brand)
model = forms.ModelChoiceField(queryset=VehicleModel.objects.all())
vehicle_colour = forms.ChoiceField(choices=COLOURS)
vehicle_number = forms.CharField(max_length=8, widget=forms.TextInput(attrs={'placeholder': 'eg: CAB-1234'}))
class Meta:
model = Vehicle
fields = ['model', 'vehicle_colour', 'vehicle_number']
class AddVehicleFormPost(forms.ModelForm):
model = forms.ModelChoiceField(queryset=VehicleModel.objects.all())
vehicle_colour = forms.ChoiceField(choices=COLOURS)
vehicle_number = forms.CharField(max_length=8, widget=forms.TextInput(attrs={'placeholder': 'eg: CAB-1234'}))
class Meta:
model = Vehicle
fields = ['model', 'vehicle_colour', 'vehicle_number']
Where the form AddVehicleForm allowed me to send the parameter as shown by typing form = AddVehicleForm(VehicleBrand.objects.filter(brand=brand).first()) in my views.py, but then when I wanted to save my form I needed to create another form in the forms.py without taking any query which is shown in AddVehicleFormPost.
Then i casually did,
if request.method == "POST":
form = AddVehicleFormPost(request.POST)
if form.is_valid():
In my views.py...
Here you have a nice tutorial on how to create dependent fields, you need to understand what's going on on the Server, and what's going on on the Client
I'm was creating ModelForm I try to make change the parent class while saving child class fields to the database, in the views.py I made but it didn't save to the database.
here is my model.py
class Table(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
book = models.BooleanField(default=False)
class People(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
taple = models.OneToOneField(Table, on_delete=models.CASCADE, null=True, blank=True)
#receiver(post_save, sender=User)
def update_people_profile(sender, instance, created, **kwargs):
try:
instance.people.save()
except ObjectDoesNotExist:
People.objects.create(user=instance)
Class People is the child class and Table is the parent class so I'm using People class for making forms. here is my forms.py
class Booking(forms.ModelForm):
class Meta:
model = People
fields = [
'taple',
]
So I want to make True book field in Table class and save it to the database when saving Booking form. here is my views.py
def booking(request):
if request.method == 'POST':
try:
people_instance = People.objects.get(user=request.user)
except Table.DoesNotExist:
people_instance = People(user=request.user)
form = Booking(request.POST, instance=people_instance)
if form.is_valid():
user = form.save(commit=False)
user.taple.booking = True
user.refresh_from_db()
user.user = request.user
user.taple = form.cleaned_data.get('taple')
user.save()
print(user.taple.booking, user.taple.id)
return redirect('booked')
else:
form = Booking()
return render(request, 'main/booking.html', {'form': form})
Any Idea?
What I understand from the snippets is that you want to be able to record if a table is booked (book Boolean Field in your Table model and if so by whom, which is the object of your People model.
If my understanding is correct, then I don't think you really need a join table (People model). Instead, I would change your model as follow:
class Table(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
booked_by = models.OneToOneField(User, null=True, on_delete=models.SET_NULL, related_name='table_booked')
#property
def is_booked(self):
# This returns True if booked_by is set, False otherwise
return self.booked_by_id is not None
This way you don't need the People model. The property decorator will allow you to use is_booked as a calculated field.
Also, note the related name which will be used in the form:
class BookingForm(forms.ModelForm):
table_booked = forms.ModelChoiceField(queryset=Table.objects.filter(booked_by__isnull=True))
class Meta:
model = User
fields = ['table_booked',]
In the form, you will see that we define a custom queryset for table_booked. THe aim is to filter for free tables only.
Then you can hopefully simplify as well your view as follow:
Update:
As table_booked is a reverse foreign key, we actually need to save the table object which contains the relation. Here is the modified view:
#login_required
def booking(request):
form = BookingForm(request.POST or None, instance=request.user)
if form.is_valid():
user = form.save(commit=False)
tbl = form.cleaned_data['table_booked']
tbl.booked_by = request.user
tbl.save()
user.save()
print(request.user.table_booked.id, request.user.table_booked.is_booked)
return redirect('/')
return render(request, 'booking/booking.html', {'form': form})
Note: I haven't tested the code so there could be some typos but that should help you getting started.
I am writing a simple blog app and I'm currently in the position where I need to implement comments on a blog post. So, I have two models:
from django.db import models
from django.shortcuts import reverse
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=120)
author = models.CharField(max_length=50)
content = models.TextField()
date = models.DateField(auto_now=True)
def get_absolute_url(self):
return reverse('articles:article-detail', kwargs={'id': self.id})
class Comment(models.Model):
author = models.CharField(max_length=50)
content = models.TextField()
date = models.DateField(auto_now=True)
post_id = models.IntegerField()
and a ModelForm:
from django import forms
from .models import Article, Comment
class CommentModelForm(forms.ModelForm):
class Meta:
model = Comment
fields = [
'content',
'author',
]
...when I submit the form, I want my Comment's post_id field to be automatically generated and correspond to my Article's id, i.e. the comment should be located on the page where it was submitted.
Here is my views.py:
def article_detail_view(request, id):
obj = get_object_or_404(Article, id=id)
comments = Comment.objects.filter(post_id=id)
comment_form = CommentModelForm(request.POST or None)
if comment_form.is_valid():
comment_form.save()
comment_form = CommentModelForm()
context = {
'object': obj,
'comments': comments,
'comment_form': comment_form
}
return render(request, 'articles/article_detail.html', context)
Any ideas how can I do that?
I suggest to change the Comment model in order to replace post_id with a foreignkey field. It allows to keep a better link between comments and articles.
class Comment(models.Model):
author = models.CharField(max_length=50)
content = models.TextField()
date = models.DateField(auto_now=True)
post_id = models.ForeignKey(Article, on_delete=models.CASCADE) # cascade will delete the comments if the article is deleted.
Then you only have to change the comment_form validation :
if comment_form.is_valid():
comment = comment_form.save(commit=False)
comment.post_id = obj
comment.save()
comment_form = CommentModelForm()
save(commit=False) allows to create the Comment instance without saving it to database and allow us to specify the post_id with the article instance obj defined above. Then comes the final commit comment.save().
If you prefer to not change your model, you can follow the same logic and replace
comment.post_id = obj by comment.post_id = id.
I'm trying to create a way to update an item of the database that has the fields CharField and ForeignKey. I want to pass the original values so that they don't have to be manually entered for each field every time, but this gives me a "Select a valid choice. That choice is not one of the available choices." warning for the group field. How do I remove this warning message?
Setting the error message (in ModelForm) to something else manually still displays an empty (unordered) list item. Setting the form in UpdateUserView to form = UserForm() gets rid of the errors, but doesn't pass default values to the form.
models.py
class User(models.Model):
username = models.CharField(max_length=50)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
class Group(models.Model):
name = models.CharField(max_length=20)
description = models.CharField(max_length=200)
forms.py
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'group']
views.py
class UpdateUserView(TemplateView):
template_name = 'update_user.html'
def get(self, request):
user_id = int(request.GET.get('user_id'))
user = User.objects.get(id=user_id)
default_name = getattr(user, 'username')
default_group = getattr(user, 'group')
form = UserForm({'username': default_name, 'group': default_group})
return render(request, self.template_name, {'form': form, 'user': user})
End Result
You have to use initial argument like this:
class UpdateUserView(TemplateView):
template_name = 'update_user.html'
def get(self, request):
user_id = int(request.GET.get('user_id'))
user = User.objects.get(id=user_id)
form = UserForm(initial={'username': user.username, 'group': user.group.id})
return render(request, self.template_name, {'form': form, 'user': user})
Look I'm using group: user.group.id here, since the model Field you are referencing there in your form is a ForeignKey.
I hope this help.
Can someone say, how create such form as in the picture below in Django?
I have model Product with field is_visable. In form I want to show all products with field is_visable. User can select checkboxes and change the value of is_visable field. In other words make products visable or invisable. I am thing about MultipleChoiceField in my form but not sure is it correct in my case.
models.py:
class Product(models.Model):
symbol = models.CharField(_('Symbol'), max_length=250)
name = models.CharField(_('Name'), max_length=250)
is_visible = models.BooleanField(default=False)
forms.py:
class ProductForm(forms.ModelForm):
product = forms.ModelChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Product.objects.all())
views.py:
if request.method == 'POST':
form = ProductForm(data=request.POST)
if form.is_valid():
ids = form.cleaned_data.get('product') # Example: ['pk', 'pk']
for id in ids:
product = Product.objects.get(pk=id)
product.is_visible = True
product.save()
I think what you want to use is a ModelChoiceField in your form with a widget of CheckboxSelectMultiple.
A queryset for the ModelChoiceField is a required argument, so you can build the queryset like this:
visible_products = Product.objects.filter(is_visible=True)
product_field = forms.ModelChoiceField(queryset=visible_products,
widget=CheckboxSelectMultiple()
See this post for more details:
Django ChoiceField populated from database values