Can't save custom optional ChooserBlock when blank - django

Came across the very same issue that is well described here : https://github.com/wagtail/wagtail/issues/7344#issue-946329708 but the fix doesn't seem to work in my case.
Basically I have a custom ChooserBlock supposed to make it possible to tie a model's given instance to a StructBlock. Rendering works differently if the ChooserBlock value is blank or not, so it is quite important that it actually can be blank.
But when left blank i stamble upon this error when trying to save :
Field 'id' expected a number but got ''.
The error's stack is exactly the same as the one depicted in the github issue I linked above.
I tried the given fix but it doesn't seem to make any difference, at least for my issue.
class CommunityChooserBlock(blocks.ChooserBlock):
target_model = Community
widget = forms.Select
# Return the key value for the select field
def value_for_form(self, value):
if value == "":
return None
else:
return super().value_from_form(value)
Did I miss anything ?
Edit:
I've also tried to override the get_prep_value and clean methods but it didn't change anything.
def get_prep_value(self, value):
if value == '':
return None
else:
super().get_prep_value(value)
def clean(self, value):
if value == '':
value = None
super().clean(value)

Update : I couldn't come up with a proper fix, so I changed my plans and went for a ChoiceBlock with dynamic choices list, as described here : https://stackoverflow.com/a/60979072/13934028.
It seems to work just fine for my case

Related

Ensure User Entered Integer

I am new to Django, and I recently created a system where users can look up a record based on a number. It's just a simple search. The system numbers have leading zeros, and I want the system to recognize the numbers with or without the zeros. I have been able to implement this system and I am converting the number the user specifies with the following code:
def get_queryset(self):
queryset = super(SearchResultsView, self).get_queryset()
search_query = int(self.request.GET.get("q"))
if search_query:
queryset = Book.objects.filter(Q(request_number__icontains=search_query)).distinct()
The code above works fine, as long as the user enters a number. If they typo and include letters, I get invalid literal for Base10. I understand the error, a letter is not an INT. I have spent most of the afternoon looking for how to prevent this error and I can't find what I'm looking for. I have tried to do something like:
if search_query:
try:
queryset = Book.objects.filter(Q(request_number__icontains=search_query)).distinct()
except ValueError:
q = 0000000
return queryset
But the letters are still interpreted and then I receive the invalid literal for Base10 error again. How can I prevent the letters from causing a problem with my query based on a number?
I have also figured out that if I remove the conversion to INT for the search query, the letters no longer cause a problem and the system returns nothing as I would expect it to so I have a work around. Just wondering how I could get the system to do both, accept the letters and also then prevent the invalid literal error and allow the system to turn the input into integers. Thanks in advance for your helpful suggestions.
As Daniel Roseman suggested, I tried to use the following form, but it doesn't seem to catch the error either...
class RequestNumberSearch(forms.Form):
q = forms.IntegerField(required=True)
def __init__(self, user, *args, **kwargs):
super(RequestNumberSearch, self).__init__(*args, **kwargs)
self.fields['q'].widget.attrs['class'] = 'name2'
def clean_q(self):
data = self.cleaned_data['q']
if q != int:
raise forms.ValidationError("Please enter valid numbers!")
return data
You are trying to cast the query to int before checking it.
search_query = self.request.GET.get("q")
if search_query.isdigit(): # check is digit
queryset = Book.objects.filter(Q(request_number__icontains=search_query)).distinct()
return queryset
elif ... : # another check
...
else:
return 'query is erroneous'

Django validate field based on value from another field

I have this django field called is_private indicating whether the posting done by the user is private or not. If the posting is private then a certain field called private_room must be mentioned otherwise a field called public_room is required.
In the clean_private_room and clean_public_room fields I'm doing a check to see the value of is_private. If the room is private then in the clean_public_room method I simply return an empty string "" and the same for clean_private_room otherwise I continue with the validation.
The problem is that checking with self.cleaned_data.get('is_private') is returning different results in those two methods. I tried debugging the code and printed the self.cleaned_data value to the terminal and in one of the methods cleaned data contains one form field and in the other method contains my full posted values.
Here's a part of my code, please read the comments in it to see where I print and what gets printed. I don't know why it's behaving this way.
class RoomForm( forms.ModelForm ):
...
def clean_is_private( self ):
if not 'is_private' in self.cleaned_data:
raise forms.ValidationError("please select the type of room (private/public)")
return self.cleaned_data.get("is_private")
def clean_public_room( self ):
print "<clean_public_room>"
# !!!!!!!!!
# when printing this one I only get one form value which is: public_room
print self.cleaned_data
if self.cleaned_data.get("is_private"):
return ""
# otherwise....
if not self.cleaned_data.get("public_room"):
raise forms.ValidationError(
'you need to mention a public room'
)
return self.cleaned_data[ 'public_room' ]
def clean_private_room( self ):
print "<clean_private_room>"
# !!!!!!!!!
# when printing this one I get all form values: public_room, private_room, is_private
print self.cleaned_data
if not self.cleaned_data.get("is_private"):
return ""
# otherwise....
if not self.cleaned_data.get("private_room"):
raise forms.ValidationError(
'you need to mention a private room'
)
return self.cleaned_data[ 'private_room' ]
Form fields are cleaned in the order they defined in the form. So you just need to put is_private field before the public_room in the fields list.

Custom Number Field is not executing "to_python"?

I am new to Django and Python.
In my project I am using CustomField defined by me so that I can encrypt the data values before storing into database and decrypt it after retreiving (there is need for encryption in my case).
I have gone through git-repository / stackoverflow / google to find the answer for my question, there are similar questions on stackoverflow but none of them got this issue. As I am unable to fix it from 2 days, would certainly need help now.
I have the code which is defining CustomField as follows -
def get_multiple_of_8(some_value):
some_value = str(some_value)
while(len(some_value) % 8 != 0):
some_value = '0'+some_value
return some_value
def is_multiple_of_8(input_str):
input_str = str(input_str)
if(len(input_str) % 8 == 0):
return True
return False
class CustomField(models.Field):
'''To Encrypt the values in Model'''
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
self.key = myProject.settings.SECRET_KEY[:8]
self.des = DES.new(self.key, DES.MODE_ECB)
kwargs['max_length'] = kwargs.get('max_length',128)
models.Field.__init__(self, *args, **kwargs)
def get_internal_type(self):
return 'CharField'
def get_prep_value(self, value):
if value is None:
return None
value = "%.2f" % value
value = get_multiple_of_8(value)
to_return = self.des.encrypt(value)
to_return = to_return.decode('latin-1')
# to_return = force_unicode(to_return)
return to_return
def to_python(self, value):
value = value.encode('latin-1')
if is_multiple_of_8(value):
to_return = self.des.decrypt(value)
try:
to_return = float(to_return)
return to_return
except:
return 0
Encryption works perfectly, I can see the encrypted field values using sqlite3 command line.
error I am getting is :
Exception Type: InvalidOperation
Exception Value: Invalid literal for Decimal: '\xc3\x85\xc3\x84\xc3\xa13\xc2\xa6\x0f(u'
On debugging found that "to_python()" is not functioning properly. (Code source from django-fields git). I am getting error which shows encrypted string on screen instead of actual value upon retrieval through views.py !
I found one question on stackoverflow having same problem but got fixed because of metaclass syntax in different Python version.
My Python version 2.7.5 and Django 1.4.5, I am developing site on Windows 7.
Please someone help me resolve the issue, also Is there a way to debug these kind of issues ?, any suggestion is accepted, thank you in advance...
Finally got the answer :)
Had to understand how the SQLite database system works!
The problem here is, when the Database which we are using, let it be any in that case. Firstly we shall format the data to be stored into it. Once the types are assigned to the database table fields, (I don't know why) still it can save data of other format (May be bug in SQLite, table had Integer field but still stored 350 characters long string, command line displayed). Problem arises when retrieval is done, Database SW will look for Integer value from the field but gets some non-supported values. (Hence error).
May be my question now shifted to the one in brackets in fore mentioned paragraph ? Is it true (because it proved like that to me) or Is there other reasons ?

django multiwidget subclass not calling decompress()

I am trying to implement a MultiValueField for IP Adress/Domain Name entries. It works as expected for entering data.
My Problem is that if I want to display the form bound to specific data, the IP Address/Domain Name field stays empty. All other fields are filled with the desired data. If I use a normal CharField, I get the data that I would expect. But it does not work with my custom field.
I have tracked it down to the fact that my custom MultiWidget does not call its decompress method.
Here is my Field:
class accessIPField(forms.MultiValueField):
"""
custom Field for access IP
"""
def __init__(self, *args, **kwargs):
self.fields=(
forms.IPAddressField(label='IP Adress'),
forms.CharField(max_length=50,label='Domain Name')
)
self.widget=accessIPWidget()
super(accessIPField,self).__init__(self.fields,self.widget, *args, **kwargs)
def compress(self,data_list):
if data_list:
return " ".join(data_list)
And here is my widget:
class accessIPWidget(forms.MultiWidget):
"""
Widget to display IP Adress / Domain name pairs
"""
def __init__(self,*args,**kwargs):
self.widgets=(forms.TextInput(),forms.TextInput())
super(accessIPWidget,self).__init__(self.widgets,*args,**kwargs)
def decompress(self,value):
print 'decompress called'
if value:
return value.rsplit()
return [None,None]
def format_output(self, rendered_widgets):
return u'\n'.join(rendered_widgets)
The whole thing is called (in a larger context) as
self.fields['access_IPs'] = accessIPField()
Now as you can see, I put a print statement in my compress method, and I never get to see that statement. Also, if I rename compress to something like foobar, I would expect (according to the django code for MultiWidget) to get the NotImplementedError, which is not the case. Any suggestions?
I am using python 2.6.5, django 1.1 on ubuntu server 10.04.
It turns out that the problem was with the value_from_datadict() method as implemented by MultiWidget. First of all, it allready returned a list, so that is why decompress() was not called in the first place. Secondly, it allways returen a [None,None] list, so that is why the bound form stayed empty.
I needed to implement my own (within my accessIPWidget class):
def value_from_datadict(self, data, files, name):
try:
return data.get(name,None).rsplit()
except AttributeError:
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
Now the last line is what the original method did. In order to get the data into the bound form, I needed to add data.get(name,None).rsplit().
As far as I understand, the original value_from_datadict method only works for unbound fields. Because it changes the name of the original field to name + '_%s', which is what you get when pressing the submit button. In order to fill in a bound method, the datadict needs to be queried for 'name' only.
Hm, not shure if there is a way around this, but it seems to me that this behaviour should at least be documented somewhere.
Maybe I misunderstood something?

Setting the selected value on a Django forms.ChoiceField

Here is the field declaration in a form:
max_number = forms.ChoiceField(widget = forms.Select(),
choices = ([('1','1'), ('2','2'),('3','3'), ]), initial='3', required = True,)
I would like to set the initial value to be 3 and this doesn't seem to work. I have played about with the param, quotes/no quotes, etc... but no change.
Could anyone give me a definitive answer if it is possible? And/or the necessary tweak in my code snippet?
I am using Django 1.0
Try setting the initial value when you instantiate the form:
form = MyForm(initial={'max_number': '3'})
This doesn't touch on the immediate question at hand, but this Q/A comes up for searches related to trying to assign the selected value to a ChoiceField.
If you have already called super().__init__ in your Form class, you should update the form.initial dictionary, not the field.initial property. If you study form.initial (e.g. print self.initial after the call to super().__init__), it will contain values for all the fields. Having a value of None in that dict will override the field.initial value.
e.g.
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
# assign a (computed, I assume) default value to the choice field
self.initial['choices_field_name'] = 'default value'
# you should NOT do this:
self.fields['choices_field_name'].initial = 'default value'
You can also do the following. in your form class def:
max_number = forms.ChoiceField(widget = forms.Select(),
choices = ([('1','1'), ('2','2'),('3','3'), ]), initial='3', required = True,)
then when calling the form in your view you can dynamically set both initial choices and choice list.
yourFormInstance = YourFormClass()
yourFormInstance.fields['max_number'].choices = [(1,1),(2,2),(3,3)]
yourFormInstance.fields['max_number'].initial = [1]
Note: the initial values has to be a list and the choices has to be 2-tuples, in my example above i have a list of 2-tuples. Hope this helps.
I ran into this problem as well, and figured out that the problem is in the browser. When you refresh the browser is re-populating the form with the same values as before, ignoring the checked field. If you view source, you'll see the checked value is correct. Or put your cursor in your browser's URL field and hit enter. That will re-load the form from scratch.
Both Tom and Burton's answers work for me eventually, but I had a little trouble figuring out how to apply them to a ModelChoiceField.
The only trick to it is that the choices are stored as tuples of (<model's ID>, <model's unicode repr>), so if you want to set the initial model selection, you pass the model's ID as the initial value, not the object itself or it's name or anything else. Then it's as simple as:
form = EmployeeForm(initial={'manager': manager_employee_id})
Alternatively the initial argument can be ignored in place of an extra line with:
form.fields['manager'].initial = manager_employee_id
Dave - any luck finding a solution to the browser problem? Is there a way to force a refresh?
As for the original problem, try the following when initializing the form:
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.base_fields['MyChoiceField'].initial = initial_value
To be sure I need to see how you're rendering the form. The initial value is only used in a unbound form, if it's bound and a value for that field is not included nothing will be selected.