I have a model
class ModelName(models.Model):
type = models.ForeignKey(AnotherModel)
slug = models.SlugField(editable=False)
class Meta:
unique_together = (('type', 'slug'),)
#models.permalink
def get_absolute_url(self):
return ('model_detail', (), {'type': self.type.slug, 'slug': self.slug})
and urls
urlpatterns = patterns('',
url(r'^(?P<type>[-\w]+)/(?P<slug>[-\w]+)/$', ModelDetailView.as_view(), name='detail'),
)
and a DetailView
class ModelDetailView(DetailView):
model = MyModel
template_name = 'detail.html'
but I get the exception MultipleObjectsReturned because the slug isn't unique. I want the urls to be /type/slug/, so the model can contain two records with same slug, but different types, so urls could be /1/slug/ and /2/slug/ with different results. How can I tell the model to use both type and slug as lookup instead of just the slug?
You don't have to 'tell the model' to use the type and string fields -- it's the class based view you have to override.
I suggest you override the get_queryset method, to restrict the queryset to objects of the correct type. An alternative would be to override the get_object method.
class ModelDetailView(DetailView):
model = MyModel
template_name = 'detail.html'
def get_queryset(self):
"""
Restrict queryset to objects of the correct type
"""
return MyModel.objects.filter(type_id=self.kwargs['type'])
See the Django docs on dynamic filtering for more details.
Related
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
I am trying to implement a base serializer and I am following the "http://www.django-rest-framework.org/api-guide/serializers/#baseserializer".
My urls.py:
url(r'^auth/myuser/(?P<pk>[0-9]+)/profile/$', UserProfileViewSet.as_view({'patch':'update'}), name='user-profile'),
Views.py:
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = UserProfileSerializer
def get(self,request,pk,*args,**kwargs):
user_instance = CustomUser.objects.get(pk=pk)
dashboard_data = UserProfileSerializer(user_instance)
content = {'result': dashboard_data}
return Response(content)
Serializers.py:
class UserProfileSerializer(serializers.BaseSerializer):
def to_representation(self,obj):
return{
'email':obj.email,
'first_name':obj.first_name,
'last_name':obj.last_name,
'date_of_birth':obj.date_of_birth,
'gender':obj.get_gender_display(),
'location':obj.location,
'calling_code':obj.callingcode,
'phone_primary':obj.phone_primary,
'phone_secondary':obj.phone_secondary,
'website':obj.website
}
But I am getting the error "User object is not JSON serializable", and I don't find any attributes of the User obj that are not serializable.
I already found some answers on SO, but I don't find any similar steps in the django rest framework api guide. So looking for a solution that is in sync with the api-guide.
Serializer:
It seems what you need is a ModelSerializer
The ModelSerializer class provides a shortcut that lets you automatically create a Serializer class with fields that correspond to the Model fields.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = CustomerUser
fields = '__all__'
I have set fields attribute to __all__ to indicate that all the fields in the model are serialized. But, you can also specify which fields to include exaclty:
fields = ('email', 'first_name', 'last_name',) # etc.
ViewSet:
The second point is about the ModelViewSet where you don't really need to implement the get method because it is already implemented for you. You just need to declare the queryset and the serializer_class just like you have already done:
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = UserProfileSerializer
I guess you'll have to render the response in JSON format before returning it.
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = UserProfileSerializer
def get(self,request,pk,*args,**kwargs):
user_instance = CustomUser.objects.get(pk=pk)
dashboard_data = UserProfileSerializer(user_instance)
content = {'result': dashboard_data}
return JSONResponse(content, status=200)
You'll need following imports for this,
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
If this doesn't work just try passing dashboard_data to the JSONResponse function
when user goes to http://127.0.0.1:8000/movies/, I don't want to show the Showtimes
but when user goes to http://127.0.0.1:8000/movies/1/ , show it .
I want to ask if there is method to do this??
The method I use now is write 2 ModelSerializer to display it.
Please guide me . Thank you!!
This is my original code (not the 2 ModelSerializer method)
urls.py:
urlpatterns = patterns(
'',
url(r'^movies/$', MovieList.as_view(), name='movie-list'),
url(r'^movies/(?P<pk>[0-9]+)/$', MovieDetail.as_view(), name='movie-detail'),
This is my views.py :
class MovieMixin(object):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
class MovieFilter(django_filters.FilterSet):
class Meta:
model = Movie
fields = ['which_run',]
class MovieList(MovieMixin, generics.ListAPIView):
filter_class = MovieFilter
class MovieDetail(MovieMixin, generics.RetrieveAPIView):
pass
This is my serializes.py
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ('id', 'title','Showtimes',)
You can do this with two serializers, by swapping out the serializer in the get_serializer_class method on the serializer. This will work with all generic views, including ViewSet instances. In your case, you can also just override serializer_class on the detail view with the custom serializer.
class MovieMixin(object):
queryset = Movie.objects.all()
class MovieList(MovieMixin, generics.ListAPIView):
filter_class = MovieFilter
serializer_class = MovieSerializer
class MovieDetail(MovieMixin, generics.RetrieveAPIView):
serializer_class = MovieDetailSerializer
There are also plugins that allow you to do this, the most notable being the one included with drf-extensions.
These will both require a new serializer that will handle just the detail view representation.
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ('id', 'title', )
class MovieDetailSerializer(MovieSerializer):
class Meta(MovieSerializer.Meta:
fields = MovieSerializer.Meta.fields + ('Showtimes', )
This will allow you to have two different serialized responses for the list view and the detail view.
My models.py looks like this:
class Person(models.Model):
Name = models.CharField(max_length=100)
class Lecture(models.Model):
Speaker = model.ForeignKey(Person)
Topic = models.CharField(max_length=100)
Choices = ((1,"Upcoming"),(2,"In Progress",),(3,"Completed"))
Status = models.SmallIntegerField(choices=Choices, default=1, max_length=1)
My admin.py looks like this:
class LectureAdmin(admin.ModelAdmin):
def get_queryset(self):
return Lecture.objects.exclude(Status='Completed')
So my change list view in the django admin for the Lecture model shows only Lectures in "Upcoming" and "In Progress" status. This works fine.
Now I need to get the URL for the list of all lectures to be passed as a view somewhere else.The standard way of doing this in the django admin is by reversing the URL, so I do this:
urlresolvers.reverse('admin:%s_%s_changelist' % (app_label, model_name))
However, when I do this,I get the the filtered Queryset with Lectures in "Completed" state missing.How do I construct a url reverse function to get entire Lecture queryset and not the filtered queryset?
Here's a workaround, looks ugly, I understand.
Add all GET parameter to the changelist url:
url = urlresolvers.reverse('admin:%s_%s_changelist' % (app_label, model_name))
url += '?all'
Call get_queryset() on super(), exclude Completed status only if there is no all in request.GET:
class LectureAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(LectureAdmin, self).get_queryset(request)
if 'all' not in request.GET:
qs = qs.exclude(Status='Completed')
return qs
UPD (applying other filters from request.GET):
from xadmin.plugin.related import RELATE_PREFIX # or just set RELATE_PREFIX = '_rel_'
qs = qs.filter(**{key[len(RELATE_PREFIX):]: value
for key, value in request.GET.iteritems()
if key.startswith(RELATE_PREFIX)})
** unpacks the dictionary into keyword arguments.
Hope it works for you.
get_queryset() is the basic queryset used in admin listing, thus you wo'nt be able to get all the records if you override it this way.
Possible solutions:
use filters ( https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.filter_vertical ) to exclude unwanted records (these with Status='Completed'
or
create proxy model for Lecture, register it in admin and use modified get_queryset() in given listing. Proxy model is required because each model can have registered only single AdminModel class
models.py
class IncompletedLecture(Lecture):
class Meta:
proxy = True
admin.py
class IncompletedAdmin(admin.ModelAdmin):
def get_queryset():
return Lecture.query.exclude(Status='Completed')
admin.site.register(IncompletedLecture, IncompletedAdmin)
I have the following ModelAdmin:
class EventAdmin(admin.ModelAdmin):
# ModelAdmin config
def queryset(self, request):
queryset = super(EventAdmin, self).queryset(request)
return queryset.exclude(date_end__lt=date.today())
admin.site.register(Event, EventAdmin)
Now I want to add a model to manage archived (older than today) events.
class EventArchiveAdmin(admin.ModelAdmin):
# ModelAdmin config
def queryset(self, request):
queryset = super(EventArchiveAdmin, self).queryset(request)
return queryset.filter(date_end__lt=date.today())
admin.site.register(Event, EventArchiveAdmin)
But if I try to do so I get AlreadyRegistered exception.
Why can't I implement another ModelAdmin with same Model and how can I get different admin views of the same model?
I know I can implement a custom list_filter in my class but I'd like to keep things separated in different pages.
Use proxy models:
class Event(db.Model):
...
class ActiveEventManager(models.Manager):
def get_queryset(self):
return super(ActiveEventManager, self).get_queryset().filter(active=True)
class ActiveEvent(Event):
class Meta:
proxy = True
objects = ActiveEventManager()
class ArchiveEventManager(models.Manager):
def get_queryset(self):
return super(ArchiveEventManager, self).get_queryset().filter(active=False)
class ArchiveEvent(Event):
class Meta:
proxy = True
objects = ArchiveEventManager()
Now, you can register 2 models without override ModelAdmin.queryset method:
class EventAdmin(admin.ModelAdmin):
# ModelAdmin config
admin.site.register(ActiveEvent, EventAdmin)
admin.site.register(ArchiveEvent, EventAdmin)
You can read mode about proxy models and managers in the doc.
Also, use this:
queryset = super(EventArchiveAdmin, self).queryset(request)
As first argument super() take current class. See doc
Note: django has renamed Manager.get_query_set to Manager.get_queryset in django==1.7.