Django custom field validator vs. clean - django

I would like to create TodayOrLaterDateField() which would subclass DateField() field as I am using this condition in many places. The purpose of this field would be avoiding putting dates from the past.
What is the most straightway way of doing this? I am confused with validator vs. clean method.
I've tried with clean() but when comparing value to datetime.date.today() I am getting "compare unicode object to date" error.
I'm using Django 1.3

Validators only validate, they don't return the improved format;
Clean methods both validate and return a (sometimes amended) value.
I think the way to go here is to just use a DateField with a validator as a inherited class of DateField with a default_validators set.
import datetime
from django.core import exceptions
from django.db import models
from django.utils.translation import ugettext_lazy as _
def validate_date_today_or_later(value):
'Place this in validators.py and import it to keep your model a bit cleaner'
if value < datetime.date.today():
raise exceptions.ValidationError(_('Date must be today or later'))
class TodayOrLaterDateField(models.DateField):
default_validators = [validate_date_today_or_later,]
edit:
You can apply the same validator to your form fields as well if you just want it there and not in your whole app.

You can extend models.DateField and override to_python method. Didn't tested on Django 1.3 but should work.
import datetime
from django.core import exceptions
from django.db import models
class TodayOrLaterDateField(models.DateField):
def to_python(self, value):
value = super(TodayOrLaterDateField, self).to_python(value)
if value < datetime.date.today():
raise exceptions.ValidationError(u'Date must be today or later')
return value

Related

How to make import-export save JSONField not as string

I'm trying to import JSONField using django import-export, it keeps saving JSON as string (adding "" to it)
models.py
from django.db import models
from django.contrib.postgres.fields import JSONField
class Governorate(models.Model):
name = models.CharField(max_length=500)
data = JSONField()
def __str__(self):
return ("%s" %(self.name))
admin.py
from django.contrib import admin
from .models import Governorate
from import_export.admin import ImportExportModelAdmin
from import_export import resources
class GovernorateResource(resources.ModelResource):
class Meta:
model = Governorate
class GovernorateAdmin(ImportExportModelAdmin):
list_display = ('id','name', 'data')
resources_class = GovernorateResource
admin.site.register(Governorate,GovernorateAdmin)
I expected the output to be: {"xx":{"xx":"xx","xx":"xx"} however it saves it as "{"xx":{"xx":"xx","xx":"xx"}"
Tried uploading XLSX and CSV.
Version 1.2.0 of import-export doesn't automatically recognise the JSONField in your Resource so it just defaults to a CharField type. It's already added to the master branch but not released.
So just override the field to use the JSONWidget which is already available in version 1.2.0:
from import_export import fields, widgets
class GovernorateResource(resources.ModelResource):
data = fields.Field(widget=widgets.JSONWidget())
class Meta:
model = Governorate
So basically, the import-export library was saving the JSONField() as a string. So my solution was to create a signal to check if the instance was a string or a dict. then fix it.
I tried to create a Field with the JSONWidget widget, but doesn't work.
models.py
#receiver(post_save, sender=Governorate)
def fix_json(sender, instance, **kwargs):
if (type(instance.data) is str):
instance.data = eval(instance.data)
instance.save()
print(instance.data)
The Django import-export library is really good, but lacks proper documentation to be honest.

django - Best way to deal with validation checks in model creation

I am trying to figure out how to use validations when creating an object in django.
From my POV, there are 2 approaches:
Override the default validate_field method of the DRF serializers.
Add field-level validators to models and catch any IntegrityError or ValidationError exception when serializer calls .save() method of the model.
Both ways seem to have their cons.
By using approach 1 my models are left "unprotected" from any other .create() call of the model besides the serializers. Approach 2 deals with the above issue, but makes the code more complex since exception handling is neccessary in serializer's .create() method.
Is there anyone that has faced a similar issue and/or found a "cleaner" way to deal with this?
As far as i understood you need Django model validation. You can try this approach (I think, this is exactly what you want).
from django.core.exceptions import ValidationError
class Foo(models.Model):
name = models.CharField(max_length=255)
def clean(self):
raise ValidationError('Problem during validation')
f = Foo(name='test')
f.full_clean() # This is what you need. f.clean() will be enough, but f.full_clean() will make field level validations (run validators) and checking about uniqueness also.
f.save()
In general Django never makes model level validations(Does not call full_clean()), during model creation.
f = Foo(**fields_dict)
f.save() # no validation performed.
call it yourself if you want,,,
f = Foo(**fields_dict)
f.full_clean() # validation performed
f.save()
full_clean() method is performed automatically, if you use ModelForm class. This is additional hook by Django.
I think that moving the validations down to the model is the safest way to ensure the highest level of reliability, because the validations are going to be applied as long as they are done through Django (if you use serializers, validations wouldn't work if Django Rest Framework is skipped).
Model level validations can be:
Field level validations: You create a method that makes the validation, and set such method as field validator:
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
from django.db import models
class MyModel(models.Model):
even_field = models.IntegerField(validators=[validate_even])
Model level validations: You override model's clean() method and perform the required validations:
import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
class Article(models.Model):
...
def clean(self):
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
raise ValidationError(_('Draft entries may not have a publication date.'))
# Set the pub_date for published items if it hasn't been set already.
if self.status == 'published' and self.pub_date is None:
self.pub_date = datetime.date.today()

Setting DateField Default value in Django Rest Framework serializer

I have a serializer with datefield which needs to be set to a default date of three days from today.
I'm unable to set it using "default" argument of the datefield in the serializer.
Any kind of help would be highly appreciated.
You can't do in normal way, So you need to write custom functions in models.
from datetime import timedelta
from django.utils import timezone
def in_three_days():
return timezone.now() + timedelta(days=3)
class TestModel(models.Model):
event = models.DateTimeField(default=in_three_days)

Django Custom Validators Not Working

I wrote a custom validator, which will raise ValidationError if given field value is negative.
def validate_positive(value):
if value < 0:
raise ValidationError(
_('%(value) is negative number'),
params = {'value': value}
)
i added this to my model field via the field’s validators argument
class Book(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
title = models.CharField(max_length=50)
price = models.IntegerField(default=0,validators=[validate_positive])
quantity = models.IntegerField(default=0,validators=[validate_positive])
But while creating object it's not raising any error if price is less than zero.
I don't know where i am doing wrong and i am new to django.
I am using Django 1.9.
Please help me .
Validators are used for forms, not for creating an object. If you're creating an object outside of a form then you need to provide an alternative way to validate input.
The easiest way to do this is to call the model's full_clean method before saving as shown in the docs
from django.core.exceptions import ValidationError
try:
article.full_clean()
except ValidationError as e:
# Do something based on the errors contained in e.message_dict.
# Display them to a user, or handle them programmatically.
pass
This is similar to what would happen from a form, and would call any validators on your model fields.
I ran into the same issue. So the validators only work when you are using Forms and Model Form to fill it.
You can validate the validators in the shell though.
python manage.py shell
>>>from app.models import Book
>>>Book.price.field.run_validators(value=<undesirable value>)
This would raise a validation error so you can be sure your validation is working.

Django: Get model from string?

In Django, you can specify relationships like:
author = ForeignKey('Person')
And then internally it has to convert the string "Person" into the model Person.
Where's the function that does this? I want to use it, but I can't find it.
As of Django 1.11 to 4.0 (at least), it's AppConfig.get_model(model_name, require_ready=True)
As of Django 1.9 the method is django.apps.AppConfig.get_model(model_name).
-- danihp
As of Django 1.7 the django.db.models.loading is deprecated (to be removed in 1.9) in favor of the the new application loading system.
-- Scott Woodall
Found it. It's defined here:
from django.db.models.loading import get_model
Defined as:
def get_model(self, app_label, model_name, seed_cache=True):
django.db.models.loading was deprecated in Django 1.7 (removed in 1.9) in favor of the the new application loading system.
Django 1.7 docs give us the following instead:
>>> from django.apps import apps
>>> User = apps.get_model(app_label='auth', model_name='User')
>>> print(User)
<class 'django.contrib.auth.models.User'>
just for anyone getting stuck (like I did):
from django.apps import apps
model = apps.get_model('app_name', 'model_name')
app_name should be listed using quotes, as should model_name (i.e. don't try to import it)
get_model accepts lower case or upper case 'model_name'
Most model "strings" appear as the form "appname.modelname" so you might want to use this variation on get_model
from django.db.models.loading import get_model
your_model = get_model ( *your_string.split('.',1) )
The part of the django code that usually turns such strings into a model is a little more complex This from django/db/models/fields/related.py:
try:
app_label, model_name = relation.split(".")
except ValueError:
# If we can't split, assume a model in current app
app_label = cls._meta.app_label
model_name = relation
except AttributeError:
# If it doesn't have a split it's actually a model class
app_label = relation._meta.app_label
model_name = relation._meta.object_name
# Try to look up the related model, and if it's already loaded resolve the
# string right away. If get_model returns None, it means that the related
# model isn't loaded yet, so we need to pend the relation until the class
# is prepared.
model = get_model(app_label, model_name,
seed_cache=False, only_installed=False)
To me, this appears to be an good case for splitting this out into a single function in the core code. However, if you know your strings are in "App.Model" format, the two liner above will work.
2020 solution:
from django.apps import apps
apps.get_model('app_name', 'Model')
per your eg:
apps.get_model('people', 'Person')
per:
Import Error :cannot import name get_model
The blessed way to do this in Django 1.7+ is:
import django
model_cls = django.apps.apps.get_model('app_name', 'model_name')
So, in the canonical example of all framework tutorials:
import django
entry_cls = django.apps.apps.get_model('blog', 'entry') # Case insensitive
In case you don't know in which app your model exists, you can search it this way:
from django.contrib.contenttypes.models import ContentType
ct = ContentType.objects.get(model='your_model_name')
model = ct.model_class()
Remember that your_model_name must be lowercase.
Another rendition with less code for the lazy. Tested in Django 2+
from django.apps import apps
model = apps.get_model("appname.ModelName") # e.g "accounts.User"
I'm not sure where it's done in Django, but you could do this.
Mapping the class name to the string via reflection.
classes = [Person,Child,Parent]
def find_class(name):
for clls in classes:
if clls.__class__.__name__ == name:
return clls
Here is a less django-specific approach to get a class from string:
mymodels = ['ModelA', 'ModelB']
model_list = __import__('<appname>.models', fromlist=mymodels)
model_a = getattr(model_list, 'ModelA')
or you can use importlib as shown here:
import importlib
myapp_models = importlib.import_module('<appname>.models')
model_a = getattr(myapp_models, 'ModelA')