I have a django model where i store the data through post request, in API View how i have a Array field in django model
Class MyModel(models.Model):
field_1 = ArrayField(models.CharField())
field_2 = ArrayField(models.CharField())
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('field_1', 'field_2')
class MyApiView(CreateAPIView):
def my_method():
validated_data = MySerializer.data
field_1 = validated_data.get('field_1', [])
field_2 = validated_data.get('field_2', [])
obj = serializer.save()
obj.field_1.append(field_1)
obj.field_1.append(field_2)
obj.save()
when i append the data to field_1 in model it is storing as list of list field_1 = [['a', 'b', 'c']] but the desired output should be field_1 = ['a', 'b', 'c'] how can i acheive this
You are getting list of lists because you are appending the field_1 and field_2 lists in the obj.field_1. What you actually need to do is to extend the obj.field_1 list like this:
obj.field_1.extend(field_1)
obj.field_1.extend(field_2)
obj.save()
Or:
obj.field_1 += field_1
obj.field_1 += field_2
obj.save()
Related
I have two serializers like this :
class MeetingLocationSerializer(serializers.ModelSerializer):
class Meta:
model = MeetingLocation
fields = '__all__'
class MeetingtSerializer(serializers.ModelSerializer):
location = MeetingLocationSerializer()
photos = MeetingPhotoSerializer(many = True)
class Meta:
model = Meeting
fields = ['id','title','description','date_time','time_zone','host','is_private','is_virtual','url','photos','location']
and this is my modelviewsets
class MeetingListViewSet(viewsets.ModelViewSet):
queryset = Meeting.objects.all()
serializer_class = MeetingtSerializer
class MeetingPhotoViewSet(viewsets.ModelViewSet):
queryset = MeetingPhoto.objects.all()
serializer_class = MeetingPhotoSerializer
def retrieve(self, request, *args, **kwargs):
param = kwargs
photos = MeetingPhoto.objects.filter(meeting=param['pk'])
serializer = MeetingPhotoSerializer(photos, many =True)
return Response(serializer.data)
when i want to post data to MeetingListViewSet and save it , the meeting filed in nested location serializer needs value which is the meeting that i am trying to create and it is not created yet! what should i do?
So you can just set the required False and if you want to create a nested list of meeting location you can just simply create a create function and define like i did.
class MeetingtSerializer(serializers.ModelSerializer):
location = MeetingLocationSerializer(many=True,required=False,source="meetinglocation_set")
photos = MeetingPhotoSerializer(many = True)
class Meta:
model = Meeting
fields = ['id','title','description','date_time','time_zone','host','is_private','is_virtual','url','photos','location']
def create(self, validated_data):
if 'meetinglocation_set' in validated_data:
cds = validated_data.pop('meetinglocation_set')
else:
cds = []
instance = super().create(validated_data)
for cd in cds:
instance.meetinglocation_set.create(**cd)
return instance
i need to do a query where i want to get specific fields, then serializate it and keep only the specific fields which I got in the query.
models.py
class Search(models.Model):
NEUTRAL = 'None'
POSITIVE = 'P'
NEGATIVE = 'N'
POLARITY_CHOICES = [
(NEUTRAL, 'Neutral'),
(POSITIVE, 'Positive'),
(NEGATIVE, 'Negative'),
]
user = models.ForeignKey(User,related_name='searched_user_id',on_delete=models.CASCADE)
word = models.CharField( max_length = 100)
social_network = models.ForeignKey(SocialNetwork,related_name='search_social_network_id',on_delete=models.CASCADE)
polarity = models.CharField(
max_length=4,
choices=POLARITY_CHOICES,
default=NEUTRAL,
)
sentiment_analysis_percentage = models.FloatField(default=0)
topic = models.ForeignKey(Topic,related_name='search_topic_id',on_delete=models.CASCADE)
liked = models.IntegerField(default=0)
shared = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
is_deleted = models.BooleanField(default=False)
updated_date=models.DateTimeField(auto_now=True)
searched_date = models.DateTimeField(auto_now_add=True)
serializers.py
class SearchSerializer(serializers.ModelSerializer):
searched_date = serializers.DateTimeField(format="%d-%m-%Y")
class Meta:
model = Search
fields = ('__all__')
class RecentSearchSerializer(serializers.ModelSerializer):
searched_date = serializers.DateTimeField(format="%d-%m-%Y")
class Meta:
model = Search
fields = ('user','social_network','word','searched_date')
class SentimentAnalysisSerializer(serializers.ModelSerializer):
searched_date = serializers.DateTimeField(format="%d-%m-%Y")
class Meta:
model = Search
fields = ('polarity','searched_date','sentiment_analysis_percentage')
SearchSerializer is the main serializer for search, RecentSearchSerializer is the serializer to pass data and filtering in the DRF api view, and finally I created SentimentAnalysisSerializer to keep the specific fields that I need:
api.py
class SearchViewSet(viewsets.ModelViewSet):
queryset = Search.objects.filter(
is_active=True,
is_deleted=False
).order_by('id')
permission_classes = [
permissions.AllowAny
]
serializer_class = SearchSerializer
pagination_class = StandardResultsSetPagination
def __init__(self,*args, **kwargs):
self.response_data = {'error': [], 'data': {}}
self.code = 0
def get_serializer_class(self):
if self.action in ['recent_search','word_details']:
return RecentSearchSerializer
return SearchSerializer
#action(methods=['post'], detail=False)
def word_details(self, request, *args, **kwargs):
try:
self.response_data['data']['word'] = kwargs['data']['word']
queryset = Search.objects.filter(
is_active=True,
is_deleted=False,
social_network=kwargs['data']['social_network'],
user_id=kwargs['data']['user'],
word=kwargs['data']['word']
).order_by('id')
import pdb;pdb.set_trace()
serializer = SentimentAnalysisSerializer(queryset, many=True)
self.response_data['data']['timeline_word_twitter_polarity'] = json.loads(json.dumps(serializer.data))
I did this solution and works good, but is there a way to have the same behaviour without create another serializer? I mean, using SearchSerializer?
I tried with the following examples and i got these erros:
(Pdb) queryset = Search.objects.filter(is_active=True,is_deleted=False,social_network=kwargs['data']['social_network'],user_id=kwargs['data']['user'],word=kwargs['data']['word']).values('polarity','sentiment_analysis_percentage','searched_date').order_by('id')
(Pdb) serializer = RecentSearchSerializer(queryset, many=True)
(Pdb) self.response_data['data']['timeline_word_twitter_polarity'] = json.loads(json.dumps(serializer.data))
*** KeyError: "Got KeyError when attempting to get a value for field `user` on serializer `RecentSearchSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'user'."
(Pdb)
(Pdb) queryset = Search.objects.filter(is_active=True,is_deleted=False,social_network=kwargs['data']['social_network'],user_id=kwargs['data']['user'],word=kwargs['data']['word']).values('polarity','sentiment_analysis_percentage','searched_date').order_by('id')
(Pdb) serializer = SearchSerializer(queryset, many=True)
(Pdb) self.response_data['data']['timeline_word_twitter_polarity'] = json.loads(json.dumps(serializer.data))
*** KeyError: "Got KeyError when attempting to get a value for field `word` on serializer `SearchSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'word'."
First I thought that those errors were related with this issue, but according with this answer i'm not passing data parameter like the issue explain, so i can't check what is the error with the Validation method (is_valid())
I'm using the last version of DRF: djangorestframework==3.10.3
I wish to get this result but with SearchSerializer (I need to do other queries with specific fields, i mean I don't need to pass al the fields of Search Model), but I don't know if it's possible
(Pdb) serializer = SentimentAnalysisSerializer(queryset, many=True)
(Pdb) self.response_data['data']['timeline_word_twitter_polarity'] = json.loads(json.dumps(serializer.data))
(Pdb) self.response_data['data']['timeline_word_twitter_polarity']
[{'searched_date': '09-10-2019', 'polarity': 'P', 'sentiment_analysis_percentage': 0.0}, {'searched_date': '09-10-2019', 'polarity': 'N', 'sentiment_analysis_percentage': 0.0}]
Thanks in advance, any help will be appreciated.
Well, the errors are clear.
You limit the query to return only certain fields using values. So then the serializer cannot serialize it because many are missing.
However, the following approach should work for you.
Note : I am not fan of this - i would rather have 2 separate serializers like you do. But it might help you.
class SearchSerializer(serializers.ModelSerializer):
searched_date = serializers.DateTimeField(format="%d-%m-%Y")
class Meta:
model = Search
fields = ('__all__')
def __init__(self, instance=None, data=empty, **kwargs):
super(SearchSerializer, self).__init__(instance, data, **kwargs)
if instance is not None and instance._fields is not None:
allowed = set(instance._fields)
existing = set(self.fields.keys())
for fn in existing - allowed:
self.fields.pop(fn)
Basically, it keeps only fields from the provided instance.
I have list [1, 2, 3]
and TestModel queryset
[ {'pk':1,'text':'one'}, {'pk':2,'text':'two'}, {'pk':3,'text':'three'}]
and I have model and serializer like following
class TestMode(models.Model):
text = models.CharField(max_length=10)
class TestModelSerializer(serializer.ModelSerializer):
class Meta:
model = TestModel
fields = ('pk', 'text')
I want to make data like
[{'pk':1, 'text':'one', 'number':1}, {..., 'number':2}, {..., 'number':3}]
I make another serializer
class WrapperSerializer(serializer.ModelSerializer):
number = serializer.IntegerField()
class Meta:
model = TestModel
fields = ('pk', 'text')
I try to like following, but i think it's not cool
serialized_data = TestModelSerializer(qs, many=True).data
for index, data in enumerate(serializerd_data):
data['number'] = list[index]
serializer = WrapperSerializer(serialized_data, many=True)
How can i do that??? i don't have another idea... please help me
If you want to add a calculated value (read-only) to your serializer, you have two options:
On the model, define a #property number:
#property
def number(self):
return self.friends.count() # or whatever you need to calculate the number
Then in your serializer, you can just use number as any other field, but need to specify the type of serializer (e.g. IntegerField).
on the serializer use a SerializerMethodField as described here:
number = serializers.SerializerMethodField()
def get_number(self, obj):
return obj.friends.count()
I am accessing the related field's data in my SerializerMethodField and there is a query for every object that is being rendered. My models look like (will keep short for brevity):
class Listing(models.Model):
variant = models.ForeignKey(to='Variant', related_name='variant_listings')
seller = models.ForeignKey(to='user.Seller', related_name='seller_listings')
locality = models.ForeignKey(to='user.Locality', blank=True, null=True)
price = models.IntegerField(blank=True, null=True)
Variant, Seller and Locality are all related models.
My Viewset:
class ListingViewSet(viewsets.ModelViewSet):
"""Viewset class for Listing model"""
queryset = Listing.objects.all()
serializer_class = ListingSerializer
pagination_class = TbPagination
filter_backends = (filters.DjangoFilterBackend,)
filter_class = ListingFilter
def get_queryset(self):
listing_qs = Listing.objects.filter(status='active')
listing_qs = ListingSerializer.setup_eager_loading(listing_qs)
return listing_qs
And my serializer:
class ListingSerializer(serializers.ModelSerializer):
"""Serializer class for Listing model"""
#staticmethod
def setup_eager_loading(queryset):
queryset = queryset.prefetch_related('variant', 'seller', 'locality')
return queryset
#staticmethod
def get_car_link(obj):
variant_name_slug = obj.variant.name.replace(' ', '-').replace('+', '')
return '/buy-' + obj.seller.city.name.lower() + '/' + variant_name_slug
car_link = serializers.SerializerMethodField(read_only=True)
#staticmethod
def get_car_info(obj):
return {
'id': obj.id,
'variant_name': obj.variant.name,
'localities': obj.locality.name,
}
car_info = serializers.SerializerMethodField(read_only=True)
#staticmethod
def get_image_urls(obj):
caption_ids = [1, 2, 3, 5, 7, 8, 18]
attachments_qs = Attachment.objects.filter(listing_id=obj.id, caption_id__in=caption_ids)
image_urls = []
for attachment in attachments_qs:
url = str(obj.id) + '-' + str(attachment.file_number) + '-360.jpg'
image_urls.append(url)
return image_urls
image_urls = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Listing
fields = ('car_link', 'car_info', 'sort_by', 'image_urls')
For each listing returned by the listing viewset, there is a query for every related field accessed in the SerializerMethodField.
I found some related questions like this. But that didn't help. Also, I tried doing prefetch_related on my get_queryset method of the viewset and also implemented eager loading with the help of this article. But nothing helped.
Is there any way to avoid these queries?
Edit
The get_car_info function written above, contains a few more fields (along with the ones already present) which are required separately in a nested JSON by the name of car_info in the final serialized data that is being rendered at the front end.
I have used this article:
http://ses4j.github.io/2015/11/23/optimizing-slow-django-rest-framework-performance/
I created a set up eager loading method in my serializer, like so:
class EagerGetProjectSerializer(serializers.ModelSerializer):
lead_researcher = UserSerializer()
participating_researcher = UserSerializer(many=True)
client = ProjectClientSerializer()
test_items = TestItemSerializer(many=True)
#staticmethod
def setup_eager_loading(queryset):
queryset = queryset.select_related('lead_researcher', 'client')
queryset = queryset.prefetch_related('participating_researcher',
'test_items')
return queryset
class Meta:
model = Project
fields = '__all__'
Notice that when referencing the objects you want to pull in the serializer you have to use the foreign key related name attribute.
and called in my view, before accessing the serializer:
class SingleProject(APIView):
def get(self, request):
ans = Project.objects.filter(id=project_id)
qs = EagerGetProjectSerializer.setup_eager_loading(ans)
serializer = EagerGetProjectSerializer(qs, many=True)
I have a model that represents a house:
class House(models.Model):
name = models.CharField(...)
long = models.FloatField(...)
lat = models.FloatField(...)
and a serializer to return a list of houses in their most basic representation:
class HouseSerializer(serializers.ModelSerializer):
class Meta:
model = House
fields = ('id', 'name')
and the view
class HouseList(generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
this works fine. I can visit /api/house/ and I see a json list of houses:
{
'id': 1,
'name': 'Big House'
},
{
'id': 1
'name': 'Small House',
}...
Now I want to create a second view/resource at /api/maps/markers/ that returns my houses as a list of Google-Map-Friendly markers of the format:
{
'id': 1,
'long': ...,
'lat': ...,
'houseInfo': {
'title': "Big House",
}
} ...
I can foresee two approaches:
perform this as a separate serializer (using the same view as before) and mapping out the alternative field layout.
perform this as a separate view (using the same serializer as before) and simply layout the fields before creating a Response
but in neither approach am I clear on how to go about it nor which approach is preferable?
Answer 1
Looks to me like you need both - different view and serializer.
Simply because the view endpoint is not a sub-url of the first one, so they are not related - different view, even if they use the same model.
And different serializer - since you have a different field layout.
Not really sure how complicated is your case, but any code duplication can probably be solved by mixins anyway.
Answer 2
Depending on the use case:
if you also need to write data using the same struct, you need to define your own field class and handle the parsing correctly
if it's just reading data, you should be fine with this:
class HouseGoogleSerializer(HouseSerializer):
houseInfo = serializers.SerializerMethodField('get_house_info')
class Meta:
model = House
fields = [...]
def get_house_info(self, obj):
return {'title': obj.name}
where HouseSerializer is your base house serializer.
this code come from a running project and offer somethig more that you ask
but can easily adapted for your need if you want remove some features.
The current implemetation allow you:
use only one url one serializer and one view
choose the output using query string param (?serializer=std)
how to use in your code:
Case 1 (one url with ability to choose the serializer via querystring)
class HouseSerializer(HouseSerializer):
houseInfo = serializers.SerializerMethodField('get_house_info')
class Meta:
model = House
def get_house_info(self, obj):
return {'title': obj.name}
class HouseList(DynamicSerializerMixin, generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
serializers_fieldsets = {'std': ('id', 'name'),
'google' : ('id', 'long', 'lat', 'houseInfo')}
Case 2 (different views)
class HouseList(DynamicSerializerMixin, generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
serializers_fieldsets = {'std': ('id', 'name')}
class GoogleHouseList(DynamicSerializerMixin, generics.ListAPIView):
queryset = House.objects.all()
serializer_class = HouseSerializer
serializers_fieldsets = {'std': ('id', 'long', 'lat', 'houseInfo')}
==============
def serializer_factory(model, base=BaseHyperlinkedModelSerializer,
fields=None, exclude=None):
attrs = {'model': model}
if fields is not None:
attrs['fields'] = fields
if exclude is not None:
attrs['exclude'] = exclude
parent = (object,)
if hasattr(base, 'Meta'):
parent = (base.Meta, object)
Meta = type(str('Meta'), parent, attrs)
if model:
class_name = model.__name__ + 'Serializer'
else:
class_name = 'Serializer'
return type(base)(class_name, (base,), {'Meta': Meta, })
class DynamicSerializerMixin(object):
"""
Mixin that allow to limit the fields returned
by the serializer.
Es.
class User(models.Model):
country = models.ForeignKey(country)
username = models.CharField(max_length=100)
email = models.EmailField()
class UserSerializer(BaseHyperlinkedModelSerializer):
country = serializers.Field(source='country.name')
class MyViewSet(DynamicSerializerViewSetMixin, BaseModelViewSet):
model = User
serializer_class = UserSerializer
serializers_fieldsets = {'std': None,
'brief' : ('username', 'email')
}
this allow calls like
/api/v1/user/?serializer=brief
"""
serializers_fieldsets = {'std': None}
serializer_class = ModelSerializer
def get_serializer_class(self):
ser = self.request.QUERY_PARAMS.get('serializer', 'std')
fields = self.serializers_fieldsets.get(ser, 'std')
return serializer_factory(self.model,
self.serializer_class,
fields=fields)