How To validate Formsets in Django? - django

I am new to Django FormSet, let say I have the form like follow.
class PillarForm(ModelForm):
class Meta:
model = Pillar
exclude = ("created_at", "updated_at", "is_active", "owner")
PillarFormSet = formset_factory(PillarForm, validate_min=True)
and my pillar model looks like follow
class Pillar(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True)
order = models.IntegerField()
#SYSTEM FIELDS
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
report = models.ForeignKey(Report, on_delete=models.CASCADE)
when my views look like follow
def pillars_create(request, report_id):
context = {}
report = Report.objects.get(pk=report_id, is_active=True)
form = PillarFormSet()
if request.POST:
form = PillarFormSet(request.POST)
print(form.errors)
return render(request, os.path.join(TEMPLATE_FOLDER, "create/index.html"),
{ 'report' : report, 'formset' : form })
but form doesn't showing the error messages for example name, order are required fields but it doesn't shows the error messages and form.is_valid is True, what mistake i made here.
but if I create formset with fields max_fields, min_fields, 'validate_max' like it shows the message.
PillarFormSet = formset_factory(PillarForm, max_num=5, min_num=4, validate_max=True, validate_min=True)

After some googling i found, By default formsets doesn't validate all forms inside it. so i create CustomFormSet by extending the BaseFormSet like follow
class PillarRequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(PillarRequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
and pass the customformset as kwargs,when creating model formset like follow.
PillarModelFormSet = modelformset_factory(Pillar, form=PillarForm, formset=PillarRequiredFormSet, extra=3)
and set the empty_permitted to false, so validation works fine.

Related

Django overridden choices failing validation

I am overriding the choices option defined in the model, which is just a blank tuple.
Here's my models.py
#For assigning Ciena loopbacks
class Dia_Address(models.Model):
order_reference = models.ForeignKey(Order, null=True, on_delete=models.CASCADE)
equipment_hostname = models.CharField(max_length=500, null=True, verbose_name="Equipment Hostname", help_text="Hostname of the DIA device.")
subnet = models.CharField(max_length=200, null=True, choices=DIA_SUBNETS, help_text="Subnet in which the DIA device requires an address.")
Contents of my forms.py, which overrides the model choices.
class Dia_AddressForm(ModelForm):
class Meta:
model = Dia_Address
fields = ['equipment_hostname', 'subnet']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#order_id = str(order_reference.id)
dcn_address_handler = AddressHandler
subnet_id = "94806"
DIA_SUBNETS = ()
NEW_SUBNETS = dcn_address_handler.get_subnet_data(dcn_address_handler, subnet_id)
DIA_SUBNETS = NEW_SUBNETS + DIA_SUBNETS
self.fields['subnet'].choices = DIA_SUBNETS
Contents of my views.py
def addDia_Address(request, pk_test):
order = Order.objects.get(id=pk_test)
dia_address_form = Dia_AddressForm()
if request.method == 'POST':
dia_address_form = Dia_AddressForm(request.POST)
dia_address_form.instance.order_reference = order
if dia_address_form.is_valid():
dia_address_form.save()
return redirect('order', pk_test)
context = {'dia_address_form':dia_address_form}
return render(request, 'orchestration/dia_address_form.html', context)
The issue I have is this form is failing validation. I am able to select the appropriate option from the form dropdown menu. Is there any way I can disable this validation for just this one charfield?
The form validation is failing because of the empty choices tuple on your model. Regardless of what you set your choices to in the form, the validation will actually consider what the model dictates - in this case nothing! Remove the empty tuple and your form should validate.

How to pass current logged user in a form class in django

I am trying to create a form where one field is a ModelChoicefield. Im trying to populate that field with objects from a different model. I have ran into a problem as i need to get the current logged user within the form to filter the queryset. Here are the 2 models
class UserExercises(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
Muscle = models.ForeignKey(Muscle, on_delete=models.CASCADE)
class Exercise(models.Model):
exercise = models.ForeignKey(UserExercises, on_delete=models.CASCADE)
weight = models.DecimalField(max_digits=6, decimal_places=3)
reps = models.PositiveIntegerField(validators=[MaxValueValidator(100)])
difficulty = models.CharField(max_length=30)
date = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE)
And here is my form
class AddExerciseForm(forms.Form):
exercise = forms.ModelChoiceField(queryset=UserExercises.objects.filter(user=1))
class Meta:
model = Exercise
fields = ['exercise', 'weight', 'reps', 'difficulty']
As you can see i am currently hard coding a filter in the ModelChoiceField, but want to replace that with the current users Id. Is there anyway of Going about this. Im new to django so any help would be Appreciated.
My View
#login_required
def add_exercise_view(request):
if request.method == 'POST':
user_id = request.user.id
form = AddExerciseForm(user_id=user_id)
if form.is_valid():
form.save()
return redirect('myfit-home')
else:
form = AddExerciseForm()
return render(request, 'users/register.html', {'form': form})
Firstly, AddExerciseForm should extend forms.ModelForm.
To initialize form data based on some paramater, you can override __init_ method of ModelForm to update form fields (that field is exercise in this case) based on some argument/parameter (which is user_id in this case).
class AddExerciseForm(forms.ModelForm):
class Meta:
model = Exercise
fields = ['exercise', 'weight', 'reps', 'difficulty']
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user_id', None)
super(AddExerciseForm, self).__init__(*args, **kwargs)
if user_id is not None:
# update queryset for exercise field
self.fields['exercise'].queryset = UserExercises.objects.filter(user=user_id)
else:
# UserExercises.objects.none() will return an empty queryset
self.fields['exercise'].queryset = UserExercises.objects.none()
And pass the user_id while initializing the form in view:
if request.user.is_authenticated():
# get user id
user_id = request.user
form = AddExerciseForm(user_id=user_id)
override __init__ method of the Form, and pass the user as argument
def __init__(self,user,*args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
self.fields['exercise'].queryset=
UserExercises.objects.filter(user=self.user))
self.fields['exercise'].widget = forms.CheckboxSelectMultiple
class Meta:
model = Exercise
fields = ['exercise', 'weight', 'reps', 'difficulty']

Django modelforms, foreignkey filter for data owned by logged in user [duplicate]

I'm trying to display a form (ModelForm) with a select field filtered by currently logged in user. The select field in this case contains a list of categories. I want to display only the categories which "belong" to the currently logged in user. The category field is a foreign key to the IngredienceCategory model.
Here is what I've come up with so far but it's giving me an error (unexpected keyword queryset). Any ideas what I'm doing wrong?
# models.py
class IngredienceCategory(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
class Ingredience(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(IngredienceCategory, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredients"
def __unicode__(self):
return self.name
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
# views.py
def home(request):
if request.user.is_authenticated():
username = request.user.username
email = request.user.email
foods = Food.objects.filter(user=request.user).order_by('name')
ingredients = Ingredience.objects.filter(user=request.user).order_by('name')
ingrcat = IngredienceCategory.objects.filter(user=request.user)
if request.method == 'POST':
form = IngredienceForm(request.POST)
if form.is_valid():
# Create an instance of Ingredience without saving to the database
ingredience = form.save(commit=False)
ingredience.user = request.user
ingredience.save()
else:
# How to display form with 'category' select list filtered by current user?
form = IngredienceForm(queryset=IngredienceCategory.objects.filter(user=request.user))
context = {}
for i in ingredients:
context[i.category.name.lower()] = context.get(i.category.name.lower(), []) + [i]
context2 = {'username': username, 'email': email, 'foods': foods, 'ingrcat': ingrcat, 'form': form,}
context = dict(context.items() + context2.items())
else:
context = {}
return render_to_response('home.html', context, context_instance=RequestContext(request))
That's happening because ModelForm does not take a queryset keyword.
You can probably achieve this by setting the queryset on the view:
form = IngredienceForm()
form.fields["category"].queryset =
IngredienceCategory.objects.filter(user=request.user)
See related question here.
Here i have another suggestion to solve the problem. You can pass request object in your form object inside view.
In view.py just pass the request object.
form = IngredienceForm(request)
In your forms.py __init__ function also add request object
from models import IngredienceCategory as IC
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
def __init__(self, request, *args, **kwargs):
super(IngredienceForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = IC.objects.filter(user=request.user)
This filter always will be applied whenever you initialize your form .

Django form.save() error

my model:
class Event(models.Model):
title = models.CharField(max_length=255)
start = models.DateTimeField()
end = models.DateTimeField()
theme = models.ForeignKey(Theme)
class Theme(models.Model):
name = models.CharField(max_length=100)
color = models.CharField(max_length=50)
text_color = models.CharField(max_length=50)
my form:
class EventForm(ModelForm):
class Meta:
model = Event
fields = ['title', 'start', 'end']
theme = forms.ModelChoiceField(
queryset=Theme.objects.filter(public=True),
empty_label='None'
)
my view:
#login_required
def index(request):
if request.method == 'POST':
form = EventForm(request.POST)
if form.is_valid():
form.save()
Now If I fill in the values in the form star, end, title and select a theme from a list that django creates for me I get an error when I try to run the form.save() method.
IntegrityError: null value in column "theme_id" violates not-null constraint
But when I look into form.cleaned_data I can see that in theme is an instance of my Theme model available.
you cannot save Event without Theme object, so you need something like
form = EventForm(request.POST)
if form.is_valid():
# get your Theme object 'your_theme_object'
event = form.save(commit=False)
event.theme = your_theme_object
event.save()
I should have commented but I don't have enough point.
I think better way to achieve this thing is:
class EventForm(ModelForm):
class Meta:
model = Event
fields = ['title', 'start', 'end', 'theme']
As 'theme' is foreign key to Event Model, it'll appear as drop down on your template.
As here you want to filter theme objects, you can achieve it by overriding init :
class EventForm(ModelForm):
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
self.fields['theme'].queryset = self.fields['theme'].queryset.filter(public=True)
class Meta:
model = Event
fields = ['title', 'start', 'end', 'theme']

Filter select field in ModelForm by currently logged in user

I'm trying to display a form (ModelForm) with a select field filtered by currently logged in user. The select field in this case contains a list of categories. I want to display only the categories which "belong" to the currently logged in user. The category field is a foreign key to the IngredienceCategory model.
Here is what I've come up with so far but it's giving me an error (unexpected keyword queryset). Any ideas what I'm doing wrong?
# models.py
class IngredienceCategory(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
class Ingredience(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(IngredienceCategory, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredients"
def __unicode__(self):
return self.name
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
# views.py
def home(request):
if request.user.is_authenticated():
username = request.user.username
email = request.user.email
foods = Food.objects.filter(user=request.user).order_by('name')
ingredients = Ingredience.objects.filter(user=request.user).order_by('name')
ingrcat = IngredienceCategory.objects.filter(user=request.user)
if request.method == 'POST':
form = IngredienceForm(request.POST)
if form.is_valid():
# Create an instance of Ingredience without saving to the database
ingredience = form.save(commit=False)
ingredience.user = request.user
ingredience.save()
else:
# How to display form with 'category' select list filtered by current user?
form = IngredienceForm(queryset=IngredienceCategory.objects.filter(user=request.user))
context = {}
for i in ingredients:
context[i.category.name.lower()] = context.get(i.category.name.lower(), []) + [i]
context2 = {'username': username, 'email': email, 'foods': foods, 'ingrcat': ingrcat, 'form': form,}
context = dict(context.items() + context2.items())
else:
context = {}
return render_to_response('home.html', context, context_instance=RequestContext(request))
That's happening because ModelForm does not take a queryset keyword.
You can probably achieve this by setting the queryset on the view:
form = IngredienceForm()
form.fields["category"].queryset =
IngredienceCategory.objects.filter(user=request.user)
See related question here.
Here i have another suggestion to solve the problem. You can pass request object in your form object inside view.
In view.py just pass the request object.
form = IngredienceForm(request)
In your forms.py __init__ function also add request object
from models import IngredienceCategory as IC
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
def __init__(self, request, *args, **kwargs):
super(IngredienceForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = IC.objects.filter(user=request.user)
This filter always will be applied whenever you initialize your form .