HyperlinkedIdentityField and null values in django-rest-framework - django

I've got two models (code is simplified):
class Document(models.Model):
data = JSONField()
class Item(models.Model):
name = models.CharField(max_length=10)
doc = models.OneToOneField(
Document,
blank=True, null=True,
on_delete=models.SET_NULL
)
I would like to make a hyperlink to the Documents details page in the serializer for Item using the HyperlinkedIdentityField.
So I have the URL defined:
url(r'^doc/(?P<version>[v1|v2]+)/(?P<pk>[0-9]+)/$',
doc_details,
name = 'api_document_details'
),
And I made the following serializer:
class ItemSerializer(serializers.ModelSerializer):
doc = serializers.HyperlinkedIdentityField(
view_name = 'api_document_details',
lookup_field = 'doc_id',
lookup_url_kwarg = 'pk'
)
class Meta:
model = Item
fields = ('name', 'doc')
The problem here is that the doc field in Item is optional. I got an error as there is no URL to be found if the doc_id = None.
Could not resolve URL for hyperlinked relationship using view name "api_document_details". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
I would like HyperlinkedIdentityField to return None (null) if the document is not defined in the record. Does anybody know how to do this?
I have tried using the SerializerMethodField() method in which I use the reverse function and return None if I get an exception, but this has two disadvantages:
I cannot see which version is requested, so I default to v1
It's a relative urlthat is generated, do the domain is not included in the returned value.

Related

Serialize many-to-many relation with intermediate model in Django Rest

I tried to check another topics, but didn't found a solution...
I have a many-to-many model, that have intermediate model with another field additional_field inside.
class BoardField(models.Model):
title = models.CharField(max_length=500, default='')
class Article(models.Model):
title = models.CharField(max_length=500, default='')
fields = models.ManyToManyField(BoardField, through='ArticleField', through_fields=('article', 'board_field'))
class ArticleField(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='task')
board_field = models.ForeignKey(BoardField, on_delete=models.CASCADE)
additional_field = models.CharField(max_length=200, blank=True, null=True)
I want serialize Article with structure:
[
"title":"Title",
"fields":[
{
"board_field": {
"title":"Title"
},
"additional_field":"Additional info"
}
]
]
So, I wrote serializer:
class BoardFieldSrl(serializers.ModelSerializer):
class Meta:
model = BoardField
fields = (
'title',
)
class ArticleFieldSrl(serializers.ModelSerializer):
board_field = BoardFieldSrl()
class Meta:
model = ArticleField
fields = (
'board_field',
'additional_field',
)
class ArticleListSrl(serializers.ModelSerializer):
fields = ArticleFieldSrl(many=True)
class Meta:
model = Article
fields = (
'title',
'fields',
)
But I always got an error:
Got AttributeError when attempting to get a value for field `board_field` on serializer `ArticleFieldSrl`.
The serializer field might be named incorrectly and not match any attribute or key on the `BoardField` instance.
Original exception text was: 'BoardField' object has no attribute 'board_field'.
I made another several examples, but they doesn't gave my result, that I need... My maximum - I got BoardField with levels, but without intermediate model...
Can you help me with serializer, that return structure, that I mentioned above? It must include intermediate model ArticleField and nested BoardField.
Try fields = ArticleFieldSrl(source='articlefield_set', many=True)
You didn't specified a related_name at M2M field so the default naming is applied which is 'Intermediate model name'_set and if you want to use the fields on M2M relation you have to tell the serializer where to look for.
EDIT:
Camel removed from articlefield_set, model name is always converted to lower case

Return errors as json django rest api

Beginner alert. I am using ListCreateAPIView for listing and creating purposes. Whenever I create through an api it returns responses in json format.
{"id":16,"title":"yyyyyyyy","destination_place":[1]}
But if there are errors like duplicate slug or title it returns errors like this
IntegrityError at /api/holidays/
duplicate key value violates unique constraint "holidays_holiday_slug_key"
DETAIL: Key (slug)=(yyyyyyyy) already exists.
Is there any way to return these errors in json format.
My views
class HolidayList(ListCreateAPIView):
queryset = Holiday.objects.all()
serializer_class = HolidaySerializer
permission_classes = [IsAdminUser, IsAuthenticated]
Model
class Holiday(models.Model):
title = models.CharField(verbose_name=_("Title"), max_length=255)
slug = models.SlugField(unique=True)
destination_place = models.ManyToManyField(to='places.Place',related_name='destination_place',null=True,blank=True)
In HolidaySerializer, add UniqueValidator on slug field.
Example:
from rest_framework.validators import UniqueValidator
class HolidaySerializer(serializers.ModelSerializer):
slug = serializers.SlugField(
max_length=255,
validators=[UniqueValidator(queryset=Holiday.objects.all())])
class Meta:
model = Holiday
fields = ('id', 'title', 'slug', 'destination_place', )
This will return back the unique constraint error in the JSON format.
You can customize the message. Look at the docs.

Add Serializer on Reverse Relationship - Django Rest Framework

I have a Cart model and a CartItem model. The CartItem model has a ForeignKey to the Cart model.
Using Django Rest Framework I have a view where the API user can display the Cart, and obviously then I want to include the CartItem in the respone.
I set up my Serializer like this:
class CartSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
cartitem_set = CartItemSerializer(read_only=True)
class Meta:
model = Cart
depth = 1
fields = (
'id',
'user',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
My problem is the second line, cartitem_set = CartItemSerializer(read_only=True).
I get AttributeErrors saying 'RelatedManager' object has no attribute 'product'. ('product' is a field in the CartItem model. If I exclude product from the CartItemSerializer I just get a new AttributeError with the next field and so on. No matter if I only leave 1 or all fields in the Serializer, I will get a error.
My guess is that for some reason Django REST Framework does not support adding Serializers to reverse relationships like this. Am I wrong? How should I do this?
PS
The reason why I want to use the CartItemSerializer() is because I want to have control of what is displayed in the response.
Ahmed Hosny was correct in his answer. It required the many parameter to be set to True to work.
So final version of the CartSerializer looked like this:
class CartSerializer(serializers.ModelSerializer):
cartitem_set = CartItemSerializer(read_only=True, many=True) # many=True is required
class Meta:
model = Cart
depth = 1
fields = (
'id',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
It's important to define a related name in your models, and to use that related name in the serializer relationship:
class Cart(models.Model):
name = models.CharField(max_length=500)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cart_items')
items = models.IntegerField()
Then in your serializer definition you use those exact names:
class CartSerializer(serializers.ModelSerializer):
cart_items = CartItemSerializer(read_only=True)
class Meta:
model = Cart
fields = ('name', 'cart_items',)
It would be wise to share your whole code, that is model and serializers classes. However, perhaps this can help debug your error,
My serializer classes
class CartItemSerializer(serializers.ModelSerializer):
class Meta:
model = CartItem
fields = ('id')
class CartSerializer(serializers.ModelSerializer):
#take note of the spelling of the defined var
_cartItems = CartItemSerializer()
class Meta:
model = Cart
fields = ('id','_cartItems')
Now for the Models
class CartItem(models.Model):
_cartItems = models.ForeignKey(Subject, on_delete=models.PROTECT)
#Protect Forbids the deletion of the referenced object. To delete it you will have to delete all objects that reference it manually. SQL equivalent: RESTRICT.
class Meta:
ordering = ('id',)
class Cart(models.Model):
class Meta:
ordering = ('id',)
For a detailed overview of relationships in django-rest-framework, please refer their official documentation

Django REST Framework: Add field from related object to ModelSerializer

I'm trying to allow a field that belongs to a related object to be readable and writable from a ModelSerializer. I have a model Group:
class Group(models.Model):
...
name = models.CharField(max_length=128)
def get_language(self):
line = self.line_set.all()[0]
return line.language
...
and corresponding serializer:
class GroupSerializer(serializers.ModelSerializer):
language = serializers.CharField(source='get_language')
class Meta:
model = Group
fields = ('id', 'name', 'language')
lookup_field= 'pk'
The Group model is related to the Line model, where the language field lives:
class Line(models.Model):
...
language = models.CharField(max_length=24)
groups = models.ManyToManyField(Group, blank=True, null=True)
...
I'm trying to expose Line.language in the Group API for reading and writing. I've tried using CharField as above, but when I go to post a new model, I get this error:
TypeError at /AO/s/v2/group/
'get_language' is an invalid keyword argument for this function
I also thought of trying a custom related field and implementing from_native, but that requires a queryset which isn't relevant here.
Thanks in advance.

Django ModelForm ValueError

I have a Django Model
class Category(MPTTModel):
name = models.CharField(max_length=50, unique=True)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
def __unicode__(self):
return self.name
class MPTTMeta:
order_insertion_by = ['name']
and ModelForm
class UploadForm(ModelForm):
file = forms.FileField()
category = mpttform.TreeNodeMultipleChoiceField(queryset=Category.objects.filter(lft=F('rght')-1))
class Meta:
model = UploadedFile
However, I'm having problem with this category field in UploadForm which is supposed to be Category instance (as definied in Model), but my queryset return list of Category objects which I use in template to show all leaf categories.If I select any category on the form and submit it, I get this error(in case I select cat5) 'Cannot assign [Category: cat5]: "UploadedFile.category" must be a "Category" instance.'
So I understand why this error is happening, but I'd like to use ModelForm because of save() method, but don't see how I can fix this.Any suggestions?
Django is telling you that you must initiate a category instance in order to iterate over the categories. So the category instance takes params from the url, url params with regex in your urls.py . So you need to capture the param and make it the category instance in the view.
*See class based generic views for which views automatically give you the params context variable.
I think
category = mpttform.TreeNodeMultipleChoiceField(queryset=Category.objects.filter(lft=F('rght')-1))
Works for a m2m relation and I guess category is a ForeignKey in the model Uploaded File. If so, you should use
category = mpttform.TreeNodeChoiceField(queryset=Category.objects.filter(lft=F('rght')-1))