How to get origin model's name in django queryset ? - django

I'm using django restframework and get a complete queryset at start in every view.
models.py
class MAccount(BasicModel):
account_id = models.CharField(max_length=45, verbose_name='ID', null=False)
...
class Meta:
db_table = 'account'
unique_together = ('account_id', 'medium',)
ordering = ['-updated_time', '-created_time', '-id']
app_label = 'account'
def __str__(self):
return "account"
CustomModelViewSet general viewset that I want to make different work by different model name in dispatch method.
class CustomModelViewSet(viewsets.ModelViewSet):
parser_classes = [JSONParser, ]
pagination_class = Pagination
# permission_classes = [IsAuthenticated, BaseDataPermission]
def dispatch(self, request, *args, **kwargs):
query_model = self.queryset.model
print(str(query_model))
# how can I get model's name as account here ?
VSAccount specified view for actual work
class VSAccount(CustomModelViewSet):
queryset = MAccount.objects.all().filter(active=True)
My question is how can I get the name of MAccount in dispatch method in CustomModelViewSet?

Try this method:
your_queryset.model.__name__
But actually you may look at self.action field in view class. It contains a string with current action name

Related

Add a field to DRF generic list view

I need to create a DRF list view that shows each course along with a boolean field signifying whether the user requesting the view is subscribed to the course.
Course subscriptions are stored in the following model:
class Subscription(models.Model):
user = models.ForeignKey(
CustomUser, related_name='subscriptions', null=False,
on_delete=CASCADE)
course = models.ForeignKey(
Course, related_name='subscriptions', null=False,
on_delete=CASCADE)
class Meta:
ordering = ['course', 'user']
unique_together = [['user', 'course']]
This is the view I am writing:
class CourseListView(generics.ListAPIView):
permission_classes = [IsAuthenticated, ]
queryset = Course.objects.all()
serializer_class = CourseSerializer
def isSubscribed(self, request, course):
sub = Subscription.objects.filter(
user=request.user, course=course).first()
return True if sub else False
def list(self, request, format=None):
queryset = Course.objects.all()
serializer = CourseSerializer(queryset, many=True)
return Response(serializer.data)
I am looking for a way to modify the list method, so as to add to the response the information about whether request.user is subscribed to each of the courses.
The best solution I have for now is to construct serializer manually, which would look (at the level of pseudo-code) something like this:
serializer = []
for course in querySet:
course['sub'] = self.isSubscribed(request, course)
serializer.append(CourseSerializer(course))
I suspect there should be a better (standard, idiomatic, less convoluted) way for adding a custom field in a list view, but could not find it. In addition, I am wondering whether it is possible to avoid a database hit for every course.
You can do that easily with Exists:
just change your queryset in your view:
from django.db.models import Exists, OuterRef
class CourseListView(generics.ListAPIView):
permission_classes = [IsAuthenticated, ]
serializer_class = CourseSerializer
def get_queryset(self):
subquery = Subscription.objects.filter(user=request.user, course=OuterRef('id'))
return Course.objects.annotate(sub=Exists(subquery))
and add a field for it in your serializer:
class CourseSerializer(serializers.ModelSerializer):
sub = serializers.BooleanField(read_only=True)
class Meta:
model = Course
fields = '__all__'

How to have a custom endpoint for a model's field in Django?

I want to have an endpoint for every image I want to add tags in.
Suppose I have:
#models.py
class ImageTag(models.Model):
name = models.CharField()
description = models.CharField()
class Image(models.Model):
image_id = models.CharField(unique=True)
image_tags = models.ManyToManyField(ImageTag, blank=True)
...
#serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = '__all__'
#views.py
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
lookup_field = 'image_id'
...
#urls.py
router.register(r'images', ImageViewSet, basename='image')
I want to POST and DELETE image_tags in an endpoint such as:
localhost:8000/my_app/images/IMG_123/image_tags
where: IMG_123 is an Image
I think I will need a separate serializer and viewset for that. But more importantly I want to know how will I add such endpoint in the router in urls.py
I am looking for something like this:
router.register(r'<image>/image_tags', ImageTagViewSet, basename='image_tag')
NOTE: I was able to change Image endpoint using its image_id (instead of ID) because of lookup_field in ImageViewSet, thus can be:
localhost:8000/my_app/images/IMG_123/
you can create custom endpoint for a viewset whit defining extra actions
you will create a method with action decorator and the router of this viewset will create url from that method name for you for example:
from rest_framework.decorators import action
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
lookup_field = 'image_id'
#action(detail=True, methods=['post'])
def image_tags(self, request, *args, **kwargs):
image_instance = self.get_object() # image that it's id has been passed by url
# you can now filter and get your ImageTags and serialize it with a serializer
this will create an endpoint like you want it
localhost:8000/my_app/images/IMG_123/image_tags/
views.py
class ImageViewSet(viewsets.ModelViewSet):
image_id = self.kwargs['id']
queryset = Image.objects.get(image_id=image_id)
serializer_class = ImageSerializer
lookup_field = 'image_id'
...
urls.py
router.register(r'(?P<id>[a-zA-Z0-9_]+)/image_tags$', ImageTagViewSet, basename='image_tag')

Annotated django queryset is getting ignored during serialization

I have a ModelViewSet where I want to annotate the list() response. I've extended the queryset with an annotation and added the field to the serializer, but the serializer just ignores the new data and doesn't add the field at all in the final data.
I am using a customized get_queryset() too (show abbreviated here) which is definitely getting called and producing the right annotations. It just doesn't show up in the REST API response.
If I set default=None on the serializer field definition, it appears in the response.
class SequenceSerializer(serializers.ModelSerializer):
unread=serializers.IntegerField(read_only=True)
.....
class SequenceViewSet(viewsets.ModelViewSet,ScopedProtectedResourceView):
authentication_classes = [OAuth2Authentication]
queryset = Sequence.objects.all()
serializer_class = SequenceSerializer
.....
def get_queryset(self):
queryset = Sequence.objects.all().filter(<..... some filter>)
queryset = queryset.annotate(unread=FilteredRelation('unreadseq',
condition=Q(unreadseq__userid=self.request.user)))
print("Seq with unread",queryset.values('id','unread')) ## <<<<this shows the correct data
return queryset
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) ##<<< this is missing the annotation
I have been banging my head against this all day and I can't for the life of me see what's going wrong.
Any ideas please?
--
more info:
class UnreadSeq(models.Model):
userid = models.ForeignKey('auth.User', on_delete=models.CASCADE)
seqid = models.ForeignKey(Sequence, on_delete=models.CASCADE)
class Meta:
unique_together=('seqid','userid')
verbose_name = "UnreadSeq"
verbose_name_plural = "UnreadSeqs"
class Sequence(models.Model):
userid = models.ForeignKey('auth.User', on_delete=models.SET_NULL,null=True)
topic = models.ForeignKey('Topic',on_delete=models.CASCADE,null=False,blank=False)
.....
class Meta:
verbose_name = "Sequence"
verbose_name_plural = "Sequences"
I think that this annotation don't return Integer. Try to annotate (you want to COUNT unreadseq) like this:
def get_queryset(self):
mytopics=getMyTopics(self.request,False)
queryset = Sequence.objects.all().filter(<..... some filter>)
count_unreadseq = Count('unreadseq', filter=Q(unreadseq__userid=self.request.user))
queryset=queryset.annotate(unread=count_unreadseq)
...
EDITED after comments to get unreadseq ids
def get_queryset(self):
mytopics=getMyTopics(self.request,False)
queryset = Sequence.objects.all().filter(<..... some filter>)
unreadseq_ids = UnreadSeq.objects.filter(seqid=OuterRef('pk'), userid=self.request.user).values('pk')
queryset=queryset.annotate(unread=Subquery(unreadseq_ids))
...
Also you need to edit serializer:
class SequenceSerializer(serializers.ModelSerializer):
unread=serializers.IntegerField(read_only=True)
.....

Django Rest Framework Nested Routes - PK alternatives

I am using drf-nested-routers to nest my resources and everything is working well. I would like, however, to use something other than the pk to refer to a parent object.
What I currently have is:
api/movies/4/scenes - generates a list of scenes from movie with pk=4.
What I would like is:
api/movies/ghost-busters/scenes - where the identifier is movie.title instead of movie.pk
Any suggestions?
Thanks
you can use slug for the url you want to make "api/movies/ghost-busters/scenes"
at first you have to make model with slugField eg.
class Blog(models.Model):
qoute = models.CharField(max_length=30)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.qoute)
super(Blog, self).save(*args, **kwargs)
during the saving model it will create a slug by "qoute" and save to the column "slug"
make your urls.py entry
url(r'^api/movies/(?P<slug>[\w-]+)/scenes/$', 'myapp.views.blog_detail', name='blog_detail'),
then for the drf you have set lookup_field in the serializer and view also.
N.B: you can user ModelSerializer or Serializer or HyperlinkSerialzer as you wish..
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ('quote', 'slug',)
lookup_field = 'slug'
and the views..
class blog_detail(generics.RetrieveUpdateDestroyAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
lookup_field = 'slug'

Filtering using viewsets in django rest framework

Please consider these three models:
class Movie(models.Model):
name = models.CharField(max_length=254, unique=True)
language = models.CharField(max_length=14)
synopsis = models.TextField()
class TimeTable(models.Model):
date = models.DateField()
class Show(models.Model):
day = models.ForeignKey(TimeTable)
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
And each of them has their serializers:
class MovieSerializer(serializers.HyperlinkedModelSerializer):
movie_id = serializers.IntegerField(read_only=True, source="id")
class Meta:
model = Movie
fields = '__all__'
class TimeTableSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TimeTable
fields = '__all__'
class ShowSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Show
fields = '__all__'
And their routers
router.register(r'movie-list', views.MovieViewSet)
router.register(r'time-table', views.TimeTableViewSet)
router.register(r'show-list', views.ShowViewSet)
Now I would like to get all the TimeTable objects (i.e. date list) by filtering all the Show objects by a specific movie object. This code seems to be the working and getting the list like I want it
m = Movie.objects.get(id=request_id)
TimeTable.objects.filter(show__movie=m).distinct()
But I have no clue how to use this in django rest framework? I tried doing this way (which I am pretty sure its wrong), and I am getting error:
views.py:
class DateListViewSet(viewsets.ModelViewSet, movie_id):
movie = Movie.objects.get(id=movie_id)
queryset = TimeTable.objects.filter(show__movie=movie).distinct()
serializer_class = TimeTableSerializer
urls.py:
router.register(r'date-list/(?P<movie_id>.+)/', views.DateListViewSet)
error:
class DateListViewSet(viewsets.ModelViewSet, movie_id):
NameError: name 'movie_id' is not defined
How can I filter using viewsets in django rest framework? Or if there is any other prefered way than please list it out. Thank you.
ModelViewSet by design assumes that you want to implement a CRUD(create, update, delete)
There is also a ReadOnlyModelViewSet which implements only the GET method to read only endpoints.
For Movie and Show models, a ModelViewSet or ReadOnlyModelViewSet is a good choice whether you want implement CRUD or not.
But a separate ViewSet for a related query of a TimeTable which describes a Movie model's schedule doesn't looks so good.
A better approach would be to put that endpoint to a MovieViewSet directly. DRF provided it by #detail_route and #list_route decorators.
from rest_framework.response import Response
from rest_framework.decorators import detail_route
class MovieViewSet(viewsets.ModelViewset):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
#detail_route()
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
This endpoint will be available by a movie-list/:id/date_list url
Docs about extra routes
Register your route as
router.register(r'date-list', views.DateListViewSet)
now change your viewset as shown below,
class DateListViewSet(viewsets.ModelViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
Use a retrieve method, which will match any GET requests to endpoint /date-list/<id>/.
Advantage is that you don't have to explicitly handle the serialization and returning response you make ViewSet to do that hard part. We are only updating the queryset to be serialized and rest framework does the rest.
Since ModelViewSet is implemented as,
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
Its implementation includes the following methods (HTTP verb and endpoint on bracket)
list() (GET /date-list/)
create()(POST /date-list/)
retrieve()(GET date-list/<id>/)
update() (PUT /date-list/<id>/)
partial_update() (PATCH, /date-list/<id>/
destroy() (DELETE /date-list/<id>/)
If you want only to implement the retrieve() (GET requests to endpoint date-list/<id>/), you can do this instead of a `ModelViewSet),
from rest_framework import mixins, views
class DateListViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
The error
class DateListViewSet(viewsets.ModelViewSet, movie_id): NameError: name 'movie_id' is not defined
happens because movie_id is being passed as parent class of DataListViewSet and not as parameter as you imagined
This example in the documentation should be what you are looking for.
Adjust your URL:
url(r'date-list/(?P<movie_id>.+)/', views.DateListView.as_view())
Adjust your Model:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Your view would look like this:
class DateListView(generics.ListAPIView):
serializer_class = TimeTableSerializer
def get_queryset(self):
movie = Movie.objects.get(id=self.kwargs['movie_id'])
return TimeTable.objects.filter(show__movie=movie).distinct()
Another way to do it would be:
Adjust your URL:
router.register(r'date-list', views.DateListViewSet)
Adjust your Model:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Your view would look like this:
class DateListViewSet(viewsets.ModelViewSet):
serializer_class = TimeTableSerializer
queryset = TimeTable.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('show__movie_id')
Which will allow you to make requests such as:
http://example.com/api/date-list?show__movie_id=1
See documentation
Ivan Semochkin has the correct answer but the detail decorator is deprecated. It was replaced by the action decorator.
from rest_framework.decorators import action
class MovieViewSet(viewsets.ModelViewset):
#action(detail=True)
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
To improve #all-is-vanity answer, you can explicitly use movie_id as a parameter in the retrieve function since you are overriding the lookup_field class property:
def retrieve(self, request, movie_id=None):
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
You can also call self.get_object() to get the object:
def retrieve(self, request, movie_id=None):
movie = self.get_object()
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)