How to display the children of a wagtail page - python

My requirement sounds simple, but I am struggling to make it work
In my wagtail project I have a BlogListingPage which has many child pages. I am trying to create a list of the children in a web page. Here is my code:
models.py
class BlogListingPage(Page):
...
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['blog_pages'] = BlogDetailPage.objects.live().child_of(self)
return context
class BlogDetailPage(Page):
...
blog_listing = models.ForeignKey(BlogListingPage,
on_delete=models.PROTECT,
blank=True,
null=True,)
views.py
def blog(request):
url = 'blog/blog_listing_page.html'
context = {'data': BlogListingPage.objects.all()}
return render(request, url, context)
blog_listing_page.html
{% block content %}
{% for blog_listing in data %}
{% for post in blog_listing.blogdetailpage %}
{{ post.blog_title }}
{% endfor %}
{% endfor %}
{% endblock content %}
I am lost in the fog of the django/wagtail pages/objects/querysets/all()/specific
Can someone please lead me to clear blue water?

A couple of suggestions regarding your approach:
Having a foreign key from BlogDetailPage to BlogListingPage is not the Wagtail way to organise parent-child relationships. All Page objects already have a tree hierarchy and you should rely on that, otherwise you lose all the built-in tree handling provided by Wagtail.
You should use the parent/subpage rules to specify how page types can be related to each other. In you case it would look something like:
class BlogListingPage(Page):
subpage_types = ['myapp.BlogDetailPage']
class BlogDetailPage(Page):
parent_page_types = ['myapp.BlogListingPage']
The get_context() method on BlogListingPage only runs when Wagtail is serving that specific page in a view. It doesn't run when you're iterating over these pages in your blog view. In order to access the children there, you need to do something like this:
{% for blog_listing in data %}
{% for post in blog_listing.get_children.live %}
{{ post.blog_title }}
{% endfor %}
{% endfor %}
i.e., you need to use the get_children() method on the page to obtain it's children. live() further filters this to exclude unpublished pages. Note that this will only work if the blog posts are children (in the Wagtail page tree sense) of the listing page. specific() isn't important here if you're only using the post URL and title - it is relevant if you want to display fields that are part of your model rather than the base Page model.

Related

Django show same content (of a model) in sidebar of every page (also in different apps)

I have two apps: blog and mysite.
In the project folder, I have a template which includes a sidebar template. This sidebar is shown on every page of the project (index pages, mysite pages, blog pages).
One part of this sidebar should show a list of the latest x blog entries (independent of the page where the user is).
blog/models.py
class Post(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
title = models.CharField(max_length=264)
text = RichTextField(config_name='detail_text_field', default='')
created_date = models.DateTimeField(default=timezone.now)
blog/views.py
class LatestBlogEntriesListView(ListView):
model = Post
template_name = 'blog/_latest_blog_entries_list.html'
def get_queryset(self):
return Post.objects.all().order_by('created_date')[-3:]
sidebar.html
<div class="row">
{% include 'blog/_latest_blog_entries_list.html' %}
</div>
_latest_blog_entries_list.html
<h4>Latest Blog Entries</h4>
{% for post in objects %}
{{ post.title }}
{% endfor %}
Unfortunately, this does not work. My sidebar only shows the h4 "Latest Blog Entries", but not the posts. How can I do this?
Any help is highly appreciated!
I found to use a context processor, as described in this post: https://dev.to/harveyhalwin/using-context-processor-in-django-to-create-dynamic-footer-45k4
This allows to access a context variable within all pages.
When using ListView the default object is called object_list. Try changing your code to this:
{% for post in object_list %}
{{ post.title }}
{% endfor %}
You can also change this variable name in the view, so that when you catch it on the template, it's a custom name.
class LatestBlogEntriesListView(ListView):
model = Post
template_name = 'blog/_latest_blog_entries_list.html'
context_object_name = "your_new_object_name"

newbie - understanding Django's class-based views dynamic filtering

I'm trying to follow and learning about dynamic filtering in Django from the docs. https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/#dynamic-filtering
I've just been following along step by step and copy/pasted the code from docs. But I don't understand the last bit about added the publisher into the context, meaning I can't figure out how to query that data in the template. The only thing I can get a "hold" of publisher.
Because in the PublisherDetail view publisher_detail.html you would just do something straight forward like this, to list all the books from a publisher:
{% for book in book_list %}
{{ book.title }}
{% endfor %}
This is part that is tripping me up.
We can also add the publisher into the context at the same time, so we
can use it in the template:
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherBookList, self).get_context_data(**kwargs)
# Add in the publisher
context['publisher'] = self.publisher
return context
Setting context['publisher'] = self.publisher in get_context_data means you can display the publisher's details in the context. For example, you could display the publisher's name above the list of book titles with:
<h2>Books published by {{ publisher.name }}</h2>
{% for book in book_list %}
{{ book.title }}
{% endfor %}
You can access related objects as described here: What is `related_name` used for in Django? .
Try calling either publisher.books.all() or publisher.book_set.all():
{% for book in publiser.book_set.all %}
{{ book.title }}
{% endfor %}

How can I create a sidebar in all my Django templates using CBV?

My problem is that I need dynamic tags list on all my pages and all posts (one post) content on that page too. How can I include tag sidebar on all the pages using Class Based Views? Thanks.
EDIT:
the tags list must be sorted by the frequency of using.
My code:
class AllTagCloudView(ListView):
model = Tag
template_name = 'tag_cloud.html'
context_object_name = 'tag_cloud'
def get_queryset(self):
qs = Tag.objects.values("name", "slug").annotate(Count("post")).order_by('-post__count')
return qs
I've tried to use
#register.inclusion_tag('tag_cloud.html', takes_context=True)
def sidebar_sorted_tags(context):
but I don't understand how to do it to work.
Also I have tried to use {% include 'tag_cloud.html' %}:
<div>
<p>Tags</p>
{% for tag in tag_cloud %}
<ul>
<li>{{ tag.name }}</li>
</ul>
{% empty %}
There is no tags yet
{% endfor %}
</div>
I think this is something stupid or I do something wrong.
This task is not related to class based views. You need to use custom inclusion template tag.
#register.inclusion_tag('tag_cloud.html')
def sidebar_sorted_tags():
return {'tag_cloud': Tag.objects.values("name", "slug")
.annotate(Count("post")).order_by('-post__count')}
And now in your base.html template write:
{% load my_tags %}
{% sidebar_sorted_tags %}
You can use context processors and global base template that all of your other templates will extend. Also you can use simple include in your template tag instead of global base template.

ListView on slug request

My URL
/tags/{slug}
points to this view:
class TagsDetailList(request, ListView):
queryset = Link.objects.filter(tags__name__in=['request.slug'])
template_name = "links/tags_detail_list.html"
So i have the request.slug object in the url.
Now i want to make a ListView that filters Link.objects by request.slug and respond to given template with the queried result.
All works but no queries are given on my template.
response template is:
{% extends "base.html" %}
{% block content %}
<h2>Tags Detail List</h2>
<ul>
{% if link in object_list %}
{% for link in object_list %}
<li>
{{ link.title }}
</li>
{% endfor %}
{% else %}
<p> Error: No Links in queryset! </p>
{% endif %}
<ul>
{% endblock %}
i dont get some elements, only the error message. its something bad on the request on my view.
who can help me and give me a hint how i can retrieve the request slug on my view?
EDIT:
nice solutions. i learned the way kwargs work(a small part).
But on the template i get still the error for no queryset. Tried both answers and also changed a bit but never really worked. Any hint what cause this?
If your urls pattern something like this:
r'^list/(?P<slug>[\w,\*]+)?$'
So in the views should be:
class TagsDetailList(ListView):
model = Link
template_name = "links/tags_detail_list.html"
def get_queryset(self):
qs = self.model.objects.all()
if self.kwargs.get('slug'):
qs = qs.filter(tags__name=self.kwargs['slug'])
return qs
What you've done here doesn't make sense: you're just asking for tags whose names are in the list consisting of the literal text " request.slug".
You need to override get_queryset so that it queries on the actual value of the slug, which is in self.kwargs.
def get_queryset(self, *args, **kwargs):
return Link.objects.filter(tags__name=self.kwargs ['slug'])
Also, I don't know what that if statement is doing in your template, but you haven't defined "link" do it will never evaluate to true, so no links will show.
Maybe it will help, but it works for me in Django 2+:
Your urls.py:
...
path('tags/<slug>/', TagsDetailList.as_view(), name='tags_detail'),
...
Your views.py:
...
class TagsDetailList(ListView):
model = Link
template_name = 'links/tags_detail_list.html'
def get_queryset(self, *args, **kwargs):
return Link.objects.filter(tags__slug=self.kwargs['slug'])
...

Separate admin page for one-to-many objects

I have the following model:
class Campaign:
...
class Location:
...
campaign = models.ForeignKey(Campaign)
I normally would edit the locations using a tabular inline, but because the number of Locations is very high I would like to move the management of the locations to another page. It should pretty much do the same as normal Admin page, except it should only display Locations for the selected campaign and when adding a new Location, it should be automatically attached to the right Campaign. To access the Location Admin page a link in the Campaigns list should be clicked.
What would be the right way to do this? Are there other options?
There is no out of the box solution for your problem, but it is pretty easy to do.
I would register Locations as a separate model admin object like this:
from django.contrib import admin
from models import Location
class LocationAdmin(EnhancedModelAdminMixin, admin.ModelAdmin):
list_filter = 'campaign',
admin.site.register(Location, LocationAdmin)
This way you will have admin interface where you can filter your locations by campaigns.
To have a direct access to Location on Campaign model admin I would use https://github.com/charettes/django-admin-enhancer
Auto selecting a filtered campaign on new Location create form it is a bit more trickier... But my approach would be to add a query parameter to Add new Location button and then catch it when initializing create form, to define initial value of field campaign.
First of all create a template on let's say your_app/admin/change_list.html, which looks like this:
{% extends "admin/change_list.html" %}
{% load i18n admin_static admin_list %}
{% load admin_urls %}
{% block object-tools %}
{% if has_add_permission %}
<ul class="object-tools">
{% block object-tools-items %}
<li>
<a href="{% url cl.opts|admin_urlname:'add' %}{% if is_popup %}?_popup=1{% if selected_campaign %}&selected_campaign={{ selected_campaign }}{% endif %}
{% elif selected_campaign %}?selected_campaign={{ selected_campaign }}{% endif %}"
class="addlink">
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
</a>
</li>
{% endblock %}
</ul>
{% endif %}
{% endblock %}
Then append your LocationAdmin class to look like this:
class LocationAdmin(EnhancedModelAdminMixin, admin.ModelAdmin):
list_filter = 'campaign',
list_display = 'location_name', 'campaign'
change_list_template = 'your_app/admin/change_list.html'
def changelist_view(self, request, extra_context=None):
if 'campaign__id__exact' in getattr(request, request.method):
selected_campaign = getattr(request, request.method)['campaign__id__exact']
if not extra_context:
extra_context = {'selected_campaign': selected_campaign}
else:
extra_context['selected_campaign'] = selected_campaign
return super(LocationAdmin, self).changelist_view(request, extra_context)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'campaign':
if 'selected_campaign' in getattr(request, request.method):
kwargs['initial'] = getattr(request, request.method)['selected_campaign']
return db_field.formfield(**kwargs)
return super(LocationAdmin, self).formfield_for_foreignkey(
db_field, request, **kwargs
)
So what we are doing here is we are overriding modeladmins changelist_view function just to add additional context variable of current 'selected_campaign'.
Then we adding it as a query parameter on Add new Location button.
Finaly we are using model admins formfield_for_foreignkey function to dynamically define initial value of campaign field based on parameter received with request when user clicks on Add new location.
Cheers! Do not forget to up-vote this answer if you feel it was helpful ;)

Categories

Resources