I had a function based view that looked like this:
def account_details(request, acc_id):
account = get_object_or_404(Account, pk=acc_id, person__user=request.user)
# ...
Which shows you details of your account on success, and 404 if you don't have permissions to access the account or it doesn't exist.
I was trying to implement the same using a class based view (extending DetailView), and came up with this:
class AccountDetailView(DetailView):
def get_object(self, queryset=None):
obj = super(AccountDetailView, self).get_object(queryset)
if obj.person.user != self.request.user:
raise Http404()
return obj
The urlconf:
url(r'^account_details/(?P<pk>[0-9a-f]{24})$',
login_required(AccountDetailView.as_view(model=Account)),
name='account_details'),
This attitude works, but introduces 2 extra queries, and looks wrong.
Is there a standard or a more elegant way to achieve the same result?
What arguments would you need to pass to get_queryset anyways? This should do it:
def get_queryset(self):
qs = super(MyView, self).get_queryset()
return qs.filter(person__user=self.request.user)
If you are worried about the queries, you can use select_related to prefetch the user profiles in the queryset:
def get_queryset(self)
return Account.objects.select_related("person", "person__user").all()
def get_object(self, queryset=None):
try:
return queryset.get(pk=self.kwargs['acc_id'], person__user=self.request.user)
except Account.DoesNotExist:
raise Http404
I have to say, it's sometimes difficult to get things to fit with class-based views
Related
I have a class-based view for text messages inheriting from the django generic view ListAPIView. As you can probably tell from the inheriting class name, the view is being used as an API with pagination (from the Django Rest framework).
I would like to turn off pagination for some specific queries, however even when I try turning off pagination for all queries via this stack overflow question (Turn off automatic pagination of Django Rest Framework ModelViewSet), I get the following error:
RuntimeError: Do not evaluate the `.queryset` attribute directly, as the result will be cached and reused between requests. Use `.all()` or call `.get_queryset()` instead.
I am overwriting the get_queryset() method in the view without issue, however by just setting the paginator_class variable to None I am receiving this error. Any help would be appreciated. Here's some code:
view.py:
class TextMessageView(generics.ListAPIView):
queryset = TextMessage.objects.all()
serializer_class = TextMessageSerializer
pagination_class = None
def get_queryset(self):
"""
If a phone number is included in the url query, return only the text messages sent by that number in the
queryset, otherwise return all text messages.
:return: A Django queryset with a variable number of text messages.
"""
from_phone_num = self.request.query_params.get('from_phone_number', None)
distinct_nums = self.request.query_params.get('distinct_numbers', None)
all_msgs = self.request.query_params.get('all_messages', None)
if from_phone_num:
return TextMessage.objects.filter(from_phone_number=from_phone_num)
elif distinct_nums == 'True':
return TextMessage.objects.order_by('from_phone_number', '-date_of_message').distinct('from_phone_number')
elif all_msgs == 'True':
return self.queryset
else:
raise Http404
Django Rest Frameworks: ListAPIView.py
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Django Rest Frameworks: mixins.py
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
you'll have to look up GenericAPIView.py because it's too big
Looking at the error message, in your get_queryset method, can you try changing
elif all_msgs == 'True':
return self.queryset
to
elif all_msgs == 'True':
return self.queryset.all()
?
It's a simple question. I've organised my models so that most objects served to the page are of one type - Item. The model contains various attributes which help me serve it in different ways.
I have articles, and videos, which are determined by a 'type' field on the model. Type = 'article' etc.
I have a listview, which shows all the objects in the Item model, sorted by date.
class ItemListView(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.all().order_by('-create_date')
I want a view which shows all the articles, sorted by date, and all the videos, sorted by date. I have a feeling I'll be writing many more such views.
Is there a better way to do it than to write a new view for every single query? As, this is what I'm currently doing:
Views.py
class ItemListViewArticle(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.filter(type__contains='article').order_by('-create_date')
class ItemListViewVideo(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.filter(type__contains='video').order_by('-create_date')
urls.py
path('new/', views.ItemListView.as_view(), name='new_list'),
path('new/articles', views.ItemListViewArticle.as_view(), name='article_list'),
path('new/videos', views.ItemListViewVideo.as_view(), name='video_list'),
You can use URL querystring(ie request.GET) to get type of the item from url and filter by it. Like this:
path('new/', views.ItemListView.as_view(), name='new_list'),
class ItemListViewArticle(generic.ListView):
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
content_type = self.request.GET.get('type')
return Item.objects.filter(type__contains=content_type).order_by('-create_date')
# usage
localhost:8000/new/?type=article
localhost:8000/new/?type=video
Or you can use URL parameter to get the type of data:
path('new/<str:content_type>/', views.ItemListView.as_view(), name='new_list'),
class ItemListViewArticle(generic.ListView):
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
content_type = self.kwargs.get('content_type')
return Item.objects.filter(type__contains=content_type).order_by('-create_date')
# usage
localhost:8000/new/article/
localhost:8000/new/video/
In the Django admin list view, i want to hide products without a name. I just excluded them in the get_query_set() method.
def get_queryset(self, request):
qs = super(ProductAdmin, self).get_queryset(request)
return qs.exclude(name=None)
This is working perfect, but i want to be able to access products without a name in the admin detail page (with a direct link).
When it try this, i have an error, because it filters the product out of the queryset:
http://127.0.0.1/product/123
product object with primary key u'123' does not exist.
Is there a workaround for this problem? If possible, without adding a custom filter to the list view.
Well, you could override get_object
def get_object(self, request, object_id, from_field=None):
"""
Returns an instance matching the field and value provided, the primary
key is used if no field is provided. Returns ``None`` if no match is
found or the object_id fails validation.
"""
model = product
field = model._meta.pk if from_field is None else model._meta.get_field(from_field)
try:
object_id = field.to_python(object_id)
return model.objects.get(**{field.name: object_id})
except (model.DoesNotExist, ValidationError, ValueError):
return None
This is undocumented but can be made to work. The standard way though is to use a custom filter.
You could check if you are in change or changelist
def get_queryset(self, request):
qs = super(ProductAdmin, self).get_queryset(request)
if request.resolver_match.url_name == "APPNAME_MODELNAME_changelist":
return qs.exclude(name=None)
return qs
Django version: 1.9
So for my project I was trying to implement custom permissions for a view. I created the permission in permissions.py, which looks like this:
class TeamViewPermission(permissions.BasePermission):
"""
Global permission for viewing team pages
"""
def has_permission(self, request, view):
team_id = self.kwargs.get('team_id')
teamqs = MAIN_TEAMS.all()
pk_list = []
for item in MAIN_TEAMS:
pk_list.append(str(item.pk))
if team_id in pk_list:
return True
return False
Pretty simple, checks if the configuration team is matching the team page you're requesting and blocking the user out if that is not the case.
The views.py:
class PlayerList(ListView):
model = player_model
template_name = 'player_list.html'
permission_classes = (TeamViewPermission, )
def get_team(self):
if not hasattr(self, '_team'):
team_id = self.kwargs.get('team_id')
self._team = team_model.objects.get(pk=self.kwargs.get('team_id'))
return self._team
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['team'] = self.get_team()
return context
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
return queryset.filter(team_id=self.kwargs.get('team_id'))
I know for a fact that the page returns True or False when it should, because I debugged it, although it's not blocking the page out? It returns False for the page, but I can still access the page like it returned True .. Am I missing something here?
It looks like you are mixing djangos class based views and the DRF views.
ListView is a class based view from django and NOT from DRF. It therefore does not allow to set permission_classes.
Check the docs to see how to use DRF api views.
I've been trying to create a Django generic deleteview, to delete an instance of a model.
I however have to check whether it is allowed to delete this item. This is done using a method defined in the model.
So far I've created this:
#login_required
def delete_employee(request, pk):
employee = None
try:
employee = Employee.objects.get(pk=pk)
except:
pass
if employee and not employee.empty():
return render(request, "error.html", None)
else:
# Load the generic view here.
Is this a decent way to go? And how can I load the generic view there?
I've tried things like EmployeeDelete.as_view() but those things don't work.
Or is there a way to check this in the generic view itself?
I've tried that as well, but I wasn't able to load an error page, just throw errors.
To do this with a DeleteView you can do this just by overriding the delete method on your inherited view. Here is an example based on what you have said. This is just an example of how you can accomplish it. You might need to tweak it for your exact scenario, specifically the else on can_delete
class EmployeeDeleteView(DeleteView):
success_url = reverse_lazy('index')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
can_delete = self.object.can_delete()
if can_delete:
return super(EmployeeDeleteView, self).delete(
request, *args, **kwargs)
else:
raise Http404("Object you are looking for doesn't exist")