Custom validation of a ModelField in Django - 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

Related

How to insert a value from a dict in a django annotation using each id from queryset [duplicate]

This question already has an answer here:
Django annotate field value from external dictionary
(1 answer)
Closed last month.
I have a simple dict containing ids with values associated like: {1:True, 2:False}
My model:
class Profile(models.Model):
user_id = models.IntegerField(unique=True, blank=True, null=True)
I also have a queryset that have the same ids listed above in an atribute named user_id, but I need to add a new field to my objects on queryset with the values from my dict.
Lets suppose the field I want to add is called is_admin, so I need to create a field on the object with id 1 on my queryset with the value True.
What I tried to do is:
my_dict= {1:True, 2:False}
queryset = queryset.annotate(
is_admin=Value(my_dict.get(F("user_id")), output_field=BooleanField())
)
But what happen is that I'm receiving null on is_admin. I tried to do the code below and it works:
queryset = queryset.annotate(
is_admin=Value(my_dict.get(1), output_field=BooleanField())
)
So I think is something wrong with my use of F expression. I will appreciate any help.
I guess it is not ok to mix non-trivial python code into the query expression, beacause it needs to be translated to raw SQL.
I suggest setting this property after you get the data from DB. Something like this:
results = list(queryset)
for r in results:
r.is_admin = my_dict.get(r.id, False)
Other alternatives, if you want to use this in multiple places:
create new #property def is_admin(self): return my_dict.get(self.id, False)
add it as a new field for your model
The problem is that the expression F is used to modify strings while your dictionary keys are integer values. Modify the my_dict.get() value by casting it to an integer using int()
>>> newString = f"{user_id}"
>>> newString
'2'
>>> print(my_dict.get(newString))
None
>>> newInt = int(newString)
>>> print(my_dict.get(newInt))
False

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.

Can't Return JSON object using MongoEngine Pymongo with Django?

So I'm trying to return a JSON object for a project. I've spent a few hours trying to get Django just returning the JSON.
Heres the view that we've been working with:
def json(request, first_name):
user = User.objects.all()
#user = User.objects.all().values()
result = simplejson.dumps(user, default=json_util.default)
return HttpResponse(result)
Here's my model:
class User(Document):
gender = StringField( choices=['male', 'female', 'Unknown'])
age = IntField()
email = EmailField()
display_name = StringField(max_length=50)
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
location = StringField(max_length=50)
status = StringField(max_length=50)
hideStatus = BooleanField()
photos = ListField(EmbeddedDocumentField('Photo'))
profile =ListField(EmbeddedDocumentField('ProfileItem'))
allProfile = ListField(EmbeddedDocumentField('ProfileItem')) #only return for your own profile
This is what it's returning:
[<User: User object>, <User: User object>] is not JSON serializable
Any thoughts on how I can just return the JSON?
With MongoEngine 0.8 or greater, objects and querysets have a to_json() method.
>>> User.objects.to_json()
simplejson.dumps() doesn't know how to "reach into" your custom objects; the default function, json_util.default must just be calling str() or repr() on your documents. (Is json_util custom code you've written? If so, showing its source here could prove my claim.)
Ultimately, your default function will need to be able to make sense of the MongoEngine documents. I can think of at least two ways that this might be implemented:
Write a custom default function that works for all MongoEngine documents by introspecting their _fields attribute (though note that the leading underscore means that this is part of the private API/implementation detail of MongoEngine and may be subject to change in future versions)
Have each of your documents implement a as_dict method which returns a dictionary representation of the object. This would work similarly to the to_mongo method provided on documents by MongoEngine, but shouldn't return the _types or _cls fields (again, these are implementation details of MongoEngine).
I'd suggest you go with option #2: the code will be cleaner and easier to read, better encapsulated, and won't require using any private APIs.
As dcrosta suggested you can do something like this, hope that will help you.
Document definition
class MyDocument(Document):
# Your document definition
def to_dict(self):
return mongo_to_dict_helper(self)
helper.py:
from mongoengine import StringField, ListField, IntField, FloatField
def mongo_to_dict_helper(obj):
return_data = []
for field_name in obj._fields:
if field_name in ("id",):
continue
data = obj._data[field_name]
if isinstance(obj._fields[field_name], StringField):
return_data.append((field_name, str(data)))
elif isinstance(obj._fields[field_name], FloatField):
return_data.append((field_name, float(data)))
elif isinstance(obj._fields[field_name], IntField):
return_data.append((field_name, int(data)))
elif isinstance(obj._fields[field_name], ListField):
return_data.append((field_name, data))
else:
# You can define your logic for returning elements
return dict(return_data)

Django: Get list of model fields?

I've defined a User class which (ultimately) inherits from models.Model. I want to get a list of all the fields defined for this model. For example, phone_number = CharField(max_length=20). Basically, I want to retrieve anything that inherits from the Field class.
I thought I'd be able to retrieve these by taking advantage of inspect.getmembers(model), but the list it returns doesn't contain any of these fields. It looks like Django has already gotten a hold of the class and added all its magic attributes and stripped out what's actually been defined. So... how can I get these fields? They probably have a function for retrieving them for their own internal purposes?
Django versions 1.8 and later:
You should use get_fields():
[f.name for f in MyModel._meta.get_fields()]
The get_all_field_names() method is deprecated starting from Django
1.8 and will be removed in 1.10.
The documentation page linked above provides a fully backwards-compatible implementation of get_all_field_names(), but for most purposes the previous example should work just fine.
Django versions before 1.8:
model._meta.get_all_field_names()
That should do the trick.
That requires an actual model instance. If all you have is a subclass of django.db.models.Model, then you should call myproject.myapp.models.MyModel._meta.get_all_field_names()
As most of answers are outdated I'll try to update you on Django 2.2
Here posts- your app (posts, blog, shop, etc.)
1) From model link: https://docs.djangoproject.com/en/stable/ref/models/meta/
from posts.model import BlogPost
all_fields = BlogPost._meta.fields
#or
all_fields = BlogPost._meta.get_fields()
Note that:
all_fields=BlogPost._meta.get_fields()
Will also get some relationships, which, for ex: you can not display in a view.
As in my case:
Organisation._meta.fields
(<django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
and
Organisation._meta.get_fields()
(<ManyToOneRel: crm.activity>, <django.db.models.fields.AutoField: id>, <django.db.models.fields.DateField: created>...
2) From instance
from posts.model import BlogPost
bp = BlogPost()
all_fields = bp._meta.fields
3) From parent model
Let's suppose that we have Post as the parent model and you want to see all the fields in a list, and have the parent fields to be read-only in Edit mode.
from django.contrib import admin
from posts.model import BlogPost
#admin.register(BlogPost)
class BlogPost(admin.ModelAdmin):
all_fields = [f.name for f in Organisation._meta.fields]
parent_fields = BlogPost.get_deferred_fields(BlogPost)
list_display = all_fields
read_only = parent_fields
The get_all_related_fields() method mentioned herein has been deprecated in 1.8. From now on it's get_fields().
>> from django.contrib.auth.models import User
>> User._meta.get_fields()
I find adding this to django models quite helpful:
def __iter__(self):
for field_name in self._meta.get_all_field_names():
value = getattr(self, field_name, None)
yield (field_name, value)
This lets you do:
for field, val in object:
print field, val
This does the trick. I only test it in Django 1.7.
your_fields = YourModel._meta.local_fields
your_field_names = [f.name for f in your_fields]
Model._meta.local_fields does not contain many-to-many fields. You should get them using Model._meta.local_many_to_many.
It is not clear whether you have an instance of the class or the class itself and trying to retrieve the fields, but either way, consider the following code
Using an instance
instance = User.objects.get(username="foo")
instance.__dict__ # returns a dictionary with all fields and their values
instance.__dict__.keys() # returns a dictionary with all fields
list(instance.__dict__.keys()) # returns list with all fields
Using a class
User._meta.__dict__.get("fields") # returns the fields
# to get the field names consider looping over the fields and calling __str__()
for field in User._meta.__dict__.get("fields"):
field.__str__() # e.g. 'auth.User.id'
def __iter__(self):
field_names = [f.name for f in self._meta.fields]
for field_name in field_names:
value = getattr(self, field_name, None)
yield (field_name, value)
This worked for me in django==1.11.8
A detail not mentioned by others:
[f.name for f in MyModel._meta.get_fields()]
get, for example
['id', 'name', 'occupation']
and
[f.get_attname() for f in MyModel._meta.get_fields()]
get
['id', 'name', 'occupation_id']
If
reg = MyModel.objects.first()
then
reg.occupation
get, for example
<Occupation: Dev>
and
reg.occupation_id
get
1
MyModel._meta.get_all_field_names() was deprecated several versions back and removed in Django 1.10.
Here's the backwards-compatible suggestion from the docs:
from itertools import chain
list(set(chain.from_iterable(
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
for field in MyModel._meta.get_fields()
# For complete backwards compatibility, you may want to exclude
# GenericForeignKey from the results.
if not (field.many_to_one and field.related_model is None)
)))
Just to add, I am using self object, this worked for me:
[f.name for f in self.model._meta.get_fields()]
At least with Django 1.9.9 -- the version I'm currently using --, note that .get_fields() actually also "considers" any foreign model as a field, which may be problematic. Say you have:
class Parent(models.Model):
id = UUIDField(primary_key=True)
class Child(models.Model):
parent = models.ForeignKey(Parent)
It follows that
>>> map(lambda field:field.name, Parent._model._meta.get_fields())
['id', 'child']
while, as shown by #Rockallite
>>> map(lambda field:field.name, Parent._model._meta.local_fields)
['id']
So before I found this post, I successfully found this to work.
Model._meta.fields
It works equally as
Model._meta.get_fields()
I'm not sure what the difference is in the results, if there is one. I ran this loop and got the same output.
for field in Model._meta.fields:
print(field.name)
In sometimes we need the db columns as well:
def get_db_field_names(instance):
your_fields = instance._meta.local_fields
db_field_names=[f.name+'_id' if f.related_model is not None else f.name for f in your_fields]
model_field_names = [f.name for f in your_fields]
return db_field_names,model_field_names
Call the method to get the fields:
db_field_names,model_field_names=get_db_field_names(Mymodel)
Combined multiple answers of the given thread (thanks!) and came up with the following generic solution:
class ReadOnlyBaseModelAdmin(ModelAdmin):
def has_add_permission(self, request):
return request.user.is_superuser
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
def get_readonly_fields(self, request, obj=None):
return [f.name for f in self.model._meta.get_fields()]
Why not just use that:
manage.py inspectdb
Example output:
class GuardianUserobjectpermission(models.Model):
id = models.IntegerField(primary_key=True) # AutoField?
object_pk = models.CharField(max_length=255)
content_type = models.ForeignKey(DjangoContentType, models.DO_NOTHING)
permission = models.ForeignKey(AuthPermission, models.DO_NOTHING)
user = models.ForeignKey(CustomUsers, models.DO_NOTHING)
class Meta:
managed = False
db_table = 'guardian_userobjectpermission'
unique_together = (('user', 'permission', 'object_pk'),)

Django, query filtering from model method

I have these models:
def Foo(Models.model):
size = models.IntegerField()
# other fields
def is_active(self):
if check_condition:
return True
else:
return False
def Bar(Models.model):
foo = models.ForeignKey("Foo")
# other fields
Now I want to query Bars that are having active Foo's as such:
Bar.objects.filter(foo.is_active())
I am getting error such as
SyntaxError at /
('non-keyword arg after keyword arg'
How can I achieve this?
You cannot query against model methods or properties. Either use the criteria within it in the query, or filter in Python using a list comprehension or genex.
You could also use a custom manager. Then you could run something like this:
Bar.objects.foo_active()
And all you have to do is:
class BarManager(models.Manager):
def foo_active(self):
# use your method to filter results
return you_custom_queryset
Check out the docs.
I had similar problem: I am using class-based view object_list and I had to filter by model's method. (storing the information in database wasn't an option because the property was based on time and I would have to create a cronjob and/or... no way)
My answer is ineffective and I don't know how it's gonna scale on larger data; but, it works:
q = Model.objects.filter(...)...
# here is the trick
q_ids = [o.id for o in q if o.method()]
q = q.filter(id__in=q_ids)
You can't filter on methods, however if the is_active method on Foo checks an attribute on Foo, you can use the double-underscore syntax like Bar.objects.filter(foo__is_active_attribute=True)