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.
Related
I have a model where users can upvote other users for specific topics. Something like:
#models.py
Class Topic(models.Model):
name = models.StringField()
def __str__(self):
return str(self.name)
Class UserUpvotes(models.Model):
"""Holds total upvotes by user and topic"""
user = models.ForeignKey(User)
topic= models.ForeignKey(Topic)
upvotes = models.PositiveIntegerField(default=0)
Using DRF, I have an API that returns the following: topic_id, topic_name, and upvotes, which is the total upvotes for a given topic.
One of the project requirements is for the API to use these field names specifically: topic_id, topic_name, and upvotes
#serializers.py
class TopicUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.StringRelatedField(source="topic")
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
My trouble is aggregating these fields. I'm filtering the UserUpvotes by user or team and then aggregating by topic.
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_id": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_id": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
At first, I tried creating a TopicSerializer, and then assigning it to the topic field in TopicUpvotesSerializer. But then, the resulting json would have a nested "topic" field and the aggragation would fail.
Attempt 1
#views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
My problem is that the topic_id and topic_name fields are not showing. I get something like:
[
{
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": "12",
"upvotes": 3
},
]
Attempt 2
Another queryset attempt:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
Which yields:
[
{
"upvotes": 14
},
{
"upvotes": 3
},
]
The aggregation worked on the queryset level, but the serializer failed to find the correct fields.
Attempt 3
This was the closest I got:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.values("topic_id", "topic", "upvotes")
.order_by("-upvotes")[:n]
)
[
{
"topic_name": 3,
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "12",
"upvotes": 3
},
]
I have no idea why "topic_name" is simply transforming the "topic_id" into a string, instead of calling the string method.
Work with a serializer for the topic:
class TopicSerializer(serializers.ModelSerializer):
upvotes = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
fields = ['id', 'name', 'upvotes']
then in the ModelViewSet, you annotate:
from django.db.models import Sum
from rest_framework.viewsets import ModelViewSet
class TopicViewSet(ModelViewSet):
serializer_class = TopicSerializer
queryset = Topic.objects.annotate(upvotes=Sum('userupvotes__upvotes'))
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_name": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
The serialized FK will always give you the ID of the related model. I am not sure why you name it topic_name if that is equal to an ID. Now, if you really want to get the name field of the Topic model
in the topic_name = serializers.StringRelatedField(source="topic") you should give it a source="topic.name"
However, if you trying to get the ID of the relation you can still use ModelSerializer :
class TopicUpvotesSerializer(serializers.ModelSerializer):
class Meta:
model = UserUpvotes
fields = "__all__"
#willem-van-onsem's answer is the correct one for the problem as I had put it.
But... I had another use case (sorry! ◑﹏◐), for when the Users API used UserUpvotes serializer as a nested field. So I had to find another solution. This is was I eventually ended up with. I'm posting in case it helps anyone.
class UserUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.SerializerMethodField()
def get_topic_name (self, obj):
try:
_topic_name = obj.topic.name
except TypeError:
_topic_name = obj.get("skill__name", None)
return _topic_name
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
I still have no idea why the SerializerMethodField works and the StringRelatedField field doesn't. It feels like a bug?
Anyways, the rub here is that, after the values().annotate() aggregation, obj is no longer a QuerySet, but a dict. So accessing namedirectly will give you a 'UserUpvotes' object is not subscriptable error.
I don’t know if there are any other edge cases I should be aware of (this is when I REALLY miss type hints in Django), but it works so far
I have a model like this
class Status(models.Model):
is_working = models.BooleanField(
default=None,
null=True
)
is_at_home = models.BooleanField(
default=None,
null=True
)
with the corresponding serializer
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
using the default ModelViewSet
class StatusViewSet(viewsets.ModelViewSet):
"""
"""
serializer_class = StatusSerializer
queryset = Status.objects.all()
Whenever I partially update a Status, by e.g calling the put method on the API, all other fields are reset to False instead of keeping their old value.
Say, I have a Status that looks like this:
{
"id": 1,
"is_working": null,
"is_at_home": null,
}
If I call put using the following JSON:
{
"is_working": true
}
my data now looks like this
{
"id": 1,
"is_working": true,
"is_at_home": false <-- GOT UPDATED
}
I however, just want to update the is_working field, so that my desired result would be:
{
"id": 1,
"is_working": true,
"is_at_home": null
}
This probably has to do with the fact that HTML forms don't supply a value for unchecked fields. I, however, don't use any HTML forms as I'm purely consuming the API through JSON requests.
Using the serializer as is, I'd need to perform an extra get request prior to updating the model, just to get the state of the fields I don't want to update.
Is there a way around that?
First off, for partially updating you need to have a PATCH request and not PUT.
Since you are using ModelViewSet drf should automatically recognize that and set partial=True and update only the fields which were sent in the api payload.
ModelViewSet doc- DRF
You can make your is_at_home field read only in serializer as PUT method is updating it. Try this in your serializer.Hope this will work for you.
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
extra_kwargs = {
'id': {'read_only': True},
'is_at_home': {'read_only': True},
}
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.
I already have a general idea of how it should be done. The only issue that I face now is how to actually send the data. I don't want to create new Projects I just want to add them to the notifications. How do I pass the data, the actual JSON?
class NotificationsScheduleSerializer(ModelSerializer):
projects = ProjectSerializer(many=True) # Thats the Many2Many Field
user = HiddenField(default=CurrentUserDefault())
class Meta:
model = NotificationsSchedule
fields = [
"pk",
"projects",
"period",
"week_day",
"created_at",
"time",
"report_type",
"user",
]
def create(self, validated_data):
breakpoint() # I don't ever get "projects" in validated_data just Empty OrderedDict
projects_data = validated_data.pop("projects", [])
notification = NotificationsSchedule.objects.create(**validated_data)
return notification
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ["pk", "name"]
I want to be able to pass something like this.
{
"projects": [290, 289],
"period": "daily",
"week_day": 2,
"time": "16:02:00",
"report_type": "word_report"
}
But it expects dict instead.
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
You have to set read_only,
projects = ProjectSerializer(many=True, read_only=True)
And when creating Notifications ,
notification = NotificationsSchedule.objects.create(**validated_data)
notification.projects.add(*self.initial_data.get("projects"))
notification.save()
Context: I am having problem accessing fields which are validated by nested serializers.
I have a very sample model as shown below.
For 2 of the fields I have their specific serializers. When I try to access the data it returns all the fields except the one validated by the specific serializers.
Models looks like this
class Sampler(models.Model):
sample_name = models.CharField(unique=True, max_length=100)
sampling_by = JSONField(max_length=100)
extractions = JSONField(max_length=100)
max_samples = models.IntegerField(default=100)
Serializers
class ExtractionsSerializer(serializers.BaseSerializer):
table_name = serializers.CharField()
extraction_type = serializers.ChoiceField(["ABC"])
dependencies = serializers.ListField(child=RecursiveField(), allow_empty=True, required=False)
class SamplingBySerializer(serializers.BaseSerializer):
"""
Validate sampling_by
"""
def to_internal_value(self, samples):
methods = ["ABC"]
sampling_not_supported = [sample for sample in samples
if sample['by'] not in methods]
if sampling_not_supported:
raise ValidationError("{} not in {}".format(sampling_not_supported, methods))
class SamplerSerializer(serializers.ModelSerializer):
extractions = ExtractionsSerializer(read_only=True)
sampling_by = SamplingBySerializer(read_only=True)
class Meta:
model = Sampler
fields = ('sample_name', 'database', 'schema', 'sampling_by', 'extractions', 'max_samples')
Now I do
data = {
"database": "postgresql://..",
"sampling_by":[{
"by":"ABC",
"value": ["l32=turn_the"]
}],
"max_samples":3,
"extractions" : [{
"table_name": "person",
"extraction_type": "ABC"
}]
}
sampler = SamplerSerializer(data=data)
sampler.is_valid() #returns True
sampler.data => does not contain data of the nested fields. Like the `sampling_by` and `extractions`. Contains all other fields
sampler.validated_data => same problem as above
Any help would be appreciated! thanks
You probably missed the fact that your nested serializers are flagged as read_only=True
class SamplerSerializer(serializers.ModelSerializer):
extractions = ExtractionsSerializer(read_only=True)
sampling_by = SamplingBySerializer(read_only=True)
Remove that part, implement the serializer's create / update and you're good to go.
On a side note, it doesn't make sense to access serializer.data when deserializing.
Edit: the authority source is validated_data.