Django Rest Framework JSON API - Update M2M relationship not persisting - django

I'm trying to update a resource in a PATCH.
The resource is updated fine, but the resources in the M2M table don't change.
Models
class StatementOfAdvice(Model):
id = HashidUnsignedAutoField(primary_key=True, salt="StatementOfAdvice", min_length=15)
loan_purposes = ManyToManyField(
to=StaticLoanPurpose, through=StatementOfAdviceLoanPurpose, related_name="loan_purposes"
)
class StaticLoanPurpose(Model):
id = CharField(db_column="loan_purpose_id", primary_key=True, max_length=150)
value = CharField(max_length=150, unique=True)
class StatementOfAdviceLoanPurpose(Model):
id = HashidUnsignedAutoField(primary_key=True, salt="StatementOfAdviceLoanPurpose", min_length=15)
statement_of_advice = ForeignKey(to="StatementOfAdvice", on_delete=DO_NOTHING)
loan_purpose = ForeignKey(to="StaticLoanPurpose", on_delete=DO_NOTHING)
Serializers
class StatementOfAdviceSerializer(Serializer):
included_serializers = {"client_account": ClientAccountSerializer, "loan_purpose": StaticLoanPurposeSerializer}
loan_purposes = ResourceRelatedField(many=True, read_only=False, queryset=StaticLoanPurpose)
class Meta:
model = StatementOfAdvice
fields = "_all_"
class StaticLoanPurposeSerializer(Serializer):
class Meta:
model = StaticLoanPurpose
fields = "_all_"
My PATCH request: http://localhost:8000/statements_of_advice/zELX1KdyZjgQGkp/
Payload:
{
"data": {
"type": "StatementOfAdvice",
"attributes": {},
"relationships": {
"loan_purposes": {
"data": [
{
"type": "StaticLoanPurpose",
"id": "construct_io"
},
{
"type": "StaticLoanPurpose",
"id": "other_purpose"
},
{
"type": "StaticLoanPurpose",
"id": "purchase_io"
}
]
}
},
"id": "zELX1KdyZjgQGkp"
}
}
The result I expect from this PATCH request is 3 records in the linking table StatementOfAdviceLoanPurpose. But I get none.
If anyone could help me here I would greatly appreciate it.

Ok, Then. The solution I found was actually using rest_framework_json_api.views.AutoPrefetchMixin in my StatementOfAdviceViewSet.
class StatementOfAdviceViewSet(BaseViewSet, AutoPrefetchMixin):
queryset = StatementOfAdvice.objects.all()
serializer_class = StatementOfAdviceSerializer
filterset_class = StatementOfAdviceFilterSet
Reference link:
https://django-rest-framework-json-api.readthedocs.io/en/stable/apidoc/rest_framework_json_api.views.html?highlight=manytomany#rest_framework_json_api.views.AutoPrefetchMixin.get_queryset
But this only fixes part of the problem. Now I can insert records in the linking table using the same payload as in the question.
But I cannot remove them. I tried changing the request method to PUT assuming that since PUT is supposed to replace all values of the resource the framework would handle it to me. But it doesn't.
So besides adding the AutoPrefetchMixin in the viewset. I also had override the update() function in the StatementOfAdviceSerializer to achieve the desired behaviour.
The advantage of using AutoPrefetchMixin is that you don't have to override the create() method.

Related

Data does not get saved in database ; unexpected keyword arguments

I have gone through many solutions posted on SO and other places but have been running into the same issue. Pretty new to django and trying to understand where I am going wrong
I am getting the following error
TypeError: Basetable() got unexpected keyword arguments:
'CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents'
I have the following JSON Data
jsonToUse = {
"CompanyId": "320193",
"CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents": [
{
"decimals": "-6",
"unitRef": "usd",
"value": "39789000000"
},
{
"decimals": "-6",
"unitRef": "usd",
"value": "50224000000"
},
{
"decimals": "-6",
"unitRef": "usd",
"value": "25913000000"
},
{
"decimals": "-6",
"unitRef": "usd",
"value": "35929000000"
}
]
}
Model:
class Basetable(models.Model):
basetable_id = models.AutoField(primary_key=True)
CompanyId = models.IntegerField()
class Cashcashequivalentsrestrictedcashandrestrictedcashequivalents(models.Model):
cashcashequivalentsrestrictedcashandrestrictedcashequivalents_id = models.AutoField(
primary_key=True)
unitRef = models.CharField(max_length=100)
value = models.CharField(max_length=100)
decimals = models.IntegerField()
basetable_id = models.ForeignKey(Basetable, on_delete=models.CASCADE)
Serializer:
class CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalentsSerializer(serializers.ModelSerializer):
class Meta:
model = Cashcashequivalentsrestrictedcashandrestrictedcashequivalents
fields = ['decimals', 'unitRef', 'value']
class CashFlowSerializer(serializers.ModelSerializer):
CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents = CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalentsSerializer(
many=True)
class Meta:
model = Basetable
fields = "__all__"
View:
.....#TRIMMED GET SYNTAX.....
check = CashFlowSerializer(data=jsonToUse)
if (check.is_valid(raise_exception=True)):
print("ready to send to db")
check.save()
return JsonResponse(jsonToUse, safe=False)
I want to save the data in the database for the provided JSON
Have a look to the full traceback error, the problem is that you need to define your own create method in your nested serializer:
TypeError: Got a `TypeError` when calling `Basetable.objects.create()`.
This may be because you have a writable field on the serializer class that is not a valid argument to `Basetable.objects.create()`.
You may need to make the field read-only, or override the CashFlowSerializer.create() method to handle this correctly.
...
TypeError: Basetable() got an unexpected keyword argument 'CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents'
You will find more information in django-rest-framework Writable nested serializers documentation.
Basically, iterate on your JSON data and create Cashequivalents objects.

Django can i only pass "id" in POST request, despite displaying nested fields?

in my post requests to OrderProduct model, i want to only have to pass order.id and product.id and it works... untill i add a serializer to retrieve product.name. It might be because i didnt understand documentation about nested requests, but im unable to advance further into my project :(
[
{
"id": 2,
"order": 1,
"product": 1,
}
]
^ here's how it looks without nested serializer, and thats the data that i wanna have to input
[
{
"id": 2,
"order": 1,
"product": {
"id": 1,
"name": "gloomhaven",
},
},
^ here's how it looks after i add an additional serializer. I pretty much want these nested fields to be read only, with me still being able to send simple post requests
here are my serializers
class OrderProductSerializer(serializers.ModelSerializer):
product = Product()
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
class Product(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"id",
"name")
Is there any way for me to accomplish this? Thank you for trying to help!
Just overwrite to_representation method of the serializer
def to_representation(self, instance):
response = super().to_representation(instance)
response['other_field'] = instance.id# also response['other_field'] = otherSerializer(instance.model)
return response
This can solve your problem
I think you are missing many=True
class OrderProductSerializer(serializers.ModelSerializer):
product = Product(many=True)
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]

Fetch and return category and all related objects

I have a Bookmark and a BookmarkCategory object. I'd like to be able to fetch JSON that looks like this:
GET -> localhost:8000/api/bookmarks
[
"python": {
"title": "Python",
"bookmarks": [
{
"title": "Python Documentation",
"url": "https://docs.python.org"
}
]
},
"javascript": {
"title": "Javascript",
"bookmarks": [
{
"title": "Python Documentation",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript"
}
]
}
]
Here's my models:
class BookmarkCategory(models.Model):
title = models.CharField(max_length=255)
class Bookmark(models.Model):
title = models.CharField(max_length=255)
url = models.CharField(max_length=255)
category = models.ManyToManyField(BookmarkCategory)
Here's how I would query all the BookmarkCategory objects:
from .models import BookmarkCategory
bookmarks = BookmarkCategory.objects.all()
The JSON doesn't have to look exactly like this. I just need to get all my BookmarkCategory objects along with all the related Bookmark objects so I can iterate over them after I make a GET request to fetch them.
You'll have to use select_related on "bookmark_set", to fetch them along. If you are using DRF, you need to create a nested serializer configuration for bookmarks. Here is an example without DRF:
categories = BookmarkCategory.objects.select_related("bookmark_set")
items = []
for category in categories:
items.append(
{
"title": category.title,
"bookmarks": category.bookmark_set.values_list("title", "url"),
}
)

Populate Related Field Based on Query Parameter

I'm trying to implement a way to show full details of a related field, rather than just the ID or a specific field, when it is specified in a query parameter, e.g http://api_url/courses?populate="author"
Currently, the ID of the author field is shown like so, with this URL - http://api_url/courses
"data": [
{
"author": "e1d5b311-f6b5-4909-8caf-da6ff025a4fc",
....
}
]
I need it to be able to be displayed in full when a ?populate="author" parameter is added to the URL - http://api_url/courses?populate="author" should show this:
"data": [
{
"author": {
"id" : "e1d5b311-f6b5-4909-8caf-da6ff025a4fc",
"first_name" : "string",
"last_name" : "string",
},
....
}
]
I can currently show full details of the field by using a nested serializer, like so, or show just the UUID (or any other field) using a SlugRelatedField.
class CourseSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Course
fields = "__all__"
Any help is much appreciated.

Altering incoming Django url queries using tastypie : handling foreign key traversal on the backend

So we've got a Django/tastypie server going that has the following (simplified) model. If I remove our alter_detail_data_to_serialize:
{
"release": false,
"resource_uri": "/api/packages/1",
"id": 1,
"branch": {
"resource_uri": "/api/branches/1",
"id": 1,
# ... more bits the client doesn't need to know about
"version": "0.1"
},
"revision": "72"
}
With the alter, it becomes:
{
"release": false,
"branch": "0.1",
"revision": "72"
}
Which is what we want to work with through the API: It removes the foreign key traversal to simplify the JSON and do any CRUD without issues: supplying a version is sufficient to identify the branch. The issue is, to query this, requires
/api/packages?branch__version=1.0, where this not intuitive and exposes the structure of the underlying database. We'd prefer to be able to query:
/api/packages?branch=1.0 and handle the foreign key traversal on the backend.
alter_detail_data_to_serialize and alter_deserialized_detail_data allow me to interface with the simplified JSON and do any non-searching CRUD without issues, but is it possible to allow a /api/packages?branch=1.0 query and have the django/tastypie server correct that to /api/packages?branch__version=1.0, hiding the database structure?
Some additional code that might be relevant:
class PackageResource(ModelResource):
branch = fields.ForeignKey(BranchResource, 'branch', full=True)
class Meta:
queryset = Packages.objects.all()
resource_name = 'packages'
collection_name = 'packages'
def alter_detail_data_to_serialize(self, request, data):
data.data['branch'] = data.data['branch'].data['version']
return data
Branch Resource:
class BranchResource(ModelResource):
class Meta:
queryset = Branches.objects.all()
resource_name = 'branches'
collection_name = 'branches'
In the object resource, you can add something like this:
class PackageResourse(ModelResource):
version = fields.CharField( attribute = 'branch__version' )
class Meta:
resource_name='package'
What this is doing it making the PackageResource have a variable that is the same as its foreign key's variable. Now you can use api/packages?version=1.0 on the PackageResource.