Django - grabbing an additional field from related table - django

I have the following models:
class User(AbstractBaseUser, PermissionsMixin):
SUPERVISOR = 1
REVIEWER = 2
VERIFIER = 3
READ_ONLY = 4
USER_TYPE = [
(SUPERVISOR, 'Supervisor'),
(REVIEWER, 'Reviewer'),
(VERIFIER, 'Verifier'),
(READ_ONLY, 'Read Only'),
]
email = models.EmailField(max_length=50, unique=True)
name = models.CharField(max_length=100)
phone = models.CharField(max_length=50, null=True)
role = models.IntegerField(
choices=USER_TYPE,
default=READ_ONLY
)
is_active = models.BooleanField(default=True)
class Comment(models.Model):
text = models.TextField()
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT
)
View:
class CommentViewSet(BaseCertViewSet):
queryset = Comment.objects.all()
serializer_class = serializers.CommentSerializer
Serializer:
class CommentSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(
read_only=True,
slug_field='name'
)
class Meta:
model = Comment
fields = ('id', 'text', 'user',)
read_only_fields = ('id',)
My question: when I hit the comment API endpoint, I'd like it to return the user role from the user model as well. How do I go about doing that?

I believe you can use a QuerySet.annotation:
EDIT: F is from django.db.models so you have to import that as well.
queryset = Comment.objects.annotate(user_role=F("user__role")) in your CommentViewSet
EDIT:
In order to get the serializer to recognize the field that you are trying to add to the QuerySet, you must also define the field on the serializer like this:
class CommentSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(
read_only=True,
slug_field='name'
)
# add line below to your code
user_role = IntegerField()
class Meta:
model = Comment
# you have to add it to the list of fields as well
fields = ('id', 'text', 'user', 'user_role')
read_only_fields = ('id',)

The solution that worked for me (not sure it's the most elegant one, happy to change to a better way of doing that):
class CommentSerializer(serializers.ModelSerializer):
"""Serializer for Comment object"""
user = serializers.SlugRelatedField(
read_only=True,
slug_field='name'
)
role = serializers.SerializerMethodField()
def get_role(self, obj):
user = obj.user_id
role = User.objects.only('id').get(
id=user).role
return role
class Meta:
model = Comment
fields = ('id', 'value', 'text', 'user', 'role',
'date_created', 'date_updated')
read_only_fields = ('id',)

Related

multiple ManyToOne relations serializer

sorry my english is not good.
Get request book_id(pk)
How do I serialize ManyToOne fields using BookSerializer to retrieve something
class Book(TimeStampedModel):
name = models.CharField(max_length=25, null=False)
owner = models.ForeignKey(User, on_delete=models.DO_NOTHING,unique=True)
...
class Meta:
db_table = 'books'
class BookMember(TimeStampedModel):
user = models.ForeignKey(User, on_delete=models.DO_NOTHING, null=False)
book = models.ForeignKey(Book, on_delete=models.CASCADE, null=False)
class Meta:
db_table = 'book_member'
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=20, unique=True)
email = models.EmailField(verbose_name=_('email'))
...
class Meta:
db_table = 'user'
class BookSerializer(serializers.ModelSerializer):
user = UserDetailSerializer(read_only=True, many=True, required=False)
owner = UserDetailSerializer(read_only=True, many=True, required=False)
class Meta:
model = Book
fields = '__all__'
I need to book retrieve
class BookViewSet(ModelViewSet):
permission_classes = [AllowAny]
queryset = Book.objects.all()
serializer_class = BookSerializer
renderer_classes = [JSONRenderer]
def retrieve(self, request, *args, **kwargs):
instance = get_object_or_404(self.queryset, many=True)
serializer = self.get_serializer(instance)
return serializer.data
You can span a ManyToManyField [Django-doc] over you BookMember model:
from django.conf import settings
class Book(TimeStampedModel):
# …
owner = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.DO_NOTHING
)
members = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through='BookMember'
)
# …
class Meta:
db_table = 'books'
class BookMember(TimeStampedModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.DO_NOTHING
)
book = models.ForeignKey(
Book,
on_delete=models.CASCADE
)
class Meta:
db_table = 'book_member'
Then you can serialize with:
class BookSerializer(serializers.ModelSerializer):
user = UserDetailSerializer(read_only=True, many=True, required=False)
members = UserDetailSerializer(read_only=True, many=True, required=False)
class Meta:
model = Book
fields = '__all__'
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Usually it is better to work with a OneToOneField [Django-doc]
instead of a ForeignKey that has unique=True. A OneToOneField is a ForeignKey with unique=True and some small
modifications: for example it uses by default the name of the model in lowercase as
related_name=… [Django-doc]
and makes accessing the relation in reverse more convenient since that does not require a manager in between.
Note: Specifying null=False [Django-doc] is not necessary: fields are by default not NULLable.

retrieving the last instance of a model in another model's modelSerializer in django rest framework

I am creating rest APIs for a website in which users can purchase one of the provided subscriptions.
In this website there is a user-info API which returns the information about the logged in user which can be used to show their info on the website.
The problem is that, the mentioned API's serializer is a modelSerializer on the "User" model and the information that I want to return is the instance of "Subscription" model which the latest instance of "SubPurchase" model refers to.
These are my serializers, models and views.And I need to somehow return the user's current subscription's ID and name along with the user's information. If you have any further questions, ask me in the comments and I'll answer them.
# models.py
class User(AbstractBaseUser, PermissionsMixin):
userID = models.AutoField(primary_key=True)
username = models.CharField(max_length=100, unique=True, validators=[RegexValidator(regex="^(?=[a-z0-9._]{5,20}$)(?!.*[_.]{2})[^_.].*[^_.]$")])
email= models.EmailField(max_length=100, unique=True, validators=[EmailValidator()])
name = models.CharField(max_length=100)
isSuspended = models.BooleanField(default=False)
isAdmin = models.BooleanField(default=False)
emailActivation = models.BooleanField(default=False)
balance = models.IntegerField(default=0)
objects = UserManager()
USERNAME_FIELD = 'username'
class Subscription(models.Model):
subID = models.AutoField(primary_key=True)
nameOf = models.CharField(max_length=50)
price = models.PositiveIntegerField()
salePercentage = models.PositiveIntegerField(default=0)
saleExpiration = models.DateTimeField(default=datetime.datetime.now, blank=True)
def __str__(self):
return f"{self.nameOf}"
class SubPurchase(models.Model):
price = models.PositiveIntegerField()
dateOf = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
subscription = models.ForeignKey(Subscription, null=True, on_delete=models.SET_NULL)
def __str__(self):
return self.subscription
# serializers.py
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
# views.py
class UserInfoViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserInfoSerializer
def get_queryset(self):
uID = getattr(self.request.user,'userID')
return get_user_model().objects.filter(userID=uID)
def get_object(self):
uID = getattr(self.request.user,'userID')
return self.queryset.filter(userID=uID)
Again, I need to change the UserInfoSerializer in a way that would give me the user's current subscription's name, ID and expiration date which would be 30 days after the purchase date
If you are only interested in the returned data, you can override the function to_representation of your serializer and create a serializer for your related model. If I understood correctly, the current subscription of your user is the last one (if sorted by "dateOf"). So something like that could do the trick
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('nameOf', 'id', 'saleExpiration ')
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
def to_representation(self, instance):
data = super().to_representation(instance)
current_subs = instance.subpurchase_set.order_by('dateOf').last().subscription
data['current_subscription'] = SubscriptionSerializer(instance=current_subs).data
return data
you can use NestedSerializers to achieve what you are looking for
basically, nested serialization is a method in which you can return, create, put..., into a model from another model, it goes like this..
models.py
class User(AbstractBaseUser, PermissionsMixin):
....
#user model data
class SubPurchase(models.Model):
...
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields =["anyfield you wanna include"]
class SubPurchaseSerializer(serializers.ModelSerializer):
class Meta:
model = SubPurchase
fields =["anyfield you wanna include"]
class UserInfoSerializer(serializers.ModelSerializer):
subpurchace = SubPurchaseSerializer()
subscription= SubscriptionSerializer() #later included in the fields of this serializer
class Meta:
model = get_user_model()
fields = ('userID','subpurchace', 'subscription', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')

Why doesnt work edit model by list_filter with custom model manager?

For Post model I created a custom manager:
#models.py
class ObjectsOnManager(models.Manager):
def get_queryset(self):
return super(ObjectsOnManager, self).get_queryset().filter(status='on')
class OnOffStatusModel(models.Model):
ON = 'on'
OFF = 'off'
STATUS_CHOICES = (
(ON, 'Показывать'),
(OFF, 'Не показывать'),
)
status = models.CharField("Статус", max_length=15, choices=STATUS_CHOICES, default=ON)
objects_on = ObjectsOnManager()
objects = models.Manager()
class Meta:
abstract = True
class Post(OnOffStatusModel):
count = models.PositiveIntegerField("Показы", default=0)
title = models.CharField("Заголовок", max_length=50)
descript = models.CharField("Описание", max_length=100)
slug = models.SlugField(max_length=50, unique=True)
category = models.ManyToManyField(Category, related_name='posts')
pub_date = models.DateTimeField("Дата публикации", default=timezone.now, blank=True)
body = models.TextField("Текст поста", max_length=2500)
main_post = models.BooleanField("Главная новость", default=False)
Then changed get_queryset in modelAdmin
#admin.py
class PostAdmin(admin.ModelAdmin):
form = PostImageControlForm
fields = ('count', 'status', 'image', 'title', 'descript', 'body', 'main_post', )
list_display = ('title', 'main_post', 'count',)
list_editable = ('main_post', 'status')
list_filter = ('category', 'main_post', 'status')
def get_queryset(self, request):
return Post.objects.all()
So if I edit model on the change model page it is ok, but if i tried to edit on the change list page I have got the error
enter image description here

Cannot query "object": Must be "User" instance

I am creating sample-api which have posts and followers. Post should visible to followers only
My models.py
from django.contrib.auth.models import User
class Post(models.Model):
creator = models.ForeignKey(User, related_name='creator_post_set', null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=25)
created_date = models.DateTimeField(auto_now_add=True)
content = models.TextField()
likes = models.BigIntegerField(null=True)
comments = models.BigIntegerField(null=True)
class Follow(models.Model):
follower = models.ForeignKey(User, related_name='following', null=True, on_delete=models.CASCADE)
followed_on = models.DateTimeField(auto_now_add=True)
following = models.ForeignKey(User, related_name='follower',null=True, on_delete=models.CASCADE)
My serializers.py for the models:
class UserSerializer(ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
)
return user
class Meta:
model = User
fields = ('password', 'username', 'first_name', 'last_name',)
class PostListSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['title', 'content', 'created_date',]
class FollowSerializer(serializers.ModelSerializer):
class Meta:
model = Follow
fields = '__all__'
My views.py:
class PostList(generics.ListCreateAPIView):
serializer_class = PostListSerializer
follow_model = FollowSerializer.Meta.model
post_model = PostSerializer.Meta.model
def get_queryset(self):
try:
followers = self.follow_model.objects.get(follower_id =
self.request.user.id)
queryset = self.post_model.objects.get(creator__in = followers)
except self.follow_model.DoesNotExist:
queryset = None
return queryset
When I call this view it returns the following error:
Cannot query "Follow object (1)": Must be "User" instance.
I need help Thanks in Advance.
As I can see, Post model's creator is FKed to User model. So you need to query using User model instance, not Follower model.
You can use the following code:
following = self.request.user.following.all().values_list('follower', flat=True) # because of related name
queryset = self.post_model.objects.filter(creator_id__in = list(following))
Here I have first retrieved the user ids using self.request.following.all() by reverse relationship. Then I have extracted the user ids using values_list. After that, I have used it in Post.objects.filter(...) method.

adding to a manytomany field in django

I am trying to save a many to many tags field when creating a post.
this is my post request to create a new post
{
"name":"testpost1",
"caption":"test caption n",
"tags":[{"name":"tag1"}]
}
Models
class Tags(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
name = models.CharField(max_length=50, unique=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="created_tags")
time_created = models.DateTimeField(auto_now_add=True)
followers = models.ManyToManyField(User, related_name="following_tags")
posts = models.ManyToManyField('Posts', related_name='tags', symmetrical=True)
class Posts(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
name = models.CharField(max_length=50)
caption = models.TextField(max_length=1000)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
The serializer for Post and tag
class TagsSerializerMini(serializers.ModelSerializer):
class Meta:
model = Tags
fields = ('name', )
class PostsSerializer(QueryFieldsMixin, serializers.ModelSerializer):
created_by = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
tags = TagsSerializerMini(many=True, required=False)
class Meta:
model = Posts
fields = ('id', 'name', 'caption', 'tags', 'num_reactions', 'num_comments', 'created_by', 'posted_time',
'updated_time', )
read_only_fields = ('id', 'posted_time', 'updated_time', )
def create(self, validated_data):
tags_data = validated_data.pop('tags', [])
post = Posts.objects.create(**validated_data)
for tag in tags_data:
t, _ = Tags.objects.get_or_create(name=tag["name"], created_by=self.context['request'].user)
t.posts.add(post)
return post
Now the issue is, when i am addding a post to tag t.posts.add(post) it is throwing an Error django.db.utils.IntegrityError: FOREIGN KEY constraint failed. I tried adding a post from the shell also, its giving the same error.
I got similar problem and I solved it by using an intermediary model (using through), here is how should look your code:
models.py:
class Tags(models.Model):
# ...
posts = models.ManyToManyField('Posts', related_name='tags', symmetrical=True, through='PostsTag')
class PostTag(models.Model):
tag = models.ForeignKey(Tags)
post = models.ForeignKey(Posts)
serializer:
# ...
def create(self, validated_data):
tags_data = validated_data.pop('tags', [])
post = Posts.objects.create(**validated_data)
for tag in tags_data:
t, _ = Tags.objects.get_or_create(name=tag["name"], created_by=self.context['request'].user)
PostTag.objects.create(tag=t, post=post) # <----- Create m2m relation
return post
Note: I didn't tested the code, but hope this helps!