Django serializer read and write multiple model field with - django

How to read and write multiple models in Django rest framework Model Serializer. like I have created a user-create model view set API, for that, I create a Model Serializer. there I need to give multiple permissions. for that, I pass the user_permissions field with an array of permission's id. now, how can I define a field in the user Model Serializer that can create a user with this permission and then get the user with permission's data?
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = AbstractUser
fields = "__all__"
extra_kwargs = {'password': {'write_only': True},}
extra_fields = ['user_permissions']
#view
class RegistrationView(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = AbstractUser.objects.all()
parser_classes = (FormParser, MultiPartParser)
permission_classes_by_action = [IsAdminUser, ]
def create(self, request, *args, **kwargs):
response_data = super(RegistrationView, self).create(request,*args,**kwargs)
return Response({"data": response_data.data})
request body
{
"username": "testuser",
"email": "testuser#example.com",
"first_name": "test",
"last_name": "test",
"password": "Abcd#123456",
"user_permissions": [1, 2, 3, 4, 5]
}
required response
{
"id": 1,
"email": "testuser#example.com",
"username": "testuser",
"first_name": "test",
"last_name": "test",
"is_superuser": false,
"is_staff": false,
"is_active": true,
"date_joined": "2022-08-17T10:25:48.446821Z",
"user_permissions": [
{
"id": 1,
"name": "Can add User",
"codename": "add_user",
"content_type": "account"
},
{
"id": 2,
"name": "Can change User",
"codename": "change_user",
"content_type": "account"
},
{
"id": 3,
"name": "Can delete User",
"codename": "delete_user",
"content_type": "account"
},
{
"id": 4,
"name": "Can view User",
"codename": "view_user",
"content_type": "account"
},
{
"id": 5,
"name": "Can view log entry",
"codename": "view_logentry",
"content_type": "admin"
}
],
"groups": []
}

now, how can I define a field in the user Model Serializer that can create a user with this permission and then get the user with permission's data?
You can override the create() method in Serializer Class
def create(self, validated_data):
instance = super().create(validated_data) #save instance first
for permission_id in validated_data['user_permissions']:
... #do something for associate user_permission to user
return instance
I have some doubts about extra_fields = ['user_permissions'].
I think you have to add a ListField in serializer named user_permissions, in this way the serializer recognize the field, validate it and you can find the field in validated_data dictionary.

Related

Proper way to return erroneous data with serializer error

Got user serializer for insert multiple users at once.
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'username',
'email',
'first_name',
'last_name',
'is_active',
)
Passing data from request to serializer to create users
payload = [
{
"username": "johndoe",
"email": "john#doe.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true
},
{
"username": "janedoe",
"email": "jane#doe.com",
"first_name": "Jane",
"last_name": "Doe",
"is_active": true
},
{
"username": "johndoe",
"email": "james#doe.com",
"first_name": "James",
"last_name": "Doe",
"is_active": true
}
]
While inserting payload via serializer, is_valid returns false because same username used twice in payload.
def create_user(request):
data = request.data["payload"]
serialized_data = CreateUserSerializer(data=data, many=is_many)
if serialized_data.is_valid():
serialized_data.save()
else:
return serialized_data.errors
The method above is returns
[{'username': [ErrorDetail(string='user with this username already exists.', code='unique')], }]
error but not indicates which data is erroneous.
Is there a way to detect erroneous data and attach data to serialize error in any way?

Displaying Django subcategories in category and products in each category json as Json Child

Hi in my Django oscar project which Implements Django oscar. I am able to implement my custom API which I use to view categories and display them. The issue with the API now is that subcategories of a category appear in my API view as categories and I would like them to be in an array indicating that they are subcategories. My categories code is as follows
customapi serializer class
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'numchild', 'name', 'description', 'image', 'slug')
Views
class CategoryList(generics.ListAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class CategoryDetail(generics.RetrieveAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
customapi/urls.py
url(r'^caty/$', CategoryList.as_view(), name='category-list'),
url(r'^caty/(?P<category_slug>[\w-]+(/[\w-]+)*)_(?P<pk>\d+)/$',
CategoryDetail.as_view(), name='category'),
Json
[
{
"id": 2,
"path": "0001",
"depth": 1,
"numchild": 4,
"name": "Clothes",
"description": "<p>Beautiful Clothes</p>",
"image": null,
"slug": "clothes"
},
{
"id": 8,
"path": "00010001",
"depth": 2,
"numchild": 0,
"name": "c",
"description": "",
"image": null,
"slug": "c"
},
{
"id": 7,
"path": "00010002",
"depth": 2,
"numchild": 0,
"name": "b",
"description": "",
"image": null,
"slug": "b"
},
{
"id": 6,
"path": "00010003",
"depth": 2,
"numchild": 0,
"name": "a",
"description": "",
"image": null,
"slug": "a"
},
{
"id": 5,
"path": "00010004",
"depth": 2,
"numchild": 0,
"name": "MsWears",
"description": "",
"image": null,
"slug": "mswears"
},]
notice the numchild is 4 for the first which signifies it is the parent category and the rest are the subcategories.
The subcategories are rendered like this from the Django-oscar model
class AbstractCategory(MP_Node):
"""
A product category. Merely used for navigational purposes; has no effects on business logic.
Uses Django-treebeard.
"""
name = models.CharField(_('Name'), max_length=255, db_index=True)
description = models.TextField(_('Description'), blank=True)
image = models.ImageField(_('Image'), upload_to='categories', blank=True,
null=True, max_length=255)
slug = SlugField(_('Slug'), max_length=255, db_index=True)
_slug_separator = '/'
_full_name_separator = ' > '
def __str__(self):
return self.full_name
#property
def full_name(self):
"""
Returns a string representation of the category and it's ancestors,
e.g. 'Books > Non-fiction > Essential programming'.
It's rarely used in Oscar's codebase, but used to be stored as a
CharField and is hence kept for backward compatibility. It's also sufficiently useful to keep around.
"""
names = [category.name for category in self.get_ancestors_and_self()]
return self._full_name_separator.join(names)
#property
def full_slug(self):
"""
Returns a string of this category's slug concatenated with the slugs
of it's ancestors, e.g. 'books/non-fiction/essential-programming'.
Oscar used to store this as in the 'slug' model field, but this field
has been re-purposed to only store this category's slug and to not
include it's ancestors' slugs.
"""
slugs = [category.slug for category in self.get_ancestors_and_self()]
return self._slug_separator.join(slugs)
def generate_slug(self):
"""
Generates a slug for a category. This makes no attempt at generating a unique slug.
"""
return slugify(self.name)
def ensure_slug_uniqueness(self):
"""
Ensures that the category's slug is unique amongst its siblings.
This is inefficient and probably not thread-safe.
"""
unique_slug = self.slug
siblings = self.get_siblings().exclude(pk=self.pk)
next_num = 2
while siblings.filter(slug=unique_slug).exists():
unique_slug = '{slug}_{end}'.format(slug=self.slug, end=next_num)
next_num += 1
if unique_slug != self.slug:
self.slug = unique_slug
self.save()
def save(self, *args, **kwargs):
"""
Oscar traditionally auto-generated slugs from names. As that is often convenient, we still do so if a slug is not supplied through other means. If you want to control slug creation, just create instances with a slug already set, or expose a field on the appropriate forms.
"""
if self.slug:
# Slug was supplied. Hands off!
super(AbstractCategory, self).save(*args, **kwargs)
else:
self.slug = self.generate_slug()
super(AbstractCategory, self).save(*args, **kwargs)
# We auto-generated a slug, so we need to make sure that it's
# unique. As we need to be able to inspect the category's siblings
# for that, we need to wait until the instance is saved. We
# update the slug and save again if necessary.
self.ensure_slug_uniqueness()
def get_ancestors_and_self(self):
"""
Gets ancestors and includes itself. Use treebeard's get_ancestors
if you don't want to include the category itself. It's a separate function as it's commonly used in templates.
"""
return list(self.get_ancestors()) + [self]
def get_descendants_and_self(self):
"""
Gets descendants and includes itself. Use treebeard's get_descendants
if you don't want to include the category itself. It's a separate function as it's commonly used in templates.
"""
return list(self.get_descendants()) + [self]
def get_absolute_url(self):
"""
Our URL scheme means we have to look up the category's ancestors. As that is a bit more expensive, we cache the generated URL. That is
safe even for a stale cache, as the default implementation of
ProductCategoryView does the lookup via primary key anyway. But if you change that logic, you'll have to reconsider the caching approach.
"""
current_locale = get_language()
cache_key = 'CATEGORY_URL_%s_%s' % (current_locale, self.pk)
url = cache.get(cache_key)
if not url:
url = reverse(
'catalogue:category',
kwargs={'category_slug': self.full_slug, 'pk': self.pk})
cache.set(cache_key, url)
return url
class Meta:
abstract = True
app_label = 'catalogue'
ordering = ['path']
verbose_name = _('Category')
verbose_name_plural = _('Categories')
def has_children(self):
return self.get_num_children() > 0
def get_num_children(self):
return self.get_children().count()
when a category is selected, the corresponding JSON so look like this
{
"url": "http://127.0.0.1:8000/nativapi/products/16/",
"id": 16,
"title": "Deall",
"images": [],
"price": {
"currency": "NGN",
"excl_tax": "1000.00",
"incl_tax": "1000.00",
"tax": "0.00"
},
"availability": "http://127.0.0.1:8000/nativapi/products/16/availability/"
},
{
"url": "http://127.0.0.1:8000/nativapi/products/13/",
"id": 13,
"title": "ada",
"images": [
{
"id": 8,
"original": "http://127.0.0.1:8000/media/images/products/2018/05/f3.jpg",
"caption": "",
"display_order": 0,
"date_created": "2018-05-26T17:24:34.762848Z",
"product": 13
},]
this means that only the products under that category are returned. and if a category has a number if a child, the number of the child should be returned in as an object Array.
I would suggest keeping category specific data separate (in details page) and only having a products API.
For getting products under a certain category, you could do something like -
views.py
from django.shortcuts import get_object_or_404
from oscar.core.loading import get_model
from rest_framework import generics
from oscarapi.serializers import ProductsSerializer
Category = get_model('catalogue', 'Category')
Product = get_model('catalogue', 'Product')
class CategoryProductsView(generics.ListAPIView):
serializer_class = ProductsSerializer
def get_queryset(self):
cat_id = self.kwargs.get('pk', None)
if cat_id is not None:
category = get_object_or_404(Category, id=cat_id)
return Product.objects.filter(
categories__path__startswith=category.path).all()
else:
return Product.objects.none()
urls.py
from views import CategoryProductsView
urlpatterns = [
...
url(r'^caty/(?P<pk>[0-9]+)/products/$', CategoryProducts.as_view(), name='category-products'),
...
]
Since we are using categories__path__startswith we'd get all products under that category, including those under the subcategory of given category, and so on.
Update
As for the subcategories you want listed, you could simply add a SerializerMethodField() to do that for you. I'd suggest getting a list of ids for the subcategories so that further fetching the details of that subcategory would be easier given it's id (simple lookup from the existing list of categories)
serializers.py
from oscarapi.utils import OscarModelSerializer
from rest_framework import serializers
class CategorySerializer(OscarModelSerializer):
subcategories = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ('id', 'numchild', 'name', 'description', 'image', 'slug',
'path', 'depth', 'subcategories')
def get_subcategories(self, obj):
return Category.objects.filter(path__startswith=obj.path,
depth=obj.depth+1
).values_list('id', flat=True)
sample output
"results": [
{
"id": 1,
"numchild": 1,
"name": "Cat1",
"description": "",
"image": "http://localhost:8001/media/categories/images/categories/cat1.jpg",
"slug": "cat1",
"path": "0001",
"depth": 1,
"subcategories": [
2
]
},
{
"id": 2,
"numchild": 0,
"name": "SubCat1",
"description": "",
"image": null,
"slug": "subcat1",
"path": "00010001",
"depth": 2,
"subcategories": [
]
},
]
django-oscar uses django-treebeard for a materialized path implementation, pretty much the opposite of the nested hierarchy you want to retrieve.
I have no experience in writing serializers along with treebeard, but i am pretty sure that you will need to rewrite your Serializer to something like
# Get all categories from /caty
class CategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField('get_children')
def get_children(self, obj):
if obj.numchild == 0:
return None
# Use treebeards built-in tree generation
[CategorySerializer(child) for child in Category.get_tree(obj)]
class Meta:
model = Category
Notice that i have NOT tested any of this, i am just trying to point you in a direction that might bring you closer to a solution.

Django: nest the object I'm serializing into the serializer?

I'm looking to nest the object I'm serializing. Here's what I mean:
My current UserSerializer:
class UserSerializer(serializers.ModelSerializer):
posts = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('__all__')
def get_posts(self, user):
posts = Posts.objects.get_posts_for_user(user=user)
return PostsSerializer(posts, many=True, context=self.context)
Here's my PostsSerializer:
class PostsSerializer(serializers.ModelSerializer):
class Meta:
model = Posts
fields = ('__all__')
Here's what's how it's being serialized:
{ "name": "Bobby Busche",
"email": "Bobby#gmail.com",
"posts": [ {"from_user": "me", "message": "Hello World"},
{"from_user": "me", "message": "Bye bye"} ],
"username": "ilovemymomma"
}
But I want the user to be grouped inside the key "user" like this:
{ "user": { "name": "Bobby Busche",
"email": "Bobby#gmail.com",
"username": "ilovemymomma" }
"posts": [ {"from_user": "me", "message": "Hello World"},
{"from_user": "me", "message": "Bye bye"} ]
}
I need a bit of guidance on what's the best approach to execute for this.
You could make a Custom serializer as Rajesh pointed out. Note that this serializer is read-only.
class UserPostsSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
posts = Posts.objects.get_posts_for_user(user=instance)
return {
'user': UserSerializer(instance).data,
'posts': PostSerialzer(posts, many=True).data
}
You would then need to remove the posts field from the UserSerializer so that the posts aren't nested inside that one also.

Django request.user.userprofile.save() not working

I suppose to save user's avatar and contact. When I create a form of userprofile and update model with request.user.userprofile.save(), it turns out that nothing changed in database.
I've tried many ways, including ModelForm, but I think it has nothing to do with the form, cuz when I directly using UserProfile.objects.filter(id=123).update(avatar=my_avatar) in view, no error raised but still nothing changed in database.
It's very strange that when I debug it, slow the program down, it worked!
Other models works just fine.
class UserProfile(models.Model):
user = models.OneToOneField(User)
avatar = models.CharField(max_length=100, blank=True, null=True)
contact = models.CharField(max_length=64, blank=True, null=True)
class PostUpdateUserProfileView(View):
#method_decorator(mobile_login_required)
def dispatch(self, request, *args, **kwargs):
return super(PostUpdateUserProfileView, self).dispatch(request, *args, **kwargs)
def post(self, request):
request.user.userprofile.contact = 'test'
request.user.userprofile.save(using='master')
# DB SETTINGS
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "db",
"USER": "<SAME_USR>",
"PASSWORD": "<SAME_PWD>",
"HOST": "<SAME_HOST>",
"PORT": "3306",
"OPTIONS": {
'init_command': "SET default_storage_engine=INNODB, sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
},
"master": {
"ENGINE": "django.db.backends.mysql",
"NAME": "db",
"USER": "<SAME_USR>",
"PASSWORD": "<SAME_PWD>",
"HOST": "<SAME_HOST>",
"PORT": "3306",
'OPTIONS': {
'init_command': "SET default_storage_engine=INNODB, sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
}
}
I thought this error might have sth to do with autocommit.

Why does PATCH on Django Rest Framework wipe an unchanged ManyToMany field?

I am PATCHing a model using DRF, and the call is, for some reason, wiping a ManyToMany field. Why is this?
I have a Feature model:
class Feature(CommonInfo):
user = models.ForeignKey(User)
name = models.CharField(max_length=50)
parent = models.ForeignKey("Feature", blank=True, null=True, related_name='children')
tags = models.ManyToManyField("Tag")
level = models.IntegerField()
box_image = ImageField()
background_image = ImageField()
...and a serializer:
class FeatureSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
children = serializers.HyperlinkedRelatedField(read_only=True, view_name='feature-detail', many=True)
level = serializers.ReadOnlyField()
def get_fields(self, *args, **kwargs):
user = self.context['request'].user
fields = super(FeatureSerializer, self).get_fields(*args, **kwargs)
fields['parent'].queryset = fields['parent'].queryset.filter(user=user)
return fields
class Meta:
model = Feature
...and a viewset:
class FeatureViewSet(viewsets.ModelViewSet):
serializer_class = FeatureSerializer
def get_queryset(self):
return self.request.user.feature_set.order_by('level', 'name')
def perform_create(self, serializer):
serializer.save(user=self.request.user)
This gives an output for GET /api/features/5/:
{
"url": "http://localhost:8001/api/features/5/",
"user": "andrew",
"children": [],
"level": 1,
"created_at": "2015-02-03T15:11:00.191909Z",
"modified_at": "2015-02-03T15:20:02.038402Z",
"name": "My Astrantia major 'Claret' plant",
"box_image": "http://localhost:8001/media/Common_Knapweed13315723294f5e2e6906593_PF07bKi.jpg",
"background_image": null,
"parent": "http://localhost:8001/api/features/1/",
"tags": [
"http://localhost:8001/api/tags/256/"
]
}
Suppose I want to run a PATCH call to update name:
import requests
r = requests.patch("http://localhost:8001/api/features/5/",
data={'name':'New name'},
auth=('user', 'password'))
r.json()
This successfully updates the object, but the result also wipes the tags from the object:
{
"url": "http://localhost:8001/api/features/5/",
"user": "andrew",
"children": [],
"level": 1,
"created_at": "2015-02-03T15:11:00.191909Z",
"modified_at": "2015-02-03T16:12:48.055527Z",
"name": "New name",
"box_image": "http://localhost:8001/media/Common_Knapweed13315723294f5e2e6906593_PF07bKi.jpg",
"background_image": null,
"parent": "http://localhost:8001/api/features/1/",
"tags": []
}