How to validate fields and get validated output - python

I'm trying to validate the values in the model and get as output the validated values. The only example in the documentation is the following code, which doesn't show how exactly this is done so I can't extend on it.
>>> from schematics.models import Model
>>> from schematics.types import StringType, BooleanType
>>> from schematics.exceptions import ValidationError
>>>
>>> class Signup(Model):
... name = StringType()
... call_me = BooleanType(default=False)
... def validate_call_me(self, data, value):
... if data['name'] == u'Brad' and data['call_me'] is True:
... raise ValidationError(u'He prefers email.')
... return value
...
>>> Signup({'name': u'Brad'}).validate()
>>> Signup({'name': u'Brad', 'call_me': True}).validate()
Traceback (most recent call last):
...
ModelValidationError: {'call_me': [u'He prefers email.']}
I made a version of it but removed data and value from arguments. Client is the name of my model. So when I do the following I get the desired result as output:
client.validate_client(client.to_native())
However,
first of all, this doesn't seem a clean way. client already has all the values and so I wouldn't need to do this.
Also, I like to get this to update the values of client as an outcome of validation.
For the first part, I did something line this:
def validate_client(self):
data = self.to_native()
...
return data
But I don't think this is the best way to do this and I am not sure about the second issue of updating the values. Is there a way to do this?
EDIT:
This is the code I have and I want the client value for employer to be set to 'unspecified' and client full_name to be set as specified in the function.
class LowerCaseEmailType(EmailType):
def convert(self, value, context=None):
value = super(LowerCaseEmailType, self).convert(value, context)
return value.lower()
class CleanedStringType(StringType):
converters = []
def __init__(self, **kwargs):
"""
This takes in all the inputs as String Type, but takes in an extra
input called converters.
Converters must be a list of functions, and each of those functions
must take in exactly 1 value , and return the transformed input
"""
if 'converters' in kwargs:
self.converters = kwargs['converters']
del kwargs['converters']
super(CleanedStringType, self).__init__(**kwargs)
def convert(self, value, context=None):
value = super(CleanedStringType, self).convert(value, context)
for func in self.converters:
value = func(value)
return value # will have a value after going through all the conversions in order
class Client(Model):
"""
Client Model
"""
first_name = CleanedStringType(required=False,
converters=[lambda x: re.sub(r"[!##$%&\(\)\^]+", ' ', x),
lambda x: x.strip()])
last_name = CleanedStringType(required=False,
converters=[lambda x: re.sub(r"[!##$%&\(\)\^]+", ' ', x),
lambda x: x.strip()])
full_name = CleanedStringType(required=False,
converters=[lambda x: re.sub(r"[!##$%&\(\)\^]+", ' ', x),
lambda x: x.strip()])
employer = StringType(required=False)
created = StringType(default=" ")
updated = StringType(default=" ")
email = LowerCaseEmailType(required=False)
def validate_client(self):
data = self.to_native()
if data.get('first_name') and data.get('last_name'):
data['full_name'] = ' '.join([data['first_name'], data['last_name']])
if data.get('full_name') is None:
if data.get('first_name') is None:
error_message = 'first name missing'
else:
error_message = 'last name missing'
logger.error('info: {} for {}'.format(error_message, data))
raise ValidationError(error_message)
if data.get('employer') is None:
logger.warning('info: employer missing for {}'.format(data))
data['employer'] = 'unspecified'
return data

I think you want pre_setattr. It is called with the value when you set a value of a model (code in question).
For the eymployer I think you want to set a default. To me this seems to behave exactly as you want.
I think fullname should not be on the model, because you can have inconsistent data where firstname + lastname != fullname. If you need it later you could implement that via #serialized.

Related

Look up items in list of dataclasses by value

I'm using python to filter data in elasticsearch based on request params provided. I've got a working example, but know it can be improved and am trying to think of a better way. The current code is like this:
#dataclass(frozen=True)
class Filter:
param: str
custom_es_field: Optional[str] = None
is_bool_query: bool = False
is_date_query: bool = False
is_range_query: bool = False
def es_field(self) -> str:
if self.custom_es_field:
field = self.custom_es_field
elif "." in self.param:
field = self.param.replace(".", "__")
else:
field = self.param
return field
filters = [
Filter(param="publication_year", is_range_query=True),
Filter(param="publication_date", is_date_query=True),
Filter(param="venue.issn").
...
]
def filter_records(filter_params, s):
for filter in filters:
# range query
if filter.param in filter_params and filter.is_range_query:
param = filter_params[filter.param]
if "<" in param:
param = param[1:]
validate_range_param(filter, param)
kwargs = {filter.es_field(): {"lte": int(param)}}
s = s.filter("range", **kwargs)
elif filter.param in filter_params and filter.is_bool_query:
....
The thing I think is slow is I am looping through all of the filters in order to use the one that came in as a request variable. I'm tempted to convert this to a dictionary so I can do filter["publication_year"], but I like having the extra methods available via the dataclass. Would love to hear any thoughts.

Extend Odoo/Openerp datatype

I want to extend an odoo datatype.
First I wanted to create a new datatype from scretch. This wasn't easy since odoo/openerp just don't know how to store that datatype.
So now I want to extend it.
from openerp import fields
class MyDataType(fields.Text):
#classmethod
def browse_my_data_type(self, value1, value2):
result = value1 + value2
if len(result) > 0:
return result
else:
return False
I tried to use it. It's possible untill I want to call the browse_my_data_type method.
from openerp import models, fields, api
import my_new_fields as my_fields
class my_data_type_test(models.Model):
_name = 'my.type.test'
name = fields.Char('Name')
my_data_type = my_fields.MyDataType("My Data Type")
result = fields.Char("Result", compute="_set_result")
#api.one
def _set_result(self):
result = self.my_data_type.browse_my_data_type("valuehere","anotheronehere")
if result:
self.result = result
else:
self.result = ""
I used this code to test the method. Sadly it gives me the error AttributeError: 'unicode' object has no attribute 'browse_my_data_type'
How can I make sure that he knows the method while using it this way? (self.my_data_type.browse_my_data_type("valuehere","anotheronehere")
Try this code may be work:
from openerp import fields
class MyDataType(fields._String):
type = 'text'
def browse_my_data_type(self, value1, value2):
result = value1 + value2
if len(result) > 0:
return result
else:
return False

How to identify a column name by a string? django/python

I am writing a basic function that takes three arguments, request, field, and user_id.
The idea is, when you pass through the info, the function returns the result which would be the column (identified by argument "field"), the row (identified by the argument "user_id").
this is my function:
def get_user_field(request, user_id, field):
result = Users.objects.raw("SELECT id, %s FROM blog_users WHERE id = %s", [field, user_id])[0]
#return result.??????
what I do not know how to do is to replace those question marks with what to return the corresponding column. If i try
return result.field
It will pass a string where "field" is. And of course a string cannot be put there.
So how can i achieve a function that works pretty much exactly like this and returns one result?
Thanks!
This can be done with the getattr Python builtin:
return getattr(result, field)
But it would be better to do it entirely differently:
def get_user_field(request, user_id, field):
return User.objects.filter(id=user_id).values_list(field, flat=True)[0]
Or, to allow for the possibility of the user not existing:
def get_user_field(request, user_id, field):
result = User.objects.filter(id=user_id).values_list(field, flat=True)
if result:
return result[0]
else:
return None # or raise an exception or whatever you like

Elegant way to avoid .put() on unchanged entities

A reoccurring pattern in my Python programming on GAE is getting some entity from the data store, then possibly changing that entity based on various conditions. In the end I need to .put() the entity back to the data store to ensure that any changes that might have been made to it get saved.
However often there were no changes actually made and the final .put() is just a waste of money. How to easily make sure that I only put an entity if it has really changed?
The code might look something like
def handle_get_request():
entity = Entity.get_by_key_name("foobar")
if phase_of_moon() == "full":
entity.werewolf = True
if random.choice([True, False]):
entity.lucky = True
if some_complicated_condition:
entity.answer = 42
entity.put()
I could maintain a "changed" flag which I set if any condition changed the entity, but that seems very brittle. If I forget to set it somewhere, then changes would be lost.
What I ended up using
def handle_get_request():
entity = Entity.get_by_key_name("foobar")
original_xml = entity.to_xml()
if phase_of_moon() == "full":
entity.werewolf = True
if random.choice([True, False]):
entity.lucky = True
if some_complicated_condition:
entity.answer = 42
if entity.to_xml() != original_xml: entity.put()
I would not call this "elegant". Elegant would be if the object just saved itself automatically in the end, but I felt this was simple and readable enough to do for now.
Why not check if the result equals (==) the original and so decide whether to save it. This depends on a correctly implemented __eq__, but by default a field-by-field comparison based on the __dict__ should do it.
def __eq__(self, other) :
return self.__dict__ == other.__dict__
(Be sure that the other rich comparison and hash operators work correctly if you do this. See here.)
One possible solution is using a wrapper that tracks any attribute change:
class Wrapper(object):
def __init__(self, x):
self._x = x
self._changed = False
def __setattr__(self, name, value):
if name[:1] == "_":
object.__setattr__(self, name, value)
else:
if getattr(self._x, name) != value:
setattr(self._x, name, value)
self._changed = True
def __getattribute__(self, name):
if name[:1] == "_":
return object.__getattribute__(self, name)
return getattr(self._x, name)
class Contact:
def __init__(self, name, address):
self.name = name
self.address = address
c = Contact("Me", "Here")
w = Wrapper(c)
print w.name # --> Me
w.name = w.name
print w.name, w._changed # --> Me False
w.name = "6502"
print w.name, w._changed # --> 6502 True
This answer is a part of an question i posted about a Python checksum of a dict
With the answers of this question I developed a method to generate checksum from
a db.Model.
This is an example:
>>> class Actor(db.Model):
... name = db.StringProperty()
... age = db.IntegerProperty()
...
>>> u = Actor(name="John Doe", age=26)
>>> util.checksum_from_model(u, Actor)
'-42156217'
>>> u.age = 47
>>> checksum_from_model(u, Actor)
'-63393076'
I defined these methods:
def checksum_from_model(ref, model, exclude_keys=[], exclude_properties=[]):
"""Returns the checksum of a db.Model.
Attributes:
ref: The reference og the db.Model
model: The model type instance of db.Model.
exclude_keys: To exclude a list of properties name like 'updated'
exclude_properties: To exclude list of properties type like 'db.DateTimeProperty'
Returns:
A checksum in signed integer.
"""
l = []
for key, prop in model.properties().iteritems():
if not (key in exclude_keys) and \
not any([True for x in exclude_properties if isinstance(prop, x)]):
l.append(getattr(ref, key))
return checksum_from_list(l)
def checksum_from_list(l):
"""Returns a checksum from a list of data into an int."""
return reduce(lambda x,y : x^y, [hash(repr(x)) for x in l])
Note:
For the base36 implementation: http://en.wikipedia.org/wiki/Base_36#Python_implementation
Edit:
I removed the return in base36, now these functions run without dependences. (An advice from #Skirmantas)
Didn't work with GAE but in same situation I'd use something like:
entity = Entity.get_by_key_name("foobar")
prev_entity_state = deepcopy(entity.__dict__)
if phase_of_moon() == "full":
entity.werewolf = True
if random.choice([True, False]):
entity.lucky = True
if some_complicated_condition:
entity.answer = 42
if entity.__dict__ == prev_entity_state:
entity.put()

to_python() never gets called (even in case of __metaclass__ = models.SubfieldBase)

Some time ago, as part of the process of learning Python+Django, I decided to write a custom MySQL-specific model field for the BIT column type. Unfortunately, I've ran into a problem.
The project: contains a single "main" app
The "main" app: contains all of the standard files created by "python manage.py startapp", plus extfields.py
The contents of extfields.py are as follows:
from django.db import models
import re
import bitstring
class BitField(models.Field):
description = 'A class representing a field of type "BIT" (MySQL-specific)'
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
bf_size = int(kwargs.pop('bitfield_size', 0))
assert bf_size > 0, '%ss must have a positive bitfield_size' % self.__class__.__name__
self._bf_size = bf_size
super(BitField, self).__init__(*args, **kwargs)
def db_type(self):
return 'BIT(%d)' % self._bf_size
def to_python(self, value):
print('to_python starts')
if isinstance(value, bitstring.BitArray):
return value
value = str(value)
regex = re.compile('^[01]{%d}$' % self._bf_size)
assert regex.search(value) == True, 'The value must be a bit string.'
print('to_python ends')
return bitstring.BitArray(bin=value)
def get_db_prep_value(self, value):
return value.bin
The contents of models.py:
from django.db import models
import extfields
class MessageManager(models.Manager):
"""
This manager is solely for the Message model. We need to alter the default
QuerySet so that we'll get the correct values for the attributes bit field
"""
def get_query_set(self):
return super(MessageManager, self).get_query_set().defer(
'attributes'
).extra(
select={'attributes': 'BIN(attributes)'}
)
class Message(models.Model):
attributes = extfields.BitField(bitfield_size=15)
objects = MessageManager()
When I use the python shell (via python manage.py shell), I get the following:
>>> from main import models
>>> m = models.Message.objects.get(pk=1)
>>> m
<Message_Deferred_attributes: Message_Deferred_attributes object>
>>> m.attributes
u'1110001110'
>>> type(m.attributes)
<type 'unicode'>
>>> m.attributes = '1312312'
>>> m.attributes
'1312312'
As you see, the m.attributes is a plain string, instead of bitstring.BitArray instance.
Could someone please tell me where I've made a mistake?
I'm using Python 2.6.5 on Ubuntu 10.04. The bitstring module I import is this one: http://code.google.com/p/python-bitstring/. Python-django package version is 1.1.1-2ubuntu1.3.
EDIT (in response to emulbreh's comment):
right now my to_python() definition looks like this:
def to_python(self, value):
print 'to_python'
if isinstance(value, bitstring.BitArray):
return value
print type(value)
value = str(value)
print'\n'
print value
print '\n'
print type(value)
regex = re.compile('^[01]{%d}$' % self._bf_size)
assert regex.search(value) == True, 'The value must be a bit string.'
value = bitstring.BitArray(bin=value)
print '\n'
print type(value)
print 'End of to_python'
return value
The console output is:
After this, an AssertionError is raised.
You don't need to do anything special in the QuerySet to support a custom field. You currently defer() your field and then add a raw extra(select=) attribute that coincidentally has the same name as your field.
If you just remove the custom manager (or the .defer().extra() calls) you should be fine.

Categories