Attach author to POST request to create an object - django-rest-framework

It's probably very basic, but I am starting to learn Django REST Framework. So far I've succeeded with read-only operations. And now I got stuck on this problem.
I have a model:
class PersonComment(AbstractComment):
person = models.ForeignKey(Person, on_delete=models.PROTECT)
author = models.ForeignKey('auth.User')
body = models.TextField(default='', blank=False)
(Here author is the author of the comment and person is a person this comment relates to; it's a genealogical site.) And a related serialiser:
class PersonCommentSerialiser(serializers.HyperlinkedModelSerializer):
class Meta:
model = PersonComment
fields = ('url', 'body', 'person', 'author')
In my HTML page, I expect a user to submit a comment by providing "body", the rest should be auto-filled. What is the best practice for filling in the "author"? In my pre-DRF Django exercises, I was doing something like this in the view:
comment = PersonComment.objects.create(
author=request.user,
body=new_comment_body,
person=person
)
But if I understand everything I've read so far, this is not the way in DRF. Any help is much appreciated.

I would do this using the default attribute in serializer fields.
class PersonCommentSerialiser(serializers.HyperlinkedModelSerializer):
author = serializers.PrimaryKeyRelatedField(read_only=True, default=CurrentUserDefault())
class Meta:
model = PersonComment
fields = ('url', 'body', 'person', 'author')
CurrentUserDefault is a class predefined in Django REST framework for exactly this purpose. This way you don't have to overwrite create in your own serializer.
I was not sure what is the difference between person and author. But you should be able to do something similar I suppose.

Related

Django Rest Framework - Updating a ForeignKey Field entry in the view

In my Django Rest Framework project, I have a ForeignKey relationship between two models:
class Book(models.Model):
...
category = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True, null=True)
...
class Category(models.Model):
name = models.CharField(max_length=100, blank=False)
As you can see, a Book can belong to a Category but it does not have to. That means the 'category' field could be null.
So, in my views.py, any Book instance can be updated/patched if the user wants to assign a certain Book to a particular Category. That views.py update method looks like this:
class UpdateBooksCategory(generics.GenericAPIView):
'''
Class-based view to update the 'category' field of a Book instance.
'''
serializer_class = BookSerializer
permission_classes = [IsAuthenticated]
def patch(self, request,*args, **kwargs):
# get the Book instance first
book = Book.objects.get(pk=request.data.get('bookId'))
# if it is not assigned to a Category, then assign it
if book and not book.category:
book.category = Category.objects.get(name=request.data.get('categoryName'))
book.save()
serializer = self.get_serializer(book, context={"request": request})
return Response(serializer.data)
# otherwise, return a generic response
return Response({'response': "You have already put the selected Book in a Category."})
If you can see, first I get the Book instance that the user wants to update by using the Book's ID. If its Category field is not already filled, I get a Category instance using the given category name and assign it.
For the sake of completeness, here are my serializer classes:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name']
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', /*some other fields*/,..., 'category']
So, finally my question: I wanted to know if this is the preferred way of updating a ForeingKey field like this? I mean looking at the UpdateBooksCategory class-based view, is this the right way of doing it? The code works ( I tested it with PostMan) but since I am new to DRF I wanted to know if such an updating process is correct.
You can change your BookSerializer:
class BookSerializer(serializers.ModelSerializer):
category_id = serializers.IntegerField(write_only=True)
category = CategorySerializer(read_only=True)
class Meta:
model = Book
fields = [
'id',
# some other fields,
'category',
'category_id',
]
category will be a nested data that is read only, then setting the category will be by including the category_id in your requests.

Writable many-to-many field in Django Rest Framework tries to save new child objects?

Edit: This is using django rest framework 2.3
I have a model structure that has 3 relationship "levels", one of which is many to many.
class Shipment(models.Model):
stuff...
class ShipmentItem(models.Model):
shipment = models.ForeignKey(Shipment)
assets = models.ManyToMany(ShipmentAsset)
class ShipmentAsset(models.Model)
serial_number = models.CharField(unique=True)
Using Django rest framework I want to be able to post to the "Shipment" endpoint with a payload that contains the ShipmentItems for the Shipment, and the ShipmentAssets for the ShipmentItems ideally in one request.
The serializers are as follows..
class ShipmentAssetSerializer(serializers.ModelSerializer):
class Meta:
model = ShipmentAsset
field = ('id', 'serial_number', )
class ShipmentItemSerializer(serializers.ModelSerializer):
assets = ShipmentAssetSerializer(
many=True, required=False, allow_add_remove=True,
)
class Meta:
model = ShipmentItem
fields = ('id', 'assets', )
class ShipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Shipment
fields = (
'id',
)
The shipmentItem/Shipment relationship seems to work when I post to it with the assets part disabled, but when I try to post assets in the payload, It appears to be trying to create NEW assets with the posted data (I get an error regarding the unique constraint on the serial number) rather than creating a new many-to-many table object. Any idea what I'm doing wrong?
Edit: Important clarification, I'm using Django Rest Framework 2.3.13

Create a serializer for custom user model in django-rest-auth?

I've installed django-rest-framework. Also i've installed django allauth.
Now i want to use django-rest-auth based on previous two, for serializing data
and sending it in json format as answer on my submit form method (for registration/login etc).
I look up into documentation is provided for django-rest-auth
but it feels for me strange, as i inherit(and must do so) from the AbstractBaseUser and not simply do OneToOneField on existing User model.
At the moment the model i assign to the AUTH_USER_MODEL in my settings.py is:
class Account(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True, db_index=True, verbose_name='Account Email')
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_employer = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
objects = AccountManager()
USERNAME_FIELD = 'email'
The question is: how can i use serializing in rest-auth the same way, but using my model instead of provided UserProfile model example ?
Yes you may inherit it, and use serializers the way you are saying. It should work. Please state where the error arises when you are using serializers for this.

DRF 3.0: UniqueTogetherValidator with a read-only field

In process of upgrading to Django REST Framework 3.0 from 2.4.4 and I want to have a read-only user field, but this is failing because 'user' is being required by the UniqueTogetherValidator (I think)
I have model (excuse typos, this is simplified and the code works fine IRL):
class ExampleModel(models.Model):
some_attr = models.PositiveSmallIntegerField()
other_attr = models.PositiveSmallIntegerField()
user = models.ForeignKey(User)
class Meta:
unique_together = ('some_attr', 'other_attr', 'user')
Viewset:
class ExampleViewSet(viewsets.ModelViewSet):
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
Serializer:
class ExampleSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = ExampleModel
Now, I keep getting errors saying: {"user":["This field is required."]}, which was not the case before. In a slightly different example with the same basic problem, I get the assertion error May not set both 'read_only' and 'required' even though I am not setting user as required.
I receive the same error regardless if I add required=False for the user attribute in the serializer, or if I add user to the the excluded fields in the serializer's meta.
When I use the handy new serializer printing, I see:
class Meta:
validators = [UniqueTogetherValidator(queryset=ExampleModel.objects.all(), fields=('user', 'some_attr', 'other_attr'))]
which gets automatically added based on the model's unique_together. If I explicitly overwrite this and do not include 'user' in the fields for UniqueTogetherValidator then everything works as before.
Is this an intended consequence of the 3.0 update? Seems to me that adding request.user in the perform_create / perform_update is very standard DRF procedure as demonstrated in the tutorial. I realize not having the new validation just means failing at the DB level instead, and the new validation probably gives better error messages, but
Is there a solution other than to override the validation for every serializer where this is an issue?
Thanks in advance for any help!
This is a known issue that we are in the process of addressing within Django REST Framework. As of right now, there is a note in the documentation about UniqueTogtherValidator that says
Note: The UniqueTogetherValidation class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with default values are an exception to this as they always supply a value even when omitted from user input.
This explains why you are seeing an error because the field is required, even though you are explicitly settings read_only=True. You may want to look into the CurrentUserDefault class, which may suit your needs while avoiding the issue with the UniqueTogetherValidator.
class ExampleSerializer(serializers.ModelSerializer):
user = UserSerializer(
read_only=True
default=serializers.CurrentUserDefault()
)
class Meta:
model = ExampleModel
This should do the same thing as your perform_create and perform_update hooks.

Cannot deserialize object in django rest framework

I'm closely reading http://www.django-rest-framework.org/api-guide/serializers and attempting to implement a basic deserializer. I'm somewhat perplexed as my experimental code is resulting in random results which make little sense.
I have a simple django model:
class ArticleType(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=15)
class Article(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=80)
body = models.TextField()
featured = models.BooleanField(default=False)
published = models.BooleanField(default=False)
children = models.ManyToManyField('self')
article_type = models.ForeignKey(ArticleType)
And I have a simple deserializer:
class ArticleSerializer(ModelSerializer):
article_type = serializers.CharField(max_length=15)
children = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Article
fields = ('id', 'featured','published','body','title','children','article_type')
In the django shell I run the following:
>>> aData = {'id':3,'featured':True,'published':True, 'body':'This is some body text!', 'title':'This is a title!', 'children':[2,3], 'article_type':'Topic'}
>>> aS = ArticleSerializer(data=aData)
Which yields:
>>> aS.is_valid()
True
>>> aS.data
{'featured': False, 'published': False, 'body': u'', 'title': u'', 'children': [], 'a_type': u''}
From this I have several questions.
Why has the data been changed?
If the data is invalid, why is the .is_valid() method returning true?
The documentation is vague on where I am to implement the transformation from CharField with length 15 to actually return the ArticleType instance in my article_type field.
NOTE: The article table is populated with a few dummy articles.
The data hasn't been changed. Those are your defaults as specified in the model. It's your serializer definition that causes the .is_valid to return true.
I think you want to define it like this:
class ArticleTypeSerializer(serializers.ModelSerializer):
model = models.ArticleType
fields= ('id', 'name',)
class ArticleSerializer(serializers.ModelSerializer):
article_type = ArticleTypeSerializer
class Meta:
model = models.Article
fields = ('id', 'featured','published','body','title','children','article_type',)
Also, I'm pretty sure that you want to add blank=True to your definition of the children in the model as otherwise every article has children, which have children, which have children, ad infinitum.
One thing I'm not sure about is what the behaviour should be when deserializing M2M fields. Normally django requires you to save the (in your case) Article and then add the children. The serializer doesn't actually do the saving so it wont be able to populate the children. That is the topic for another question however.

Resources