DRF, use method for read, and field for write - django-rest-framework

Say you have a quantity field in your order model.
You have get_quantity method which you want to use for read (serializing).
At the same time, you want to use quantity field to create or update.
quantity = serializers.IntegerField(source="get_quantity", required=False)
fails to use quantity when writing.
How to do something like
read_source="get_quantity"
write_source="quantity"

Method - 1: override the to_representation(...) method
class FooSerializer(serializers.Serializer):
quantity = serializers.IntegerField(required=False) # do not set `source` parameter
def to_representation(self, instance):
rep = super().to_representation(instance)
rep["quantity"] = instance.get_quantity()
return rep
Method - 2: Use two different serializer
class FooReadSerializer(serializers.Serializer):
quantity = serializers.IntegerField(required=False, source="get_quantity")
class FooWriteSerializer(serializers.Serializer):
quantity = serializers.IntegerField(required=False)

Related

Increasing the count field of model whenever a search is applied

I have a search api
class SearchViewSet(RetrieveModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
query = self.request.query_params.get("query")
final_queryset = search(query,queryset,#some logic)
#logic to generate serialiser and return serialiser.data
serialiser = self.get_serializer(final_ueryset, many=True)
search function returns a list of articles i.e
type(final_queryset) is List
And. I wan't to return the articles order_by('count) as well.
Now I wan't to increase the count of top 3 articles from the final_queryset is there a way of doing this.
Add created_at field in your model, then use this field to find top 3 item or latest 3 items like this, and then increase count
queryset = Article.objects.order_by('-created_at')[:3] # it will return a queryset of latest 3 items
for article in queryset:
article.count += 1
article.save()
serializer = ArticleSerializer(article)
Note: I only shared the logic part here, and i can't test it in my local, but it should work and help you to get the idea. Use it on your need.
Figured out a way for doing this
Just simply iterate on the list and increment the count of the objects in the list
class SearchViewSet(RetrieveModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
query = self.request.query_params.get("query")
final_queryset = search(query,queryset,#some logic)
# to increment the count of top 3 entity_aliases
for instance in final_queryset[:3]:
instance.count += 1
instance.save()
#logic to generate serialiser and return serialiser.data
serialiser = self.get_serializer(final_ueryset, many=True)

queryset filter between 2 values whit foreing keys?

I have problems with the following queryset, I probe it in django shell and it returns an empty list.
The situation is that I'm occupying a lforeing key, I did the same exercise with the model "Tarifa_Sem" and returns the value without any problem, just replace the F ('') by a variable x = 1000
The situation is that the table of the model "Tarifa_Sem" is only for consultation.
Where I am going to manage and save the response of the queryset is in the "Calculadora_isr" model
Model 1
class Tarifa_Sem(models.Model):
limite_inferior_isr = models.DecimalField(max_digits=10, decimal_places=2)
limite_inferior_subsidio = models.DecimalField(max_digits=10, decimal_places=2)
limite_superior = models.DecimalField(max_digits=10, decimal_places=2)
Model 2
class Calculadora_isr(models.Model):
tarifa = models.ForeignKey(Tarifa_Sem, on_delete=models.CASCADE, blank=True)
base_gravada = models.DecimalField(max_digits=10, decimal_places=2, blank=True)
limite_inf_calculo = models.DecimalField(max_digits=10, decimal_places=2, blank=True)
Queryset and save()
def limite_inferior(self):
queryset = Calculadora_isr.objects.filter(tarifa__limite_superior__gte=F('base_gravada'),tarifa__limite_inferior_isr__lte=F('base_gravada')).distinct().values('tarifa__limite_inferior_isr')
return queryset
def save(self):
self.limite_inf_calculo = self.limite_inferior
super (Calculadora_isr, self).save()
In the shell of django the list appears empty.
>>> queryset = Calculadora_isr.objects.filter(tarifa__limite_superior__gte=F('base_gravada'),tarifa__limite_inferior_isr__lte=F('base_gravada')).distinct().values('tarifa__limite_inferior_isr')
And in the admin when I give him save he tells me:
conversion from method to Decimal is not supported
thanks for the support
I finally found the solution.
To solve the problem of passing the "base_gravada" field, use another variable that returns all the values ​​of "base_gravada"
qs1 = Calculadora_isr.objects.values_list('base_gravada')
And in my second query, use the variable qs1:
qs2 = Tarifa_Sem.objects.filter(limite_superior__gte=qs1,limite_inferior_isr__lte=qs1).distinct().values('limite_inferior_isr')

use generic types in python to refactor code

I have been trying several ways to refactor the following code as these classes are recurring in my app:
class CreateRecord(Mutation):
record = Field(lambda: Unit)
class Arguments:
input = CreateInput(required=True)
def mutate(self, info, input):
data = input_to_dictionary(input)
data['createdAt'] = datetime.utcnow()
# data['createdBy'] = <user> # TODO: <user> input
record = UnitModel(**data)
db_session.add(record)
db_session.commit()
return CreateRecord(record=record)
class UpdateRecord(Mutation):
record = Field(lambda: Unit)
class Arguments:
input = UpdateInput(required=True)
def mutate(self, info, input):
data = input_to_dictionary(input)
data['updatedAt'] = datetime.utcnow()
# data['updatedBy'] = <user> # TODO: <user> input
record = db_session.query(UnitModel).filter_by(id=data['id'])
record.update(data)
db_session.commit()
record = db_session.query(UnitModel).filter_by(id=data['id']).first()
return UpdateRecord(record=record)
class DeleteRecord(Mutation):
record = Field(lambda: Unit)
class Arguments:
input = DeleteInput(required=True)
def mutate(self, info, input):
data = input_to_dictionary(input)
data['deletedAt'] = datetime.utcnow()
# data['deletedBy'] = <user> # TODO: <user> input
data['isDeleted'] = True
record = db_session.query(UnitModel).filter_by(id=data['id'])
record.update(data)
db_session.commit()
record = db_session.query(UnitModel).filter_by(id=data['id']).first()
return DeleteRecord(record=record)
I was thinking of using generic types but I'm kinda' stuck on how to implement it. I've tried creating a master class and in the
def mutate:
method I'd just check if it's a create, update or delete action but I still want to work with generic types before I do that.
Any help is highly appreciated. TIA.
I've solved this particular problem for myself with a mixin class that includes the following method:
from graphene.utils.str_converters import to_snake_case
class MutationResponseMixin(object):
#classmethod
def get_operation_type(cls):
"""
Determine the CRUD type from the mutation class name.
Uses mutation's class name to determine correct operation.
( create / update / delete )
"""
return to_snake_case(cls.__name__).split('_')[0]
This allows me to include a mutation method in the mixin that is shared by create, update, and delete methods and takes conditional action based on value of get_operation_type.
I also needed a way to determine the base record from the mixin's mutation (which in your case would be UnitModel) so my case I ended up declarding it explicitly as an attribute of each mutation class.

How to pass values from sale to invoice (through invoiceable lines) in odoo10?

class SaleAdvancePaymentInv(models.TransientModel):
_inherit = "sale.advance.payment.inv"
#api.multi
def _create_invoice(self, order, so_line, amount):
inv_obj = super(SaleAdvancePaymentInv, self)._create_invoice(order, so_line, amount)
inv_obj.write({'service_id':order.service_id.id})
This is my code.service id does not pass sale order to invoice through invoiceable lines.
But when I use downpayment the service id is passed to invoice.
*What is the reason behind this.?
How to pass the values through the invoiceable line?*
#api.multi
def _prepare_invoice(self):
dict_obj = super(SaleOrder, self)._prepare_invoice()
dict_obj.update({'service_id': self.service_id.id})
I override the prepare invoice function update the service value in dictionary

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.

Resources