Django Rest creating Nested-Objects (ManyToMany) - python

I looked for an answer to this question specifically for Django Rest, but I haven't found one anywhere, although I think a lot of people have this issue. I'm trying to create an object with multiple nested relationships but something is keeping this from happening. Here are my models for reference:
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True, null=True)
tmp_password = models.CharField(max_length=32)
photo = models.ImageField(upload_to='media/', blank=True, null=True)
likes = models.IntegerField(blank=True, null=True)
dislikes = models.IntegerField(blank=True, null=True)
def __unicode__(self):
return unicode(self.user.username)
class Item(models.Model):
"""Item Object Class"""
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=125, blank=True)
price = models.FloatField(default=0, blank=True)
rating = models.IntegerField(default=0, blank=True)
description = models.TextField(max_length=300, blank=True)
photo = models.ImageField(upload_to="media/", blank=True)
barcode = models.CharField(max_length=20, blank=True)
photo_url = models.URLField(max_length=200, blank=True)
item_url = models.URLField(max_length=200, blank=True)
def __unicode__(self):
return unicode(self.name)
class Favorite(models.Model):
user = models.OneToOneField(User, null=True)
items = models.ManyToManyField(Item)
def __unicode__(self):
return unicode(self.user.username)
def admin_names(self):
return '\n'.join([a.name for a in self.items.all()])
And here are my serializers:
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('id', 'name', 'price', 'description', 'rating', 'photo', 'barcode', 'photo_url','item_url' )
class FavoriteSerializer(serializers.ModelSerializer):
class Meta:
model = Favorite
exclude = ('id', 'user')
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserProfile
fields = ('likes', 'dislikes', 'photo', 'tmp_password')
class UserSerializer(serializers.HyperlinkedModelSerializer):
userprofile = UserProfileSerializer()
favorite = FavoriteSerializer()
class Meta:
model = User
fields = (
'id', 'username', 'url',
'email', 'is_staff', 'password',
'userprofile', 'favorite'
)
def create(self, validated_data):
profile_data = validated_data.pop('userprofile')
favorites_data = validated_data.pop('favorite')
user = User.objects.create_user(**validated_data)
user_profile = UserProfile.objects.create(user=user, **profile_data)
favorite = Favorite(user=user)
favorite.save()
print favorite.items
for item in favorites_data:
favorite.items.add(item)
print favorite.items
return user
What I am having trouble with is the create() method on UserSerializer. What's happening is I can't .add() the data from favorites_data to the favorite object. I get an error saying invalid literal for int() with base 10: 'items'. I guess this makes sense, but if I try this instead of using the for loop:
favorite.items.add(**favorites_data)
I just get an error saying add() got an unexpected keyword argument 'items'. Finally, If I try this:
favorite.items.add(favorites_data)
I just get this error: unhashable type: 'OrderedDict'
What am I doing wrong in this approach? Obviously, favorites_data exist, but I'm not inserting it properly. Thanks for any help!

I think favorite.items.add expects you to pass in a single instance of an Item, so you should replace this:
for item in favorites_data:
favorite.items.add(item)
With this:
for key in favorites_data:
for item in favorites_data[key]:
favorite.items.add(item)

Related

How to return correct model fields in serializer?

I am implementing a search bar that returns the users and the posts.
I am able to return the data but when I clear the search bar I get the error returned:
AttributeError: Got AttributeError when attempting to get a value for field username on serializer SearchUserSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Post instance.
The original exception text was: 'Post' object has no attribute 'username'.
My Models:
class User(AbstractUser):
avi_pic = models.ImageField(
max_length=400, null=True, blank=True, upload_to='images')
name = models.CharField(max_length=50, blank=True, null=True)
username = models.CharField(max_length=30, unique=True)
class Post(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, default=None
)
cover = models.CharField(max_length=300, default='', blank=True)
title = models.CharField(max_length=300, default='', blank=True)
date = models.DateTimeField(editable=False, auto_now_add=True)
My Serializers:
class SearchPostSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('id', 'title', 'user', 'username', 'cover')
def get_username(self, post):
return post.user.username
class SearchUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'username', 'avi_pic')
and my Views.py:
class SearchView(generics.ListAPIView):
def get_serializer_class(self):
queryset = self.get_queryset()
if len(queryset) == 0:
return None
if isinstance(queryset[0], User):
return SearchUserSerializer
elif isinstance(queryset[0], Post):
return SearchPostSerializer
else:
return None
def get_queryset(self):
query = self.request.query_params.get('q', None)
if query is not None:
queryset_users = User.objects.filter(
Q(name__icontains=query) | Q(username__icontains=query))
queryset_posts = Post.objects.filter(
Q(playlist_title__icontains=query))
results = list(queryset_users) + list(queryset_posts)
return results
else:
return User.objects.none()
I've tried many things with zero luck.
Try this:
class SearchPostSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = Post
fields = ('id', 'title', 'user', 'username', 'cover')
This should do the trick .
You can remove the get_username function
You can take a look here for the explanation:
https://www.django-rest-framework.org/api-guide/fields/#source

Django list_select_related gives me this error

#store/admin.py
#admin.register(models.Product)
class ProductView(admin.ModelAdmin):
list_display = ['title', 'unit_price', 'inventory_status', 'collection_title',]
list_editable = ['unit_price']
list_per_page = 10
list_select_related = ['collection']
#store/models.py
class Collection(models.Model):
title = models.CharField(max_length=255)
featured_product = models.ForeignKey(
'Product', on_delete=models.SET_NULL, null=True, related_name='+')
class Meta:
ordering = ['title']
'''
def __str__(self) -> str:
return self.title
'''
class Product(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField()
description = models.TextField()
unit_price = models.DecimalField(max_digits=6, decimal_places=2)
inventory = models.IntegerField()
last_update = models.DateTimeField(auto_now=True)
collection = models.ForeignKey(Collection, on_delete=models.PROTECT)
promotions = models.ManyToManyField(Promotion)
#Error I got:
SystemCheckError
PS - I know I can use 'collection' in list_display directly as it is already a field in my product model, but I want to preload a related field/table using list_select_related and use 'collection_title' in list_display. Please help. Thank You.
ERRORS:
<class 'store.admin.ProductView'>: (admin.E108) The value of 'list_display[3]' refers to 'col
lection_title', which is not a callable, an attribute of 'ProductView', or an attribute or method on 'store.Product'.
I think you're wrong, as far as I know list_select_related is to reduce your query, but your error is something else, here it says "list_display [3]" referred to colection_title, which is not a attribute or callable method of model
Product or ProductView class.
django validate this items(list_display) In the following link, you can see the validate function(_check_list_display):
https://github.com/django/django/blob/950d697b95e66deb3155896e0b619859693bc8c6/django/contrib/admin/checks.py#L732
If you want to access the related field in other models, you can create a function in your ProductView link this:
class ProductView(admin.ModelAdmin):
list_display = ['title', 'unit_price', 'inventory_status', 'collection_title',]
list_editable = ['unit_price']
list_per_page = 10
list_select_related = ['collection']
def collection_title(self, obj):
return obj.collection.title

ValueError: Field 'id' expected a number but got string in Django Rest Framework

In DRF I am facing an issue, whenever I do a POST request on the endpoint, on the field "name" which is a text field I get an exception "Field 'id' expected a number but got 'TITLE'", but when I change the value of "name" to an integer the request is successful I don't understand it becauses name is TextField in model and why its mixing Id and Name field with each other. I have deleted the migration files from the Project and DB and re-run the Migrations, but still facing this issue.
Following is my code:
models.py
class Project(models.Model):
admin = models.ForeignKey(User, on_delete=models.CASCADE, related_name='project_crated_by')
name = models.TextField(max_length=225, blank=False, null=False)
project_members = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='members', null=True, blank=True)
created_on = models.DateField(default=timezone.now)
tags = ArrayField(models.CharField(max_length=225, default=''), blank=True)
def __str__(self):
return self.name
objects = models.Manager()
views.py
class ProjectView(viewsets.ViewSet):
def create(self, request):
project_name_exist = Project.verify_project_name(request.data['admin'], request.data['name'])
if project_name_exist:
return Response({'message': 'You already have a project with this name',
'status': status.HTTP_200_OK})
serialized_project = ProjectSerializer(data=request.data)
if serialized_project.is_valid():
serialized_project.save()
return Response({'message': 'Project Created Successfully', 'status': status.HTTP_201_CREATED})
else:
return Response({'error': serialized_project.errors, 'status': status.HTTP_400_BAD_REQUEST})
serializer.py
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = '__all__'
A more generic and non-DIY solution is to use UniqueTogetherValidator on your serializer and let Django sort it out.
from rest_framework.validators import UniqueTogetherValidator
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = '__all__'
validators = [
UniqueTogetherValidator(
queryset=Project.objects.all(),
fields=['admin', 'name'],
message='You already have a project with this name'
)
]
And/or add it to the model for enforcing it on the database.
class Project(models.Model):
admin = models.ForeignKey(User, on_delete=models.CASCADE, related_name='project_crated_by')
name = models.TextField(max_length=225, blank=False, null=False)
project_members = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='members', null=True, blank=True)
created_on = models.DateField(default=timezone.now)
tags = ArrayField(models.CharField(max_length=225, default=''), blank=True)
def __str__(self):
return self.name
objects = models.Manager()
class Meta:
unique_together = ("admin", "name")

add new record to many to many relation in django rest framework

I was trying to add new relation to many to many records,
for example i have these models:
models.py
class Team(models.Model):
name = models.CharField(blank=True, unique=True, max_length=100)
players = models.ManyToManyField(User, blank=True, related_name='players')
class TeamInvite(models.Model):
from_user = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name='invite_by', blank=True, null=True)
to_user = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name='invite_to', blank=True, null=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='invite_to_team', blank=True, null=True)
status = models.NullBooleanField(blank=True, null=True, default=None,)
and my serializer:
serializers.py
class TeamInviteCreateSerializer(serializers.ModelSerializer):
team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all())
from_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = TeamInvite
fields = ('id', 'from_user', 'to_user', 'team', 'status')
after that the user which in to_user will take an action to TeamInvite like accept or decline.
I need the serializer which will take the new user and add him to the existing team like the following serializer:
class TeamInviteAcceptDeclineSerializer(serializers.ModelSerializer):
method_name = serializers.SerializerMethodField()
class Meta:
model = TeamInvite
fields = ('id', 'from_user', 'date_time', 'team', 'method_name', 'status')
def get_method_name(self, *args, **kwargs):
method_name = None # kwargs['context']['request'].method_name
return method_name
def update(self, instance, validated_data):
instance.team = validated_data.get('team', instance.team)
method_name = validated_data.get('method_name')
instance.status = validated_data.get('status', instance.status)
instance.to_user = validated_data.get('to_user', instance.to_user)
if method_name == 'decline':
instance.status = False
else:
instance.status = True
team = Team.objects.get(pk=instance.team.pk)
team.players.add(instance.to_user)
# team.players.create(team_id=team, user_id=instance.to_user)
team.save()
instance.save()
return instance
update function does not add the user to existing team and doesn't raise any error either. What am i missing here?
my request was:
{
"from_user": 1,
"to_user": 23
"team": 64,
"method_name": "accept",
"status": null
}
thank you
I got the missing point in my code..
it was in:
class TeamInviteAcceptDeclineSerializer(serializers.ModelSerializer):
method_name = serializers.SerializerMethodField()
class Meta:
model = TeamInvite
fields = ('id', 'from_user', 'date_time', 'team', 'method_name', 'status')
fields = missed 'to_user' pram

nested serialization dont now create a forignkey it requires a new Object instead of 'id'

here is my model, serializer and output but when i want to create a new page it ask me to add a whole new user as its just a foreign-key it need to be a number like 1 (user id) and same in the case of categories how can i solve it.... help me please
serializers.py
class TeamMembersSerializer(serializers.ModelSerializer):
class Meta:
model = TeamMembers
fields = [
'user',
'page',
]
depth = 1
class SocialAccountsSerializer(serializers.ModelSerializer):
social = SocialCatSerializer()
class Meta:
model = SocialAccounts
fields = [
'page',
'social',
'link'
]
depth = 1
class PageImageSerializer(serializers.ModelSerializer):
class Meta:
model = PageImages
fields = [
'page',
'image',
]
depth = 1
class PageSerializer(serializers.ModelSerializer):
owner = UserSerializer()
catagory = BusinessCatSerializers()
business_type = BusinessTypeSerializer()
TeamMembers = TeamMembersSerializer(read_only=True)
social_accounts = SocialAccountsSerializer(read_only=True)
images = PageImageSerializer(read_only=True)
class Meta:
model =Page
fields = [
'id',
'owner',
'catagory',
'name',
'username',
'images',
'start_date',
'business_type',
'contect_number',
'email_address',
'website',
'TeamMembers',
'social_accounts',
'about',
'impression',
'Awards',
'Product',
'privacy_policy',
'is_active',
]
Models.py
class Page(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
catagory = models.ForeignKey(BusinessCatagories, on_delete=models.CASCADE, null=True, blank=True, default=None)
name = models.CharField(max_length=254, unique=True ,default=None, blank=True)
username = models.CharField(max_length=254, unique=True, blank=True)
start_date = models.DateTimeField(auto_now_add=True)
business_type = models.ForeignKey(BusinessType, on_delete=models.CASCADE, null=True, blank=True, default=None)
contect_number = models.CharField(max_length=254, default=None, blank=True)
email_address = models.EmailField(default=None, blank=True)
website = models.URLField(default=None, blank=True)
about = models.TextField(default=None, blank=True)
impression = models.TextField(default=None, blank=True)
Awards = models.CharField(max_length=254, default=None, blank=True)
Product = models.CharField(max_length=254, default=None, blank=True)
privacy_policy = models.URLField(default=None, blank=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
class TeamMembers(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None, blank=True)
page = models.ForeignKey(Page, on_delete=models.CASCADE, default=None, blank=True)
def __str__(self):
return self.user.name
class SocialAccounts(models.Model):
page = models.ForeignKey(Page, on_delete=models.CASCADE, default=None, blank=True)
social = models.ForeignKey(SocialCats, on_delete=models.CASCADE, default=None, blank=True)
link = models.URLField(default=None, blank=True)
def __str__(self):
return self.link
class PageImages(models.Model):
page = models.ForeignKey(Page, on_delete=models.CASCADE, default=None, blank=True)
image = models.ImageField(default=None, blank=True, upload_to=settings.MEDIA_ROOT)
def __str__(self):
return self.page.name
output is this but i need images field TeamMember field in it but its not appearing plus it want me to add a new user instead of asking for Foreignkey "id"
What you want is to use Primary key related field. It helps you to represent the target of the relationship using its primary key.
It should look somewhat like this.
Note: Mind the typo for any name
class PageSerializer(serializers.ModelSerializer):
owner = serializers.PrimaryKeyRelatedField(queryset = User.objects.all())
catagory = serializers.PrimaryKeyRelatedField(queryset = BuisnessCat.objects.all())
business_type = BusinessTypeSerializer()
TeamMembers = TeamMembersSerializer(read_only=True)
social_accounts = SocialAccountsSerializer(read_only=True)
images = PageImageSerializer(read_only=True)
You can read more about PrimaryKeyRelatedField here.
Now whenever creating any Page, all you need to supply are primary key for owner and category.
You are looking for Writable Nested Serializer.
In short you have to override create() method of PageSerializer
class PageSerializer(serializers.ModelSerializer):
....
....
your code
def create(self, validated_data):
# pop data of every related fields like "owner", "cateagory" etc from validated_data
owner = validated_data.pop("owner")
owner = User.objects.create(**owner)
category = validated_data.pop("category")
# create or retrieve category instance ,as above
...
...
...
# finally
return Page.objects.create(**validated_data,owner=owner,category=category, and other related instances)

Categories

Resources