Django: DetailView and multiple slugs - python

I have an issue with my DetailView. I want to make sure both values are in the url string and then want to display the page. However I am always receiving this error here:
KeyError at /orders/ticket/ug2dc78agz-1/d04fkjmo37/
'order_reference'
views.py
class TicketView(DetailView):
model = Attendee
template_name = 'orders/ticket_view.html'
def get_queryset(self):
return Attendee.objects.filter(
order__order_reference=self.kwargs['order_reference'],
).filter(
access_key=self.kwargs['access_key'],
)
urls.py
urlpatterns = [
path(
'ticket/<slug:ticket_reference>/<slug:access_key>/',
TicketView.as_view(),
name='ticket'
),
]

You get the error because you are trying to access self.kwargs['order_reference'], but you don't use order_reference in the path().
Your path() uses,
'ticket/<slug:ticket_reference>/<slug:access_key>/'
therefore you can use self.kwargs['ticket_reference'] and self.kwargs['access_key'].
Since your path does not contain slug or pk, Django will not know how to fetch the object for the detail view. I would override get_object instead of get_queryset:
def get_object(self):
return get_object_or_404(
Attendee,
order__order_reference=self.kwargs['slug:ticket_reference'],
access_key=self.kwargs['access_key'],
)

You have ticket_reference url variable, but in view using order_reference. You should rename it:
class TicketView(DetailView):
model = Attendee
template_name = 'orders/ticket_view.html'
def get_queryset(self):
return Attendee.objects.filter(
order__order_reference=self.kwargs['ticket_reference'],
).filter(
access_key=self.kwargs['access_key'],
)

Related

Not good template loaded in Django generic list view when using filter

I have a strange behavior in my generic views. Below is the classic FBV scheme I want to reproduce in a CBV.
My FBV
def post_list(request, tag_name=None):
if tag_name:
# Filter post according to tag name if provided
posts = Post.objects.filter(tag__tag_name=tag_name)
else:
posts = Post.objects.all()
context = {"posts": posts}
return render(request, "blog/post_list.html", context)
def post_detail(request, post_id):
post = Post.objects.get(pk=post_id)
context = {"post": post}
return render(request, "blog/post_detail.html", context)
My CBV
class PostList(ListView):
model = Post
context_object_name = "post_list"
template_name = "blog/post_list.html"
def get_queryset(self):
if "tag_name" in self.kwargs:
return Post.objects.filter(tag__tag_name=self.kwargs["tag_name"])
else:
return Post.objects.all()
class PostDetail(DetailView):
model = Post
context_object_name = "post_detail"
template_name = "blog/post_detail.html"
Here are my models
from django.db import models
# Create your models here.
class Tag(models.Model):
tag_name = models.CharField(max_length=100)
def __str__(self):
return self.tag_name
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
tag = models.ManyToManyField(Tag, blank=True)
def __str__(self):
return self.title
And here are my URLs
from django.urls import path
from .views import PostList, PostDetail
urlpatterns = [
path("", PostList.as_view(), name="blog-index"),
path("<tag_name>", PostList.as_view(), name="blog-index"),
path("<int:pk>", PostDetail.as_view(), name="post-detail")
]
As you can see, I want to use the same generic view for the list of my posts with an optional tag provided in the URL. It is well filtering my articles when I provide a URL with a tag, something like this .../blog/my_tag, but problem is that the DetailView process does not work anymore. It always loads my blog/post_list.html with an empty list instead of my blog/detail_post.html template. The DetailView process works well when I remove the process to filter with tag.
What am I doing wrong?
The URL pattern is the problem here: All your detail view request are getting matched with Second path i.e: path("<tag_name>", PostList.as_view(), name="blog-index"), try to provide a path pattern with converters that they match as you want in your application.The sequence matters here as first match is first served.
Instead of this:
urlpatterns = [
path("", PostList.as_view(), name="blog-index"),
path("<tag_name>", PostList.as_view(), name="blog-index"),
path("<int:pk>", PostDetail.as_view(), name="post-detail")
]
You can differentiate by some prefixes and path converters so that they match the exact case as you want for example:
urlpatterns = [
path('postlist/', PostList.as_view(),name='blog-index'),
path('postlist/<str:tag_name>/', PostList.as_view(),name='blog-index-tag'),
path('postdetail/<int:pk>/', PostDetail.as_view(),name='post-detail'),
]

How can I create a view that retrieves all objects with matching primary key in url?

I have two models that form a one-to-many relationship. One pallet has multiple images associated with it.
models.py
class Pallet(models.Model):
pallet_id = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.pallet_id)
class Meta:
ordering = ('pallet_id',)
class Image(models.Model):
created = models.DateTimeField(auto_now_add=True)
pallet = models.ForeignKey(Pallet, on_delete=models.CASCADE)
image = models.FileField(upload_to="")
def __str__(self):
return str(self.created)
I'm trying to create a view where I get all images associated with a particular pallet_id from the url.
serializers.py
class ImageSerializer(serializers.HyperlinkedModelSerializer):
pallet = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Image
fields = '__all__'
class PalletSerializer(serializers.ModelSerializer):
class Meta:
model = Pallet
fields = '__all__'
urls.py
urlpatterns = [
url(r'^pallets/', include([
url(r'^(?P<pk>[0-9]+)/$', views.PalletDetail.as_view(), name='pallet-detail'),
])),
]
I think the issue is in the views.py with the PalletDetail class. I am confused on how to write the view based on the primary key from the URL. I've tried to use **kwargs['pk'] but does using this make it a function-based view? If so, would it be bad form to mix class-based and function-based views? How can I get similar behavior from class-based views?
I'm really struggling with the views here:
views.py
class PalletDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Image.objects.prefetch_related('pallet').all()
serializer_class = ImageSerializer
In Function based view
pass pk parameter
example:
def Example_function(request, pk):
queryset = Model.objects.filter(id=pk)
In Class-Based views
get url parameter
pk = self.kwargs.get('pk')
queryset = Model.objects.filter(id=pk)
You're going about it the wrong way. You want to return a list of images belonging to the pallet so what you need is a list view for the images and not a pallet detail view. The url should look like this:
/pallets/<pallet_id>/images/
In this way the url is self-descriptive and by REST standards you can easily see that you are requesting for images belonging to a pallet and not the pallet itself
In urls.py
urlpatterns = [
url(r'^pallets/', include([
url(r'^(?P<pallet_pk>[0-9]+)/images/$', views.PalletImagesListView.as_view(), name='pallet-images-list'),
])),
]
In views, you have to overide the get_queryset() method, so that it only returns the images for the pallet specified
class PalletImagesListView(generics.ListAPIView):
serializer_class = ImageSerializer
def get_queryset(self):
return Image.objects.prefetch_related('pallet').filter(pallet__pk=self.kwargs.get('pallet_pk'))
So now you can request the first pallet's images with /pallets/1/images/
You are using the serializer and view in a wrong way.
The problem with yours PalletDetail view is that you are using the wrong queryset. The queryset should return Pallet objects, since the lookup will be on that queryset (default lookup field is 'pk'). Also, you will then need to use the serializer for Pallet since that is supposed to handle the data for the Pallet object.
class PalletDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Pallet.objects.all()
serializer_class = PalletSerializer
Have you tried filter_by() method
queryset = Model.query.filter_by(id=pk).first()

Django: DetailView and two slug fields

My database model has different organizers who can have several events. I now want to filter my DetailView down to organizer and then a specific event. My solution is that one here, but I still have in mind, that there shouldn't be two slug fields in the get_object method. Is there another approach to what I am trying to do?
views.py
class EventDetailView(DetailView):
context_object_name = 'event'
def get_object(self):
organiser = self.kwargs.get('organiser')
event = self.kwargs.get('event')
queryset = Event.objects.filter(organiser__slug=organiser)
return get_object_or_404(queryset, slug=event)
urls.py
urlpatterns = [
path(
'<slug:organiser>/<slug:event>/',
EventDetailView.as_view(),
name='event'
),
]

Custom view on admin site

I would like to have a custom view on a model admin.
Current behavior:
When i click on Model object(1) it takes me to site/app_label/1/change,
This leads to edit of the object with its inlines.
What I want to achieve:
when i click on a object should redirect to site/app_label/1 it should show a list view of its inlines (Not a editable list) i will put a separate button which will lead to site/app_label/1/change
I want this list view to be similar to Django default style listing.
My two models are:
class RunConfig(models.Model):
config_name = models.CharField(max_length=100,blank=False,null=False, unique=True )
class Jobs(models.Model):
config_name = models.ForeignKey('RunConfig',on_delete=models.CASCADE,)
job_name = models.TextField(blank=True,null=True, )
parameters = models.TextField(blank=True,null=True, )
exec_order = models.IntegerField(help_text="Execution Order")
admin.py
class JobsInline(SortableInlineAdminMixin, admin.TabularInline):
model = Jobs
extra = 1
class RunConfigAdmin(admin.ModelAdmin):
list_display = ('config_name',)
What I have tried till now:
My plan was to override the changelist template but that did not work as it started to complain about no reverse match found for app_list
in admin.py added custom url
def get_urls(self):
urls = super(RunConfigAdmin, self).get_urls()
my_urls = [
url(r'^(?P<pk>[0-9]+)/$', self.admin_site.admin_view(RunConfigDetailView.as_view(), cacheable=True), name='rc-detail'),
]
return my_urls + urls
created a detailed view on views.py i was expecting it wil be easier to use the context['job'] to achive the result but I failed :(
class RunConfigDetailView(LoginRequiredMixin,DetailView):
model = RunConfig
context_object_name = 'rc_detail'
def get_context_data(self,**kwargs):
context = super(RunConfigDetailView, self).get_context_data(**kwargs)
context['opts'] = self.model._meta
context['jobs'] = Jobs.objects.filter(config_name=self.object.id)
return context

Pass variable to TemplateView [duplicate]

I have a custom class-based view
# myapp/views.py
from django.views.generic import *
class MyView(DetailView):
template_name = 'detail.html'
model = MyModel
def get_object(self, queryset=None):
return queryset.get(slug=self.slug)
I want to pass in the slug parameter (or other parameters to the view) like this
MyView.as_view(slug='hello_world')
Do I need to override any methods to be able to do this?
If your urlconf looks something like this:
url(r'^(?P<slug>[a-zA-Z0-9-]+)/$', MyView.as_view(), name = 'my_named_view')
then the slug will be available inside your view functions (such as 'get_queryset') like this:
self.kwargs['slug']
Every parameter that's passed to the as_view method is an instance variable of the View class. That means to add slug as a parameter you have to create it as an instance variable in your sub-class:
# myapp/views.py
from django.views.generic import DetailView
class MyView(DetailView):
template_name = 'detail.html'
model = MyModel
# additional parameters
slug = None
def get_object(self, queryset=None):
return queryset.get(slug=self.slug)
That should make MyView.as_view(slug='hello_world') work.
If you're passing the variables through keywords, use what Mr Erikkson suggested: https://stackoverflow.com/a/11494666/9903
It's worth noting you don't need to override get_object() in order to look up an object based on a slug passed as a keyword arg - you can use the attributes of a SingleObjectMixin https://docs.djangoproject.com/en/1.5/ref/class-based-views/mixins-single-object/#singleobjectmixin
# views.py
class MyView(DetailView):
model = MyModel
slug_field = 'slug_field_name'
slug_url_kwarg = 'model_slug'
context_object_name = 'my_model'
# urls.py
url(r'^(?P<model_slug>[\w-]+)/$', MyView.as_view(), name = 'my_named_view')
# mymodel_detail.html
{{ my_model.slug_field_name }}
(both slug_field and slug_url_kwarg default to 'slug')
If you want to add an object to the context for the template you can override get_context_data and add to its context. The request is also a part of self in case you need the request.user.
def get_context_data(self, **kwargs):
context = super(MyTemplateView, self).get_context_data(**kwargs)
if 'slug' in self.kwargs:
context['object'] = get_object_or_404(MyObject, slug=self.kwargs['slug'])
context['objects'] = get_objects_by_user(self.request.user)
return context
You can pass parameters from urls.py
https://docs.djangoproject.com/en/1.7/topics/http/urls/#passing-extra-options-to-view-functions
This also works for generic views. Example:
url(r'^$', views.SectionView.as_view(), { 'pk': 'homepage', 'another_param':'?'}, name='main_page'),
In this case the parameters passed to the view should not necessarily be instance variables of the View class. Using this method you don't need to hardcode default page name into YourView model, but you can just pass it as a parameter from urlconf.
As stated by Yaroslav Nikitenko, if you don't want to hardcode a new instance variable to the View class, you can pass extra options to view functions from urls.py like this:
url(r'^$', YourView.as_view(), {'slug': 'hello_world'}, name='page_name')
I just wanted to add how to use it from the view. You can implement one of the following methods:
# If slug is optional
def the_function(self, request, slug=None):
# use slug here
# if slug is an optional param among others
def the_function(self, request, **kwargs):
slug = kwargs.get("slug", None)
other_param = kwargs.get("other_param", None)
# If slug is required
def the_function(self, request, slug):
# use slug here
For django 3.0, this is what worked for me:
# myapp/views.py
from django.views.generic import DetailView
class MyView(DetailView):
template_name = 'detail.html'
slug = None
def get_object(self, queryset=None):
self.slug = self.kwargs.get('slug', None)
return queryset.get(slug=self.slug)
# myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('slug/<slug:slug>/', views.MyView.as_view(), name='myview_by_tag'),
]

Categories

Resources