Model validation values in custom Django form error messages - validation

I have created a Django form:
class AddFaceForm(forms.ModelForm):
class Meta:
model = Face
fields = ('person', 'x', 'y', 'w', 'h')
In the Face model, w is defined as
w = models.DecimalField(
'Relative width',
validators=[MinValueValidator(0.05), MaxValueValidator(1)]
)
If a user enters 0 for the width, the error message Django returns upon form validation is
Ensure this value is greater than or equal to 0.05.
So it nicely contains the value from the MinValueValidator. I would like to change it to "Relative width must be >= 0.05".
To achieve it, I tried customising errors in AddFaceForm Meta
class AddFaceForm(forms.ModelForm):
class Meta:
model = Face
fields = ('person', 'x', 'y', 'w', 'h')
error_messages = {
'w': {
'min_value': 'Relative width must be >= 0.05'
}
}
But this means hard-coding the 0.05 value. I would like to read it from the model definition. How can this be done?

Great question.
MinValueValidator is a subclass of BaseValidator. And, BaseValidator takes an optional message in its constructor. From the code you can see that the message can use the following three parameters: limit_value, show_value and value. limit_value is the minimum value for the validator which is 0.05 in this case. show_value is the cleaned value the user enters and value is the raw value they enter. Hence, you can do the following to achieve your goal:
from django.core.validators import MinValueValidator
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Face(models.Model):
w = models.DecimalField(
'Relative width',
max_digits=5,
decimal_places=2,
validators=[MinValueValidator(0.05, _('Relative width must be >= %(limit_value)s'))],
)
Alternatively, if you'd rather specify the error message in a form then you can do the following:
from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import Face
class FaceForm(forms.ModelForm):
class Meta:
model = Face
fields = ['w']
error_messages = {
'w': {
'min_value': _('Relative width must be >= %(limit_value)s')
}
}
The DecimalField docs tell you that you can interpolate limit_value.

Related

SqlAlchemy query filter for Enum class property of Column(Enum(...))

I have a declarative class that has an Enum column, and the Enum has a property that returns True/False based on the specific enumerated name or value. It would simplify life if I could do a query with a filter based on this property, such as the following (see implementation below):
session.query(MyTable).filter(MyTable.letter.is_vowel)
using something like the below straightforward attempt at an expression fails with
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with MyTable.letter has an attribute 'is_vowel'
The below implementation is too simple to allow for construction of the necessary query. Is there a way to do this? I thought maybe something in a Comparator might work, or maybe there's something more sophisticated that would do it?
import enum
from sqlalchemy import (
Column,
Enum,
Integer,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
MyDeclarativeBase = declarative_base()
class Letter(enum.Enum):
A = 1
B = 2
C = 3
D = 4
E = 5
# and so on...
#property
def is_vowel(self):
return self.name in 'AEIOU'
class MyTable(MyDeclarativeBase):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True, autoincrement=True)
letter = Column(Enum(Letter), nullable=False)
#hybrid_property
def is_vowel(self):
"""Return True if the row's letter is a vowel."""
return self.letter.is_vowel
#is_vowel.expression
def is_vowel(cls):
return cls.letter.is_vowel

Choosing from a list of names using factory boy integrated with faker

I am trying to use factory.faker to randomly choose from a list of four companies and use them as a traffic source for a list of generated names. I am using the below code:
from django.db import models
import factory
import factory.django
from datetime import datetime
from django.core.validators import MinValueValidator, MaxValueValidator
from faker import Faker
from faker.providers import BaseProvider
import random
fake = Faker()
class User(models.Model):
name = models.CharField(max_length=64)
address = models.CharField(max_length=128)
phone_number = models.CharField(max_length=32)
login_date = models.DateTimeField(default=datetime.now(), blank=True)
session_duration = models.IntegerField(default = 0, validators= [
MinValueValidator(0),
MaxValueValidator(5)
])
traffic_source = models.CharField(max_length=32)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
name = factory.Faker('name')
address = factory.Faker('address')
phone_number = factory.Faker('phone_number')
login_date = factory.Faker('date')
session_duration = factory.Faker('random_int')
traffic_source = random.choice(['XYZ', 'ABC', '123', '456'])
The issue is that for all 200 iterations I perform using the following in the python shell:
for _ in range(200):
UserFactory.create()
I get the same company for every name, i.e. 'XYZ' for all 200 names.
Am I missing something? I want to get a different company for each of the 200 iterations. Any help is much appreciated. Thank you!
This comes from Python's parsing rules.
Why?
When you write this:
class UserFactory(factory.django.DjangoModelFactory):
...
traffic_source = random.choice(['XYZ', 'ABC', '123', '456'])
Python will execute the following steps:
Read the class declaration body;
Reach the line traffic_source = random.choice(['XYZ', 'ABC', '123', '456']);
Evaluate the call to random.choice, which might return 'ABC';
Once each line of the class body has been read (and its function calls evaluated), create the class:
UserFactory = type(
name='UserFactory',
bases=[factory.django.DjangoModelFactory],
{'traffic_source': 'ABC', ...},
)```
As you can see, the call to random.choice is performed only once, when parsing the class declaration.
This is, basically, the reason for all the factory.XXX declarations: they yield an object that will only execute its specific rules when building an instance from the factory.
So, what should you do?
Here, you should use:
Either factory.Faker using Faker's random_choices provider;
Or factory.fuzzy.FuzzyChoice:
class UserFactory(factory.django.DjangoModelFactory):
...
traffic_source = factory.Faker('random_choices', elements=['XYZ', 'ABC', '123', '456'])
alt_traffic_source = factory.fuzzy.FuzzyChoice(['XYZ', 'ABC', '123', '456'])
The main difference between factory.Faker('random_choices') and factory.fuzzy.FuzzyChoices is that factory.fuzzy.FuzzyChoices supports lazily evaluating generators; this is useful if you want to choose from a queryset:
factory.Faker('random_choices', elements=Company.objects.all()) will perform a DB query at import time;
factory.fuzzy.FuzzyChoice(Company.objects.all()) will only query the DB the first time UserFactory.create() is called.
While not truly random, the effect you're looking for when choosing from among a set of pre-existing records can also be achieved by using FactoryBoy's Iterator, which can also work with a QuerySet. For example, here I wanted every object to be created by someone different from the set of existing fake users:
from django.contrib.auth import get_user_model
...
# Then, within a factory class, for one of the fields:
created_by = factory.Iterator(get_user_model().objects.all())

Django rest framework does model field validation before own validation

I don't understand django rest framework validation process.
The doc say that validation is performed entirely on the serializer class.
However,
In the case of a PositiveIntegerField in a Model, when ModelSerializer receive a negative value, the model validation is processed before the custom ModelSerializer validate_myfield method.
class Blop(models.Model):
quantity = models.PositiveSmallIntegerField()
class BlopSerializer(serializers.ModelSerializer):
def validate_quantity(self, value):
return max(15, value)
class Meta:
model = Blop
if quantity if lesser than zero, validate_quantity is never called...
I've also tried :
to use model "clean" method but rest framework doesn't use it.
to disable min value validator in model by setting min_value to None, but it make rest framework to crash
to force MinValueValidator in model field : validators=[MinValueValidator(-math.inf, 'Minimum value is -Infinity')], it work, but it's too ugly
How can I do to have a serializer ensure non negative value in this case ?
I think your problem is that you are invoking the serializer.is_valid() method with no kwargs or raise_exception=False.
With raise_exception=True the serializer raises ValidationError, if you pass invalid data.
For better understanding, lets take a look at the Serializer.to_internal_value method.
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, dict):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
})
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = list(exc.messages)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
As you can see here, firstly, the serializer invokes the field.run_validation method, which uses django-fields validators and they raise DjangoValidationError exception and because of that, your validation method is never invoked.

How can I create a complete_name field in a custom module for a custom hierarchy like used on product categories in Odoo?

I'm trying to create a field “complete_name” that displays a hierarchy name similar to whats done on the product categories grid but I can't seem to get it to work. It just puts Odoo in an endless loading screen when I access the relevant view using the new field "complete_name".
I have tried to copy the code used in addons/product/product.py and migrate to work with Odoo 9 API by using compute instead of .function type but it did not work.
Can someone help me understand whats wrong? Below is my model class which works fine without the complete_name field in my view.
class cb_public_catalog_category( models.Model ):
_name = "cb.public.catalog.category"
_parent_store = True
parent_left = newFields.Integer( index = True )
parent_right = newFields.Integer( index = True )
name = newFields.Char( string = 'Category Name' )
child_id = newFields.One2many( 'catalog.category', 'parent_id', string = 'Child Categories' )
complete_name = newFields.Char( compute = '_name_get_fnc', string = 'Name' )
def _name_get_fnc( self ):
res = self.name_get( self )
return dict( res )
Your compute function is supposed to define the value of an attribute of your class, not return a value. Ensure the value you are assigning complete_name is a string.
Also name_get() returns a tuple. I am not sure if you really want a string representation of this tuple or just the actual name value.
Try this
def _name_get_fnc( self ):
self.complete_name = self.name_get()[1]
If you really want what is returned by name_get() then try this.
def _name_get_fnc( self ):
self.complete_name = str(self.name_get())
If you are still having issues I would incorporate some logging to get a better idea of what you are setting the value of complete_name to.
import logging
_logger = logging.getLogger(__name__)
def _name_get_fnc( self ):
_logger.info("COMPUTING COMPLETE NAME")
_logger.info("COMPLETE NAME: " + str(self.name_get()))
self.complete_name = self.name_get()
If this does not make it apparent what the issue is you could always try statically assigning it a value in the off chance that there is a problem with your view.
def _name_get_fnc( self ):
self.complete_name = "TEST COMPLETE NAME"
After further review I think I have the answer to my own question. It turns out as with a lot of things its very simple.
Simply use "_inherit" and inherit the product.category
model. This gives access to all the functions and fields
of product.category including the complete_name field
and computes the name from my custom model data. I was
able to remove my _name_get_func and just use the inherited
function.
The final model definition is below. Once this
update was complete I was able to add a "complete_name" field
to my view and the results were as desired!
class cb_public_catalog_category( models.Model ):
_name = "cb.public.catalog.category"
_inherit = 'product.category'
_parent_store = True
parent_left = newFields.Integer( index = True )
parent_right = newFields.Integer( index = True )
name = newFields.Char( string = 'Category Name' )
child_id = newFields.One2many( 'catalog.category', 'parent_id', string = 'Child Categories' )

need checkboxes for a list using Django form

forms.py
from django import forms
from .models import VendorApplication, VendorAppWorkFlow
import MySQLdb as mdb
from django.forms.fields import DateField, ChoiceField, MultipleChoiceField
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
from django.forms.extras.widgets import SelectDateWidget
con = mdb.connect('', '', '', '');
cursor1 = con.cursor()
cursor1.execute("select * from vendorapp_sharetable;")
columns = cursor1.description
num_fields = len(cursor1.description)
field_names = [i[0] for i in cursor1.description]
class CheckForm(forms.Form):
for index in range(num_fields):
field_names[index] = forms.BooleanField(required=False, label=field_names[index])
I want to add field_names values in forms.py to checkbox list without choices or how can I iterate them in choices to have checkboxMultiselect widget. As the count of field_names varies in my application am facing difficulty to add them to choices and use checkbox widget.
Something like:
field_names = [i[0] for i in cursor1.description]
choices = [(x, y) for (x, y) in enumerate (field_names)]
class CheckForm (forms.Form):
fields = forms.CheckboxMultiSelect (choices = choices, required = False)

Resources