Related
I'm new to Django/Django REST FW (and new to this community). I've spent a lot of time reading the documentation but I'm spinning my wheels at this point. I apologize in advance for being so long-winded here.
My back end DB is Postgres. I've got 3 Models, User, Item and ShoppingList. I need Item to contain a description (the item_name field) and its location. The user will select an item and add it to the ShoppingList for that day. The idea is that the user will then "check off" the item once acquired and it'll be "removed" from the view of the ShoppingList.
Here is where I'm having trouble: I don't want to duplicate the item_name and item_location fields in the shopping_list table, but I need to display those fields in the view of the shopping list (shopping_lists.py).
There is a one-to-many relationship between Item and ShoppingList respectively. The Item object is considered a "master items table" that stores descriptions and locations for each item. The ShoppingList object holds a temporary list of these "master items". I need a queryset that contains all fields from ShoppingList and 2 or more fields from Item.
I think this would be what Django REST FW considers a Reverse Relationship. I've tried a variety of changes to my Serialzer(s) and Models (including adding the Item Serializer to the ShoppingList Serializer) and gotten a variety of errors.
models/item.py:
from django.db import models
from django.contrib.auth import get_user_model
class Item(models.Model):
item_name = models.CharField(max_length=50, db_index=True)
item_location = models.CharField(max_length=10, blank=True, db_index=True)
item_class = models.CharField(max_length=20, blank=True)
# This is a relationship with User model
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE
)
def __str__(self):
return f"item_name: {self.item_name}, item_location: {self.item_location}, shopper_id: {self.shopper_id}"
models/shopping_list.py:
from django.db import models
from django.contrib.auth import get_user_model
from .item import Item
class ShoppingList(models.Model):
item_num = models.ForeignKey(
'Item',
on_delete=models.DO_NOTHING # we don't want to delete the item from the "master" item list, just from this shopping list
)
# This is a relationship with user model.
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE # ...but we do want to delete the item if the user goes away as items are user-specific
)
item_qty = models.PositiveIntegerField()
item_complete = models.BooleanField(default=False)
added_on = models.DateField(auto_now=True)
# setting list_num to blank=True for this version
list_num = models.PositiveIntegerField(blank=True)
def __str__(self):
return f"item_num: {self.item_num}, shopper_id: {self.shopper_id}, item_qty: {self.item_qty}, item_complete: {self.item_complete}"
serializers/item.py:
from rest_framework import serializers
from ..models.item import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'item_name', 'item_location', 'item_class', 'shopper_id')
serializers/shopping_list.py:
from rest_framework import serializers
from ..models.shopping_list import ShoppingList
class ShoppingListSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingList
fields = ('id', 'item_num', 'shopper_id', 'item_qty', 'item_complete', 'added_on', 'list_num')
Getting ERROR AttributeError: Manager isn't accessible via ShoppingList instances when I execute the GET method in class ShoppingListItemView in views/shopping_lists.py below:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from rest_framework.exceptions import PermissionDenied
from ..models.shopping_list import ShoppingList
from ..serializers.shopping_list import ShoppingListSerializer
from ..models.item import Item
from ..serializers.item import ItemSerializer
class ShoppingListsView(APIView):
def get(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
data = ShoppingListSerializer(shopping_list_items, many=True).data
return Response(data)
def post(self, request):
request.data['shopper_id'] = request.user.id
list_item = ShoppingListSerializer(data=request.data, partial=True)
if list_item.is_valid():
list_item.save()
return Response(list_item.data, status=status.HTTP_201_CREATED)
else:
return Response(list_item.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
response_data = shopping_list_items.delete()
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class ShoppingListsAllView(APIView):
def get(self, request):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
data = ShoppingListSerializer(shopping_items, many=True).data
return Response(data)
class ShoppingListItemView(APIView):
def get(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_entry = list_item.objects.select_related('Item').get(id=pk)
print(list_entry)
data = ShoppingListSerializer(list_item).data
return Response(data)
def delete(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def patch(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
request.data['shopper_id'] = request.user.id
updated_list_item = ShoppingListSerializer(list_item, data=request.data, partial=True)
if updated_list_item.is_valid():
updated_list_item.save()
return Response(updated_list_item.data)
else:
return Response(updated_item.errors, status=status.HTTP_400_BAD_REQUEST)
if you want to display only a few properties of an item in your ShoppingList you can use the SerializerMethodField method in your serializer
this would work as -
class ShoppingListSerializer(serializers.ModelSerializer):
itemProperty1 = serializers.SerializerMethodField()
itemProperty2 = serializers.SerializerMethodField()
class Meta:
model = ShoppingList
fields = ('id', "itemProperty1", "itemProperty2", 'more_fields')
def get_itemProperty1(self, instance):
return instance.item.anyPropertyOfItem if instance.item else ''
def get_itemProperty2(self, instance):
return instance.item.anyPropertyOfItem if instance.item.else ''
anyPropertyOfItem can be anything from item models.
Setting your serializer this way, your ShoppingList view will automatically show 2 new fields.
or you can also define read only fields with the help of #property in models to get the required field.
If you want to display all the properties of the item in the ShoppingList view, you can write here, will edit my answer. There you need to use related_name and get the item serializer in Shoppinglist serializer as the extra field.
For reverse relationship you should use related_name when defining the model or using the suffix _set.
The related_name attribute specifies the name of the reverse relation
from the User model back to your model. If you don't specify a
related_name, Django automatically creates one using the name of your
model with the suffix _set
Copied from What is related_name used for? by Wogan
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'm new to Django and I'm trying to save Unit and current_user foreign keys in the database based pk obtained but every time I try doing so but run into two types of error serializers.is_valid() raises an exception error or the serializer returns "Invalid data. Expected a dictionary, but got Unit."
I have tried a very very ugly way of bypassing serializers by getting rid but i get ee8452a4-2a82-4804-a010-cf2f5a41e006 must be an instance of SavedUnit.unit.I have also tried saving the foreign key directly using SavedUnit.objects.create() without luck
model.py
class SavedUnit(models.Model):
"""
Saving units for later models
relationship with units and users
"""
id = models.UUIDField(primary_key=True, default=hex_uuid, editable=False)
unit = models.ForeignKey(Unit, on_delete=models.CASCADE)
user = models.ForeignKey('accounts.User', on_delete=models.CASCADE, related_name='user')
published_at = models.DateTimeField(auto_now_add=True)
serializers.py
class SavedSerializer(serializers.ModelSerializer):
unit = UnitSerializer()
class Meta:
model = SavedUnit
fields = [
'id',
'unit'
]
views.py
class SavedUnitView(APIView):
"""
Query all the unites saved
"""
#staticmethod
def get_unit(request, pk):
try:
return Unit.objects.get(pk=pk)
except Unit.DoesNotExist:
return Response(status=status.HTTP_400_BAD_REQUEST)
#staticmethod
def post(request, pk):
if request.user.is_authenticated:
unit = get_object_or_404(Unit, id=pk)
serializers = SavedSerializer(data=unit)
if serializers.is_valid(raise_exception=True):
created = SavedUnit.objects.get_or_create(
user=request.user,
unit=unit)
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_401_UNAUTHORIZED)
def get(self, request):
units = SavedUnit.objects.filter(user=self.request.user.id)
try:
serializers = SavedSerializer(units, many=True)
return Response(serializers.data, status=status.HTTP_200_OK)
except Unit.DoesNotExist:
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Your code is using serializer only for validation, but it is possible use it to insert or update new objects on database calling serializer.save().
To save foreign keys using django-rest-framework you must put a related field on serializer to deal with it. Use PrimaryKeyRelatedField.
serializers.py
class SavedSerializer(serializers.ModelSerializer):
unit_id = serializers.PrimaryKeyRelatedField(
source='unit',
queryset=Unit.objects.all()
)
unit = UnitSerializer(read_only=True)
class Meta:
model = SavedUnit
fields = [
'id',
'unit_id',
'unit'
]
views.py
class SavedUnitView(APIView):
permission_classes = (permissions.IsAuthenticated,) # For not handling authorization mannually
def post(request):
serializer = SavedSerializer(data=request.data)
serializer.is_valid(raise_exception=True) # Trigger Bad Request if errors exist
serializer.save(user=request.user) # Passing the current user
return Response(serializer.data, status=status.HTTP_201_CREATED)
Now, the id of the unit will be passed in the request body like this
POST /saved-units/
Accept: application/json
Content-Type: application/json
Authorization: Token your-api-token
{
"unit_id": 5 # Id of an existing Unit
}
Actually the problem is here:
def post(request, pk):
if request.user.is_authenticated:
unit = get_object_or_404(Unit, id=pk)
serializers = SavedSerializer(data=unit) <-- Here
You are passing a unit instance, but it should be request.data, like this:
serializers = SavedSerializer(data=request.data)
( I am unsure about what you are doing, if you already have PK, then why are you even using serializer? because you don't need it as you already have the unit, and you can access current user from request.user, which you are already doing. And I don't think you should use #staticmethod, you can declare the post method like this: def post(self, request, pk) and remove static method decorator)
Here is my view:
class SampleViewSet(viewsets.ModelViewSet):
serializer_class = SampleSerializer
queryset = Sample.objects.all()
def get_queryset(self):
queryset = self.queryset
test_code = self.request.query_params.get('test_code', None)
if test_code is not None:
queryset = queryset.filter(test__test_code=test_code)
return queryset
Here is my Model:
class Sampe(models.Model):
test = models.OneToOneField(Test, null=True, blank=True, on_delete=models.CASCADE)
# few more fields- not so important
Here is my serializer:
class SampleSerializer(serializers.ModelSerializer):
test_code = serializers.CharField(source='test.test_code')
class Meta:
model = Sample
fields = '__all__'
This works when I hit /api/sample?test_code="existing_param"
So when I do /api/sample?test_code="Not_Existing_param", I was expecting it should throw me 404 instead, it throws 500 Test matching query does not exist. Really appreciate any help.
Thank you
Well the problem is that the function will raise an error, and the web server can not really interpret what caused this error, so it is more accurate to raise a 500 in general, than a 404.
But what you can do is use a get_list_or_404 which acts like a get_object_or_404, but then with a .filter(..) instead of a .get(..):
from django.shortcuts import get_list_or_404
class SampleViewSet(viewsets.ModelViewSet):
serializer_class = SampleSerializer
queryset = Sample.objects.all()
def get_queryset(self):
queryset = self.queryset
test_code = self.request.query_params.get('test_code', None)
if test_code is not None:
queryset = get_list_or_404(queryset, test__test_code=test_code)
return queryset
This will thus filter the queryset, and raise a 404 exception in case the resulting queryset turns out to be empty. In case you chain filters together, it is sufficient to do this on the last element, since then you inspect the actual queryset you will return, and thus avoid checking multiple quersets that are not used later in the process.
I'm trying to figure it out why when i submit my form, my tags are not saved in my db. Pretty new with the django-rest-framework and Django-taggit too, i think i'm doing something wrong :)
First, before making my API with the rest-framework, i was using a generic view (CreateView and UpdateView) to register/validate my event. It was working fine but i decided to go further and try to build an API since i'm using Angularjs now.
Now my model event is created but without my tag and i have some errors. I put some code and i'll describe my errors after.
events/models.py
class Event(models.Model):
[...]
title = models.CharField(max_length=245, blank=False)
description = models.TextField(max_length=750, null=True, blank=True)
start = models.DateTimeField()
end = models.DateTimeField()
created_at = models.DateTimeField(editable=False)
updated_at = models.DateTimeField(editable=False)
slug = AutoSlugField(populate_from='title', unique=True, editable=False)
expert = models.BooleanField(choices=MODE_EXPERT, default=0)
home = models.BooleanField(choices=HOME, default=0)
nb_participant = models.PositiveSmallIntegerField(default=1)
price = models.PositiveSmallIntegerField(default=0)
cancelled = models.BooleanField(default=0)
user = models.ForeignKey(User, editable=False, related_name='author')
address = models.ForeignKey('Address', editable=False, related_name='events')
participants = models.ManyToManyField(User, related_name='participants', blank=True, editable=False,
through='Participants')
theme_category = models.ForeignKey('EventThemeCategory', unique=True, editable=False)
tags = TaggableManager(blank=True)
class Meta:
db_table = 'event'
def save(self, *args, **kwargs):
if not self.pk:
self.created_at = timezone.now()
self.updated_at = timezone.now()
super(Event, self).save(*args, **kwargs)
[...]
i'm using the serializers.HyperlinkedModelSerializer.
api/serializer.py
from taggit.models import Tag
class TagListSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = ('url', 'id', 'name')
class EventSerializer(serializers.HyperlinkedModelSerializer):
address = AddressSerializer()
user = UserSerializer(required=False)
tags = TagListSerializer(blank=True)
class Meta:
model = Event
fields = ('url', 'id', 'title', 'description', 'start', 'end', 'created_at', 'updated_at', 'slug', 'expert','home', 'nb_participant', 'price', 'address', 'user', 'theme_category', 'tags')
depth = 1
api/views/tags_views.py
from rest_framework import generics
from api.serializers import TagListSerializer
from taggit.models import Tag
class TagsListAPIView(generics.ListCreateAPIView):
queryset = Tag.objects.all()
model = Tag
serializer_class = TagListSerializer
class TagsDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Tag.objects.all()
model = Tag
serializer_class = TagListSerializer
api/views/events_views.py
class EventListAPIView(generics.ListCreateAPIView):
queryset = Event.objects.all()
model = Event
serializer_class = EventSerializer
paginate_by = 100
def pre_save(self, obj):
"""
Set the object's owner, based on the incoming request.
"""
obj.user = self.request.user
return super(EventListAPIView, self).pre_save(obj)
api/urls.py
url(r'^events/(?P<slug>[0-9a-zA-Z_-]+)/$', EventDetailAPIView.as_view(), name='event-detail'),
So first when i call /api/events/name-of-my-event the API send me the good resource with my tags on it. The GET method is working fine.
I was thinking that rest-framework follow the query set. So if i can get the resource with with all my tags why when i use POST my tags are not register ?
Actually i have two problems with the POST method:
first one if i send a tag which i have already created, he send me an error saying that the tag must be unique. I understand that, i don't want to create a new one, i just want it to be linked with my object. I don't have this problem when i use the generic view (it's done by magic :) and all is working fine)
Secondly, when i try to create a new tag, my new event is saved but without my tags.
You can see the response received by angularjs for my tag... He send me the name of the tag but without id, url (hyperlinked). When i checked my db the tag has not been created.
I think i have to make a custom get_queryset(self) in my tags_views but i'm not sure.
I'll will continue to investigate. If someone have already to that and have some advise, i'll be very API. Thanks.
meet the same question. But I just want to save the tag list directly by TaggableManager (without TagListSerializer and TagsListAPIView). My solution is:
class MyModel(models.Model):
...
tags = TaggableManager(blank=True)
def get_tags_display(self):
return self.tags.values_list('name', flat=True)
class MyModelSerializer(serializers.HyperlinkedModelSerializer):
...
tags = serializers.Field(source='get_tags_display') # more about: http://www.django-rest-framework.org/api-guide/fields#generic-fields
...
class MyModelViewSet(viewsets.ModelViewSet):
...
def post_save(self, *args, **kwargs):
if 'tags' in self.request.DATA:
self.object.tags.set(*self.request.DATA['tags']) # type(self.object.tags) == <taggit.managers._TaggableManager>
return super(MyModelViewSet, self).post_save(*args, **kwargs)
The post data of tags data will be ['tagA', 'tagB',...], the TaggableManager will handle it. Thx.
For DRF>3.1, you just need to override create and update in your ModelSerializer class:
class StringListField(serializers.ListField): # get from http://www.django-rest-framework.org/api-guide/fields/#listfield
child = serializers.CharField()
def to_representation(self, data):
return ' '.join(data.values_list('name', flat=True)) # you change the representation style here.
class MyModelSerializer(serializers.ModelSerializer):
tags = StringListField()
class Meta:
model = models.MyModel
def create(self, validated_data):
tags = validated_data.pop('tags')
instance = super(MyModelSerializer, self).create(validated_data)
instance.tags.set(*tags)
return instance
def update(self, instance, validated_data):
# looks same as create method
I used to follow the following ways to serialize taggit objects but currently django-taggit provide a built in serializer https://github.com/jazzband/django-taggit/blob/master/taggit/serializers.py and it was vendor from the package I mentioned previously.
"""
Django-taggit serializer support
Originally vendored from https://github.com/glemmaPaul/django-taggit-serializer
"""
import json
# Third party
from django.utils.translation import gettext_lazy
from rest_framework import serializers
class TagList(list):
def __init__(self, *args, **kwargs):
pretty_print = kwargs.pop("pretty_print", True)
super().__init__(*args, **kwargs)
self.pretty_print = pretty_print
def __add__(self, rhs):
return TagList(super().__add__(rhs))
def __getitem__(self, item):
result = super().__getitem__(item)
try:
return TagList(result)
except TypeError:
return result
def __str__(self):
if self.pretty_print:
return json.dumps(self, sort_keys=True, indent=4, separators=(",", ": "))
else:
return json.dumps(self)
class TagListSerializerField(serializers.Field):
child = serializers.CharField()
default_error_messages = {
"not_a_list": gettext_lazy(
'Expected a list of items but got type "{input_type}".'
),
"invalid_json": gettext_lazy(
"Invalid json list. A tag list submitted in string"
" form must be valid json."
),
"not_a_str": gettext_lazy("All list items must be of string type."),
}
order_by = None
def __init__(self, **kwargs):
pretty_print = kwargs.pop("pretty_print", True)
style = kwargs.pop("style", {})
kwargs["style"] = {"base_template": "textarea.html"}
kwargs["style"].update(style)
super().__init__(**kwargs)
self.pretty_print = pretty_print
def to_internal_value(self, value):
if isinstance(value, str):
if not value:
value = "[]"
try:
value = json.loads(value)
except ValueError:
self.fail("invalid_json")
if not isinstance(value, list):
self.fail("not_a_list", input_type=type(value).__name__)
for s in value:
if not isinstance(s, str):
self.fail("not_a_str")
self.child.run_validation(s)
return value
def to_representation(self, value):
if not isinstance(value, TagList):
if not isinstance(value, list):
if self.order_by:
tags = value.all().order_by(*self.order_by)
else:
tags = value.all()
value = [tag.name for tag in tags]
value = TagList(value, pretty_print=self.pretty_print)
return value
class TaggitSerializer(serializers.Serializer):
def create(self, validated_data):
to_be_tagged, validated_data = self._pop_tags(validated_data)
tag_object = super().create(validated_data)
return self._save_tags(tag_object, to_be_tagged)
def update(self, instance, validated_data):
to_be_tagged, validated_data = self._pop_tags(validated_data)
tag_object = super().update(instance, validated_data)
return self._save_tags(tag_object, to_be_tagged)
def _save_tags(self, tag_object, tags):
for key in tags.keys():
tag_values = tags.get(key)
getattr(tag_object, key).set(tag_values)
return tag_object
def _pop_tags(self, validated_data):
to_be_tagged = {}
for key in self.fields.keys():
field = self.fields[key]
if isinstance(field, TagListSerializerField):
if key in validated_data:
to_be_tagged[key] = validated_data.pop(key)
return (to_be_tagged, validated_data)
http://blog.pedesen.de/2013/07/06/Using-django-rest-framework-with-tagged-items-django-taggit/
With the release of the Django Rest Framework 3.0, the code for the TagListSerializer has changed slightly. The serializers.WritableField was depreciated in favour for serializers.Field for the creation of custom serializer fields such as this. Below is the corrected code for Django Rest Framework 3.0.
class TagListSerializer(serializers.Field):
def to_internal_value(self, data):
if type(data) is not list:
raise ParseError("expected a list of data")
return data
def to_representation(self, obj):
if type(obj) is not list:
return [tag.name for tag in obj.all()]
return obj
I now use the bulit in taggit serializer which was taken from https://github.com/glemmaPaul/django-taggit-serializer library.
I had a bunch of errors but i found a way to resolve my problem. Maybe not the best as i'm pretty new with all of this but for now it works.
I'll try to describe all my errors maybe it'll help someone.
First my angularjs send a json which match exatly the queryset
So for example with my model events below, angularjs send to the API:
Now let's begin with all my errors:
"A tag with this name already exist"
When i re-use a tag i have this error. Don't know why because with a classic validation without the API, all is working fine.
With a new tag nothing is saved too.
When i try to use a new tag on my event event model nothing is saved on the database. Angularjs received a response with the tag name but with an id of null (see the pitcure on my original question)
"AttributeError: 'RelationsList' object has no attribute 'add'"
Now i'm trying to think that to register my tags i need to have an instance of event already created. Thanks to that i will be able to add my tag on it like it's describe in the doc.
apple.tags.add("red", "green", "fruit")
So i decided to add a post_save in my events_views.py:
class EventListAPIView(generics.ListCreateAPIView):
queryset = Event.objects.all()
model = Event
serializer_class = EventSerializer
paginate_by = 100
def pre_save(self, obj):
"""
Set the object's owner, based on the incoming request.
"""
obj.user = self.request.user
return super(EventListAPIView, self).pre_save(obj)
def post_save(self, obj, created=False):
print 'tags', self.request.DATA
obj.tags.add(self.request.DATA['tags'])
return super(EventListAPIView, self).post_save(obj)
But now as is said i have this error AttributeError: 'RelationsList' object has no attribute 'add'.
Actually, it's obvious since obj.tags is a list of object and not the TaggableManager anymore.
So i decided to start over and send my tags not in 'tags' but in another custom property 'tagged' to avoid conflit with the TaggableManager.
"TypeError: unhashable type: 'list'"
New error :) I found the solution with this django-taggit-unhashable-type-list
def post_save(self, obj, created=False):
map(obj.tags.add, self.request.DATA['tagged'])
return super(EventListAPIView, self).post_save(obj)
"TypeError: unhashable type: 'dict'"
Now, i figured it out that the tags i sent are not well formatted. I changed it (on the angularjs side) to send an array like this ['jazz','rock'] instead of [object, object]. Stupid mistake from a beginner.
Now the magic happen, response received by angularjs is good:
Sorry for my english. I know it may not be the best solution and i will try to update it when i'll find another solution.