Slug and generic views - python

i have an app called blog and blog and Post and Categories model.
url(r'^(?P<slug>[-\w]+)/$', ListView.as_view(
queryset=Post.objects.filter(category=4)[:5],
template_name='blog.html')),
i can see above code like this
127.0.0.1/categories/php/
with above url , i can see the 5 articles with category 4 . What i want i that making it dynamic.. For example, python is category 3
queryset=Post.objects.filter(category='P<pk>\d')[:5]
i tried this but does not working. Any other way?

You can create a custom ListView and add the functionality in there:
class BlogPostView(ListView):
template_name = 'blog.html'
def get(self, request, *args, **kwargs):
slug = kwargs.get('slug')
slug_id = ... # code here to determine id of slug's category
self.queryset = Post.objects.filter(category=slug_id)[:5]
return super(BlogPostView, self).get(request, *args, **kwargs)
with the following as URL configuration:
url(r'^(?P<slug>[-\w]+)/$', BlogListView.as_view()),
Django will only parse the URL string/regex to determine what parameters it has, you can't pass a string/regex to filter objects for a queryset.

Related

How to list all objs and retrieve single obj using django drf generic views

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/

Add argument to all views without having to edit them all to include variable in Django

What I need
I'm developing a Pull Notification System to an existing Django Project. With there begin over 100+ views I'm looking to find a way to incorporate a argument(notification queryset) into all the views, as this is rendered to my base.html which I can do by passing it into a view's arguments dictionary.
Problem
I want to do this without editing all of the views as this would take a long time and would be required for all future views to include this variable.
What I've tried
Creating a template filter and pass in request.user as variable to return notification for that user. This works, however when the user selects a 'New' notification I want to send a signal back to the server to both redirect them to the notification link and change the status of viewed to 'True' (POST or AJAX). Which would required that specific view to know how to handle that particular request.
What I've considered
• Editing the very core 'View' in Django.
I've tried my best to explain the issue, but if further info is required feel free to ask.
Thanks!
models.py
class NotificationModel(models.Model):
NOTIFICATION_TYPES = {'Red flag':'Red flag'}
NOTIFICATION_TYPES = dict([(key, key) for key, value in NOTIFICATION_TYPES.items()]).items()
notification_type = models.CharField(max_length=50, choices=NOTIFICATION_TYPES, default='')
user = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(blank=True, null=True)
text = models.CharField(max_length=500, default='')
viewed = models.BooleanField(default=False)
views.py Example
class HomeView(TemplateView):
template_name = 'app/template.html'
def get(self, request, *args, **kwargs):
notifications = NotificationsModel.objects.filter(user=request.user)
args={'notifications':notifications}
return render(request, self.template_name, args)
def notification_handler(self, request)
if 'notification_select' in request.POST:
notification_id = request.POST['notification_id']
notification = NotificationModel.objects.get(id=notification_id)
notification.viewed = True
notification.save()
return redirect #some_url based on conditions
def post(self, request, *args, **kwargs):
notifications = self.notification_handler(request, *args, **kwargs)
if notifications:
return notifications
return self.get(self, request)
With there begin over 100+ views I'm looking to find a way to incorporate a argument(notification queryset) into all the views, as this is rendered to my base.html which I can do by passing it into a view's arguments dictionary.
You don't have to put it in the views (and actually, you shouldn't - views shouldn't have to deal with unrelated responsabilities) - you can just write a simple custom template tag to fetch and render whatever you need in your base template. That's the proper "django-esque" solution FWIW as it leaves your views totally decoupled from this feature.
Have you considered mixins ?
class NotificationMixin:
my_var = 'whatever'
class MyDjangoView(NotificationMixin,.....,View)
pass
Better, Using django builtins..
from django.views.generic import base
class NotificationMixin(base.ContextMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['notification'] = 'whatever'
return context
and use this mixin with all your views AS THE FIRST CLASS INHERITED, See this.
For your case, Typing it instead of applying it to the base class is better, The base view class is not intended to be altered, It's meant to be extended, That's why we can solve the issue by this...
from django.generic.views import View as DjangoBaseView
from .mixins import NotificationMixin
class View(NotificationMixin, DjangoBaseView):
pass
and use any IDE to change all the imported Views to your View and not the django one.

Django POST with ModelViewSet and ModelSerializer 405

How can I make a ModelViewSet accept the POST method to create an object? When I attempt to call the endpoint I get a 405 'Method "POST" not allowed.'.
Within views.py:
class AccountViewSet(viewsets.ModelViewSet):
"""An Account ModelViewSet."""
model = Account
serializer_class = AccountSerializer
queryset = Account.objects.all().order_by('name')
Within serializers.py:
class AccountSerializer(serializers.ModelSerializer):
name = serializers.CharField(required=False)
active_until = serializers.DateTimeField()
class Meta:
model = Account
fields = [
'name',
'active_until',
]
def create(self, validated_data):
with transaction.atomic():
Account.objects.create(**validated_data)
within urls.py:
from rest_framework import routers
router = routers.SimpleRouter()
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
Do I need to create a specific #action? my attempts to do so have yet to be successful. If that is the case what would the url = reverse('app:accounts-<NAME>') be such that I can call it from tests? I haven't found a full example (urls.py, views.py, serializers.py, and tests etc).
I discovered what the issue was, I had a conflicting route. There was a higher level endpoint registered before the AccountViewSet.
router.register(
prefix=r'v1/auth',
viewset=UserViewSet,
base_name='users',
)
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL.. I should have been ordered this way:
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
router.register(
prefix=r'v1/auth',
viewset=UserViewSet,
base_name='users',
)
despite the fact that reverse('appname:acccounts-list') worked, the underlying URL router still thought I was calling the UserViewSet.
From the docs:
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create().
And here is a list of supported actions:
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
So no post is not directly supported but create is.
So your end point would be v1/auth/accounts/create when using the a router instead v1/auth/accounts/post.
I honestly prefer using class based or function based views when working with DRF. It resembles regular django views more closely and makes more sense to me when working with them. You woul write you views and urls pretty much like regular django urls and views.

Django - Multiple <pk> in urls.py with DetailViews

I'm trying to build a script using Django Generic display views.
My urls.py
url(r'^page/(?P<pk>[0-9]+)/$', PageDetails.as_view(), name='page-details'),
My views.py
class PageDetails(DetailView):
model = Pages
def get_context_data(self, **kwargs):
context = super(PageDetails, self).get_context_data(**kwargs)
return context
The problem
How can I set multi <pk> in my urls like this?
url(r'^page/(?P<pk>[0-9]+)/subpage/(?P<pk>[0-9]+)$', PageDetails.as_view(), name='page-details'),
In my views I need to take data from first and second <pk>.
Change the second pk argument in your url to something else, for example pk_alt:
^page/(?P<pk>[0-9]+)/subpage/(?P<pk_alt>[0-9]+)$
The url parameter pk_alt will then be available in your views function as part of the self.kwargs dictionary, so you can access it using:
self.kwargs.get('pk_alt', '')
You could then update your views to something like the following:
class PageDetails(DetailView):
model = Pages
def get_context_data(self, **kwargs):
context = super(PageDetails, self).get_context_data(**kwargs)
page_alt = Pages.objects.get(id=self.kwargs.get('pk_alt', ''))
context['page_alt'] = page_alt
return context
You will then be able to access the alternative model in your template using {{ page_alt }}
If you need two arguments in the url try this:
url(r'^page/(?P<pk_1>[0-9]+)/subpage/(?P<pk_2>[0-9]+)$', PageDetails.as_view(), name='page-details'),
Then you have pk_1 and pk_2 available as kwargs

django - filtering url responses

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

Categories