Django Test RequestFactory post with foreign key - django

I'm trying to test a view-function with django's RequestFactory's post-method. The view-function should create a new ObjA-instance. ObjA has a foreign key field to an ObjB.
My test currently looks like this (names changed for better reading):
request = self.factory.post('/create-objA/', data={'objB': objB.id, 'field1': 'dummy'})
request.user = self.user
request.POST.is_bound = True
create_objA(request)
self.assertTrue(ObjA.objects.filter(field1='dummy').exists())
objB does exist, this is tested few lines before in the same test function.
However the test in the snippet fails. The reason is that in the following create function form.is_valid() is never true:
def create_objA(request):
if request.method == 'POST':
form = ObjAFormCreate(request.POST)
if form.is_valid():
....
So the ObjA is not created. Form is not valid because it has an error in the ObjB reference field:
Select a valid choice. That choice is not one of the available choices.
though objB.id is inside form.data.
Question: How should I write the test, so that the form would not have the error?
Model:
class ObjA(models.Model):
id = models.BigAutoField(primary_key=True)
obj_b_id = models.ForeignKey(ObjB, on_delete=models.CASCADE)
field1 = models.CharField(max_length=10)
Form:
class ObjAFormCreate(ModelForm):
objB = forms.ModelChoiceField(queryset=ObjB.objects.all())
field1 = forms.CharField(max_length=10)

Answering my own question. It looks like the code is correct and there is a bug either in Django or in my IDE, because after some changes and tries it started to work with exactly the same code. I don't know why the behaviour is not deterministic at some points.

Related

Not able to initialise choice fields in Unit test for django form where choice field is getting populated from db

I have one django form where a choice field is getting populated from db.
class TestForm(forms.Form):
CLASS_CHOICE = []
classes = Class.objects.filter(std__gte=4)
for cls in classes:
CLASS_CHOICE.append((cls.code,
"{} - {}".format(cls.code,cls.std)))
name = forms.CharField()
class = forms.ChoiceField(choices = CLASS_CHOICE)
def _post_clean(self):
# some validation
pass
When writing its unit test as:
class SampleTest(TestCase):
#classmethod
def setUpClass(cls):
super(SampleTest, cls).setUpClass()
cls.class = Class.objects.create(std=10,code='A')
def test_valid_form(self):
post_data = {'name':'testing',
'class':'A' }
f = TestForm(data=post_data)
self.assertTrue(f.is_valid())
Now the problem is, when running test, the application is loaded first before initializing db hence the setUpClass for unit test is not getting called and CLASS_CHOICE remains blank and form validation is getting failed. How can I avoid this or reinitialize choice field after i create one entry in Class table.
The way you have it set up the class choices are only going to be populated once, when the module containing your code is executed for the first time. This is less than ideal because:
if new classes are added they will not be available as choice options
the database may not be connected when this module is loaded into memory, causing runtime errors
You need to make it so that choices is built dynamically every time a new form is created. To do this make choices a callable function rather than a list:
def get_class_choices():
class_choice = []
classes = Class.objects.filter(std__gte=4)
for cls in classes:
class_choice.append((cls.code, "{} - {}".format(cls.code, cls.std)))
return class_choice
class TestForm(forms.Form):
name = forms.CharField()
class = forms.ChoiceField(choices=get_class_choices)
def _post_clean(self):
# some validation
pass

Django validate data when updating model with primary key

I am having trouble with updating fields of a model instance. The model is as follows:
class commonInfo(models.Model):
mothers_id = models.IntegerField(primary_key=True)
date = models.DateField()
data_collector = models.CharField(max_length=50)
Essentially, I just want to do this, but it won't work because commonInfo has a user defined primary key
commonInfo_form(request.POST or None).is_valid()
Since I am updating, I am overriding date and data_collector, but not mothers_id. So I would want to do something like this, but this specific code is not working
obj = commonInfo.objects.get(pk=commonInfo_id)
form = commonInfo_form(request.POST)
date = form.cleaned_data['data_collector'] #this line is not working
data_collector = form.cleaned_data['data_collector'] #this line is not working
obj.update(**{'date':date, 'data_collector':data_collector})
any ideas? I feel like it is just those two lines that I need to fix. Or if there is a more pythonic way or built method in Django?
Just validate with isinstance. so like,
if isinstance(request.POST['date'], datetime.date) and isinstance(request.POST['data_collector'], str):
# you might have to use getattr for request.POST here, I'm not sure
# and request.POST['date'] would have to be converted from a string to datetime.date I think
date = request.POST['date']
data_collector = request.POST['data_collector']
obj.update(**{'date':date, 'data_collector':data_collector})
The process for adding a record from a form is different from updating an existing instance. All you need to do differently is indicate which instance to bind the form to when you create it, ex:
obj = commonInfo.objects.get(pk=commonInfo_id)
form = commonInfo_form(request.POST, instance=obj)

Django inline_formset with can_delete=True and can_order=True doesn't work

I have a form with models and foreign key modes represented as inline formsets.
I'm having a helluva time saving the ordered formsets. In fact, every time I try to delete one, it gets multiplied.
in forms.py:
class PublicationForm(ModelForm):
class Meta:
model = Publication
fields = ['title']
SectionFormSet = inlineformset_factory(Publication, Section, can_delete=True, can_order=True, extra=2)
and in views.py:
if publication_form.is_valid():
pub = publication_form.save(commit=False)
section_formset = SectionFormSet(request.POST, instance=pub, prefix='section')
if section_formset.is_valid():
pub.save()
for s in section_formset.ordered_forms:
s.instance.order = s.cleaned_data['ORDER']
s.save()
I've loked on S.O. but found nothing.
Does anybody have a solution?
Thanks!!
Yes, you have the saving all jumbled up. Something like this:
1 - Your formset holds the sets of SectionForm, so you are testing for the form to be valid in the wrong loop. You want this:
if request.method == 'POST':
section_formset = SectionFormSet(request.POST, instance=<an instance of Publication>, prefix='section')
if section_formset.is_valid():
instances = section_formset.save(commit=False)
for instance in instances:
#do something
instance.save()
return HttpResponseRedirect('<a url>')
else:
section_formset = SectionFormSet(instance=<an instance of Publication>, prefix='section')
You don't need to do the instances loop if you're just saving the section forms 'order' value to the section models order attr - it's done for you. You can just call section_formset.save().

Django form with custom init for ModelChoiceField failing validation

I'm having strange behavior with a form ModelChoiceField. A little background. I need a form that has a variable queryset for a certain field. Looking at this question and this, I have created an init method for my form to handle this based on the request passed in to the init method.
class QueryTimeEntryForm(forms.Form):
query_start_date = forms.DateField(label='Start Date:', required=True, widget=forms.TextInput(), input_formats=['%m/%d/%Y', '%Y-%m-%d'])
query_end_date = forms.DateField(label='End Date:', required=True, widget=forms.TextInput(), input_formats=['%m/%d/%Y', '%Y-%m-%d'])
time_query_unit = forms.ModelChoiceField(queryset=Unit.objects.all().order_by('unit'), label='', required=False, empty_label='Choose a unit', widget=forms.Select())
time_query_employee = forms.ModelChoiceField(queryset=Employee.objects.none(), label='', required=False, empty_label='Choose an employee', widget=forms.Select())
time_query_radio = forms.ChoiceField(label='', widget=forms.RadioSelect(attrs={'class':'queryRadio'}), choices=QUERY_CHOICES, initial='1')
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super (QueryTimeEntryForm, self).__init__(*args, **kwargs)
#depending on the user, set the queryset of the employee drop down
#get the employee category for the user
today = datetime.today()
emp = Employee.objects.filter(user__exact=self.request.user)
ec = EmployeeCategory.objects.filter(employee__exact=emp[0]).filter(effectiveDate__lte=today).filter(Q(enddate__gte=today) | Q(enddate__isnull=True))[0]
if ec.category.category == 1:
self.fields['time_query_employee'].queryset = Employee.objects.filter(user__exact=self.request.user)
elif ec.category.category == 2:
#get the unit for this category 2 employee
unit = EmployeeUnit.objects.filter(employee__exact=emp).filter(effective_date__lte=today).filter(Q(end_date__gte=today) | Q(end_date__isnull=True))[0].unit
#get the employees who currently share the unit with the current category 2 employee, excluding the current category 2 employee
self.fields['time_query_employee'].queryset = Employee.objects.filter(employee_for_employeeunit__unit__exact=unit).filter(Q(employee_for_employeeunit__end_date__gte=today) | Q(employee_for_employeeunit__end_date__isnull=True)).exclude(employee_for_employeeunit__exact=emp).order_by('user__first_name')
else:
#get category 1
cat = Category.objects.filter(category__exact=1)[0]
self.fields['time_query_employee'].queryset = Employee.objects.filter(employee_for_employeecategory__category__exact=cat).filter(Q(employee_for_employeecategory__enddate__gte=today) | Q(employee_for_employeecategory__enddate__isnull=True)).order_by('user__first_name')
When the form isn't bound, everything works just fine. I get just the Employees I expect in the drop down in the html. The problem I'm having is that when posting the form, the ModelChoiceField fails validation. As I step through, I notice that similar to this question, I'm getting the "Select a valid choice" error, probably because the queryset is Employees.objects.none() when super is called and the validation occurs. Should I clear all the errors and redo a full_clean after setting the queryset, or should I take a different approach? Basically I'm stuck, not understanding exactly what's going on, nor where to go from here. Everything was working fine before I added the init method and had a standard Employee queryset, so it must be something I'm doing with that.
Please help. Thanks!
Have you been able to test all three of your category branches? Personally I would probably insert a pdb.set_trace() call at the beginning of the __init__ call, run it using the Django development server, and see what happens when I post the form.
As a readability tip - you can omit __exact, and you can replace your filter(Q() | Q()) calls here with a .exclude making the inverse comparison, since null values will never be true. That is, instead of your original:
unit = EmployeeUnit.objects.filter(employee__exact=emp).filter(effective_date__lte=today).filter(Q(end_date__gte=today) | Q(end_date__isnull=True))[0].unit
you can write:
unit = EmployeeUnit.objects.filter(employee=emp, effective_date__lte=today).exclude(end_date__lt=today)[0].unit
#Shawn, I encountered this same issue today. I noticed when debugging (in Eclipse, with the Variables pane shown/active) the form's __init__() method and line-stepping through the code that I'd get the "Select a valid choice" error. However, if I clear my breakpoints and just let it run, or if I line-step debug with the Variables pane not shown/not active, then I don't get the error. Something with the rendering of the variables in Eclipse results in the error.

Django : Validate data by querying the database in a model form (using custom clean method)

I am trying to create a custom cleaning method which look in the db if the value of one specific data exists already and if yes raises an error.
I'm using a model form of a class (subsystem) who is inheriting from an other class (project).
I want to check if the sybsystem already exists or not when i try to add a new one in a form.
I get project name in my view function.
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def clean(self,project_name):
cleaned_data = super(SubsytemForm, self).clean(self,project_name)
form_subsystem_name = cleaned_data.get("subsystem_name")
Subsystem.objects.filter(project__project_name=project_name)
subsystem_objects=Subsystem.objects.filter(project__project_name=project_name)
nb_subsystem = subsystem_objects.count()
for i in range (nb_subsystem):
if (subsystem_objects[i].subsystem_name==form_subsystem_name):
msg = u"Subsystem already existing"
self._errors["subsystem_name"] = self.error_class([msg])
# These fields are no longer valid. Remove them from the
# cleaned data.
del cleaned_data["subsystem_name"]
return cleaned_data
My view function :
def addform(request,project_name):
if form.is_valid():
form=form.save(commit=False)
form.project_id=Project.objects.get(project_name=project_name).id
form.clean(form,project_name)
form.save()
This is not working and i don't know how to do.
I have the error : clean() takes exactly 2 arguments (1 given)
My model :
class Project(models.Model):
project_name = models.CharField("Project name", max_length=20)
Class Subsystem(models.Model):
subsystem_name = models.Charfield("Subsystem name", max_length=20)
projects = models.ForeignKey(Project)
There are quite a few things wrong with this code.
Firstly, you're not supposed to call clean explicitly. Django does it for you automatically when you call form.is_valid(). And because it's done automatically, you can't pass extra arguments. You need to pass the argument in when you instantiate the form, and keep it as an instance variable which your clean code can reference.
Secondly, the code is actually only validating a single field. So it should be done in a specific clean_fieldname method - ie clean_subsystem_name. That avoids the need for mucking about with _errors and deleting the unwanted data at the end.
Thirdly, if you ever find yourself getting a count of something, iterating through a range, then using that index to point back into the original list, you're doing it wrong. In Python, you should always iterate through the actual thing - in this case, the queryset - that you're interested in. However, in this case that is irrelevant anyway as you should query for the actual name directly in the database and check if it exists, rather than iterating through checking for matches.
So, putting it all together:
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def __init__(self, *args, **kwargs):
self.project_name = kwargs.pop('project_name', None)
super(SubsystemForm, self).__init__(*args, **kwargs)
def clean_subsystem_name(self):
form_subsystem_name = self.cleaned_data.get("subsystem_name")
existing = Subsystem.objects.filter(
project__project_name=self.project_name,
subsytem_name=form_subsystem_name
).exists()
if existing:
raise forms.ValidationError(u"Subsystem already existing")
return form_subsystem_name
When you do form=form.save(commit=False) you store a Subsystem instance in the variable form but the clean method is defined in SubsystemForm. Isn't it?