DRF - Serializer fields "source" argument unclear behaviour - django

So, consider the following:
>>> d = {'macAddress': '00:00:00:00:00:00'}
>>> s = DeviceSerializer(data=d)
>>> s
DeviceSerializer(data={'macAddress':'00:00:00:00:00:00'}):
mac_address = CharField(max_length=20, source='macAddress')
>>> s.is_valid()
False
>>> s.errors
{'mac_address': [ErrorDetail(string='This field is required.', code='required')]}
Based on the simple above example and my current understanding of the source field argument I would expect the mac_address field to be automatically mapped to the macAddress in the input data and the serializer to be valid.
Why this is not the case?
Thanks to anyone willing to help out :)

It is just the other way around. source is what is on the python side and the field name on the external/API side.
data = {'mac_address':'00:00:00:00:00:00'}
would lead to:
validated_data == {'macAddress':'00:00:00:00:00:00'}

Related

Django. Error saving empty string to DateTimeField(blank=True)

I have a model with a DateTimeField (blank=True, null=True). However, when I try to save an empty string in it, I get the following error:
>>> from courses.models import Event
>>> x = Event.objects.last()
>>> x.date_start = ""
>>> x.save()
django.core.exceptions.ValidationError: ['“” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format.']
I have never seen this, any ideas? All my migrations are up to date.
blank=True [Django-doc] has impact on modelforms, not on the model itself. It does not mean that the element accepts an empty string. blank=True simply means that the field is not required. In case the value is not specified, it will use the default value.
But at the model layer, nothing changes. Since your field has null=True [Django-doc], the default in case you do not fill in the value will be None, so you can use:
>>> from courses.models import Event
>>> x = Event.objects.last()
>>> x.date_start = None
>>> x.save()

Django get_or_create method doesn't return exact object

Here is my models.py:
class Foo(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=30)
In django manage.py shell:
>>> a,b = Foo.objects.get_or_create(name='hi')
>>> b
True
>>> vars(a)
{'_state': <django.db.models.base.ModelState object at 0x02761390>, 'id': None,
'name': 'hi'}
The problem here is the customized id attribute has no value. And if I continue to do the following:
>>> c = foo.objects.get(name='hi')
>>> c.id
1
>>> a is c
False
So that newly created object (denoted by c) already exists now, but b is not an exact reference to it like c is.
Is this a bug? My django version is 1.6.5.
By default, if you have not specified the id field for you model, Django will create AutoField:
id = models.AutoField(primary_key=True)
In your case you've created just IntegerField. I suppose this is the point. In the database, it will still be incremented, but django won't assign the id value from database to the object, returned by get_or_create
By the way, in your question:
>>> b is c
False
b - is a bool value. I think you were trying to do
>>> a is c
But also this is not a good idea. If you want to compare django model objects, use ==, not is:
>>> a == c
You will get False anyway, as a has its id set to None.
You have declared your id field as an IntegerField, so Django does not know it needs to autoincrement. You could define it as an AutoField, but actually there is no reason to declare it at all: Django will automatically define it for you if you leave it out.
Note this has nothing to do with get_or_create: you would have exactly the same problem with a normal create/save.

django forms get field output in view

I have model form with several fields works as expected. Now I need, for specific reasons, to get form field in view but got error 'EditPostForm' object has no attribute 'about' when I call mydata1 = form.about in view. But about field exist of course. form.data.about also wont work etc. So how can I get it? Thanks.
If you form has instance associated to it, you can try
post = EditPost.objects.get(id=id)
form1 = EditPostForm(instance=post)
form1.instance.about
Based on your comment below if you are using ManyToMany relation you can get the value as
>>> bf = BookForm(instance=book)
>>> bf.instance.authors
<django.db.models.fields.related.ManyRelatedManager object at 0x0000000004658B38>
>>> bf.instance.authors.all() #which returns a query set of related objects
[<Author: Kotian>]
>>> bf.instance.authors.all()[0]
<Author: Kotian>
>>> bf.instance.authors.all()[0].name
u'Kotian'
or based on how you have defined the ManyToMany
>>> af = AuthorForm(instance=author)
>>> af.instance.name
u'MyName'
>>> af.instance.book_set
<django.db.models.fields.related.ManyRelatedManager object at 0x0000000004658C18>
>>> af.instance.book_set.all() # returns queryset
[<Book: Book object>, <Book: Book object>]
>>> af.instance.book_set.all()[0] #accessing first object here
<Book: Book object>
>>> af.instance.book_set.all()[0].name
u'Lepord'

Mapping a model fields with a non model fields in a ModelForm

I've spent nearly the whole day reading the documentation about forms and ModelForms. I managed to use the basic stuff but now I'm having really trouble because I don't find any hints in the documentation about mapping model field with non-model fields. That's what I mean:
I have this model:
class Process(models.Model):
key = models.CharField(max_length=32, default="")
name = models.CharField(max_length=30)
path = models.CharField(max_length=215)
author = models.CharField(max_length=100)
canparse = models.NullBooleanField(default=False)
last_exec = models.DateTimeField(null = True)
last_stop = models.DateTimeField(null = True)
last_change = models.DateTimeField(null = True, auto_now=True)
The only fields that the users are going to modify are name and author. path is the absolute real path of the configuration file of my Process. The directory is fixed, users are not going to care whether the directory is /home/measure/conf or /var/whatever), they only care about the filename. That's why I want a filename field in my ModelForm.
My form looks like:
class ProcessForm(MonitorForm):
filename = forms.CharField(max_length=250)
class Meta:
model = Process
fields = ('name', 'filename', 'author')
Now, what I want is that filename contains the file name stored in Process.path and not the entire path, that's what I mean:
>>> from monitor.forms import ProcessForm
>>> from remusdb.models import Process
>>>
>>> p = Process(name="test1", path="/tmp/config/a.cnf", author="pablo")
>>> f = ProcessForm(instance=p)
>>> print f["filename"].value()
---> here I want to get "a.cnf"
The problem is I don't know how to write a.cnf to the filename field once I call ProcessForm(instance=p). I thought about doing it in the clean function but I'm not sure whether this is a good place at all. I assume that at this point it would be too late, because the fields are more or less read-only, once initilaized you cannot change their value. So how should I do it? Should I create a custom field and override __init__?
I got this idea from reading form field default cleaning and wanted to test if first. I didn't want to override init first and I thought of first playing with theto_python method like in the documentation. So I created the class RemusFilenameField:
class RemusFilenameField(forms.CharField):
def to_python(self, value):
print "to_python's value is %s" % value
return value.upper()
def clean(self, value):
print "clean's value is %s" % value
return value.upper()
and changed the filename line on ProcessForm to
filename = RemusFilenameField(max_length=250)
I added the prints to see where/when this methods get called. But the methods are not called at all. I suspect because the form is not bounded. So I did this instead:
>>> p = {"name": "test1", "filename": "a.cnf", "author": "pablo"}
>>> f = ProcessForm(p)
>>> f.is_valid()
clean's value is a.cnf
True
>>> print f["filename"].value()
a.cnf
The to_python method is also not getting called and I expected to see A.CNF because clean returns the something different.
I have no idea how to solve this and even whether this was a good idea at all. My next probelm is when f.save() is executed the correct path has to be generated out of filename and stored in the instance. I would do that in the clean method or is there a better options for this?
EDIT: I think I have a solution for the creation of the form (I had to read the whole source code to identify in python2.6/site-packages/django/forms/models.py the model_to_dict usage:
from django.forms.models import model_to_dict
import os
class ProcessForm(MonitorForm):
filename = RemusFilenameField(max_length=250)
def __init__(self, *args, **kwargs):
super(ProcessForm, self).__init__(*args, **kwargs)
try:
proc = kwargs["instance"]
filename = os.path.basename(proc.path)
self.initial.update({'filename': unicode(filename)})
except:
pass
class Meta:
model = Process
fields = ('name', 'filename', 'author')
It know works :) now I have to figure out how to fix the save() method
The save() method is the appropriate place to put any additional save logic in your form.
def save(self, commit=True):
proc = super(ProcessForm, self).save(commit=False)
filename = cleaned_data['filename']
# additional logic to alter filename
proc.path = filename
if commit:
proc.save()
return proc
In terms of calling to_python() method on the field in your form your code is correct, but you have to get value from cleaned_data dictionary:
>>> p = {"name": "test1", "filename": "a.cnf", "author": "pablo"}
>>> f = ProcessForm(p)
>>> f.is_valid()
clean's value is a.cnf
True
>>> f.cleaned_data['filename']
'A.CNF'
If you need some logic on model save you can override save() in model. It's also described in django docs.
You should also consider to override save() method in your ModelForm as drewman said - it won't affect your code on calling save() method on model instance from other places in your code.

Custom validation of a ModelField in Django

my model looks like this:
class MyModel(models.Model):
field1 = models.FloatField(default=0)
field2 = models.FloatField(default=0)
This is how it behaves:
>>> m = MyModel()
>>> m.full_clean()
>>> m = MyModel(field1=8.9)
>>> m.full_clean()
>>> m = MyModel(field1='')
>>> m.full_clean()
ValidationError: {'field1': [u'This value must be a float.'], ...
I want it to accept blank strings and have it use 0. I also want it to accept values such as "5:56" and have it use "5.93333". (I already have a function that can do this)
Currently I have this working with a custom form field (and it's ugly as sin), but I want to move it all to use model validation. Whats the best way to go about this?
Make a clean_field1 function in your form and verify it in there. Throw a validation error if it's improper, and reformat and return the "proper" value if it is. Example:
http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute
Update:
Question clarified: user wants a Model Field that does this.
Override FloatField and the get_prep_value and to_python routines within:
class MagicalFloatField(FloatField):
def to_python( self, value_from_db ):
...
return what_should_go_into_model_field_value
def get_prep_value( self, value_from_field ):
...
return what_should_go_into_db
So you can do the "5:26" <--> "5.26" there. Then use your field in your model:
class MagicalModel(Model):
foo = MagicalFloatField()
Reference:
http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.to_python
Also, for an example of what it expects and how to raise validation errors, look at what you're subclassing -- look up FloatField in site-packages/django/db/models/fields/__init__.py