I need some help understand Django form validators - django

I've read the Django documentation here:
http://docs.djangoproject.com/en/dev/ref/forms/validation/
I've also browsed a number of search results on Google and Stack Overflow, but I haven't been able to answer my questions below.
As an example, say I have a model named "Widgets" with a CharField named "product_name". Now say that I want to restrict the allowable characters in "product_name" to [a-zA-Z0-9] plus apostrophes, dashes and underlines (i.e. ' - _) and show a form error to the user if they enter a restricted character.
From the above research, I gather that I need to create a validation function somewhere to check for these characters.
My specific questions:
1. What is best practice as to where a validation function like this should live in my Django project?
2. From where do I call this validation function?
3. How do I show an error to the user if a "bad" character is entered?
4. Would someone be so kind as to post a sample validation function?
Thank you, I appreciate any help you can provide.

Go with Chefsmart's answer if you can. Here is an example of a sample validation function in case it helps:
class MyCustomInvoiceForm(forms.Form):
serial_number = forms.IntegerField(min_value = 1, initial = 1)
# Other fields. I am interested in serial_number.
def clean_serial_number(self):
serial_number = self.cleaned_data.get('serial_number')
if not serial_number:
return
return _my_custom_validation(serial_number)
def _my_custom_validation(self, serial_number):
serial_number = int(serial_number)
if not ((serial_number - 1) % 10 == 0):
message = "Serial number should be 1 or one greater than a multiple of 10 (Example: 1, 11, 21 etc.)"
raise forms.ValidationError(message)
return serial_number
This is a snippet of code from a project I did. The customer required some interesting verification logic for serial number.

If you are using a Django form, you have the option of using a RegexField for your product_name field. See http://docs.djangoproject.com/en/1.2/ref/forms/fields/#regexfield
That would be the cleanest approach for your situation.

Related

Django ORM - LEFT JOIN with WHERE clause

I have made a previous post related to this problem here but because this is a related but new problem I thought it would be best to make another post for it.
I'm using Django 1.8
I have a User model and a UserAction model. A user has a type. UserAction has a time, which indicates how long the action took as well as a start_time which indicates when the action began. They look like this:
class User(models.Model):
user_type = models.IntegerField()
class UserAction:
user = models.ForeignKey(User)
time = models.IntegerField()
start_time = models.DateTimeField()
Now what I want to do is get all users of a given type and the sum of time of their actions, optionally filtered by the start_time.
What I am doing is something like this:
# stubbing in a start time to filter by
start_time = datetime.now() - datetime.timedelta(days=2)
# stubbing in a type
type = 2
# this gives me the users and the sum of the time of their actions, or 0 if no
# actions exist
q = User.objects.filter(user_type=type).values('id').annotate(total_time=Coalesce(Sum(useraction__time), 0)
# now I try to add the filter for start_time of the actions to be greater than or # equal to start_time
q = q.filter(useraction__start_time__gte=start_time)
Now what this does is of course is an INNER JOIN on UserAction, thus removing all the users without actions. What I really want to do is the equivalent of my LEFT JOIN with a WHERE clause, but for the life of me I can't find how to do that. I've looked at the docs, looked at the source but am not finding an answer. I'm (pretty) sure this is something that can be done, I'm just not seeing how. Could anyone point me in the right direction? Any help would be very much appreciated. Thanks much!
I'm having the same kind of problem as you. I haven't found any proper way of solving the problem yet, but I've found a few fixes.
One way would be looping through all the users:
q = User.objects.filter(user_type=type)
for (u in q):
u.time_sum = UserAction.filter(user=u, start_time__gte=start_time).aggregate(time_sum=Sum('time'))['time_sum']
This method does however a query at the database for each user. It might do the trick if you don't have many users, but might get very time-consuming if you have a large database.
Another way of solving the problem would be using the extra method of the QuerySet API. This is a method that is detailed in this blog post by Timmy O'Mahony.
valid_actions = UserAction.objects.filter(start_time__gte=start_time)
q = User.objects.filter(user_type=type).extra(select={
"time_sum": """
SELECT SUM(time)
FROM userAction
WHERE userAction.user_id = user.id
AND userAction.id IN %s
""" % (%s) % ",".join([str(uAction.id) for uAction in valid_actions.all()])
})
This method however relies on calling the database with the SQL table names, which is very un-Django - if you change the db_table of one of your databases or the db_column of one of their columns, this code will no longer work. It though only requires 2 queries, the first one to get the list of valid userAction and the other one to sum them to the matching user.

Django - filter queryset disregarding spaces

I have a model that contains phone numbers external_number, stored as a char field:
models.py
class PhoneRecord(models.Model):
def __unicode__(self):
return "Call to %s (%s)" % (self.external_number, self.call_date.strftime("%c"))
INBOUND = "I"
OUTBOUND = "O"
DIRECTION_CHOICES = (
(INBOUND, "Inbound"),
(OUTBOUND, "Outbound"),
)
external_number = models.CharField(max_length=20)
call_date = models.DateTimeField()
external_id = models.CharField(max_length=20)
call_duration = models.TimeField()
call_direction = models.CharField(max_length=1, choices=DIRECTION_CHOICES, default=INBOUND)
call = models.FileField(upload_to='calls/%Y/%m/%d')
The form is cleaning and storing the data using the UKPhoneNumberField from https://github.com/g1smd/django-localflavor-UK-forms/blob/master/django/localflavor/uk/forms.py
This means that the number is stored in the database in the format 01234 567890 i.e. with a space in.
I have created a filter using django-filters which works well, when searching for partial phone number except that it doesn't filter correctly when the search term doesn't include the space. i.e.
search for 01234 returns the record for the example above
search for 567890 returns the record for the example above
search for 01234 567890 returns the record for the example above
search for 01234567890 does not return the record for the example above
Now, I could subject the form on the filter to the same restrictions (i.e. UKPhoneNumberField as the input screen, but that then takes away the ability to search for partial phone numbers.
I have also explored the possibility of using something like django-phonenumber-field that will control both the model and the form, but the validation provided by UKPhoneNumberField allows me to validate based on the type of number entered (e.g. mobile or geographical).
What I would ideally like is either
My filter to ignore spaces that are either input by the user in their search query, or any spaces that are in the stored value. Is this even possible?
Apply the validation provided by UKPhoneNumberField to another field type without having to analyse and re-write all the regular expressions provided.
Some other UK Phone number validation I have not found yet!
You could setup the filter to do something like this:
phone_number = "01234 567890"
parts = phone_number.split(" ")
PhoneRecord.objects.filter(
external_number__startswith="{} ".format(parts[0]),
external_number__endswith=" {}".format(parts[1]),
)
That way the filter is looking for the first half of the number with the space and then the second half of the number with the space as well. The only records that would be returned would be ones that had the value of "01234 567890"
I ended up adding a custom filter with a field_class = UKPhoneFilter. This means I can't search on partial numbers, but that was removed as a requirement from the project.
You should think about normalizing the data before you save them into your DB.
You could just use django-phonenumber-field which does just that.
In regarding your problem you can always use django's regex field postfix to query over regular expression.
e.g. MyModel.objects.filter(myfiel__regex=r'[a-Z]+')

Django Models - Prepare the data for the database

I have a form that askes for a phone number. I need to make sure that only digits [0-9] get saved in the database.
In the Django documentation it says:
What happens when you save?
3) Prepare the data for the database. Each field is asked to provide its current value in a data type that can be written to the database.
How does this happen? Or more specifically, how can I make sure this is cleaned? I know that I can just override the models save method, but it seems like there is a better way and I'm just not sure how to do it.
I guess I could write a custom field for it, but that seems like overkill here.
Also, I realize that I can put the validation on the form, but it really feels like stripping out the characters belongs on the model.
Your question specifically about point 3 is a little different from "cleaning" in the way django uses the term.
3) Prepare the data for the database. Each field is asked to provide its current value in a data type that can be written to the database.
Point 3 is about converting the python object values to one suitable for a database. Specifically, this is done in Field.get_prep_value and Field.get_db_prep_value
https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.Field.get_prep_value
It's the opposite of to_python which takes a DB value and converts it to a python object.
As for ensuring only digits 0-9 get stored, that would be done in a Fields clean method (subclass IntegerField), form clean method, form clean_FIELDNAME method, or model clean.
You can add a custom Form Cleaning method to your objects model - take a look at this article https://docs.djangoproject.com/en/dev/ref/forms/validation/#form-field-default-cleaning
Look at "Cleaning a specific field attribute"
use django model form + custom form field cleaning
Below is a quick example of what you might be looking for, where MyModel is the model containing the phone number field, which I named it tel here.
import re
class MyForm(ModelForm):
class Meta:
model = MyModel
def clean_tel(self):
tel = self.cleaned_data.get('tel', '') # this is from user input
# use regular expression to check if tel contains only digits; you might wanna enhance the regular expression to restrict the tel number to have certain number of digits.
result = re.match(r'\d+', tel)
if result:
return tel # tel is clean so return it
else:
raise ValidationError("Phone number contains invalid character.")

Sort ordering in Django: possible to ignore "The" prefix when sorting?

This seems like the kind of thing Django makes simple, wondering if anyone has any experience with it.
I have a table of bands, some of whom are called 'The Geeks' (for example). I want them to appear alphabetically under 'G'.
I know I can do some fancy SQL to rewrite them on the fly and sort that way, but does Django offer anything built-in so I can keep my nice Band.objects.all() syntax and still filter on a custom sort?
I know I could change my data to include a has_prefix field or something and store the bandname as 'Geeks, The' or whatever, but I'd rather not touch the data itself if possible.
Any tips?
Here's how I did it on a recent Django project:
from django.db import models
SomeClass(models.Model):
title = models.CharField()
#property
def alphabetical_title(self):
"""
Returns an alphabetical-friendly string of a title attribute.
"""
title = self.title
# A list of flags to check each `title` against.
starts_with_flags = [
'the ',
'an ',
'a '
]
# Check each flag to see if the title starts with one of it's contents.
for flag in starts_with_flags:
if title.lower().startswith(flag):
# If the title does indeed start with a flag, return the title with
# the flag appended to the end preceded by a comma.
return "%s, %s" % (title[len(flag):], title[:len(flag)-1])
else:
pass
# If the property did not return as a result of the previous for loop then just
# return the title.
return self.title
Since this is a property and not an actual column in the database I need to retrieve a QuerySet first and then sort it after the fact in a view. Here's how I did that:
from operator import attrgetter
from someapp.models import SomeClass
some_objects = SomeClass.objects.all()
sorted_objects = sorted(some_objects, key=attrgetter('alphabetical_title'), reverse=False)
I'm sure you've long since found a solution but I figured it might help to post it anyways as someone else in the future might run into this same problem.
You can't do it with order_by()
You can
Do some fancy SQL. If you want to keep it nice and short, you should write a custom model manager.
Do sorting at Python-level, not SQL-level
Store "sort title" and "display title" separately.
Using the model and a couple hints from the above (by respondcreate), and an adaptation of this answer, I came up with the following one line approach:
mysortedlist = sorted([x for x in SomeClass.all()],
key=lambda y: re.sub("^(the|a|an) ","",y.title.lower()) )
This generates a list from the QuerySet and sorts it according to a lambda function that replaces the, a, and an at the beginning of a lowercased version of the title.

Django regex field - unique and without blank spaces

I have a ModelForm, in which I'm having a CharField, which is declared as unique in the Model.
But I have 2 problems:
If I fill in the form with a field having the same name I don't get an error message.
I'd like this field not to contain white spaces.
Is it possible to do that using a ModelForm?
You can do something close to this:
class MyModelForm(forms.ModelForm):
# your field definitions go here
def clean_myuniquefield(self):
# strip all spaces
data = str(self.cleaned_data['myuniquefield']).replace(' ', '')
model = self._meta.model
# check if entry already exists
try:
obj = model.objects.get(myuniquefield=data)
except model.DoesNotExist:
return data
raise forms.ValidationError("Value already exists!")
To get rid of spaces, make a clean_fieldname function to strip the spaces.
http://docs.djangoproject.com/en/dev/ref/forms/validation/#ref-forms-validation
As for uniqueness, also note about the meta-field unique_together. I don't know if you need it, but I didn't know about it until I dug around.
If you really need to do uniqueness checking before trying to add and failing, you can also do that in the clean_* function. However, it might be better to assume that the database will take care of it and fail in a standard way, and just set up your error messages properly. That way, if you change constraints later, it will flow through more easily. And if others have to maintain your code, it will be more standard.
Hope this helps.