Ensure User Entered Integer - django

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'

Related

Can't save custom optional ChooserBlock when blank

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

Chaining filters

Django 1.10.1
On a page I have prepared a lot of controls. Some of them are organized as dynamically changing formsets. So, I don't even know how many of them are present.
I need a chained filters with AND, OR and NOT logical operations.
For example, I need something like this:
Entry.objects.filter(headline__startswith='What').exclude(pub_date__gte=datetime.date.today()).filter(pub_date__gte=datetime(2005, 1, 30)).filter(Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
Once more the number of filters is changing.
I was planning to ac like that: loop through request.POST and depending on a dozen of conditions prepare a string. The same string:
"Entry.objects.filter(headline__startswith='What').exclude(pub_date__gte=datetime.date.today()).filter(pub_date__gte=datetime(2005, 1, 30)).filter(Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))"
So, the string is correct. But I can't make it work with exec().
I asked here: why it is not working. And the answer was: it will not work, run the Python code directly.
I can construct something like that:
entries = Entry.objects.filter( **kwargs )
But this is just one filter. I can't imagine how to chain such filters.
Could you help me here&
You can chain a queryset on multiple lines - adding additional queries depending on view logic. These querysets are not executed as they are created, only when they are called.
I am not sure if it is the "best" or most pythonic way, but something like this works very well for me. This is using Django Rest Framework helper methods but it should be applicable without:
def get(self, request, format=None, *args, **kwargs):
name = request.GET.get('name', None)
location = request.GET.get('location', None)
id = request.GET.get('id', None)
results = Model.objects.all().order_by('-created') # this would be the default with no filters on it
results = results.filter(name__iexact=name) if name else results
results = results.filter(location__icontains=location) if location else results
results = results.filter(id__exact=id) if id else results
# process/serialize/send response
Not knowing which parameters you will get can be tricky, but an idea may be to loop over your kwargs and follow the same pattern in the loop - here's a stab (note I haven't tested it):
def get(self, request, format=None, *args, **kwargs):
results = Model.objects.all().order_by('-created') # this would be the default
for param, value in kwargs.items():
results = results.filter(***need to convert param to orm field***= value)

Forcing Django to use INNER JOIN instead of LEFT OUTER JOIN

I have implemented search in my Django application to allow searching by more than one field. This results in Django always using a LEFT OUTER JOIN, which in my case generates the wrong results. However, when I change the SQL generated from a LEFT OUTER JOIN to an INNER JOIN, it returns the correct result.
I am thinking it has to do with the way Q object in my code below.
from django.db import models, transaction
...
def construct_search(field_name):
if field_name.startswith('^'):
return "%s__istartswith" % field_name[1:]
elif field_name.startswith('='):
return "%s__iexact" % field_name[1:]
elif field_name.startswith('#'):
return "%s__search" % field_name[1:]
else:
return "%s__icontains" % field_name
class CoreSearchMixin(object):
"""Subclasses must define search_fields = [field_1, ...field_n]
where the field is a string, the name of a field, and can contain the following prefix characters:
'^': the search field must start with the search term, case insensitive
'=': the search field must exactly equal the search term, case insensitive
'#': full-text search
If no prefix is given, any string that contains the search field will match.
"""
search_fields = None
search_form_class = SearchForm
#cachedproperty
def search_form(self):
return self.search_form_class(getattr(self.request, self.request.method))
def get_query_help_message(self):
"""Returns a comma separated list of fields that are used in the search, to help the user
create a search.
"""
fields = []
if self.search_fields:
for search_field in self.search_fields:
field = get_field_from_path(self.model, search_field)
fields.append(field.verbose_name.title())
return ",".join(fields)
def get_filtered_queryset(self, queryset):
if self.search_form.is_valid():
self.query = self.search_form.cleaned_data['q']
else:
self.query = None
if self.search_fields and self.query:
orm_lookups = (construct_search(str(search_field).replace('.', '__'))
for search_field in self.search_fields)
chained_or_queries = None
for bit in self.query.split():
or_queries = (models.Q(**{orm_lookup: bit})
for orm_lookup in orm_lookups)
if chained_or_queries:
chained_or_queries = itertools.chain(chained_or_queries, or_queries)
else:
chained_or_queries = or_queries
return queryset.filter(reduce(operator.or_, chained_or_queries))
else:
return queryset
def get_context_data(self, **kwargs):
return super(CoreSearchMixin, self).get_context_data(
search_form=self.search_form,
query_help_message=self.get_query_help_message(),
search_fields=self.search_fields,
**kwargs
)
How can I ensure that an INNER JOIN is used instead of a LEFT OUTER JOIN in the case of my code above?
According to your question, you want to search over multiple fields. However, following the logic you have, the first result that is found in the OR sequence is returned—without returning possible subsequent matches in the OR sequence; remember, the OR operator stops evaluating upon a truthy result.
In order to convert your OUTER LEFT JOINs to INNER JOINs you would need to have AND/OR Q object permutations of search fields combinations (optimal?), or query them separately and do an intersection on the result (sub-optimal), or write the SQL yourself (sub-optimal).
PS: I've run into this issue before a writing a Datatables API wrapper for use with Django.
PS: I'd consider refactoring, and further commenting your code—specifically get_filtered_queryset; it took a few minutes for me to wrap my head around what was going on here.

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, auto generating unique model fields and recursively calling auto generator if not unique

I am working on a Django project where a Thing would have a unique 10 digit Key, in addition to the standard auto incrementing ID integerfield. I use a simple random number function to create it. [I'm sure there's a better way to do this too]
When a Thing is created, a 10 digit Key is created. I use the .validate_unique() to check the Key's uniqueness. If its not unique, is there a simple way I can recursively call the Key generator (makeKey()) until it passes? Code follows:
Models.py:
class Thing(models.Model):
name=models.CharField(max_length=50)
key=models.IntegerField(unique=True)
Views.py:
def makeKey():
key=''
while len(key)<10:
n=random.randint(0,9)
key+=`n`
k=int(key)
#k=1234567890 #for testing uniqueness
return k
def createThing(request):
if ( request.method == 'POST' ):
f = ThingForm(request.POST)
try:
f.is_valid()
newF=f.save(commit=False)
newF.key=makeKey()
newF.validate_unique(exclude=None)
newF.save()
return HttpResponseRedirect(redirect)
except Exception, error:
print "Failed in register", error
else:
f = ThingForm()
return render_to_response('thing_form.html', {'f': f})
Thank you
No need for recursion here - a basic while loop will do the trick.
newF = f.save()
while True:
key = make_key()
if not Thing.objects.filter(key=key).exists():
break
newF.key = key
newF.save()