AttributeError: 'collections.OrderedDict' object has no attribute 'pk' with Django Rest Framework while adding item to a many to many relationship - django-rest-framework

First question here, so please bear with me. I'm learning Django and the Django Rest Framework, and I'm getting an exception when adding a related object in a many to many relationship. What I do not understand is that the object gets created correctly, and I am getting the error when getting the response.
This is an excerpt of my urls.py:
path('mybox/', views_api.MyBoxList.as_view(), name='mybox-list'),
path('mybox/<int:pk>/', views_api.MyBoxDetail.as_view(), name='mybox-detail'),
path('mybox/<int:pk>/documents/', views_api.DocumentList.as_view(), name='mybox-document-list'),
path('documents/', views_api.DocumentList.as_view(), name='document-list'),
path('documents/<int:pk>/', views_api.DocumentDetail.as_view(), name='document-detail'),
My models.py:
class MyBox(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='boxes')
name = models.CharField(_('name'), max_length=100, default='my_box')
description = models.CharField(_('description'), max_length=250, null=True, blank=True)
time_created = models.DateTimeField(_('time created'), auto_now_add=True)
def __str__(self):
return self.name
def user_folder_doc_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/docs/<filename>
return 'user_{0}/docs/{1}'.format(instance.owner.id, filename)
class Document(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='documents')
file = models.FileField(_('file'), upload_to=user_folder_doc_path, null=True, blank=True)
description = models.TextField(_('description'), null=True, blank=True)
time_created = models.DateTimeField(_('time created'), auto_now_add=True)
mybox = models.ManyToManyField(MyBox, related_name='documents', blank=True)
My serializers:
class MyBoxSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
class Meta:
model = MyBox
fields = ['url', 'id', 'user', 'name', 'description', 'documents']
class DocumentSerializer(serializers.HyperlinkedModelSerializer):
mybox = MyBoxSerializer(many=True, read_only=True)
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Document
fields = ['url', 'id', 'owner', 'description', 'file', 'mybox']
extra_kwargs = {'mybox': {'required': False}}
And my views.py:
class DocumentList(generics.ListCreateAPIView):
#queryset = Document.objects.all()
serializer_class = DocumentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
"""
This view should return a list of all the documents
for the currently authenticated user.
"""
owner = self.request.user
mybox_id = self.kwargs.get('pk', None)
if mybox_id:
return Document.objects.filter(mybox=mybox_id, owner=owner)
else:
return Document.objects.filter(owner=owner)
def perform_create(self, serializer):
mybox_id = self.kwargs.get('pk', None)
if mybox_id:
print("Hay ID de MyBox")
mybox = MyBox.objects.get(pk=mybox_id)
serializer = DocumentSerializer(data=self.request.data)
if serializer.is_valid():
document = serializer.save(owner=self.request.user)
mybox.documents.add(document)
else:
print("No hay ID de MyBox")
serializer.save(owner=self.request.user)
I've tried several suggestions I found online, with no luck. I believe the issue is my serializers, and I tried adding this to MyBoxSerializer: documents = serializers.HyperlinkedIdentityField(view_name='mybox-document-list', lookup_field='mybox_id')
But I think I do not understand serializers enough when dealing with M2M relationships.
Just to reiterate, after going to mybox/1/documents/ and creating a new document here, the document gets properly created and the relationship with MyBox id:1 (in this example) is also there, but I never get a response, only the exception
Thanks for any help/suggestions!

So I finally managed to fix the issue, thanks to the solution provided by somebody else. In case anyone gets to this page with a similar issue.
The problem was in the perform_create function, which had a serializer too many and I had to save the document too. This is the correct version that works for me:
def perform_create(self, serializer):
mybox_id = self.kwargs.get('pk', None)
if mybox_id:
mybox = MyBox.objects.get(pk=mybox_id)
if serializer.is_valid():
document = serializer.save(owner=self.request.user)
document.mybox.add(mybox)
document.save()
else:
serializer.save(owner=self.request.user)
I hope this helps

Related

Update method of serializer only creates and doesn't update (Django rest framework)

I'm having a hard time with nested serializers, especially updating them. So far I can update the lesson description and all the main fields before the nested serializer (like course description, title etc). Ideally I'd like to search using lesson_id, and not lesson.title like it is now, and update description and title at the same time. Is there a workaround for that?
My models.py
class Course (models.Model):
title = models.CharField (max_length=150)
description = models.CharField(max_length=250, default="No Description")
student = models.ManyToManyField(Student, related_name='courses', blank=True)
teacher = models.ManyToManyField(Teacher, related_name='teacher', blank=True)
def __str__(self):
return self.title
class Lesson (models.Model):
title = models.CharField(max_length=100)
description = models.TextField(default="No Description")
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
def __str__(self):
return self.title
Serializers.py where the problem is
class CourseSerializer (serializers.ModelSerializer):
lessons = LessonSerializer(many=True, required=False)
#teacher = TeacherSerializer(many=True)
class Meta:
model = Course
fields = ('id', 'title', 'description', 'lessons') #to separate serializer with students for teachers later
def update(self, instance, validated_data):
lessons = validated_data.pop('lessons', [])
instance = super().update(instance, validated_data)
for lesson in lessons:
lesson, updated = Lesson.objects.update_or_create( defaults={'description': lesson["description"]}, title= lesson["title"])
#pk = instance.lessons_id doesn't work, I cannot get this id
instance.save()
return instance
Views
class CourseDetailDeleteView (generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminUserOrAuthenticatedOrReadOnly,)
queryset = Course.objects.all()
serializer_class = CourseSerializer
def update(self, request, *args, **kwargs):
serializer = CourseSerializer(instance=self.get_object(), data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
If your problem is that validated_data["lessons"][0]["id"] doesn't exists:
By default the id field of a ModelSerializer is read_only, so id is not included in validated_data in .create() or .update(). Thus you'll have to override that:
class LessonSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
....
## Optional: in case you don't want `id` getting explicitly set
def create(self, validated_data):
validated_data.pop("id", None)
return super().create(self, validated_data)
## Optional: in case you don't want `id` of the instance getting updated
def update(self, instance, validated_data):
validated_data.pop("id", None)
return super().update(self, instance, validated_data)
Another note:
There's no way to know if serializer.save() is even called in CourseDetailDeleteView.update. I would suggest to set serializer.is_valid(raise_exception=True) instead so it can return error messages and show you why it didn't save.

How to restrict fields when creating post request in DRF?

I am making a POST api using DRF. In that api, I need only few fields(name, size, customer_name, customer_address), but don't require this fields(status, ordered_time) because these fields I want to save these fields in run time as status='open' and ordered_time=DateTimeField.now()
views.py
class PizzaOrderCustomerView(APIView):
def post(self, request):
orders = request.data.get('orders')
# Create an article from the above data
serializer = ArticleSerializer(data=orders)
if serializer.is_valid(raise_exception=True):
article_saved = serializer.save()
return Response({"success": "Article '{}' created successfully".format(article_saved.name)})
models.py
class PizzaOrder(models.Model):
name = models.CharField(max_length=120)
size = models.CharField(max_length=10, choices=SIZE_CHOICE, default='MEDIUM')
customer_name = models.CharField(max_length=120)
customer_address = models.TextField()
ordered_time = models.DateTimeField(default=timezone.now, editable=False)
status = models.CharField(max_length=20, default='open', editable=False)
serializers.py
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
# fields = '__all__'
read_only_fields = ('status',)
But when I try to create an order, it needed status and ordered_time also. But it should save at the time of creating order automatically.
Suggest a good way to do it.
from rest_framework import viewsets, mixins
class PizzaViewsets(viewsets.ViewSet, mixins.CreateModelMixin):
model = PizzaOrder
serializer_class = OrderSerializer
queryset = model.objects.all(
serializer, it is always good practise to mention all fields instead of
all
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = ('status','ordered_time','name', 'size', 'customer_name', 'customer_address',)
read_only_fields = ('status','ordered_time',)

Change model field if requested particular url

Im making API using django rest framework . I only want to change one field in model which is the read field if i go to a particular url
my model:
class Notification(PolymorphicModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="creatednotifications")
created_on = models.DateTimeField(default=timezone.now)
created_for = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="receivednotifications")
read = models.DateTimeField(default=None, null=True, blank=True)
message = models.CharField(default=None, blank=True, null=True, max_length=800)
The APis i made lists the notifications for a logged in user.
What i want to implement is that :
notification/<:id>/markread
notification/<:id>/markunread
If i go to this particular url i want to modify the read field ..For example make it None if to mark unread. Also i need to check if the logged in user has received the notification with that id.I know the basics and how to create the urls
class NotificationMarkRead(generics.UpdateAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
queryset = Notification.objects.filter(created_for=self.request.user)
return queryset
class NotificationMarkUnread(generics.UpdateAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
queryset = Notification.objects.filter(created_for=self.request.user)
return queryset
def update
My initial try is to override the put method in update_API view
Write a simple function:
#api_view(['PUT'])
def notification_toggle_read_status(request, pk, read_status):
notification = Notification.objects.get(pk=pk)
if read_status == 'markread':
notification.read = timezone.now()
else:
notification.read = None
notification.save(update_fields=['read'])
serializer = NotificationSerializer(instance=notification)
return Response(serializer.data)
use this url path:
notifications/<int:pk>/<string:read_status>/
As you have already coding with DRF why not try with viewset link . And from front-end just pass update fields with put request.

Handling viewset that has a serializer with multiple foreignkeys

I have this serializer:
class ReviewSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Review
fields = ('pk', 'title', 'user', 'movie', 'timestamp', 'review_text',)
I want to mention that user and movie are both ForeignKeys defined in models.py for Review model. Here is the model:
class Review(models.Model):
title = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reviews')
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='reviews')
review_text = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{movie} review by {user}'.format(user=self.user, movie=self.movie)
I want to create a viewset, but if I do something like this:
class ReviewsViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = ReviewSerializer
and register it like this:
router.register(r'reviews', views.ReviewsViewSet, 'reviews')
won't work, it requires me to provide a movie field as logged here:
AttributeError at /api/reviews/
Got AttributeError when attempting to get a value for field `movie` on serializer `ReviewSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Movie` instance.
Original exception text was: 'Movie' object has no attribute 'movie'.
Thanks in advance.
Edited.

DRF - html post options only for user logged in

I have the following serializer:
class WidgetSerializer(serializers.ModelSerializer):
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model=Widget
fields = ('id', 'title', 'description', 'username', 'code', 'owner', 'list')
The problem is that the 'list' field, which is a drop down, gives all lists whereas I only want it to display lists that are owned by the user currently logged in.
Here's the respective models:
class WidgetList(MPTTModel):
name = models.CharField(max_length=100)
description = models.CharField(max_length=1024)
owner = models.ForeignKey('MyUser')
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
def __str__(self):
return self.name
class MPTTMEta:
order_insertion_by = ['name']
class Widget(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=1024)
username = models.CharField(max_length=50)
code = models.CharField(max_length=1024)
owner = models.ForeignKey('MyUser', related_name='MyUser_owner')
list = models.ForeignKey('WidgetList')
I am a beginner in django. I hope that I could help.
Just try this
WidgetList.objects.filter(owner=request.user)
I have to limit it through a SlugRelatedField as per the documentation here -
http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield
I then used it like so -
list = serializers.SlugRelatedField(
queryset=WidgetList.objects.filter(owner=3),
many=True,
slug_field='name'
)
All I need to figure out now is to pass the serializers.CurrentUserDefault() in the filter for the queryset, or pass request.user.

Resources