I have a Django generic List View that I want to filter based on the value entered into the URL.
For example, when someone enters mysite.com/defaults/41 I want the view to filter all of the values matching 41.
I have come accross a few ways of doing this with function based views, but not class based Django views.
I have tried:
views.py
class DefaultsListView(LoginRequiredMixin,ListView):
model = models.DefaultDMLSProcessParams
template_name = 'defaults_list.html'
login_url = 'login'
def get_queryset(self):
return models.DefaultDMLSProcessParams.objects.filter(device=self.kwargs[device])
urls.py
path('<int:device>', DefaultsListView.as_view(), name='Default_Listview'),
You are close, the self.kwargs is a dictionary that maps strings to the corresponding value extracted from the URL, so you need to use a string that contains 'device' here:
class DefaultsListView(LoginRequiredMixin,ListView):
model = models.DefaultDMLSProcessParams
template_name = 'defaults_list.html'
login_url = 'login'
def get_queryset(self):
return models.DefaultDMLSProcessParams.objects.filter(
device_id=self.kwargs['device']
)
It is probably better to use devide_id here, since then it is syntactically clear that we compare identifiers with identifiers.
It might also be more "idiomatic" to make a super() call, such that if you later add mixins, these can "pre-process" the get_queryset call:
class DefaultsListView(LoginRequiredMixin,ListView):
model = models.DefaultDMLSProcessParams
template_name = 'defaults_list.html'
login_url = 'login'
def get_queryset(self):
return super(DefaultsListView, self).get_queryset().filter(
device_id=self.kwargs['device']
)
Related
I am trying to create a single class based view for retrieving, listing, and creating all of my orders. I have gotten create and retrieve to work but listing is giving some problems. I am using DRF generic views to extend my view and have added the generics.ListApiView to my class. Once I added this however my retrieve route started acting unusual. It is returning to me a list of all the orders when I just want a specific one.
I tried to just add the generics.ListApiView to my class and override the list and get_queryset functions but that just started to affect my retrieve view.
class Order(generics.ListAPIView, generics.CreateAPIView, generics.RetrieveAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = addOrderSerializer
def get_queryset(self, **kwargs):
user = self.request.user
return Order.objects.filter(user=user)
def get_object(self):
pk = self.kwargs['pk']
obj = Order.objects.get(pk=pk)
return obj
def get_item_serializer(self, *args, **kwargs):
return addItemSerializer(*args, **kwargs)
def get_shipping_serializer(self, *args, **kwargs):
return addShippingSerializer(*args, **kwargs)
def create(self, request, *args, **kwargs):
user = request.user
data = request.data
orderItems = data.get('orderItems')
print(data)
if not bool(orderItems):
return Response('No Order Items', status=status.HTTP_400_BAD_REQUEST)
else:
# TODO - Create Order
orderSerializer = self.get_serializer(data=data)
orderSerializer.is_valid(raise_exception=True)
order = orderSerializer.save(user=user)
# TODO - Create Shipping Address
shippingSerializer = self.get_shipping_serializer(data=data)
shippingSerializer.is_valid(raise_exception=True)
shippingSerializer.save(order=order)
# TODO - Create Order Items and Set Order <> OrderItem Relationship
for item in orderItems:
product = Product.objects.get(pk=item['product'])
itemSerializer = self.get_item_serializer(data=item)
itemSerializer.is_valid(raise_exception=True)
item = itemSerializer.save(order=order, product=product)
# # TODO - Update Product CountInStock
product.countInStock -= item.qty
product.save()
return Response(data=orderSerializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
s = self.get_serializer(instance=instance)
return Response(data=s.data)
def list(self, request, *args, **kwargs):
qs = self.get_queryset()
s = self.get_serializer(qs, many=True)
return Response(s.data)
from django.contrib import admin
from django.urls import path, include
from .. import views
urlpatterns = [
path('', views.Order.as_view()),
path('add/', views.Order.as_view()),
path('<int:pk>/', views.Order.as_view({'get':'retrieve'})),
]
So in conclusion the list functionality of my view is working now but it has messed up my retrieve functionality. So that the retrieve function is only returning a list even tho I am adding the pk in my url.
Can you please post your urls.py as well for better clarity ?
Currently based on your question, the issue I see is mentioned below:
You are calling class "Order" for retrieve method with some url pattern eg path/int:pk -- Which is a GET request
You are also calling same class "Order" for list method with some url pattern eg path/ -- Which is a Get request
The issue is that both Retrieve and List Generic api has a GET method: snippet added :
Conclusion:
This is an example of Method Resolution Order in python inheritance.
Therefore, even though you are trying to invoke a GET method for retrieve it is envoking the GET method of LIST api because in your class definition
class Order(generics.ListAPIView, generics.CreateAPIView, generics.RetrieveAPIView) you have inherited ListAPIView first.
Solution :
You should separate out the classes eg: ListCreateAPIView, RetrieveUpdateDestroyAPIView
Alternatively :
You can also follow below stackoverflow answer to route GET request to specific method in same class :
Multiple get methods in same class in Django views
[EDIT]
The above suggested stackoverflow answer seems to be incorrect.
For multiple GET or POST request within same class, you can use Django Viewset and routers.
I found the below link to be well explained with examples:
https://testdriven.io/blog/drf-views-part-3/
I have a list of all my objects when I use get method by api/movies in my api, and this is ok. I want also to get only one, specyfic object when use get method by api/movies/1 but now I still have a list of all my objects... What to change in my MoviesView or in urls?
My views.py:
class MoviesView(APIView):
def get(self, request):
movies = Movie.objects.all()
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
My appurls.py:
urlpatterns = [
url('movies', MoviesView.as_view(), name="MoviesView"),
]
And my project urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include("api.urls")),
]
When I use routers everythig crushes... Could you help me?
You can simply use viewsets.ModelViewSet that natively implements list and retrieve.
You declare something like router.register('movies', my_views.MoviesViewSet) in you urls.py and
class MoviesViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self):
return self.queryset
def get_object(self):
movie_id = self.kwargs['pk']
return self.get_queryset().filter(id=movie_id)
def retrieve(self, request, *args, **kwargs):
try:
instance = self.get_object()
except (Movie.DoesNotExist, KeyError):
return Response({"error": "Requested Movie does not exist"}, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(instance)
return Response(serializer.data)
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This approach implies that you declare a Serializer, just like:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
Django simply maps HOST/movies/ to list (multiple objects) and HOST/movies/PK/ to retrieve method (one single object).
Docs:
https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
Hope it helps.
BR.
Eduardo
I would suggest you if you want to retrieve just 1 element to use a Generic View, i.e RetrieveAPIView
It would give you all you need for getting 1 element.
from rest_framework import generics
class MoviesView(generics.RetrieveAPIView):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
but you need also to change urls.py
url(r'movies/(?P<pk>[0-9]+)/$', MoviesView.as_view(), name="MoviesView"),
When you make a GET request to "api/movies/1", the url is matched to the "api/movies" path (read more in the docs), and the MoviesView's get method is called. And your get() implementation just fetches all the movies (movies = Movie.objects.all()), serializes and returns them - that's why you get the entire list.
If you want to retrieve one specific object, you need to somehow specify which object you have in mind, using its primary key (in your case, id).
1. You have to define a separate path: movies/<int:pk>/ (btw, which Django version are you using? url has been deprecated, use path instead!)
2. You have to define a detail view to handle this new case, and pass it to the path function as the second argument.
This general problem can really be solved in many ways, and depending on your app you may want to use a ViewSet instead of views. Then you don't have to define paths (urls) separately - you can use a router. You can't use routers with your view, because router needs a viewset class as its argument.
If you provide more details, I could try to suggest something more specific.
My appurls.py:
use path method
urlpatterns = [
path('movies', MoviesView.as_view(), name="MoviesView"),]
Maybe it works
Start by adding a format keyword argument to both of the views, like so
def snippet_list(request, format=None):
and
def snippet_detail(request, pk, format=None):
Now update the snippets/urls.py file slightly, to append a set of format_suffix_patterns in addition to the existing URLs
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
I would like to return to a specific page after I edit a record using Django "UpdateView", however this page url needs an argument passed to it as well (see urls.py below). I am pretty sure I need to use "get_absolute_url", which works when I am just redirecting to an unfiltered page, but can't seem to get the syntax to redirect to a filtered page.
Models.py
class DefaultDMLSProcessParams(models.Model):
device = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,)
customerTag = models.CharField(max_length=50,)
processNotes = models.TextField(max_length=300,blank=True,default = "")
def __str__(self):
return str(self.defaultParamDescrip)
def get_absolute_url(self):
#self.object.pk? pass this below somehow?
return reverse('Default_Listview',)
views.py
class defaultUpdateView(LoginRequiredMixin,UpdateView):
model = models.DefaultDMLSProcessParams
fields = ['processNotes','customerTag']
template_name = 'default_edit.html'
login_url = 'login'
urls.py
path('<int:device>', DefaultsListView.as_view(), name='Default_Listview'),
Specify the parameter by args argument of reverse() function
def get_absolute_url(self):
# self.object.pk? pass this below somehow?
return reverse('Default_Listview', args=[self.id, ])
You could find more example in the Official Django doc .
I have a django listview working fine.
Its receive url parameters to filter data.
Its paginated.
Now, I want to maintain these data along the user session. (page number and url parameters).
Example:
I'm in the products list view.
I search for 'foo'
I select page 2
And then, I click in any product detail.
The page will redirect to detail view.
When I return to product list view, I whant to keep the search argument 'foo' and selected page 2.
What is the better way to do this?
I'm using Django 2.0.6
Models.py
class Product(models.Model):
name= models.CharField(_('name'), max_length=150)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.0)
Views.py
class ProductList(ListView):
model = Product
paginated_by = 10
def get_queryset(self):
queryset = Product.objects.all()
name = self.request.GET.get('name', None)
if name:
queryset = queryset.filter(name__icontains=name)
return queryset
Urls.py
path('products/', views.ProductList.as_view(), name='product_list'),
For this you have to put the URL as a get request so that you can fetch the get values form the URL and use them in your filter to maintain your selection like:
url/?variable=value
Then in your Django view, you can access this by request.GET.get('variable') and pass this as the context in your HTML render page then use that variable in your filter selection.
Setting variable in session:
For setting the variable in the session you can set it by:
request.session['variable'] = 'value'
and this value can be retrieve by:
if 'variable' in request.session:
variable1 = request.session['variable']
You can refer this docs.
One common trick I use to do this is to use GET parameters and save directly the entire url in session (it saves time compared to saving each individual parameter individually)
class ProductList(ListView):
model = Product
paginated_by = 10
def get_queryset(self):
self.request.session['saved_product_list_url'] = self.request.get_full_path()
....
Then you can use it like this in templates :
product list
Or like this in views :
saved_product_list_url = self.request.session.get('saved_product_list_url')
if saved_product_list_url:
return redirect(saved_product_list_url)
else:
return redirect('product_list')
Also in your filter form you should add a "reset filters" like this :
reset filters
I have a URL (urls.py)
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from rds import views
urlpatterns = patterns('',
url(r'^markit/cdxcomposites/$', views.File_List.as_view()),
url(r'^markit/cdxcomposites/(?P<pk>[0-9]+)/$', views.File_Detail.as_view()), 'cdxcomposites_date'),
)
urlpatterns = format_suffix_patterns(urlpatterns, allowed=['csv', 'json', 'raw', 'xml', 'yaml'])
And I'm trying to use the following view (views.py) -
class File_List(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = cdx_composites_csv.objects.using('markit').all()
serializer_class = CDX_compositesSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
to be able to filter by the model field 'Date'
http://localhost:8080/markit/cdxcomposites/?Date=2014-06-26&format=xml
It never seems to filter always returning all results.
I've tried all the samples provided here -
http://www.django-rest-framework.org/api-guide/filtering#overriding-the-initial-queryset
But nothing seems to be taking. I've tried scrapping the mixins and doing a basic class based view and still no dice.
I know I'm missing something obvious but I'm unsure what.
For example doing the most simple of views -
class File_List(generics.ListAPIView):
serializer_class = CDX_compositesSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
Date = self.kwargs['Date']
return cdx_composites_csv.objects.using('markit').filter(Date__Date=Date)
results in the error -
KeyError at /markit/cdxcomposites/
'Date'
Usually the GET parameters are passed in a dictionary attached to the request object. See the documentation on request objects.
Try:
Date = self.request.GET.get('Date')
'Date' in your case is not a keyword argument but a query parameter.
I would do that like this:
# first import the needed modules, classes, functions, ...
import re
from django.http import Http404
class File_List(generics.ListAPIView):
serializer_class = CDX_compositesSerializer
def get_queryset(self):
queryset = cdx_composites_csv.objects.using('markit').all()
# get the query parameter or None in case it is empty
date = self.request.QUERY_PARAMS.get('Date', None)
# regex for correct date
regex_date = re.compile(r'^\d{4}-\d{2}-\d{2}$')
# filter your queryset if you really got a correct date
if date is not None and regex_date.match(date):
queryset = queryset.filter(Date__contains=date)
# if there are no results return 404 NOT FOUND
if not queryset:
raise Http404
return queryset
Perhaps there are better solutions, but this should work. Remember always to check the user input.
I don't know your model, but maybe you could implement a manager that would return all objects using 'markit'.
Than you could get the queryset like this (just a possible example):
queryset = cdx_composites_csv.markit.all()
Here 'markit' is an attribute in your model holding your custom manager:
MarkitManager(models.Manager):
def get_query_set(self):
return Super(MarkitManager, self).get_query_set().using('markit')
More about filtering against query parameters can be found in the official documentation of DRF:
http://www.django-rest-framework.org/api-guide/filtering#filtering-against-query-parameters