DRF Create queryset by adding foreign key queryset into it - json

I want to get foreign data(Child model) into Parent Model queryset.
I am able to add foreign data into Parent data but it is JSON. I want to convert that JSON into queryset because get_queryset() returns model/queryset.
I googled a lot but unable to find anything helpful.
class Parent(models.Model):
parent_name = models.TextField()
child = models.ForeignKey(Child, related_name="+", on_delete=models.CASCADE, null=True)
class Child(models.Model):
child_name = models.TextField()
class ParentViewSet(viewsets.ModelViewSet):
queryset = Parent.objects.all()
serializer_class = ParentInfoSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ['parent_name']
def get_queryset(self):
response = []
parent_name = self.request.GET.getlist("parent_name")[0]#
parent_queryset =
Parent.objects.filter(parent_name=parent_name)
for par_queryset in parent_queryset:
parent_result = self.serializer_class(phy_queryset).data
child_id = physician_info_result["child"]
child_instance = Child.objects.get(pk=child_id)
child_result = ChildSerializer(child_instance).data
parent_result["child"] = child_result
response.append(parent_result)
return response
URL -
http://localhost:9000/api/parent?parent_name=xyz
Response output is:
[{
"parent_name": "xyz",
"child": [{
"id": 1
"child_name": "abc"
}]
}]
But above output it is JSON which I don't want. I want output into queryset/model.
NOTE: Queryset output should contain foreign queryset.

You could just replace the default field for child to child serializer and it'll work like you want.
class ParentSerializer(models.Model):
child = ChildSerializer()
class Meta:
models = Parent
fields = ('parent_name', 'child')
And you also don't need to override the get_queryset method.

Related

ValidationError from Composite key with marshmallow_sqlalchemy, sqlalchemy, marshmallow

I am making an API with Flask and I am using sqlalchemy/flask-sqlalchemy, marshmallow and marshmallow_sqlalchemy for handling the modeling of the database.
I am loading in the data for the Character table through the code below
character = {
'name': raw_character['name'],
'original_name': raw_character['original_name'],
'alternative_name': raw_character['alternative_name'],
}
characters_serialized.append(character)
schema = CharacterSchema()
characters = schema.load(data=characters_serialized, many=True, session=db.session)
raw_character is json as seen below:
{
"name": "Olaa",
"original_name": "olå",
"alternative_name": ["ol", "oå"]
}
The model itself is defined as a table for Character and a table representing the list of alternative names
class CharacterAlternativeName(db.Model):
__tablename__ = "character_alternative_name"
character_id = sa.Column(sa.Integer, sa.ForeignKey("character.id"), primary_key=True)
alternative_name = sa.Column(sa.String, primary_key=True)
def __repr__(self):
return "<CharacterAlternativeName(alternative_name={self.alternative_name!r})>".format(self=self)
class Character(db.Model):
__tablename__ = "character"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
original_name = sa.Column(sa.String)
alternative_name = relationship("CharacterAlternativeName")
def __repr__(self):
return "<Character(name={self.name!r})>".format(self=self)
class CharacterSchema(SQLAlchemySchema):
class Meta:
model = Character
include_relationships = True
load_instance = True # Optional: deserialize to model instances
id = auto_field()
name = auto_field()
original_name = auto_field()
alternative_name = auto_field()
The problem I am facing is that it seems to struggle to create the composite key in the CharacterAlternativeName table, as when it tries to deserialize them it gives the following error message
"marshmallow.exceptions.ValidationError: {0: {'alternative_name': {0: ["Could not deserialize related value 'ol'; expected a dictionary with keys ['character_id', 'alternative_name']"], 1: ["Could not deserialize related value 'oå'; expected a dictionary with keys ['character_id', 'alternative_name']"]}}}"
Which seems to suggest it struggles to create the composite key. Any ideas how to make the composite key work with sqlalchemy and marshmallow?

Object of type "" is not JSON serializable

I am trying to create an API and pass the context data in Response but I am getting the error:
Object of type TakenQuiz is not JSON serializable
Below is the code:
taken_quizzes = quiz.taken_quizzes.select_related('supplier__user').order_by('-date')
total_taken_quizzes = taken_quizzes.count()
quiz_score = quiz.taken_quizzes.aggregate(average_score=Avg('score'))
least_bid = quiz.taken_quizzes.aggregate(least_bid=Min('least_bid'))
extra_context = {'taken_quizzes': taken_quizzes,
'total_taken_quizzes': total_taken_quizzes,
'quiz_score': quiz_score, 'least_bid': least_bid, 'matching_bids': matching_bids,
'driver_num': driver_num, 'lat_lon_orig': lat_lon_orig, 'lat_lon_dest': lat_lon_dest,
'user_pass': user_pass, 'username': username, 'password': password, }
print("extra content is ", extra_context)
return Response(extra_context)
Here is the context data:
extra content is {'taken_quizzes': <QuerySet [<TakenQuiz: TakenQuiz object (1)>]>, 'total_taken_quizzes': 1, 'quiz_score': {'average_score': 0.0}, 'least_bid': {'least_bid': 899}, 'matching_bids': [], 'driver_
num': 0, 'lat_lon_orig': '36.1629343, -95.9913076', 'lat_lon_dest': '36.1629343, -95.9913076', 'user_pass': ('jkcekc', 'efce'), 'username': 'efw', 'password': 'sdf'}
The error I believe is because of the queryset in the extra_context, How do I resolve this ?
I tried json.dumps but it still doesn't work
Serializer.py
class takenquizSerializer(serializers.ModelSerializer):
class Meta:
model = TakenQuiz
fields = "__all__"
Models.py
class TakenQuiz(models.Model):
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, related_name='taken_quizzes')
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='taken_quizzes')
score = models.FloatField()
date = models.DateTimeField(auto_now_add=True)
least_bid = models.IntegerField(default=0)
confirmed = models.CharField(max_length=100, default='Not Confirmed')
UPDATE
taken_quizzes = quiz.taken_quizzes.select_related('supplier__user').order_by('-date')
taken_quizzs = takenquizSerializer(taken_quizzes).data
You need to serialize taken_quizzes objects either via some serializer or calling
".values()" and specifying the required key, if any (otherwise it will give all values of the model as a dictionary)
{
'taken_quizes': TakenQuizSerializer(taken_quizzes, many=True).data,
# or
'taken_quizzes': taken_quizzes.values(),
....
}
as ruhaib mentioned you need to serialize the data. If i dont want to define special serializers for models this is what I do.
from django.core import serializers
taken_quizzes=....
data=serializers.serialize('json',taken_quizzes)
you can do this before you populate extra_content with some data.

How to modify a many-to-many collection using django rest framework

I am trying to create an endpoint where, having a User entity, I can add / remove existing Group entities to user.groups many-to-many field. But when I try to do it, django-rest-framework tries to create new group objects instead of finding existing ones.
I have defined two serializers where UserSerializer has a nested GroupSerializer:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ['id', 'name']
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'groups']
groups = GroupSerializer(many=True)
def update(self, instance, validated_data):
data = validated_data.copy()
groups = data.pop('groups', [])
for key, val in data.items():
setattr(instance, key, val)
instance.groups.clear()
for group in groups:
instance.groups.add(group)
return instance
def create(self, validated_data):
data = validated_data.copy()
groups = data.pop('groups', [])
instance = self.Meta.model.objects.create(**data)
for group in groups:
instance.groups.add(group)
return instance
When I send a JSON through a PUT REST call (from django-rest-framework web interface):
{
"id": 6,
"username": "user5#example.com",
"email": "user5#example.com",
"groups": [
{
"id": 1,
"name": "AAA"
}
]
}
I expect serializer to find the Group with given id and add it to User. But instead, it tries to create a new user group and fails with duplicate key error:
{
"groups": [
{
"name": [
"group with this name already exists."
]
}
]
}
I searched over the internet and debugged myself and found no solution to this use case.
The create and update methods inside UserSerializerclass are never reached.
Edit: as asked, here are my views and urls:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
Urls:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
This seems to be the validation error due to a nested serializer model that contains unique constraint, see this post. According to the article, DRF did not handle this condition since it's hard to realize if the serializer is a nested serializer within another one. And that's why the create() and update() never been reached since the validation is done before calling them.
The way to work around this is to remove the uniqueness validator manually in GroupSerializer as follow:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ['id', 'name']
extra_kwargs = {
'name': {'validators': []},
}
BTW, there are some points that can be improved or should be corrected in your update() and create() code. Firstly, you didn't do instance.save() so the instance won't be update after the whole process done. Second, the groups are just a list of dictionary, and you should not add object that way. The following are the modification based on your OP code:
def update(self, instance, validated_data):
data = validated_data.copy()
groups = data.pop('groups', [])
for key, val in data.items():
setattr(instance, key, val)
instance.save() # This will indeed update DB values
group_ids = [g['id'] for g in groups]
instance.groups.clear()
instance.groups.add(*group_ids) # Add all groups once. Also you can replace these two lines with
# instance.groups.set(group_ids)
return instance
Though am late, here is how I did it, adding to Tsang-Yi Shen answer. However this worked for me because I was using django-role-permissions https://django-role-permissions.readthedocs.io/en/stable/setup.html
For the group serializer am only interested in the name.
from rolepermissions.roles import assign_role
class GroupModelSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
class Meta:
model = Group
fields = ['name']
extra_kwargs = {
'name': {'validators': []},
}
def get_name(self, obj):
"""
This method modifies the way the name field is returned in a get request.
"""
return [group.name for group in obj.objects.all()]
In the UserSerializer, I modify the groups field to be returned as a list of groups a user belongs to, and received as a list instead of an OrderedDict in the JSON payload for creating a User, which can look something like this:
{
"email": "testuser#example.com",
"username": "testuser",
"name": "Test User",
"password": "password123",
"groups": ["doctor", "nurse", ]
}
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
groups = ListField(required=False, default=[], write_only=True)
user_groups = serializers.SerializerMethodField(read_only=True)
class Meta:
model = User
fields = ["id", "email", "username", "name", "password", "groups", "user_groups"]
depth = 2
def get_user_groups(self, obj):
"""
This method modifies the way the `groups` field is returned in a get request.
"""
return [group.name for group in obj.groups.all()]
def create(self, validated_data):
password = validated_data.pop('password')
gropus_to_add_user = validated_data.pop("groups")
user = User(**validated_data)
user.set_password(password)
user.save()
for group_name in groups_to_add_user:
# The assign_role function adds the user to a group.
# In this case group and roles may mean the same thing.
assign_role(user, group_name)
return user
You will have something like this when creating a user, here am using Swagger:

DRF - Serializer Multiple Models

How can I POST this JSON
{
"campaign": 27,
"campaignName": "Prueba promo",
"promotionType": 999,
"items": [
{ "item_nbr": 1234567890123, "plu": 2},
{ "item_nbr": 12345678901, "plu": 3}
]
}
Currently, I only get this response JSON
{
"items": [],
"campaign": 27,
"campaignName": "Prueba promo",
"promotionType": 999,
"start_date": "2019-03-04T12:02:16.574874-03:00",
"end_date": null,
"active": true
}
How can I do it? I read the DRF documentation but it didn't work, what I'm doing wrong?
here is my code
my models.py
class Item(models.Model):
promocion = models.ForeignKey(Promocion, related_name='items', on_delete=models.CASCADE, null=True)
item_nbr = models.IntegerField(primary_key=True, help_text="Numero de Item")
modular = models.ForeignKey(Modular, on_delete=models.CASCADE, null=True)
price = models.FloatField()
q_min = models.PositiveIntegerField(default=1, help_text="Cantidad mínima")
q_mul = models.PositiveIntegerField(default=1, help_text="Multiplo de cajas cerradas")
vensil1 = models.CharField(max_length=30, help_text="Atributo item relevante")
vensil2 = models.CharField(max_length=30, help_text="Atributo item relevante")
vensil3 = models.CharField(max_length=30, help_text="Atributo item relevante")
FG = "Fleje grande, 1/3 Carta"
FP = "Fleje pequeño 1/6 Carta"
CP = "Carteleria media Carta"
opciones = ((FG, "Fleje grande, 1/3 Carta"),
(FP, "Fleje pequeño 1/6 Carta"),
(CP, "Carteleria media Carta"),)
print_type = models.CharField(choices=opciones, help_text="Fleje a imprimir", max_length=255)
depto = models.IntegerField(default=1, help_text="Departamento")
descri = models.CharField(max_length=100, help_text="Descripción producto")
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, null=True)
vendor_pack = models.IntegerField(default=1)
container = models.CharField(max_length=6, default="MAY")
size = models.CharField(max_length=20, help_text="Tamaño pack")
cont_net = models.FloatField(default=1, help_text="Contenido Neto")
sell_unit = models.CharField(max_length=5, help_text="Unidad de venta")
weight_drain = models.FloatField(default=0, help_text="Peso drenado")
cod_bal = models.IntegerField(null=True, blank=True, help_text="Código balanza")
plu = models.BigIntegerField(help_text="Código de barra")
here are my serializer.py
class ItemPromoSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_nbr', 'plu')
class PromoSerializer(serializers.ModelSerializer):
items = ItemPromoSerializer(many=True, read_only=True)
#steps = ScalePromoSerializer(many=True)
class Meta:
model = Promocion
fields = ('items', 'campaign', 'campaignName', 'promotionType',
'start_date', 'end_date', 'active')
my viewsets.py
class PromoViewSet(viewsets.ModelViewSet):
queryset = Promocion.objects.all()
serializer_class = PromoSerializer
and my routes.py
router.register(r'promo', PromoViewSet)
I've tried methods to_internal_value() and to_representation() but the result was
"non_field_errors": ["Invalid data. Expected a dictionary, but got list."]
If this issue happens during a POST request, it means that you need to adapt the way you save your data. Django REST Framework doesn't support writing nested objects in DB out of the box.
What I usually do for these use cases is
I don't use viewset for such complex use cases, instead I prefer using CreateAPIView which enables me to use specific serializers to validate inputs and to present data.
I hook in create of CreateAPIView and use with transaction.atomic(): when writing to several tables at the same time to make sure all transactions are invalidated in case an error comes up.
I use 2 serializers one for the parent model and one for the child model.
In your case the code could like this:
serializer.py
class PromoSerializer(serializers.ModelSerializer):
class Meta:
model = Promocion
fields = ('campaign', 'campaignName', 'promotionType', 'start_date', 'end_date', 'active')
class ItemPromoSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_nbr', 'plu')
viewsets.py
from rest_framework import status
from django.db import transaction
class PromoCreateAPI(CreateAPIView):
queryset = Promocion.objects.all()
serializer_class = PromoSerializer
# We skip perform_create
def create(self, request, *args, ***kwargs):
try:
items_data = request.data.pop('items')
except KeyError:
return Response({}, status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
with transaction.atomic():
instance = serializer.save()
# Validate each item
for item in items_data:
s = ItemPromoSerializer(data=item)
s.is_valid(raise_exception=True)
s.save(campaign=instance)
headers = self.get_success_headers(serializer.data)
serializer.data['items'] = items_data
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Of course this code is untested, but I hope it helps you get where you need to.

Django Rest Framework: Deserializing and get the primary key from validated_data

I defined a nested model Product as follow. Each Product can belong to a lot of Productlist.
class Product(models.Model):
product_id = models.AutoField(primary_key=True)
product_name = models.CharField(max_length=50)
class Productlist(models.Model):
productlist_id = models.AutoField(primary_key=True)
productlist_name = models.CharField(max_length=50)
product = models.ManyToManyField(Product, related_name='productlists')
The corresponding serializers are:
class ProductlistSerializer(serializers.ModelSerializer):
class Meta:
model = Productlist
fields = ('productlist_id', 'productlist_name',)
class ProductSerializer(serializers.ModelSerializer):
productlists = ProductlistSerializer(many=True, required=False)
class Meta:
model = Product
fields = ('product_id', 'product_name', 'product lists')
def create(self, validated_data):
#some codes
When I POST a new Product (url(r'^api/products/$', views.ProductEnum.as_view()), I would like to update the product lists for adding the new product to the corresponding product lists. The JSON file I prefer to use is:
{
"product_name": "product1"
"productlist": [
{
"productlist_id": 1,
"productlist_name": "list1",
},
{
"productlist_id": 2,
"productlist_name": list2"
}
]
}
The problem is that I cannot get the productlist_id from validated_data. In Django Rest Framework, you always need to call to_internal_value() for deserializing data and generate validated_data. After some degugging, I checked the code of DRF and find the following snippets in to_internal_value():
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 = [
field for field in self.fields.values()
if (not field.read_only) or (field.default is not empty)
]
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
Please notice the to_internal_value's fields has ignored the IntegerField(read_only=True) for it cannot satisfy the following condition:
fields = [
field for field in self.fields.values()
if (not field.read_only) or (field.default is not empty)
]
So the validated_data will just have the following data:
{
"product_name": "product1"
"productlist": [
{
"productlist_name": "list1",
},
{
"productlist_name": list2"
}
]
}
How could I get the primary key of product list? Thanks in advance!
After some digging, I found that the read_only fields are only for output presentation. You can find the similar question on the offcial github link of Django REST Framework.
So the solution is overriding the read_only field in the serializer as follow:
class ProductlistSerializer(serializers.ModelSerializer):
productlist_id = serializers.IntegerField(read_only=False)
class Meta:
model = Productlist
fields = ('productlist_id', 'productlist_name',)