key and data are dictionary how I can access data of specific key in serializer
class Setting(models.Model):
key = models.CharField(max_length=255, primary_key=True)
data = JSONField(null=True, blank=True)
In serializer, something like this
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SettingsSerializer(serializers.ModelSerializer):
sample_filed = serializers.SerializerMethodField()
class Meta:
model = Settings
fields = ('key', 'data', 'sample_filed')
def get_sample_field(self):
""" a sample field function to demonstrate srialzer method"""
""" you can access all keys here as self.key "
return self.key + self.data
Note:- By using model serializer you will just have to specify the model. then restframework will handle all generic stuff.
you can use serializer methodfield() if you want extra field in the response. there you can use the key as stated above
(or) you can use django -orm
you can use django orm queries outside the serializer.
to get a specific row
Settings.objects.get(key = 'key-value') (only if key is unique. this will throw an exception if there is no value)
(or)
Settings.objects.filter(key = 'key-value') (recommded. will give you all the results.)
Settings.objects.all() will give you all records
Settings.objects.filter(key = 'sdaf', data = 'sds') Specific records with key and data .
work with this code
ssh_instance = Setting.objects.get(key='ssh-port')
print(ssh_instance.data["value"])
Related
I have created a nested serializer, when I try to post data in it it keeps on displaying either the foreign key value cannot be null or dictionary expected. I have gone through various similar questions and tried the responses but it is not working for me. Here are the models
##CLasses
class Classes(models.Model):
class_name = models.CharField(max_length=255)
class_code = models.CharField(max_length=255)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.class_name
class Meta:
ordering = ['class_code']
##Streams
class Stream(models.Model):
stream_name = models.CharField(max_length=255)
classes = models.ForeignKey(Classes,related_name="classes",on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.stream_name
class Meta:
ordering = ['stream_name']
Here is the view
class StreamViewset(viewsets.ModelViewSet):
queryset = Stream.objects.all()
serializer_class = StreamSerializer
Here is the serializer class
class StreamSerializer(serializers.ModelSerializer):
# classesDetails = serializers.SerializerMethodField()
classes = ClassSerializer()
class Meta:
model = Stream
fields = '__all__'
def create(self,validated_data):
classes = Classes.objects.get(id=validated_data["classes"])
return Stream.objects.create(**validated_data, classes=classes)
# def perfom_create(self,serializer):
# serializer.save(classes=self.request.classes)
#depth = 1
# def get_classesDetails(self, obj):
# clas = Classes.objects.get(id=obj.classes)
# classesDetails = ClassSerializer(clas).data
# return classesDetails
I have tried several ways of enabling the create method but like this displays an error {"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}. Any contribution would be deeply appreciated
This is a very common situation when developing APIs with DRF.
The problem
Before DRF reaches the create() method, it validates the input, which I assume has a form similar to
{
"classes": 3,
"stream_name": "example"
}
This means that, since it was specified that
classes = ClassSerializer()
DRF is trying to build the classes dictionary from the integer. Of course, this will fail, and you can see that from the error dictionary
{"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}
Solution 1 (requires a new writable field {field_name}_id)
A possible solution is to set read_only=True in your ClassSerializer, and use an alternative name for the field when writing, it's common to use {field_name}_id. That way, the validation won't be done. See this answer for more details.
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
'classes_id',
)
extra_kwargs = {
'classes_id': {'source': 'classes', 'write_only': True},
}
This is a clean solution but requires changing the user API. In case that's not an option, proceed to the next solution.
Solution 2 (requires overriding to_internal_value)
Here we override the to_internal_value method. This is where the nested ClassSerializer is throwing the error. To avoid this, we set that field to read_only and manage the validation and parsing in the method.
Note that since we're not declaring a classes field in the writable representation, the default action of super().to_internal_value is to ignore the value from the dictionary.
from rest_framework.exceptions import ValidationError
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
def to_internal_value(self, data):
classes_pk = data.get('classes')
internal_data = super().to_internal_value(data)
try:
classes = Classes.objects.get(pk=classes_pk)
except Classes.DoesNotExist:
raise ValidationError(
{'classes': ['Invalid classes primary key']},
code='invalid',
)
internal_data['classes'] = classes
return internal_data
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
)
With this solution you can use the same field name for both reading and writing, but the code is a bit messy.
Additional notes
You're using the related_name argument incorrectly, see this question. It's the other way around,
classes = models.ForeignKey(
Classes,
related_name='streams',
on_delete=models.CASCADE,
)
In this case it should be streams.
Kevin Languasco describes the behaviour of the create method quite well and his solutions are valid ones. I would add a variation to solution 1:
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
classes_id = serializers.IntegerField(write_only=True)
def create(self,validated_data):
return Stream.objects.create(**validated_data, classes=classes)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'classes_id',
'created_date',
)
The serializer will work without overriding the create method, but you can still do so if you want to as in your example.
Pass the value classes_id in the body of your POST method, not classes. When deserializing the data, the validation will skip classes and will check classes_id instead.
When serializing the data (when you perform a GET request, for example), classes will be used with your nested dictionary and classes_id will be omitted.
You can also solve this issue in such a way,
Serializer class
# Classes serializer
class ClassesSerializer(ModelSerializer):
class Meta:
model = Classes
fields = '__all__'
# Stream serializer
class StreamSerializer(ModelSerializer):
classes = ClassesSerializer(read_only=True)
class Meta:
model = Stream
fields = '__all__'
View
# Create Stream view
#api_view(['POST'])
def create_stream(request):
classes_id = request.data['classes'] # or however you are sending the id
serializer = StreamSerializer(data=request.data)
if serializer.is_valid():
classes_instance = get_object_or_404(Classes, id=classes_id)
serializer.save(classes=classes_instance)
else:
return Response(serializer.errors)
return Response(serializer.data)
I have these two classes:
Class A:
data
Class B:
a = models.ForeignKey(A)
How can I get an array of A in which each A contains the B's related to them?
I need it because I must return the two tables joined in a JSON.
First set the back relation of a and then migrate the database:
Class A(models.Model):
data
Class B(models.Model):
a = models.ForeignKey(A, related_name='b_relations', null=False, blank=False, on_delete=models.CASCADE)
Now you can access the back relation. You need to call .all() on b_relations because it is lazy loaded.
A.objects.first().b_relations.all()
To get json data I suggest to use django rest framework's ModelSerializer:
class BSerializer(serializers.ModelSerializer):
class Meta:
model = B
fields = (
'pk', # primary key
'a',
)
depth = 0
class ASerializer(serializers.ModelSerializer):
b_relations = BSerializer(many=True, read_only=True))
class Meta:
model = A
fields = (
'pk', # primary key
'b_relations',
)
depth = 0
To get json data:
a_relations: QuerySet = A.objects.all()
serializer = ASerializer(a_relations, many=True)
json_data: Dict = serializer.data
In case you've never used Django Rest Framework: Django Rest Framework - Getting Started
Have fun!
I have the following (simplified) data model:
class Article(Model):
uuid = models.CharField(primary_key=True, max_length=128)
class Attribute(Model):
uuid = models.CharField(primary_key=True, max_length=128)
article = models.ForeignKey(Article, related_name='attributes')
type = models.CharField(max_length=256)
value = models.CharField(max_length=256)
An example usage would be an article with an attribute attached to it with type="brand" and value="Nike". Now I want to write an API which can get all articles with a certain brand, but I can't seem to write the filter for it. This is what I have so far:
class PhotobookFilter(df.FilterSet):
brand = df.CharFilter(method='filter_brand')
class Meta:
model = Article
def filter_brand(self, queryset, name, value):
return queryset.filter('order__attributes')
class PhotobookViewSet(AbstractOrderWriterViewSet):
queryset = Article.objects.all()
serializer_class = ArticlePhotobookSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = PhotobookFilter
The line with queryset.filter is obviously not correct yet. I need to create a filter here that returns all articles that contain an attribute with type="brand" and value=value. How would I do this?
Are you sure you want to condense both lookups (type and value of Attribute) into one filter? Why not allow filtering on both fields separately?
E.g.
class PhotobookFilter(df.FilterSet):
type = df.CharFilter(method='filter_type')
value = df.CharFilter(method='filter_value')
class Meta:
model = Article
def filter_type(self, queryset, name, value):
return queryset.filter(**{'attributes__type': value})
def filter_value(self, queryset, name, value):
return queryset.filter(**{'attributes__value': value})
And now a query like ?type=brand&value=Nike should work.
Obviously you could condense both conditions into one filter and for example hard code the band part:
class PhotobookFilter(df.FilterSet):
brand = df.CharFilter(method='filter_brand')
def filter_brand(self, queryset, name, value):
return queryset.filter(**{'attributes__type': 'brand', 'attributes__value': value})
But keeping them separate feels way more flexible.
You could also filter in reverse like this:
class PhotobookFilter(df.FilterSet):
brand = df.CharFilter(method='filter_brand')
class Meta:
model = Article
def filter_brand(self, queryset, name, value):
articles = Attribute.objects.filter(type="brand", value=value).values_list('article_id', flat=True)
return queryset.filter(id__in=articles)
This will create subquery for Attribute, which will still be one sql request in the end
Use search_fields .For correct result rename your 'type' attribute name http://www.codesend.com/view/09ca65d42248fe1d89d07ce151f4f050/
I have a nested ModelSerializer that I'm having trouble validating.
The problem I'm running into is that upon parent serializer creation I may or may not need to create the nested serializer/model as it may already exist in the database and I just want to link to it.
Code Setup:
models.py
class ModelA(models.Model):
modelb = ForeignKey(ModelB, null=true, blank=true)
...
class ModelB(models.Model):
...
serializers.py
class ModelASerializer(serializers.ModelSerializer):
modelb = ModelBSerializer(required=False)
class Meta:
model = ModelA
depth = 1
class ModelBSerializer(serializers.ModelSerializer):
class Meta:
model = ModelB
So, given 3 data scenarios I run into validation errors on all 3.
First, if I pass the NestedModel as data like so
data = {
'nestedmodel': NestedModel(**args),
...
}
I get the validation error saying there was a non_field_error and that it was expecting a dictionary but got a NestedModel instance.
Second, if I pass the data of the NestedModel (instead of the object):
data = {
'nestedmodel': {'id': 'this', ... },
}
I get the validation error equivalent of a duplicate key since the Nested Model has a unique key ('id') and that already exists in the database.
And third, if I just pass it the id of the nestedmodel, I get a similar error to the first situation except it says it got Unicode instead of the NestedModel instance.
data = {
'nestedmodel': 'this',
}
I understand why all three of these situations are happening and validation is failing, but that doesn't help me in my goal of trying to link an already existing NestedModel.
How do I go about doing that? What am I doing wrong?
Can you try this:
serializers.py
class ModelASerializer(serializers.ModelSerializer):
modelb = ModelBSerializer(required=False)
class Meta:
model = ModelA
depth = 1
fields = ('id', 'modelb', )
def create(self, validated_data):
modelb_id = self.validated_data.pop("nestedmodel")
modelb = ModelB.objects.get(id=modelb_id["id"])
modela = ModelA.objects.create(modelb=modelb, **validated_data)
return modela
Pass the data as follows:
Input
data = {"nestedmodel": {"id": 1 # add nestedmodel fields here}, }
I have a basic setup using the Django Rest Framework. I have two models and a nested serializer setup:
# models.py
from django.db import models
class Plan(models.Model):
name = models.CharField(max_length='100')
def __unicode__(self):
return u'%s' % (self.name)
class Group(models.Model):
plan = models.ForeignKey('plan')
name = models.CharField(max_length='50')
weight = models.SmallIntegerField()
def __unicode__(self):
return u'%s - %s' % (self.name, self.plan.name)
# serializer.py
from plans.models import Plan, Group
from rest_framework import serializers
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name', 'weight')
class PlanSerializer(serializers.ModelSerializer):
group = GroupSerializer(many=True, read_only=True)
class Meta:
model = Plan
fields = ('name', 'group')
# views.py
from rest_framework import viewsets
from plans.models import Plan
from plans.serializers import PlanSerializer
class PlanViewSet(viewsets.ModelViewSet):
queryset = Plan.objects.all()
serializer_class = PlanSerializer
When I view the serializers relationships in Django's Shell it shows the relationship correctly:
PlanSerializer():
name = CharField(max_length='100')
group = GroupSerializer(many=True, read_only=True):
name = CharField(max_length='50')
weight = IntegerField()
What I end up getting back via cURL is:
[
{
name: Test Plan
}
]
What I expect to get back is:
[
{
name: Test Plan,
group: [
{
name: Test Group,
weight: 1
}
]
}
]
There is no nested data coming through. I'm at a lose for what I've not setup correctly here. Can anyone point me in the correct direction?
The problem comes from your queryset: queryset = Plan.objects.all(). None of the items in this queryset has .group attribute that's why your result is empty. By default Django creates a reverse relation of the plan ForeignKey called group_set (unless you don't rename it via related_name) (this means that every plan item in the queryset have a group_set attribute which is a queryset containing all the groups of this plan). You can use this attribute in order to get a proper serialization. This means to change:
class PlanSerializer(serializers.ModelSerializer):
group_set = GroupSerializer(many=True, read_only=True)
class Meta:
model = Plan
fields = ('name', 'group_set')
If you really want to stick with group (btw this is a very bad name for a list of groups). You can hack it with prefetch_related like so:
queryset = Plan.objects.prefetch_related('group_set', to_attr='group')
this way every plan item will have a group attribute - a queryset containing all the groups for this plan.
Never forget to give related name for the foreign key. for eg
In models
plan = modles.ForeignKey(Plan, related_name="plan")
In Serializers
plan = PlanSerializers(many = True, read_only = True)