How to use a ManyToMany field in JsonResponse? - django

I have this model in Django:
class Post(models.Model):
poster = models.ForeignKey('User', on_delete=models.CASCADE, related_name='posts')
body = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
likers = models.ManyToManyField('User', blank=True, null=True, related_name='liked_posts')
likes = models.IntegerField(default=0)
def serialize(self):
return {
'id': self.pk,
'poster': self.poster.username,
'body': self.body,
'timestamp': self.timestamp.strftime('%b %d %Y, %I:%M %p'),
'likes': self.likes
}
It works but when I try to add likers to it, I get an error which says I can't use manytomany fields. How can I do such thing?
I fetch it in JavaScript like this:
fetch('/posts')
.then(response => response.json())
.then(data => {
console.log(data);
});
Using this view:
def posts_view(request):
posts = Post.objects.all()
posts = posts.order_by('-timestamp').all()
return JsonResponse([post.serialize() for post in posts], safe=False)

You don't have to serialize your models manually, i.e. defining serialize(self) method for each model. Try to use django-rest-framework, everything is already made for you.
Define serializers for your models in serializers.py.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class PostSerializer(serializers.ModelSerializer):
poster = UserSerializer()
likers = UserSerializer(many=True, allow_null=True, default=None)
# if you want short info about likers (list of ids), use snippet below
# likers = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), many=True)
class Meta:
model = Post
fields = "__all__"
And then in your view:
def posts_view(request):
posts = Post.objects.all().order_by('-timestamp')
data = PostSerializer(posts, many=True).data
return JsonResponse(data)

Related

Adding a custom, non-model attribute to query set in Django?

Newbie to DRF and have a model called posts. And another called user. The post object looks as follows:
class Post(models.Model):
"""
Post model
"""
title = models.CharField(max_length=250)
body = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='forum_posts')
parent_post = models.ForeignKey('self',
on_delete=models.CASCADE,
blank=True,
null=True)
time_stamp = models.DateTimeField(default=timezone.now)
objects = models.Manager()
The serializer for this model is:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = models.Post
fields = ('id', 'title', 'body', 'parent_post', 'author', 'time_stamp')
extra_kwargs = {'id': {'read_only': True},
'author': {'read_only': True}}
When returning data for this model, I want to add an extra attribute to each object within the query set called "author_username". The username should be the username belonging to the post's author id. I also want to do this without modifying the model to add another attribute such as "author_username" since this'll be redundant (already have an FK for author). So, ideally, the json for an object would look like:
'post_id': 1
'post_title': 'Example post'
'post_body': 'Example post'
'author_id': 1
'parent_post_id': null
'time_stamp': '2022'
'author_username': 'testUser'
How can I go about doing this?
Here's my view:
class PostList(generics.ListCreateAPIView):
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = models.Post.objects.all()
serializer_class = serializers.PostSerializer
The source argument can be passed to a serializer field to access an attribute from a related model
class PostSerializer(serializers.ModelSerializer):
author_username = serializers.CharField(source="author.username", read_only=True)
class Meta:
model = models.Post
...
You should add a select_related call to your view's queryset
class PostList(generics.ListCreateAPIView):
...
queryset = models.Post.objects.select_related('author')
...

How to post manytomany field value in Postman for API

I have a field which is ManyToMany. I would like to enter the value in POSTMAN for API post operation. But everytime It says: "This field is required." even though I provided the value.
Models:
class Day(models.Model):
day_name = models.CharField(
_("Day Name"), max_length=255, null=True, blank=True)
def __str__(self):
return self.day_name
class TutorProfile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availablility = models.ManyToManyField(
Day,blank=True)
Serializer:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
Viewsets:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
With the following models.py (notice that your current Day.__str__ can raise an exception if day_name does not exist):
class Day(models.Model):
day_name = models.CharField(_("Day Name"), max_length=255, blank=True, null=True)
def __str__(self):
return self.day_name if self.day_name else "Unnamed"
class TutorProfile(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availability = models.ManyToManyField(Day, blank=True)
You do not need to explicitly add tutor_availability nor user as serializer fields:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = "__all__"
class TutorProfileSerializer(serializers.ModelSerializer):
# Omitting `image_url` as not reflected in `models.py`
# image_url = serializers.SerializerMethodField('get_image_url')
class Meta:
model = TutorProfile
fields = "__all__"
With this viewset:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorProfileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
Then, after creating days with IDs 1 and 2 in admin, by sending the tutor_availability field as you are doing it, it should work. Request:
{
"user": 1,
"tutor_availability": [1, 2]
}
Response:
{
"id": 1,
"user": 1,
"tutor_availability": [
1,
2
]
}
Notice as well that I've changed availablility to availability and that it may be unsafe to allow authenticated users to pass the user field in the request, you may want to infer that from the user who makes the request.
In your TutorProfileSerializer you are using the DaySerializer for tutor_availablility field so when you do a post request your post action will wait for a list of dict, what you you need to do in first is to delete this line : from your TutorProfileSerializer and it will works.
tutor_availablility = DaySerializer(many=True)
If you still have the problem then you need to verify the validate method of the TutorProfileSerializer.
And if it works but you want a list of dict(of Day object) for GET request, you need to override the get_serializer_class() of your ViewSet and create two serializers one for post request and a second for get request:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
def get_serializer_class(self):
if self.action.method == 'GET':
return TutorGETProfileSerializer
return super(TutorprofileViewSet, self).get_serializer_class()
and the 2 serializers:
class TutorGETProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = TutorProfile
fields = '__all__'
read_only_fields = ('user',)

How to add the entry, which has foreign key, from frontend having only the pk of fk

Probably not the best heading, but I will try my best to explain the question.
I have two models set up in my Django app:
class Category(models.Model):
class Meta:
verbose_name_plural = "Categories"
name = models.CharField(max_length=20)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('home')
class Post(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
body = models.TextField()
post_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title + " | " + str(self.author)
def get_absolute_url(self):
return reverse('post-details', args=(str(self.id)))
with respective serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'first_name', 'last_name']
class CategorySerializer(ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name']
class PostSerializer(ModelSerializer):
category = CategorySerializer()
author = UserSerializer()
class Meta:
model = Post
fields = ['id', 'title', 'author', 'category', 'body', 'post_date']
and a default ViewSet for Post:
from rest_framework.viewsets import ModelViewSet
class PostViewSet(ModelViewSet):
serializer_class = PostSerializer
queryset = Post.objects.all().order_by('-post_date')
I want to add a new entry by sending an axios POST request to my Django app from the Vue frontend and I would like to do it in such way:
const formData = {
title: this.title,
category: this.category,
body: this.body,
author: this.$store.state.user_id,
}
axios({
method: "POST",
url: '/api/blog/posts/',
headers: {"Content-Type": "application/json"},
data: formData
})
.then(response => {
this.$router.push({ name: 'home' });
})
.catch(err=> {
console.log(err.response.data)
})
However, this obviously returns a Bad Request error, because I am passing the IDs of the author and category, and not the dictionaries with respective fields.
My question is: is it possible to pass only IDs in my request and create new entries based on them, or should I somehow get the user and category first, and return them in my POST request after?
I think you need to set the additional fields for uploading author id and the category id.
class PostSerializer(ModelSerializer):
category_id = serializers.IntegerField(write_only = True)
author_id = serializers.IntegerField(write_only = True)
category = CategorySerializer(read_only = True)
author = UserSerializer(read_only = True)
class Meta:
model = Post
fields = ['id', 'title', 'author', 'category', 'body', 'post_date', 'author_id', 'category_id']
And in the frontend, you need to upload integer ids.
const formData = {
title: this.title,
category_id: parseInt(this.category, 10),
body: this.body,
author_id: parseInt(this.$store.state.user_id, 10),
}

How to filter data dynamically by supply values from form using django

I want to filter Blog Post objects or records based on the Post Category and a User that uploaded the Post record, it gives me an error when I try to do filter, this is the error.
ValueError at /dashboard/filter-post/
The QuerySet value for an exact lookup must be limited to one result using slicing.
Here is my models.py
class Category(models.Model):
cat_name = models.CharField(max_length=100, verbose_name='Category Name')
cat_desc = models.TextField(blank=True, null=True)
def __str__(self):
return self.cat_name
class Meta():
verbose_name_plural='Category'
class Post(models.Model):
pst_title = models.CharField(max_length=150)
pst_image = models.ImageField(blank=True, null=True, upload_to='uploads/')
user = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.pst_title
#property
def img_url(self):
if self.pst_image:
return self.pst_image.url
on forms.py
class FilterForm(forms.ModelForm):
user = forms.ModelChoiceField(
queryset=User.objects.all(),
widget=forms.Select(attrs={'class': 'form-control'}))
category = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
widget=forms.SelectMultiple(attrs={'class': 'form-control js-example-disabled-results'}))
catch_bot = forms.CharField(required=False,
widget=forms.HiddenInput, validators=[validators.MaxLengthValidator(0)])
class Meta():
fields = ['user', 'category' ]
model = Post
on views.py
def filter_post(request):
post = FilterForm(request.GET)
queryset = Post.objects.all()
if post.is_valid():
user=post.cleaned_data.get('user')
category=post.cleaned_data.get('category')
if user and category:
queryset = queryset.filter(user__username=user, category__cat_name=category)
return render(request, 'backend/filter-post.html', {'query':queryset, 'post':post})
I am having challenges properly filtering this in my views any help?
Try this:
instead of this:
queryset = queryset.filter(user__username=user, category__cat_name=category)
use this:
queryset = queryset.filter(user=user, category=category)
Also don't name your model fields after the model name, just use name instead of pst_name or cat_name, you will see that when you will try access these values there will be no confusion.
UPDATE
Ok, maybe try to rewrite your view like this:
def filter_post(request):
posts = Post.objects.all()
form = FilterForm(request.GET) # its best practice to call your form instance `form` in the view so that the next line has better readability
if form.is_valid():
user=post.cleaned_data['user']
category=post.cleaned_data['category']
if user:
posts = posts.filter(user=user)
if category:
posts = posts.filter(category=category)
return render(request, 'backend/filter-post.html', {'posts':posts})

Django list data from dfiferent models in List View with correct ordering

Heys guys.. I am making a Django project which is a simple clone of Twitter.. Got the idea from Justin Mitchell's Udemy course..
So i implemented a Tweet model and a Retweet model which has ForeignKey to the original Tweet and the User..
The thing is that in the homepage i want both the Tweets and Retweets to show and in the order they were created..
I am using Django Rest Framework for the CRUD functionality of Tweet using ModelViewSet
Any idea on how i achieve that using Rest Framework or if that isn't possible could you please give me some other idea..
Thank you in advance..
models.py
class Tweet(models.Model):
content = models.CharField(max_length=140)
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class Meta:
ordering = "-created_on", "content", "user",
def __str__(self):
return self.content
def get_absolute_url(self):
return reverse("tweet_api:tweet-detail", args=[self.id])
class Retweet(models.Model):
tweet = models.ForeignKey(Tweet, on_delete=models.CASCADE, related_name="retweet")
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = "-created_on", "user",
def __str__(self):
return self.tweet.content
serializers.py
class TweetSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
created_on = serializers.SerializerMethodField()
date_display = serializers.SerializerMethodField()
class Meta:
model = models.Tweet
fields = "id", "content", "created_on", "date_display", "user",
def get_created_on(self, obj):
return obj.created_on.strftime("%I:%M %p - %d %b %Y")
def get_date_display(self, obj):
obj_date = obj.created_on
days = (timezone.datetime.now() - obj_date).days
if days > 0:
return obj_date.strftime("%d %b")
else:
return naturaltime(obj_date)
class RetweetSerializer(serializers.ModelSerializer):
tweet = TweetSerializer()
user = UserSerializer(read_only=True)
date_display = serializers.SerializerMethodField()
class Meta:
model = models.Retweet
fields = "id", "tweet", "user", "created_on", "date_display",
def get_date_display(self, obj):
obj_date = obj.created_on
days = (timezone.datetime.now() - obj_date).days
if days > 0:
return obj_date.strftime("%d %b")
else:
return naturaltime(obj_date)
views.py
class TweetViewSet(ModelViewSet):
serializer_class = serializers.TweetSerializer
queryset = models.Tweet.objects.all()
pagination_class = DefaultPagination
filter_backends = filters.SearchFilter,
search_fields = "content", "user__username", "user__first_name", "user__last_name",
def perform_create(self, serialiazer):
return serialiazer.save(user=self.request.user)
class RetweetViewSet(ModelViewSet):
serializer_class = serializers.RetweetSerializer
queryset = models.Retweet.objects.all()
pagination_class = DefaultPagination
filter_backends = filters.SearchFilter,
search_fields = "tweet__content", "user__username", "user__first_name", "
As Tweet and Retweet data reside on completely different models, what you want to do is not simple with your current model structure. To combine them in a single view, you'd need to override many things on ViewSet, and do the operations like sorting in-memory, which would not be scalable. One possible solution could be to use model inheritance, have a base model for both Tweet and Retweet, and build your serialzier and view set on that model. A model structure like the following could be used:
class Post(models.Model):
"""
Base model for user posts
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
class Tweet(Post):
content = models.CharField(max_length=140)
updated_on = models.DateTimeField(auto_now=True)
class Retweet(Post):
tweet = models.ForeignKey(Tweet, on_delete=models.CASCADE, related_name="retweet")
With these models in-place, you can create a PostSerializer and PostViewSet, and use these only for listing posts, you can keep using Tweet and Retweet views and serializers for creating and updating.