Modify field format in listview Django - python

Is there any good way that I can make modifications to individual fields of data in a list view? For example, I have a blog post model that looks like
class Blog(models.Model):
create_time = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=250)
document = models.TextField(blank=True)
and my list view looks like this:
class BlogPostListView(ListView):
model = Blog
template_name = 'blog/posts.html'
context_object_name = 'posts'
ordering = ['-create_time']
Obviously I want to display a list of blog posts in a table format, and I want to modify how the create_time looks (currently showing 'March 7, 2020, 10:16 a.m.', but I only want 2020-3-7 10:16); and the post document can be too long to display in a cell, I want to truncate it into only 150 words.
so what are the better ways to achieve this in a listview view?
(I read some sort-of similar questions in StackOverflow, but it is either done in the template such as reformating the time which does not solve my second requirement, or too vague to understand).

If you want to customise the representation between model and view. A faster way to do that is to use serializer and RetrieveAPIView of DRF. Check out installation link. Then, you can make the use of serializer to customise representations of model's fields.
Here is pseudo code:
from rest_framework import serializers
from rest_framework.generics import RetrieveAPIView
class BlogSerializer(serializers.ModelSerializer)
def get_create_time(self, blog):
return self.create_time..strftime("%m/%d/%Y, %H:%M)
def get_document(self, blog):
return self.document[:151]
class BlogPostListView(RetrieveAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
template_name = 'blog/posts.html'

As Chouvic suggested that I should use ModelSerializer, I managed to get it work in the listView. Here is my solution:
serializer:
class BlogListSerializer(serializers.ModelSerializer):
create_time_str = serializers.SerializerMethodField()
short_document_str = serializers.SerializerMethodField()
class Meta:
model = Blog
fields = '__all__'
def get_create_time_str(self, obj):
return obj.create_time.strftime("%Y/%m/%d")
def get_short_document_str(self, obj):
return obj.document[0:150]
as for the ListView:
class BlogListView(ListView):
model = Blog
template_name = 'blog/posts.html'
context_object_name = 'posts'
ordering = ['-create_time']
queryset = Blog.objects.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
posts = context['posts']
mod_posts = BlogListSerializer(posts, many=True)
context['posts'] = mod_posts.data
return context
BlogListSerializer(posts, many=True) takes in posts (a list of post) and return a new list of post each with the new 'attributes' of create_time_str and short_document_str.
In the template, I just need to refer it as <td>{{ post.create_time_str }}</td>.

Related

How to optimize N+1 SQL queries when serializing a post with mptt comments?

I have the following serializer for a detailed post:
class ArticleDetailSerializer(serializers.ModelSerializer):
author = ArticleAuthorSerializer(read_only=True)
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = '__all__'
Comment Serializer:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
def get_fields(self):
fields = super(CommentSerializer, self).get_fields()
fields['children'] = CommentSerializer(many=True, required=False, source='get_children')
return fields
When working with a list of comments, I get 2 sql queries if I work with get_cached_trees()
class CommentListAPIView(generics.ListAPIView):
serializer_class = serializers.CommentSerializer
queryset = Comment.objects.all().get_cached_trees()
But how do you get the same thing to work for an article with a list of comments?
class ArticleDetailAPIView(generics.RetrieveAPIView):
serializer_class = serializers.ArticleDetailSerializer
queryset = Article.custom.all()
lookup_field = 'slug'
def get_queryset(self):
queryset = self.queryset.prefetch_related(Prefetch('comments', queryset=Comment.objects.all().get_cached_trees()))
return queryset
I used prefetch_related() but it didn't work. I used Prefetch(), it gave me an error:
'list' object has no attribute '_add_hints'
I seem to be lost in the ability to optimize the mptt comments for the article.
But if you use the same comments, rendering according to the documentation in the Django template, then this problem is not observed. I ask for your help, dear programmers and experts.
This kind of solution worked for me.
class ArticleDetailSerializer(serializers.ModelSerializer):
author = ArticleAuthorSerializer(read_only=True)
comments = serializers.SerializerMethodField()
class Meta:
model = Article
fields = '__all__'
def get_comments(self, obj):
qs = obj.comments.all().get_cached_trees()
return CommentSerializer(qs, many=True).data
This reduced all queries from 11 SQL to 4.

Serializing model queryset showing empty [OrderedDict()]

I am building a blog app with React and Django and I am serializing model's instances saved by particular user, First I am just trying to test with .all() then I am planning to filter by specific user But when I serialize queryset with Serializer like:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
queryset = Blog.objects.all()
output_serializer = BlogSerializer(queryset, many=True)
print(output_serializer.data)
return "Testing"
It is showing in console:
[OrderedDict(), OrderedDict()]
and when I access it like
print(output_serializer)
Then it is showing:
BlogSerializer(<QuerySet [<Blog: user_1 - Blog_title>, <Blog: user_2 - second_blog_title>]>, many=True):
serializer.py:
class BlogSerializer(serializers.Serializer):
class Meta:
model = Blog
fields = ['title']
models.py:
class Blog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=30, default='')
def __str__(self):
return f"{self.user} - {self.title}"
What I am trying to do:
I am trying to serialize queryset to show on page in react frontend, I will relate with specific user later.
I have tried many times by changing CBV serialization method by generics.ListAPIView instead of viewsets.ModelViewSet but still same thing.
There is a concept error here. The get_queryset function is not supposed to return serialized data. It must return a QuerySet of model objects.
To achieve what you want you can just do:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.all()
The Django Rest Framework will take care of serializing data.
In fact, you can even do it way more simple. Defining the view's queryset field like this:
class BlogSerializerApiView(viewsets.ModelViewSet):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
Additional:
You said you will relate to current user later. You could achieve that in fact in the get_queryset method filtering aginst the user
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.filter(user_id=USER_ID)
Hope this helps!
I was using
class BlogSerializer(serializers.Serializer):
.......
so it was showing empty results (no idea why, I think its deprecated)
After replaceing it with
class BlogSerializer(serializers.HyperlinkedModelSerializer):
It worked

How to Change Values of Serialized Data in RetrieveUpdateDestroyAPIView

First, I would like to present how my managers, models, serializers and views look like upfront.
class PublishedManager(models.Manager):
"""
Only published articles. `due_date` is past.
"""
def get_queryset(self):
now = timezone.now()
return super().get_queryset().filter(due_date__lt=now)
class UnpublishedManager(models.Manager):
"""
Only unpublished articles. `due_date` is future.
"""
def announced(self):
return self.get_queryset().filter(announced=True)
def get_queryset(self):
now = timezone.now()
return super().get_queryset().filter(due_date__gt=now)
class Article(models.Model):
content = models.TextField()
due_date = models.DateTimeField()
announced = models.BooleanField()
# managers
objects = models.Manager() # standard manager
published = PublishedManager()
unpublished = UnpublishedManager()
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ("content", "due_date")
class ArticleRUDView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = ArticleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Article.objects.all()
In this code, ArticleRUDView naturally responds with all Article because of Article.objects.all(), yet this is not what I want to do. What I want to do is:
If the user is authenticated, then Article.objects.all().
If the user is anonymous,
If the entry is published (which means its due_date is less than now), then serialize all fields.
If the entry is not published (which means its due_date is greater than now), then still serialize, but content should be null in JSON.
Or, in short, how do I alter the serializer's data in a view?
Troubleshooting
This section might get updated in time. I will elaborate on what I find.
Overriding get_serializer Method from GenericAPIView
So I've found out I can get an instance of ArticleSerializer. So I did below:
def get_serializer(self, *args, **kwargs):
serializer = super().get_serializer()
if self.request.user.is_authenticated:
return serializer
obj = self.get_object() # get_object, hence the name, gets the object
due_date = obj.due_date
now = timezone.now()
if due_date > now:
serializer.data["content"] = None
return serializer
However, my tests didn't go well at all. This, oddly, returns an empty string on content field in JSON. I've tried different things but got that empty string. I do not have any single clue about what to do from here.
Environment
Python 3.7.4
Django 2.2.7
Django Rest Framework 3.10.3
I think you want to use get_serializer_class as opposed to get_serializer. You can allow the serializer class to choose what to stick in content instead of all the mucking around with managers, since you want to serialize all objects anyway. Something like this should work:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ("content", "due_date")
class AnonymousArticleSerializer(ArticleSerializer):
content = serializers.SerializerMethodField()
#staticmethod
def get_content(obj):
if obj.due_date > timezone.now():
return None
return obj.content
class ArticleRUDView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Article.objects.all()
def get_serializer_class(self):
if self.serializer_class:
return self.serializer_class
if self.request.user.is_authenticated:
self.serializer_class = ArticleSerializer
else:
self.serializer_class = AnonymousArticleSerializer
return self.serializer_class
One thing I don't like about this solution is that if you have a more complicated serializer field you're overwriting, you'd have to put the logic somewhere, but in this case (context being a text field) it's pretty simple.

Filter "get_context_data" using multiple data on a ListView

In my ListView I would like to filter the data by the current user logged from the context_data in :
views.py
class DashboardListView(LoginRequiredMixin,ListView):
model = Links
template_name = 'dashboard/home.html'
context_object_name ='links_list'
paginate_by = 15
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['dashboard_list']= Dashboard.objects.filter()[:15]
context['todo_list']= Todo.objects.all().order_by('-pk')[:15]
context['todo_complete']= Todo.objects.all().count()
context['PasswordUsername_list']= PasswordUsername.objects.all()
return context
I tried to override with a query_set but it does work only for the links model
Well, you need to filter all those queries.
context['dashboard_list']= Dashboard.objects.filter(user=self.request.user)[:15]
context['todo_list']= Todo.objects.filter(user=self.request.user).order_by('-pk')[:15]
etc - assuming your models all have a user FK field pointing to the User model.
See the answer below it's working but do not forget to add :
def get_queryset(self):
return self.model.objects.filter(user=self.request.user)
To your ListView otherwise the first model will still be shown.

Cannot POST using Rest API

So I can retrieve my data perfectly fine but when I try to post I get
{"detail":"Method \"POST\" not allowed."}
views.py
class ClubFullList(generics.ListAPIView):
serializer_class = ClubSerializer
def get_queryset(self):
return Club.objects.all()
class ClubList(generics.ListAPIView):
serializer_class = ClubSerializer
def get_queryset(self):
username = self.kwargs['username']
return Club.objects.filter(abv=username)
models.py
class Club(models.Model):
name = models.CharField(max_length=255)
abv = models.CharField(max_length=255)
serializers.py
class ClubSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Club
fields = ['name', 'abv']
How can I solve this?
You are sending POST request on an endpoint which only allows GET request.
ListAPIView is a read-only generic view. To create model objects using POST request, use CreateAPIView or ListCreateAPIView.
From the docs for ListApiView:
Used for read-only endpoints to represent a collection of model instances.
If you want to post to your endpoint, you'll need to use a different view class.

Categories