Django form unittest with ChoiceField and MultipleChoiceField failing is_valid() - python

I'm running into a small problem with writing a unit test for a Django form. I really just want to check the is_valid() method and have seen examples but my code isn't working and after a day or so of reading up on Google I've yet to find the answer I'm looking for. Below is the code for the forms.py and test_forms.py
forms.py
class DataSelectForm(forms.Form):
#these are done in the init funct.
result_type = forms.ChoiceField(widget=forms.Select(attrs={'class': 'field-long'}))
band_selection = forms.MultipleChoiceField(widget=forms.SelectMultiple(attrs={'class': 'multiselect field-long'}))
title = forms.CharField(widget=forms.HiddenInput())
description = forms.CharField(widget=forms.HiddenInput())
def __init__(self, result_list=None, band_list=None, *args, **kwargs):
super(DataSelectForm, self).__init__(*args, **kwargs)
if result_list is not None and band_list is not None:
self.fields["result_type"] = forms.ChoiceField(choices=result_list, widget=forms.Select(attrs={'class': 'field-long'}))
self.fields["band_selection"] = forms.MultipleChoiceField(widget=forms.SelectMultiple(attrs={'class': 'multiselect field-long'}), choices=band_list
test_forms.py
def test_data_select_form(self):
results = ResultType.objects.all()
results_value = []
for result in results:
results_value.append(result.result_type)
bands = SatelliteBand.objects.all()
bands_value = []
for band in bands:
bands_value.append(band.band_name)
form_data = {'result_type': results_value, 'band_selection': bands_value, 'title': 'a title', 'description': 'some description'}
form = DataSelectForm(data = form_data)
print(form['title'].value())
print(form['description'].value())
print(form['result_type'].value())
print(form['band_selection'].value())
self.assertTrue(form.is_valid())
The only thing I get when I run the test case is "AssertionError: False is not true" I understand the error, just not why I'm getting it. I'm passing in all the data and I can see it when I run the print statements. I've tried taking the result_type and band_selection and passing it into the constructor instead of it being a part of the form_data but that didn't work either. What am I missing?

You need to pass result_list and band_list when you construct your form.
# These aren't the actual choices you want, I'm just showing that
# choices should be a list of 2-tuples.
result_list = [('result1', 'result1'), ('result2', 'result2'), ...]
band_list = [('band1', 'band1'), ('band2', 'band2'), ...]
DataSelectForm(result_list=result_list, band_list=band_list, data=form_data)
If you don't pass the values to the form, then you don't set the choices for the fields. If the fields don't have any choices, then the values in the data dict cannot be valid, so the form will always be invalid.

Related

Django save tuple of string unexpectedly

I'm updating data in django, but the string data becomes tuple string when saved in the database.
#api_view(["POST"])
def cate_edit(req):
if not req.user.is_staff:
return HttpResponseNotFound()
data=jsonload(req.body)
if not has(data,["id","title","other_title","introduction"]):
return HttpResponseForbidden()
id=toNumber(data["id"])
if id==None:
return HttpResponseForbidden()
if id==0:
c=Category(
title=data["title"],
other_title=data["other_title"],
introduction=data["introduction"]
)
c.save()
return HttpResponse(c.id)
else:
c=get_object_or_404(Category,id=id)
c.title = data["title"],
c.other_title = data["other_title"],
c.introduction = data["introduction"]
c.save()
return HttpResponse(c.id)
The problem happened in the final else, I can make sure the data is a valid and normal dict, such as
{'id': 1, 'title': '1', 'other_title': '2', 'introduction': '3'}
but after this save process, the data in database is
title: "('1',)"
other_title:"('2',)"
introduction: '3'
introduction is correct actually.
Additionally, here is the model of category
class Category(models.Model):
title = models.CharField(max_length=50)
other_title = models.CharField(max_length=50,blank=True)
image = models.ImageField(blank=True,null=True,upload_to=file_path)
introduction = models.TextField(default="",blank=True)
last_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
Thanks
Update:
It's cool to use query and update, but why above situation happens? I used to do like that but works fine.
You have commas at the end of your assignments.
c.title = data[“title”],
Should be:
c.title = data[“title”]

Django CursorPagination for ordering by several fields

I am using Django DRF's CursorPagination for lazy loading of my data, and currently my goal is to sort the data by more than one field.
This is how my code looks like now:
class EndlessPagination(CursorPagination):
ordering_param = ''
def set_ordering_param(self, request):
self.ordering = request.query_params.get(self.ordering_param, None)
if not self.ordering:
raise ValueError('Url must contain a parameter named ' +
self.ordering_param)
if self.ordering.startswith("\"") or self.ordering.endswith("\""):
raise ValueError('Ordering parameter should not include quotation marks'
def paginate_queryset(self, queryset, request, view=None):
# This function is designed to set sorting param right in the URL
self.set_ordering_param(request)
return super(EndlessPagination, self).paginate_queryset(queryset, request, view)
This code works fine for urls like my_url/sms/270380?order_by=-timestamp, but what if I want to sort by several fields ?
Use str.split() to split the url params
class EndlessPagination(CursorPagination):
ordering_param = 'order_by'
def set_ordering_param(self, request):
ordering_param_list = request.query_params.get(self.ordering_param, None)
self.ordering = ordering_param_list.split(',')
# here, "self.ordering" will be a "list", so, you should update the validation logic
"""
if not self.ordering:
raise ValueError('Url must contain a parameter named ' +
self.ordering_param)
if self.ordering.startswith("\"") or self.ordering.endswith("\""):
raise ValueError('Ordering parameter should not include quotation marks'
"""
def paginate_queryset(self, queryset, request, view=None):
# This function is designed to set sorting param right in the URL
self.set_ordering_param(request)
return super(EndlessPagination, self).paginate_queryset(queryset, request, view)
Example URLs
1. my_url/sms/270380?order_by=-timestamp
2. my_url/sms/270380?order_by=-timestamp,name
3. my_url/sms/270380?order_by=-name,foo,-bar
UPDATE-1
First of all thanks to you for giving a chance to dig deep :)
As you said, me too didn't see comma seperated query_params in popular APIs. So, Change the url format to something like,my_url/sms/270380??order_by=-name&order_by=foo&order_by=-bar
At this time, the request.query_params['order_by'] will be a list equal to ['-name','foo','-bar']. So, you don't want to use the split() function, hence your set_ordering_param() method become,
def set_ordering_param(self, request):
self.ordering = request.query_params.get(self.ordering_param, None)
#...... your other validations

Returning multiple fetched users

I have a view which fetches multiple users from a database based on passed in skills. It works almost as desired except if it returns more than one user it only passes back the most recently fetched user. How do I aggregate fetched users to be passed back to the template. I've tried passing them back as a list but they didn't appear.
Here is my code:
form = FilterFreelancerForm(request.POST)
filtered_skills = set((request.POST.getlist('skills_select')))
match_fl = Freelancer.object.annotate(c=Count('skills')).filter(c=len(filtered_skills))
candidate_freelancers = None
for skill in filtered_skills:
candidate_freelancers = match_fl.filter(skills=skill)
freelancers = None
for freelancer in candidate_freelancers:
freelancers = User.objects.filter(freelancer=freelancer.id)
return render(request, 'freelancestudent/browsefreelancers.html', {'freelancers': freelancers,
'filter_form': form})
I previously had this:
freelancers = []
for freelancer in candidate_freelancers:
freelancers.append(User.objects.filter(freelancer=freelancer.id))
which returns nothing to the template.
Instead of:
for freelancer in candidate_freelancers:
freelancers = User.objects.filter(freelancer=freelancer.id)
try:
freelancers = User.objects.filter(freelancer__in=[freelancer.id for freelancer in candidate_freelancers])
out:
[<User: user1>, <User: user2>]

How to save a non ModelForm form to database in Django

I'm a newbie Django user, struggling with submitting form data to the database. So that I can generate dynamic forms I am using a non-ModelForm form to capture field data.
I'm commented out validation for now but I am trying to capture the POST data from the form to the database. The latest 'draft' of my views.py code is as follows - most interested in format from form_to_save = Scenario(...):
def scenario_add(request, mode_from_url):
urlmap = {
'aviation': 'Aviation',
'maritime': 'Maritime',
'international_rail': 'International Rail',
'uk_transport_outside_london': 'UK Transport (Outside London)',
'transport_in_london': 'Transport in London',
}
target_mode = urlmap[mode_from_url]
m = Mode.objects.filter(mode=target_mode)
tl = m[0].current_tl.threat_l
scenario_form = ScenarioForm(request.POST or None, current_tl=tl, mode=target_mode)
if request.method == 'POST':
#if scenario_form.is_valid():
form_to_save = Scenario(
target = Target(descriptor = scenario_form.fields['target']),
t_type = ThreatType(t_type = scenario_form.fields['t_type']),
nra_reference = NraReference(nra_code = scenario_form.fields['nra_reference']),
subt = scenario_form.fields['subt'],
ship_t = ShipType(ship_t = scenario_form.fields['ship_t']),
likelihood = scenario_form.fields['likelihood'],
impact = scenario_form.fields['impact'],
mitigation = scenario_form.fields['mitigation'],
compliance_score = scenario_form.fields['compliance_score'],
notes = scenario_form.fields['notes']
)
form_to_save.save()
# This needs to be changed to a proper redirect or taken to
# a scenario summary page (which doesn't yet exit.)
return render(request, "ram/new_scenario_redirect.html", {
'scenario_form': scenario_form,
'mode': mode_from_url,
'mode_proper': target_mode
})
else:
# if there is no completed form then user is presented with a blank
# form
return render(request, 'ram/scenario_add.html', {
'scenario_form': scenario_form,
'current_tl': tl,
'mode': mode_from_url,
'mode_proper': target_mode
})
Any advice gratefully received. Many thanks.
You've commented out the is_valid check, for some reason. You need that: as well as checking for validity, it also populates the form's cleaned_data dictionary which is what you should be getting the data from to create your object. (And don't call that object "form_to_save": it's a model instance, not a form).
if request.method == 'POST':
if scenario_form.is_valid():
scenario = Scenario(
target = Target(descriptor=scenario_form.cleaned_data['target']),
t_type = ThreatType(t_type = scenario_form.cleaned_data['t_type'])
...etc
Plus, you should move the final "return render" call out of the else block, so that it is caught when the form is not valid, to show any errors.
However, as petkostas says, you would almost certainly be better off using an actual ModelForm in the first place.
You can add custom options by overriding the init function in your form. For example:
class SomeForm(forms.Form):
department = forms.ChoiceField(widget=forms.Select, required=True)
...
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['department'].choices = Department.objects.all().order_by('department_name).values_list('pk', 'department_name')
You can also change the queryset in the init function:
where Department is a foreign key for example
queryset = Department.objects.filter(your logic)
self.fields['department'].queryset = queryset

Modified django form cleand_data do is not renedered in data

I want to add data to submitted data in a django form.
Until now I did something like this:
form = NewADServiceAccount(data=request.POST)
if form.is_valid():
data=request.POST.copy()
if not 'SVC' in data['Account_Name']:
data['Account_Name'] = 'SVC_'+data['Account_Name']
form = NewADServiceAccount(data=data)
This works, but I would like to do this check in a clean method, so I defined:
def clean_Account_Name(self):
data = self.cleaned_data['Account_Name']
if not 'SVC' in self.cleaned_data['Account_Name']:
data = 'SVC' + data
return data
However, when I run this code with the clean method, I see that clean_data does not match data,
and my rendered form does not contain a correct Account_Name (e.g. it does not have SVC in it):
ipdb> p form.cleaned_data['Account_Name']
u'SVCoz123'
ipdb> p form.data['Account_Name']
u'oz123'
The Account_Name from data is the one rendered to HTML, how can I fix this, so that Account_Name from cleaned_data is rendered?
update:
I found another way to do it, but I am still not sure it is the right way:
# inside forms.py
class NewADServiceAccount(NewAccount):
Account_Name = forms.CharField(min_length=3, max_length=21, required=True,
#initial="Please write desired name of "
#+ "this service account.",
help_text="Please write the desired name "
+ "of this account. The prefix 'SVC_' will"
+ " be added to this name",)
def set_prefix(self, prefix='SVC_'):
data = self.data.copy()
data['Account_Name'] = prefix+data['Account_Name']
self.data = data
# in views.py:
if form.is_valid():
form.set_prefix()
second update:
After looking at my solution I decided my clean method can be a bit better, so I did:
def clean_Account_Name(self):
data = self.data.copy()
if not 'SVC' in data['Account_Name']:
data['Account_Name'] = 'SVC' + data['Account_Name']
self.data = data
the above solution works, although the django documentation says:
Always return the cleaned data, whether you have changed it or not.
So, now I am quite confused.
I found a solution, but I need reaffirming it is a valid and good one. I would be happy if someone comments here about it.
If I understood you have been attempts uses the clean method. If I right, you did it a little wrong. Try use def clean() with a form's field:
forms.py
class AccountNameField(forms.CharField):
def clean(self, value):
value = u'SVC' + value
return value
class NewADServiceAccount(forms.Form):
Account_Name = AccountNameField(min_length=3, max_length=21, required=True,
#initial="Please write desired name of "
#+ "this service account.",
help_text="Please write the desired name "
+ "of this account. The prefix 'SVC_' will"
+ " be added to this name",)
views.py
form = NewADServiceAccount(request.POST or None)
if form.is_valid():
...
prefix is used only into a forms. If I am not mistaken prefix would be assign each fields of the form as prefix-namefield

Categories