Django Rest Framework ignoring custom field - django-rest-framework

I have a model with a nullable boolean field that I'd like to have serialized in a way that converts null in the output to false.
My model:
class UserPreferences(models.Model):
receive_push_notifications = models.BooleanField(
null=True, blank=True,
help_text=("Receive push notifications))
I'm trying to do it with a custom field like so:
class StrictlyBooleanField(serializers.Field):
def to_representation(self, value):
# Force None to False
return bool(value)
def to_internal_value(self, data):
return bool(data)
class UserPreferencesSerializer(serializers.ModelSerializer):
class Meta(object):
model = UserPreferences
fields = ('receive_push_notifications',)
receive_push_notifications = StrictlyBooleanField()
but this isn't working, I'm still seeing null in my API responses.
I think I must be missing something simple in wiring it up because I don't even get an error if I replace my to_representation with:
def to_representation(self, value):
raise
DRF doesn't seem to be calling my method at all... What am I missing here?

Explanation
After looking into rest framework's Serializer's to_representation method, you will find that it iterates through all of the fields and calls field.get_attribute method for each field. If the value returned from that method is None it skips calling field.to_representation entirely and set None as the field value.
# Serializer's to_representation method
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
# We skip `to_representation` for `None` values so that fields do
# not have to explicitly deal with that case.
#
# For related fields with `use_pk_only_optimization` we need to
# resolve the pk value.
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret
Solution
Override field.get_attribute by calling super().get_attribute and return False if the value is None
class StrictlyBooleanField(serializers.Field):
def get_attribute(self, instance):
attribute = super().get_attribute(instance)
return bool(attribute)
def to_representation(self, value):
return value
def to_internal_value(self, data):
return bool(data)

You can just write a simple function inside your serializer
class UserPreferencesSerializer(serializers.ModelSerializer):
yourField = serializers.SerializerMethodField(read_only=True)
class Meta(object):
model = UserPreferences
fields = ['receive_push_notifications', 'yourField']
def get_yourField(self, obj):
if obj.receive_push_notifications == null:
return False
else:
return True

Related

How create Ruby Class with same object id

I need to create a class where if the attribute value is the same it does not generate a new object id, example:
result:
described_class.new('01201201202')
<PixKey:0x00007eff5eab1ff8 #key="01201201202">
if i run it again with the same value it should keep the same object id
0x00007eff5eab1ff8
is similar behavior with the symbol
test:
describe '#==' do
let(:cpf) { described_class.new('01201201202') }
it 'verifies the key equality' do
expect(cpf).to eq described_class.new('01201201202')
end
end
Running the test shows an error, because the obejct id changes:
expected: #<PixKey:0x00007eff5eab1ff8 #key="01201201202">
got: #<PixKey:0x00007eff5eab2070 #key="01201201202">
Class:
class PixKey
def init(key)
#key = key
end
end
The other answers are fine, but they are a little more verbose than needed and they use class variables, which I find to be a confusing concept because of how they are shared among various classes.
class PixKey
#instances = {}
def self.new(id)
#instances[id] ||= super(id)
end
def initialize(id)
#key = id
end
end
p PixKey.new(1)
p PixKey.new(2)
p PixKey.new(2)
p PixKey.new(1)
Running the test shows an error, because the object id changes
Not quite. It shows an error because the objects are not equal. And the error message prints both objects including their id. But the object id is not what's causing the test to fail.
I need to create a class where if the attribute value is the same it does not generate a new object id
That would probably work, but you're likely approaching the problem from the wrong side. In Ruby, equality doesn't mean object identity. Two objects can be equal without being the same object, e.g.
a = 'foo'
b = 'foo'
a.object_id == b.object_id
#=> false
a == b
#=> true
There's no need to tinker with object ids to get your test passing. You just have to implement a custom == method, e.g.:
class PixKey
attr_reader :key
def initialize(key) # <- not "init"
#key = key
end
def ==(other)
self.class == other.class && self.key == other.key
end
end
The == method checks if both objects have the same class (i.e. if both are PixKey instances) and if their key's are equal.
This gives:
a = PixKey.new('01201201202')
b = PixKey.new('01201201202')
a == b
#=> true
Create a class method to create instances and have it look up a hash.
class PixKey
##instances = {}
def PixKey.create(id)
if not ##instances.has_key?(id)
##instances[id] = PixKey.new(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
c = PixKey.create(123)
d = PixKey.create(123)
puts a
puts b
puts c
puts d
Output:
#<PixKey:0x000000010bc39900>
#<PixKey:0x000000010bc38078>
#<PixKey:0x000000010bc33eb0>
#<PixKey:0x000000010bc33eb0>
Notice the last two instances created with the PixKey.create(id) method return the same instance.
Note that Ruby's new method is just a method on Class and can be overridden like any other. The docs describe the default implementation.
Calls allocate to create a new object of class's class, then invokes that object's initialize method, passing it args. This is the method that ends up getting called whenever an object is constructed using .new.
So, if you want to keep the .new syntax and still get the same objects back, we can override new on the class and call super. This is exactly what OscarRyz' answer does, just with .new and super rather than a separate helper function.
class PixKey
##instances = {}
def PixKey.new(id)
if not ##instances.has_key?(id)
##instances[id] = super(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
puts a
puts b

Dynamic table name in Ruby sqlite query based on class name

I have a parent class that looks like this:
class Record
attr_accessor :id, :url, :votes, :title, :first_name, :last_name, :selfdb
def initialize(args = {})
args.each { |name, value| instance_variable_set("##{name}", value) }
#selfdb = "#{self.class.name.downcase}s"
end
def self.find(id)
DB.results_as_hash = true
hasharray = DB.execute("SELECT * FROM ? WHERE id = ?", #selfdb, id)
hasharray.empty? ? nil : new(hasharray[0].transform_keys(&:to_sym))
end
end
Each child class of Record has a matching database table whose name is "#{name of the class}s", so the class "Post" is connected to a table named "posts".
My goal is to have self.find(id) to work on any children of this class. The solution I tried was to save the class' name into a string variable with an "s" at the end (so that class Post -> "posts", for example), to match the name of the database, as I tried in the instance variable #selfdb, but this does not work.
Calling #selfdb on the children classes confirms that it does correctly create the string for different classes, but running the sqlite with it inserted as the table name just returns nil.
This might be a very roundabout way of doing it, any suggestions are welcome. I am still learning and this is just a bootcamp assignment.
Edit: i realized one mistake I made: since self.find(id) is a class method, it can't use an instance variable. However, when I change the class method to work like this:
def self.find(id)
selfdb = "#{self.name.downcase}s"
DB.results_as_hash = true
hasharray = DB.execute("SELECT * FROM ? WHERE id = ?", selfdb, id)
hasharray.empty? ? nil : new(hasharray[0].transform_keys(&:to_sym))
end
... it still does not properly insert into the sqlite string.
You define #selfdb in the initialize method which means it is only available in on the instance level. But your self.find method is a class method and therefore #selfdb is undefined on the class level.
I would suggest adding a class method that returns the table name like this
def self.table_name
"#{name.downcase}s"
end
which you can then be used in the find class method like this
def self.find(id)
# ...
hasharray = DB.execute("SELECT * FROM ? WHERE id = ?", table_name, id)
# ...
end
and in instance methods (for example to save a record) you need to use self.class.table_name or you could add a delegator to forward a table_name instance method call to the class method:
extend Forwardable
def_delegators :"self.class", :table_name

maximum recursion depth exceeded Odoo

class SomeOrder(models.Model):
_inherit = 'some.order'
#api.multi
def write(self, vals):
result = super(SomeOrder, self).write(vals)
for rec in self:
rec.update_other_order()
return result
#api.model
def update_pos_quatation(self):
# my logic.write
class OtherOrder(models.Model):
_inherit = "other.order"
#api.multi
def write(self, vals):
result = super(OtherOrder, self).write(vals)
if 'amount_paid' in vals:
self.update_Some_order()
return result
#api.model
def update_some_order(self):
# my logic.write
i have method that at the end writes vals to sales order but i overridden sales write method for another method to update sales order. The problem is that i get “maximum recursion depth exceeded” because update_sales_order triggers something method. how can i avoid this recursion error? probably i need to add some context to write method
So basically both methods call each other with write.
You just need a termination or break condition here.
For example, if you call another write logic if a special field value was changed, which won't change the field value again:
#api.multi
def write(self, vals):
res = super(MyModel, self).write(vals)
if 'my_trigger_field_name' in vals:
self.my_other_logic(vals)
#api.multi
def my_other_logic(self, vals):
my_trigger_field_value = vals.get('my_trigger_field_name')
if my_trigger_field_value == 1:
self.write({'another_field': 2})
Or if you have to change the values, which had to be written, just use to context to stop a recursion:
#api.multi
def write(self, vals):
res = super(MyModel, self).write(vals)
if 'stop_write_recursion' not in self.env.context:
self.my_other_logic(vals)
#api.multi
def my_other_logic(self, vals):
# get values to write
self.with_context(stop_write_recursion=1).write(other_values)

Add method in same class or in String class - Best practices

I want define a new method to validate a string like this:
# lib/rips/variables/variable.rb
class Variable
# Check if value is a valid number (...,-1,0,1...)
def number? (value)
/\A[-]?\d+\z/ === value
end
end
And it invokes like this in the same class and derived classes:
# lib/rips/variables/inmediate.rb
class Inmediate < Variable
# value = "+42" -> return false
def valid_syntax? (value)
number?(value)
end
end
But other way to do this it's adding method number? to String class:
# lib/rips/utils/string.rb
class String
# Check if value is a valid number (...,-1,0,1...)
def number?
/\A[-]?\d+\z/ === self
end
end
And now it invokes like this:
# lib/rips/variables/inmediate.rb
require "lib/rips/utils/string"
class Inmediate < Variable
# value = "+42" -> return false
def valid_syntax? (value)
value.number?
end
end
So I have number?(value) if I declare method in same class or value.number? if I add method to String class. Isn't only method, I want to add more new methods too. I prefer second way but I don't know if the best practice in this case.
Which is the best practice?
The best way is use Refinements (by link described why and how to use it functionality). It is available since ruby 2.0.
There is a third option, where you get the convenience of calling your custom methods directly on the String object, but without adding it to the String class.
module StringExtensions
def number?
/\A[-]?\d+\z/ === self
end
end
string = "1"
string.number? # => NoMethodError: undefined method `number?' for "1":String
string.extend(StringExtensions)
string.number? # => true
"2".number? # => NoMethodError: undefined method `number?' for "1":String

Django form __init__() got multiple values for keyword argument

Hello, I'm trying to use a modified __init__ form method, but I am encountering the following error:
TypeError
__init__() got multiple values for keyword argument 'vUserProfile'
I need to pass UserProfile to my form, to get to dbname field, and I think this is a solution (my form code):
class ClienteForm(ModelForm):
class Meta:
model = Cliente
def __init__(self, vUserProfile, *args, **kwargs):
super(ClienteForm, self).__init__(*args, **kwargs)
self.fields["idcidade"].queryset = Cidade.objects.using(vUserProfile.dbname).all()
Calls to constructor ClienteForm() without POST are successful and show me the correct form. But when the form is submitted and the constructor is called with POST, I get the previously described error.
You've changed the signature of the form's __init__ method so that vUserProfile is the first argument. But here:
formPessoa = ClienteForm(request.POST, instance=cliente, vUserProfile=profile)
you pass request.POST as the first argument - except that this will be interpreted as vUserProfile. And then you also try to pass vUserProfile as a keyword arg.
Really, you should avoid changing the method signature, and just get the new data from kwargs:
def __init__(self, *args, **kwargs):
vUserProfile = kwargs.pop('vUserProfile', None)
For the help of those others who Google to here: the error comes from init picking up the argument from both a positional argument and the default argument. Daniel Roseman's is accurate for the question as asked.
This can be either:
You put the argument by position and then by keyword:
class C():
def __init__(self, arg): ...
x = C(1, arg=2) # you passed arg twice!
You forgot to put self as the first argument:
class C():
def __init__(arg): ...
x = C(arg=1) # but a position argument (for self) is automatically
# added by __new__()!
I think this is the case with ModelForm, but need to check. For me, the solution was:
def __init__(self, *args, **kwargs):
self.vUserProfile = kwargs.get('vUserProfile', None)
del kwargs['vUserProfile']
super(ClienteForm, self).__init__(*args, **kwargs)
self.fields["idcidade"].queryset = Cidade.objects.using(self.vUserProfile.dbname).all()

Resources