How to validate ModelForm with unique_together fields in Django? - django

How to validate the unique_together error and how to return the custom error message to the html through the view.py. Please help.
model.py:
class Pcq(models.Model):
product = models.ForeignKey(Product, null=False)
component = models.ForeignKey(Component, null=False)
quantity = models.IntegerField('Quantity', null=False)
class Meta:
unique_together = ("product", "component")
def __unicode__(self):
return self.product.productname
Form.py
class PcqForm(ModelForm):
class Meta:
model = Pcq
fields = ['component', 'quantity']
exclude = ['product']
Views.py
def pcq_add(request, product_id):
if request.method == 'POST':
form = PcqForm(request.POST or None)
if form.is_valid():
pcq = form.save(commit=False)
pcq.product_id = product_id
pcq.save()
form = PcqForm()
successmsg = "Component added successfully! Add another Component..."
return render(request, 'maps/pcq/pcq_add.html', {'form': form, 'product_id': product_id, 'successmsg': successmsg})
form = PcqForm()
return render(request, 'maps/pcq/pcq_add.html', {'form': form, 'product_id': product_id})

You need a custom clean function for the form
class PcqForm(ModelForm):
class Meta:
model = Pcq
fields = ['component', 'quantity']
exclude = ['product']
def clean(self):
cleaned_data = super(PcqForm, self).clean()
component = cleaned_data.get('component')
quantity = cleaned_data.get('quantity')
if component and quantity:
try:
Pcq.objects.get(
component=component,
quantity=quantity,
)
except Pcq.DoesNotExist:
# Yay
pass
else
raise forms.ValidationError(_("Error message goes here"))
UPDATE
Same concept as above but in the view.
def pcq_add(request, product_id):
if request.method == 'POST':
form = PcqForm(request.POST or None)
data = {
'form': form,
'product_id': product_id
}
if form.is_valid():
pcq = form.save(commit=False)
pcq.product_id = product_id
try:
pcq.save()
except IntegrityError:
# You'll need to check the exception that is raised
# Handle failed unique_together
pass
else:
form = PcqForm()
data['successmsg'] = (
"Component added successfully! Add another Component.")
else:
form = PcqForm()
data = {'form': form, 'product_id': product_id}
return render(request, 'maps/pcq/pcq_add.html', data)
Alternatively:
remove the exclude = ['product']
on GET pass product_id to the form form = PcqForm(initial_data={'product': product_id})
Use the form to validate unique_together (Not sure you even need a custom clean then)

Related

Handling form fields in django for logged in user

Im trying to handle the existing name of a Category, so that users wont be allowed to create 2 categories with the same name, but at the moment its taking all categories from the database, not only for the logged-in user. I dont know how and where to implement request.user.
I`m building an inventory app where everyone creates their own categories and adds items.
Thank you.
This is my model:
class Category(models.Model):
user = models.ForeignKey(User, default=1, on_delete=models.CASCADE,
related_name='category', null=True)
name = models.CharField(max_length=100, null=False, blank=False)
slug = models.SlugField(max_length=100)
created_on = models.DateTimeField(auto_now_add=True)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-timestamp']
verbose_name = 'category'
verbose_name_plural = 'categories'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('category_detail', kwargs={'slug': self.slug})
This is my form:
class CategoryForm(forms.ModelForm):
add_category = forms.BooleanField(widget=forms.HiddenInput, initial=True)
class Meta:
model = Category
fields = ['name']
def clean_name(self):
name = self.cleaned_data.get('name')
if (name == ""):
raise forms.ValidationError('This field cannot be left blank')
for instance in Category.objects.all():
if instance.name == name:
raise forms.ValidationError('There is a category with the name ' + name)
return name
This is my view:
#login_required
def index(request):
categories = Category.objects.filter(user=request.user)
items = Item.objects.all()
add_item = ItemForm()
add_category = CategoryForm()
query = None
if request.method == 'POST':
if 'add_item' in request.POST:
add_item = ItemForm(request.POST)
if add_item.is_valid():
form = add_item.save(commit=False)
form.category = get_object_or_404(
Category, name=request.POST.get('category'),
user=request.user)
add_item.save()
return redirect('home')
else:
add_category = CategoryForm(request.POST)
if add_category.is_valid():
category_form = add_category.save(commit=False)
category_form.save()
messages.success(request, f'{name} has been added')
return redirect('home')
Edit Category View
#login_required
def category_edit(request, pk):
category = Category.objects.get(id=pk)
if request.method == 'POST':
form = CategoryForm(request.POST, instance=category)
if form.is_valid():
form.save()
messages.info(request, f'{category.name} has been updated!')
return redirect('home')
else:
form = CategoryForm(instance=category, user=request.user)
context = {
'form': form,
}
return render(request, 'category_edit.html', context)
Ive tried adding user = request.user in the form class, but that resulted in an error Ive tried adding category_form.user = request.user before saving the form but that was still taking names from every other user
Pass the request's user to the form:
class CategoryForm(forms.ModelForm):
add_category = forms.BooleanField(widget=forms.HiddenInput, initial=True)
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
class Meta:
model = Category
fields = ['name']
def clean_name(self):
name = self.cleaned_data.get('name')
if (name == ""):
raise forms.ValidationError('This field cannot be left blank')
qs = Category.objects.filter(user=self.user, name=name)
if self.instance.pk:
# EXCLUDE CURRENT INSTANCE TO ENABLE EDIT
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise forms.ValidationError('There is a category with the name ' + name)
return name
then in the view you need to pass the user:
#login_required
def index(request):
categories = Category.objects.filter(user=request.user)
items = Item.objects.all()
add_item = ItemForm()
add_category = CategoryForm(user=request.user)
query = None
if request.method == 'POST':
if 'add_item' in request.POST:
add_item = ItemForm(request.POST)
if add_item.is_valid():
form = add_item.save(commit=False)
form.category = get_object_or_404(
Category, name=request.POST.get('category'),
user=request.user)
add_item.save()
return redirect('home')
else:
add_category = CategoryForm(user=request.user, data=request.POST)
if add_category.is_valid():
category_form = add_category.save(commit=False)
category_form.save()
messages.success(request, f'{name} has been added')
return redirect('home')

ModelForm inserts number in foreign key field

I have model from which I created a ModelForm:
models.py:
class City(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return f'{self.name}'
class Profile(models.Profile):
name = models.CharField(max_length=50)
user = models.OneToOneField(User, on_delete=models.CASCADE, unique=False)
location = models.ForeignKey('City', on_delete=models.SET_NULL, blank=True, null=True)
forms.py
from django import forms
from .models import Profile, City
class LocationField(forms.CharField):
def clean(self, value):
try:
city = City.objects.get(name=value)
except ObjectDoesNotExist:
city = City.objects.create(name=value)
return city
class ProfileForm(forms.ModelForm):
location = LocationField()
class Meta:
model = Profile
exclude = ['user']
views.py
def profile_update_view(request):
template_name = 'profiles/update.html'
user = request.user
profile = Profile.objects.get(user__id=user.id)
if request.method == 'GET':
form = ProfileForm(instance=profile)
else:
form = ProfileForm(request.POST, instance=profile)
if form.is_valid():
obj = form.save(commit=False)
obj.user = user
obj.save()
return redirect('profile_view')
context = {'form': form}
return render(request, template_name, context=context)
When I'm saving form, I'm satisfied how it's working, but when I load form again to update in, it fills LocationField() as an City pk integer, but I want it to load name instead. Is there a way to do this?
I've added in views.py:
if request.method == 'GET':
initial = {}
if profile.location:
initial = {'location': profile.location.name}
form = ProfileForm(instance=profile, initial=initial)
now it's working. But it's some workaround. I've thought there is some parameter maybe

how to save this form?

I tried how to save this code. it shows the form in the template but when the form filled and submit the data isn't saving to the database here is my views.py
def create_tables(request):
restaurant = Restaurant.objects.get(user=request.user)
print(restaurant)
form = TablesForm(request.POST or None)
if form.is_valid():
user = form.save(commit=False)
user.refresh_from_db()
user.restaurant = restaurant
user.name = form.cleaned_data.get('name')
user.time = form.cleaned_data.get('time')
user.save()
return redirect('/')
args = {
'table': restaurant,
'form': form
}
return render(request, 'main/tt.html', args)
and also here is my forms.py
class TablesForm(forms.ModelForm):
name = forms.CharField(max_length=250, label="Table Name")
time = forms.ModelChoiceField(queryset=Time.objects.all())
class Meta:
model = Table
fields = [
'name',
'time',
]
so help me please.
In the views.py remove the instance and user.refresh_from_db and change to this
form = TablesForm(request.POST or None)
if form.is_valid():
user = form.save(commit=False)
user.restaurant = restaurant
user.name = form.cleaned_data.get('name')
user.time = form.cleaned_data.get('time')
user.save()
I hope this will help.

Django: Create profile page creates everything except Multiple Choice Field in the database

I am using the same form for profile_edit and create_profile functionality. It is updating the multi-choice values in the profile_edit page but does not create in create_profile.
Below is the form code in forms.py
class ProfileForm(ModelForm):
full_name = forms.CharField(required=True)
current_position = forms.CharField(required=True)
about_me = forms.Textarea(attrs={'required':True})
topic_name = forms.ModelMultipleChoiceField(Topic.objects.all())
class Meta:
model = Profile
fields =(
"full_name",
"current_position",
"about_me",
"topic_name",
)
Below is the views.py for profile creation
def create_profile(request, user_id):
if request.method == "POST":
form = ProfileForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
user = get_object_or_404(User, id=user_id)
form.user = user
print(form.topic_name.all()) # Prints empty queryset
form.save()
return redirect("profile_view", user_id=user_id)
else:
context = {"form": form}
return render(request, "profile/create_profile.html", context)
else:
form = ProfileForm()
context = {
"form": form
}
return render(request, "profile/create_profile.html", context)
Below is Model.py
class Topic(models.Model):
topic = models.CharField(max_length=12)
def __str__(self):
return self.topic
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True,)
full_name = models.CharField(max_length=60, null=True)
current_position = models.CharField(max_length=64, null=True)
about_me = models.TextField(max_length=255, null=True)
topic_name = models.ManyToManyField(Topic)
def __str__(self):
return self.full_name
Both create_profile and edit_profile templates are exactly the same.
It saves everything except Multichoice field.
When you do save(commit=False),
you need to use mymodelform.save_m2m() below save(commit=True) on your ModelForm,
because many to many relationships cannot be saved without an ID.
see this docs
so in your views.py
if form.is_valid():
profile = form.save(commit=False)
user = get_object_or_404(User, id=user_id)
profile.user = user
profile.save()
form.save_m2m()
return redirect("profile_view", user_id=user_id)

django: ForeignKeyField limit_choices_to "parents pk"?

I've a model (Parent model):
class Post(models.Model):
image = models.ImageField(upload_to='%Y/%m/%d')
title = models.CharField(max_length=200)
width = models.DecimalField(max_digits=3, decimal_places=0)
height = models.DecimalField(max_digits=3, decimal_places=0)
year = models.PositiveIntegerField()
def __str__(self):
return self.title
and another model (Child model):
class Addimg(models.Model):
post = models.ForeignKey('Post', null=True)
addimg = models.ImageField(upload_to='%Y/%m/%d')
def __str__(self):
return self.post
My Addimg Form:
class AddimgForm(forms.ModelForm):
class Meta:
model = Addimg
fields = ('post', 'addimg', 'width', 'height',)
views.py using the form:
def addimg(request, pk):
if request.method == "POST":
form = AddimgForm(request.POST, request.FILES)
post = get_object_or_404(Post, pk=pk)
if form.is_valid():
addimg = form.save(commit=False)
addimg.addimg = request.FILES['addimg']
addimg.save()
return redirect('blog.views.detail', pk=post.pk)
else:
form = AddimgForm()
return render(request, 'blog/edit.html', {'form': form})
And my Problem is that when I create a "Child model" my post field returns all instances of allready created Post models as choices. What I want is that it automatic only displays the one Post it is related to without choices. Is ForeignKey the right model for that?
Any Ideas how this could work. thanks
ForeignKey field is translated into ModelChoiceField inside a Django ModelForm. If you inspect that class you will notice that this type of field has an queryset attribute required. By default Django provides the full set of objects. You can override this inside your form __init__ method by providing the parent object the form will need.
Consider the following example code:
def addimg(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = AddimgForm(request.POST, request.FILES, post=post)
#...
else:
form = AddimgForm(post=post)
return render(request, 'blog/edit.html', {'form': form})
class AddimgForm(forms.ModelForm):
class Meta:
model = Addimg
fields = ('post', 'addimg', 'width', 'height',)
def __init__(self, *args, **kwargs):
post = kwargs.pop('post')
super(AddimgForm, self ).__init__(*args, **kwargs)
self.fields['post'].queryset = Post.objects.filter(id=post.id)
What you want to do is create a Many-to-one relationship. For example,
post = models.ForeignKey('Post', null=True)
This means you can filter on it for example,
Addimg.objects.filter(post=Post)
or
Post.objects.get(pk=1)
Post.addimg_set.filter(xyz=etc)
Read more here: https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/