I'm currently learning how to use the class-based views in django 1.3. I'm trying to update an application to use them, but I still don't uderstand very well how they work (and I read the entire class-based views reference like two or three times EVERY day).
To the question, I have an space index page that needs some extra context data, the url parameter is a name (no pk, and that can't be changed, it's the expected behaviour) and the users that don't have that space selected in their profiles can't enter it.
My function-based code (working fine):
def view_space_index(request, space_name):
place = get_object_or_404(Space, url=space_name)
extra_context = {
'entities': Entity.objects.filter(space=place.id),
'documents': Document.objects.filter(space=place.id),
'proposals': Proposal.objects.filter(space=place.id).order_by('-pub_date'),
'publication': Post.objects.filter(post_space=place.id).order_by('-post_pubdate'),
}
for i in request.user.profile.spaces.all():
if i.url == space_name:
return object_detail(request,
queryset = Space.objects.all(),
object_id = place.id,
template_name = 'spaces/space_index.html',
template_object_name = 'get_place',
extra_context = extra_context,
)
return render_to_response('not_allowed.html', {'get_place': place},
context_instance=RequestContext(request))
My class-based view (not working, and no idea how to continue):
class ViewSpaceIndex(DetailView):
# Gets all the objects in a model
queryset = Space.objects.all()
# Get the url parameter intead of matching the PK
slug_field = 'space_name'
# Defines the context name in the template
context_object_name = 'get_place'
# Template to render
template_name = 'spaces/space_index.html'
def get_object(self):
return get_object_or_404(Space, url=slug_field)
# Get extra context data
def get_context_data(self, **kwargs):
context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
place = self.get_object()
context['entities'] = Entity.objects.filter(space=place.id)
context['documents'] = Document.objects.filter(space=place.id)
context['proposals'] = Proposal.objects.filter(space=place.id).order_by('-pub_date')
context['publication'] = Post.objects.filter(post_space=place.id).order_by('-post_pubdate')
return context
urls.py
from e_cidadania.apps.spaces.views import GoToSpace, ViewSpaceIndex
urlpatterns = patterns('',
(r'^(?P<space_name>\w+)/', ViewSpaceIndex.as_view()),
)
What am I missing for the DetailView to work?
The only problem I see in your code is that your url's slug parameter is named 'space_name' instead of 'slug'. The view's slug_field attribute refers to the model field that will be used for slug lookup, not the url capture name. In the url, you must name the parameter 'slug' (or 'pk', when it's used instead).
Also, if you're defining a get_object method, you don't need the attributes queryset, model or slug_field, unless you use them in your get_object or somewhere else.
In the case above, you could either use your get_object as you wrote or define the following, only:
model = Space
slug_field = 'space_name'
Related
So I have a simple model called Pages. Every Page belongs to a certain category, since this is a ForeignKey relation, a Page can only belong to a single category.
Besides categories we also use tags to furthermore filter the different pages.
We use a category view to display all pages belonging to a certain category, easy peasy.
The thing is, we use django-filters to filter the pages by selecting different tags. The list of tags is increasing by the amount of pages. Therefore I would like to only show related tags to the category.
urls.py
path('<slug:category_slug>/', views.PageByCategoryView.as_view(), name='page_by_category'),
views.py
class PageByCategoryView(FilterView):
logger.info("Category view is called")
model = Page
filterset_class = PageByCategoryFilter
strict = False
queryset = Page.published_objects.all()
template_name = 'pages/page_by_category.html'
filters.py
class PageByCategoryFilter(django_filters.FilterSet):
tags = django_filters.ModelMultipleChoiceFilter(
queryset=Tag.objects.filter(page__category_id='2'), <-- actually works!
conjoined=True,
widget=forms.CheckboxSelectMultiple()
)
class Meta:
model = Page
fields = [
'tags__slug'
]
So the tags used in the filter actually get filtered by page__category_id = 2, this is exactly what I want to achieve though I want to do this dynamically. I tried to define the qs like so;
#property
def qs(self):
queryset = super(PageByCategoryFilter, self).qs
current_category = self.request.GET.get('category_slug')
if current_category:
logger.info("Current category is in url")
return queryset.filter(category__slug=current_category)
return queryset
This just doesn't seem to be working, how can i get the current_category from the url?
Alright, what I did below does actually work but it look kinda hackish..
Doe anyone have a better answer on solving this issue?
def category_filter(request):
path = request.path
category_slug = re.sub('\/+', r'', path)
current_category = category_slug
return Tag.objects.filter(page__category__slug=current_category).distinct()
With best regards,
Kevin
Your original view function should be able to take a parameter category_slug where the category slug of the URL is passed in, like in this example from the Django docs (notice how num is declarad as an int in the URL and then passed as an argument to page):
In urls.py
path('blog/page<int:num>/', views.page),
In your view function
def page(request, num=1):
...
If you're using Django REST, you should be able to get the URL parameter your kwargs member attribute, like so:
In urls.py
url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
In your view classes:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
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.
"""
username = self.kwargs['username'] # this is what you're after
return Purchase.objects.filter(purchaser__username=username)
I am able to render class based view generic ListView template using parameter hard coded in views.py.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
# def get(self, request):
# if request.GET.get('q'):
# query = request.GET.get('q')
# print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
However, when parameter is sent via form by GET method (below),
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
def get(self, request):
if request.GET.get('q'):
query = request.GET.get('q')
print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
I receive this error
The view creations.views.ResourceSearchView didn't return an
HttpResponse object. It returned None instead.
Note that the parameter name q and associated value is being retrieved successfully (confirmed using print(query)).
So with CBV in Django, you have to return some kind of valid response that the interpreter can use to perform an actual HTTP action. Your GET method isn't returning anything and that's what is making Django angry. You can render a template or redirect the user to a view that renders a template but you must do something. One common pattern in CBV is to do something like:
return super().get(request, *args, **kwargs)
...which continues up the chain of method calls that ultimately renders a template or otherwise processes the response. You could also call render_to_response() directly yourself or if you're moving on from that view, redirect the user to get_success_url or similar.
Have a look here (http://ccbv.co.uk) for an easy-to-read layout of all the current Django CBVs and which methods / variables they support.
Thanks for the responses. Here is one solution.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
def get_queryset(self):
query = self.request.GET.get('q')
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
return queryset
I'm trying to use detail view based on the values in the parameter.
URL:
url(r'^mousesmall/(?P<name>.*)/$', IDView.as_view()),
view:
class IDView(DetailView):
model = RNA
template_name = "home/details.html"
def get_context_data(self, **kwargs):
context = super(IDView, self).get_context_data(**kwargs)
data = self.kwargs['name']
context['object'] = RNA.objects.filter(rna_id=data)
return context
I'm not exactly sure how to call the view with an object pk.
You need to set slug_field on your class based view so that it know which field you are considering as the slug (which I think in your case is rna_id. You will also need to set slug_url_kwarg in your case as you are using name as your URLConf value
class IDView(DetailView):
model = RNA
template_name = "home/details.html"
slug_field = 'rna_id'
slug_url_kwarg = 'name'
You won't need to overwrite get_context_object now. You should also give your url a name:
url(r'^mousesmall/(?P<name>.*)/$', IDView.as_view(), name="rna_detailview"),
so it's easier to reverse in your template:
{% url 'rna_detailview' name=myobject.rna_id %}
This is a continuation of my other question about how to include additional queryset (with request) in an app (Userena) view. If I do what #limelights suggested, this is what my code looks like:
view:
from django.views.generic import list_detail
def requestuserswampers(request):
qs = Thing.objects.filter(user=request.user)
return list_detail.object_list(
request,
queryset = Thing.objects.all(),
template_object_name = 'thing',
extra_context = {'swamp_things': qs},
)
url:
url(r'^accounts/(?P<username>(?!signout|signup|signin)[\.\w-]+)/$',
requestuserswampers,
name='userena_profile_detail'),
This generates a TemplateDoesNotExist error: Template does not exist at myapp/swamp_things.html.
If I try to include the template name and location using template_name = 'userena/profile_detail.html', on the other hand, the right template is rendered, but some of the context is now missing, like the user information that is normally rendered in the default userena "profile_detail" template..
How do I add an extra queryset to the Userena profile detail view which allows for request so that I can filter objects based on the logged in user? Thanks for your ideas!
I didn't realize that it was accepted practice to re-write another app's view, figuring that it was antithetical to DRY principles. But since I had not discovered another method of achieving what I needed to do, and since it was endorsed by another senior user in the comments above, I went ahead and tried to re-write the Userena view. Just needed to add my queryset into the extra_context:
def profile_detail(request, username,
template_name=userena_settings.USERENA_PROFILE_DETAIL_TEMPLATE,
extra_context=None, **kwargs):
user = get_object_or_404(get_user_model(),
username__iexact=username)
profile_model = get_profile_model()
try:
profile = user.get_profile()
except profile_model.DoesNotExist:
profile = profile_model.objects.create(user=user)
if not profile.can_view_profile(request.user):
return HttpResponseForbidden(_("You don't have permission to view this profile."))
if not extra_context: extra_context = dict()
extra_context['profile'] = user.get_profile()
extra_context['hide_email'] = userena_settings.USERENA_HIDE_EMAIL
#### Added the line below
extra_context['swamp_things'] = Thing.objects.filter(user=user)
return ExtraContextTemplateView.as_view(template_name=template_name,
extra_context=extra_context)(request)
I have a blog app that uses django_taggit. My HomePageView subclasses ArchiveIndexView and works well.
Now I'd like the following link to work: http://mysite.com/tag/yellow and I'd like to use the ArchiveIndexView generic class and pass in a modified queryset that filters on tag_slug. I want to do this because I want to use the same template as the homepage.
My urls.py is
url(r'^$', HomePageView.as_view(paginate_by=5, date_field='pub_date',template_name='homepage.html'),
),
url(r'^tag/(?P<tag_slug>[-\w]+)/$', 'tag_view'), # I know this is wrong
My views.py is
class HomePageView(ArchiveIndexView):
"""Extends the detail view to add Events to the context"""
model = Entry
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context['events'] = Event.objects.filter(end_time__gte=datetime.datetime.now()
).order_by('start_time')[:5]
context['comments'] = Comment.objects.filter(allow=True).order_by('created').reverse()[:4]
return context
I realize I'm lost here, and would like some help in finding out how to create a new class TagViewPage() that modifies the queryset by filtering on tag_slug.
The key thing is to override the get_queryset method, so that the queryset only includes returns entries with the chosen tag. I have made TagListView inherit from HomePageView, so that it includes the same context data - if that's not important, you could subclass ArchiveIndexView instead.
class TagListView(HomePageView):
"""
Archive view for a given tag
"""
# It probably makes more sense to set date_field here than in the url config
# Ideally, set it in the parent HomePageView class instead of here.
date_field = 'pub_date'
def get_queryset(self):
"""
Only include entries tagged with the selected tag
"""
return Entry.objects.filter(tags__name=self.kwargs['tag_slug'])
def get_context_data(self, **kwargs):
"""
Include the tag in the context
"""
context_data = super(TagListView, self).get_context_data(self, **kwargs)
context_data['tag'] = get_object_or_404(Tag, slug=self.kwargs['tag_slug'])
return context_data
# urls.py
url(r'^tag/(?P<tag_slug>[-\w]+)/$', TagListView.as_view(paginate_by=5, template_name='homepage.html')),