Django: overriding single model field validation - django

I am using django.db.models.fields.DecimalField in one case, but its validation error is quite BAD.
like, when user enters 3,4 instead of 3.4 it says - 'Enter a number'. Well 3,4 is as much as number in some countries as 3.4 is. At least to those who perhaps are not well versed in computer stuff.
So for that reason i am trying to override this fields validation so i could validate it myself.
My problem is - before modelforms clean_my_field() is called, models own validation works and it already raises an error.
So i looked up https://docs.djangoproject.com/en/dev/ref/models/instances/#validating-objects
After reading this i understood that i could do
def full_clean(self):
super(MyModel, self).full_clean(exclude = 'my_field')
and my_field would be excluded from validation and i could validate it myself in
def clean(self)
pass
#how do i access cleaned data here anyway?
#self.cleaned_data does not exist
#is self.my_field the only way?
But alas - it does not work. self.my_field value is old value in clean() method and cleaned_data is nowhere to be found.
All this makes me think my approach is wrong. I could write my own field which extends django's DecimalField i guess. I thought this approach would work... Can someone clear this up for me as - WHY it does not work. why is that exclude there if it does not work? Django version 1.4.2 by the way.
Alan
Edit: I dug deeper. It seems that even if i override all models cleaning methods and dont use super in them at all - the fields are STILL cleaned at some point and the error is already raised by then.
I guess i will be doing some extending to django.db.models.fields.DecimalField in this case.
An answer about why the exclude is there in the full_clean method would still be nice. Why is it there if it does not work?

I know it's an old question but for the ones that didn't find an answer, what I did was to add localize=True in the ModelAdmin for the Admin Site:
formfield_overrides = {
models.DecimalField: {'localize': True},
}
That will make the FormField locale aware, accepting comma as decimal separator (depending of the current locale, of course). It will also display it localized.
https://docs.djangoproject.com/en/dev/topics/i18n/formatting/#locale-aware-input-in-forms
If you are targeting a single DecimalField or you are writing a custom Form or ModelForm just follow the instructions on the URL.

def views_name(request):
......
field = dec_num(form.cleaned-data['....'])
.........
return render(request, 'page.html', {.....})
def dec_num(value):
return value.replace(",",".")

Related

Django POST validation seeing number as string

I'm a bit confused where to go with this as I thought it would be part of Django's validation... I'm on 1.8 because I'm using an older database connection library that was last tested with 1.8 (rewriting a frontend for old data).
models.py:
class Order(models.Model):
#rest of class#
RequestorNumber = models.SmallIntegerField(db_column='requestor_no')
class Requestor(models.Model):
RequestorNumber = models.SmallIntegerField(primary_key=True, db_column="requester_no")
Requestor = models.CharField(max_length=20, db_column = "requester")
def __str__(self):
return self.Requestor
forms.py
class OrderForm(forms.ModelForm):
RequestorNumber = forms.ModelChoiceField(queryset=Requestor.objects.all().order_by('RequestorNumber'), label="Requestor")
So this creates a correct dropdown in the template, with values as integers and text as the descriptions ex:
<option value="1" selected="selected">JOHN DOE</option>
When the form is submitted, the POST QueryDict has a proper entry when printing the entire request:
...
'OrderForm-RequestorNumber': ['1']
...
but this is coming in as a string (as I would expect), but the validator when doing is_valid() kicks back and the webpage gets:
'JOHN DOE' value must be an integer.
Is this by design? I feel like it's trying ignore the value of the selected for the form and referring back to the object's __str__ definition as what needs to be saved. If this is dumb, i'm also all ears to figure out what a more correct method is, the only problem is I can't change the DB schema, and all tables are managed=False in the meta.
EDIT: I overwrote the clean_RequestorNumber in the form to literally output the value it thinks is supposed to be saved, and it's giving the value of the __str__ of the method rather than the primary key.
I need to change this behavior but I can't nail down the spot in source code where the validation is being done. Between models.py, fields.py, and widgets.py i can see the required, valid_choice, and other validations but I can't spot where this is being pushed around. Once I can spot it I can try writing my own class but I can't figure out what to overwrite.
class OrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.fields['RequestorNumber'].choices = [(x.pk, x.Requestor) for x in Requestor.objects.all()]
RequestorNumber = forms.ChoiceField()
Doing it the old way before ModelChoiceField was a thing validates correctly. Not sure if this was fixed past 1.8 but I'm still having trouble finding the spot in the source that would provide the incorrect behavior for the validation. If someone can point me in the correct direction I'd be happy as I would still rather fix the version I'm on so I can have some cleaner code to deal with. Going to leave this up for a week if someone can help and then just mark this as the answer.

Django filter the queryset of ModelChoiceField - what did i do wrong?

I know that many questions exist about this same topic, but i am confused on one point.
My intent is to show two ModelChoiceFields on the form, but not directly tie them to the Game model.
I have the following:
forms.py
class AddGame(forms.ModelForm):
won_lag = forms.ChoiceField(choices=[('1','Home') , ('2', 'Away') ])
home_team = forms.ModelChoiceField(queryset=Player.objects.all())
away_team = forms.ModelChoiceField(queryset=Player.objects.all())
class Meta:
model = Game
fields = ('match', 'match_sequence')
Views.py
def game_add(request, match_id):
game = Game()
try:
match = Match.objects.get(id=match_id)
except Match.DoesNotExist:
# we have no object! do something
pass
game.match = match
# get form
form = AddGame(request.POST or None, instance=game)
form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )
# handle post-back (new or existing; on success nav to game list)
if request.method == 'POST':
if form.is_valid():
form.save()
# redirect to list of games for the specified match
return HttpResponseRedirect(reverse('nine.views.list_games'))
...
Where i am confused is when setting the queryset filter.
First i tried:
form.home_team.queryset = Player.objects.filter(team=match.home_team )
but i got this error
AttributeError at /nine/games/new/1
'AddGame' object has no attribute 'home_team'
...
so i changed it to the following: (after reading other posts)
form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )
and now it works fine.
So my question is, what is the difference between the two lines? Why did the second one work and not the first? I am sure it is a newbie (i am one) question, but i am baffled.
Any help would be appreciated.
Django Forms are metaclasses:
>>> type(AddGame)
<class 'django.forms.forms.DeclarativeFieldsMetaclass'>
They basically create a form instance according to the information given in its definition. This means, you won't get exactly what you see when you define the AddGame form. When you instantiate it, the metaclass will return the proper instance with the fields provided:
>>> type(AddGame())
<class 'your_app.forms.AddGame'>
So, with the instance, you can access the fields by simply doing form.field. In fact, it is a bit more complicated than that. There are two types of fields you can access. With form['field'] you'll be accessing a BoundField. Which is used for output and raw_input.
By doing form.fields['fields'] you'll be then accessing to a field that python can understand. This is because if the from already got any input, there's where validation and data conversion take places (in fact, those are the fields used for this, the general process of validation is a bit more complicated).
I hope this might clear a little the issue for you but as you may see, the whole form's API is really big and complicated. Is very simple for end-users but it has a lot of programming behind the curtains :)
Reading the links provides will help clear your doubts and will improve your knowledge about this very useful topic and Django in general.
Good luck!
UPDATE: By the way, if you want to learn more about Python's Metaclasses, this is a hell of an answer about the topic.
In you views.py, you have this line:
form = AddGame(request.POST or None, instance=game)
So form is a Form object of class AddGame (Side note: you should change the name to AddGameForm to avoid confusion).
Since home_team is a field in AddGame class, it's not an attribute in form object. That's why you can't access it via form.home_team.
However, Django Form API provides fields attribute to any form object, which is a dict contains all form fields. That's why you can access form.fields['home_team'].
And finally since home_team is a ModelChoiceField, it can contain a queryset attribute, that's why you can access form.fields['home_team'].queryset

Django: Editable BINARY field which displays as HEX in Admin

update I have now figured that there is a reason to define get_prep_value() and that doing so improves Django's use of the field. I have also been able to get rid of the wrapper class. All this has, finally, enabled me to also eliminate the __getattribute__ implementation with the data model, which was annoying. So, apart from Django callingto_python()` super often, I'm now fine as far as I can see. /update
One morning, you wake up and find yourself using Django 1.4.2 along with DjangoRESTFramework 2.1.2 on Python 2.6.8. And hey, things could definitely be worse. This Django admin magic provides you with forms for your easily specified relational data model, making it a pleasure to maintain the editorial part of your database. Your business logic behind the RESTful URLs accesses both the editorial data and specific database tables for their needs, and even those are displayed in the Django admin, partially because it's easily done and nice to have, partially because some automatically generated records require a mini workflow.
But wait. You still haven't implemented those binary fields as BINARY. They're VARCHARS. You had put that on your ToDo list for later. And later is now.
Okay, there are those write-once-read-many-times cases with small table sizes where an optimization would not necessarily pay. But in another case, you're wasting both storage and performance due to freuquent INSERTs and DELETEs in a table which will get large.
So what would you want to have? A clear mapping between the DB and Django, where the DB stores BINARY and Django deals with hex strings of twice the length. Can't be that hard to achieve, can it?
You search the Web and find folks who want CHAR instead for VARCHAR, others who want BLOBs, and everybody seems to do it a bit differently. Finally, you end up at Writing custom model fields where the VARCHAR -> CHAR case is officially dealt with. So you decide to go with this information.
Starting with __init__(), db_type() and to_python(), you notice that to_python() gets rarely called and add __metaclass__ = models.SubfieldBase only to figure that Django now calls to_python() even if it has done so before. The other suggestions on the page suddenly start to make more sense to you, so you're going to wrap your data in a class, such that you can protect it from repeated calls to to_python(). You also follow the suggestion to Put a __str__() or __unicode__() method on the class you're wrapping up as a field and implement get_prep_value().
While the resulting code does not do what you expect, one thing you notice is that get_prep_value() never gets called so far, so you're removing it for now. What you do figure is that Django consistently appears to get a str from the DB and a unicode from the admin, which is cool, and end up with something like this (boiled down to essentials, really).
class MyHexWrappeer(object):
def __init__(self, hexstr):
self.hexstr = hexstr
def __len__(self):
return len(self.hexstr)
def __str__(self):
return self.hexstr
class MyHexField(models.CharField):
__metaclass__ = models.SubfieldBase
def __init__(self, max_length, *args, **kwargs):
assert(max_length % 2 == 0)
self.max_length = max_length
super(MyHexField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
return 'binary(%s)' % (self.max_length // 2)
def to_python(self, data):
if isinstance(data, MyHexWrapper): # protect object
return data
if isinstance(data, str): # binary string from DB side
return MyHexWrapper(binascii.b2a_hex(data))
if isinstance(data, unicode): # unicode hex string from admin
return MyHexWrapper(data)
And... it won't work. The reason, of course, being that while you have found a reliable way to create MyHexWrapper objects from all sources including Django itself, the path backwards is clearly missing. From the remark above, you were thinking that Django calls str() or unicode() for admin and get_prep_value() in the direction of the DB. But if you add get_prep_value() above, it will never be called, and there you are, stuck.
That can't be, right? So you're not willing to give up easily. And suddenly you get this one nasty thought, and you're making a test, and it works. And you don't know whether you should laugh or cry.
So now you try this modification, and, believe it or not, it just works.
class MyHexWrapper(object):
def __init__(self, hexstr):
self.hexstr = hexstr
def __len__(self):
return len(self.hexstr)
def __str__(self): # called on its way to the DB
return binascii.a2b_hex(self.hexstr)
def __unicode__(self): # called on its way to the admin
return self.hexstr
It just works? Well, if you use such a field in code, like for a RESTful URL, then you'll have to make sure you have the right kind of string; that's a matter of discipline.
But then, it still only works most of the time. Because when you make such a field your primary key, then Django will call quote(getattr()) and while I found a source claiming that getattr() "nowdays" will use unicode() I can't confirm. But that's not a serious obstacle once you got this far, eh?
class MyModel((models.Model):
myhex = MyHexField(max_length=32,primary_key=True,editable=False)
# other fields
def __getattribute__(self, name):
if (name == 'myhex'):
return unicode(super(MyModel, self).__getattribute__(name))
return super(MyModel, self).__getattribute__(name)
Works like a charm. However, now you lean back and look at your solution as a whole. And you can't help to figure that it's a diversion from the documentation you referred to, that it uses undocumented or internal behavioural characteristics which you did not intend to, and that it is error-prone and shows poor usability for the developer due to the somewhat distributed nature of what you have to implement and obey.
So how can the objective be achieved in a cleaner way? Is there another level with hooks and magic in Django where this mapping should be located?
Thank you for your time.

Tutorial about how to write custom form fields in django?

Is there any good articles that explain custom form fields in django, not custom model fields? I couldn't find any through google.
Form fields are easy to customize:
class UpperCaseField(forms.CharField):
def clean(self, value)
try:
return value.upper()
except:
raise ValidationError
basically you just create a class that inherits from the field that most resembles what you want, then rewrite the clean() method so that it returns the value you want. Here is another example:
class MyObjectField(forms.ModelChoiceField):
# in this case, 'value' is a string representing
# the primary key of a MyObject
def clean(self, value):
try:
return MyObject.objects.get(pk=value)
except:
raise ValidationError
custom widgets on the other hand, are a little more useful, but a little more hard to do because there are a few more methods that need to be written so that they work smoothly.
As always with open-source code, you'll learn a great deal by reading the source itself. See the django.forms.fields module to see how all the different form fields are defined - most of them are subclasses of others already, so you can just replicate that and change what you need.
It's not a tutorial, but django's docs talks about this a little:
If the built-in Field classes don't
meet your needs, you can easily create
custom Field classes. To do this, just
create a subclass of
django.forms.Field. Its only
requirements are that it implement a
clean() method and that its __init__()
method accept the core arguments
mentioned above (required, label,
initial, widget, help_text).
You can read about the clean method and see an example at the django docs. Again, not a tutorial, but useful.
I find I am learning a lot by reading the code in some of the the django app projects that are available, such as django-extensions, which override the form fields and are good learning tools (for me, at least). This can help get you started.

Does model.CharField('blank=False') work with save()?

I've a model like this with Django 1.1:
class Booking(models.Model):
name = models.CharField(max_length=100)
By default, I'm reading that both 'null' and 'blank' are False.
So with a test like this...
class SimpleTest(TestCase):
def test_booking_save(self):
b = Booking()
b.save()
... I expected the save to throw an exception. But it doesn't. It seems quite happy to create a new record with a blank name (Postgres and SQLite3).
I note that via the admin interface a save does indeed fail with a "this field is required".
Questions are:
Is the 'blank' attribute only applied by forms?
Is the fix to override the save() method and explicitly check that len(name) != 0?
Have I misunderstood something which once understood resolves my misunderstanding?
UPDATE: See the model validation documentation in recent Django versions.
Original answer: blank=True/False only applies to forms. Data validation currently only happens at the form level; this will change when the model-validation Google Summer of Code work gets merged in to trunk.
The only kind of validation that currently happens at the model layer is whatever errors your database backend will throw if it can't handle what it gets. In the case of an empty CharField you'll generally never get errors from the database, as Django sets the field to an empty string by default.
For now, you should use the save() method for any model-level validation you want. Soon (if you're on trunk) or when 1.2 comes out, use the model validation stuff.
From the Django Docs:
"Note that empty string values will always get stored as empty strings, not as NULL. Only use null=True for non-string fields such as integers, booleans and dates."
Your code is storing an empty string.
To illustrate this, try:
class SimpleTest(TestCase):
def test_booking_save(self):
b = Booking()
b.name = None
b.save()