Obtaining a list of Wagtail posts to display in a Django template outside of Wagtail - django

I have a main Django website, which consists of a main page at / using standard django templates, and then a wagtail blog situated at /blog
On my homepage, I would like someone to be able to show the most recent published articles from the wagtail blog.
I'm having a lot of trouble doing this as I can't find any resources or guides.
So far I added a model for my homepage (which didn't have one previously), just to obtain blogs:
from myblog.models import BlogPage
class HomePage(Page):
def blogs(self):
blogs = BlogPage.objects.all()
blogs = blogs.order_by('-date')
return blogs
Then for my homepage template I have:
{% for blog in page.blogs %}
<div class="col-md-4">
<a class="blog-post-link" href="{% pageurl blog %}">
<h3>{{ blog.title }}</h3>
</a>
<div class="blog-intro">
{{ blog.body|richtext|truncatewords_html:50 }}
<a class="read-more" href="{% pageurl blog %}">Read More »</a>
</div>
</div>
{% endfor %}
However nothing is displayed, nor are any errors given.
How can I obtain and display a list of published Wagtail posts in an app outside of Wagtail?

If your homepage is handled by a standard Django view and URL route, then defining a HomePage model will not change that by itself - to go down that path you would also need to:
update your URL routes so that the root path is handled by Wagtail, not just the /blog/ path (i.e. remove the existing route for your homepage view and change path("blog/", include(wagtail_urls)) to path("", include(wagtail_urls)))
create an instance of the HomePage within the Wagtail admin
Adjust the site setup under Settings -> Sites so that the default site points to that homepage as its root page, rather than your blog index page
However, none of this is necessary if you just want to add some Wagtail-derived data to your existing homepage setup - you can just add the BlogPage query to the data that your view function passes to the template. Assuming the view function currently looks something like:
def home(request):
# ... do some processing here ...
return render(request, "home/homepage.html", {
'foo': 'bar',
})
this would become:
def home(request):
# ... do some processing here ...
blogs = BlogPage.objects.all()
blogs = blogs.order_by('-date')
return render(request, "home/homepage.html", {
'foo': 'bar',
'blogs': blogs,
})
You can then access the variable blogs in your template - e.g. {% for blog in blogs %}.

I'm not an expert in Wagtail but did you import wagtail tags in your template?

Related

Why is Django rendering one of my app's templates as plain text

I am working on a blog development in Django and there are different categories of posts or articles on category_list.html which have been created using ORM library and to each category i have added some articles using ORM library with the help of category slug which are rendered on category_view.html and problem is that list of categories are shown perfectly on category_list.html after adding from admin panel of django and when I click on any category name(contains get_absolute_url link), it takes me perfectly to category_view.html page but on category_view.html, data is shown in the form of text means renders the complete content of category_view.html in the form of plain text. All other templates are getting rendered by django perfectly but what is the reason that category_view.html is getting rendered as plain text? Please help me
def category_list(request,category_slug=None):
category=None
categories=CategoryList.objects.all()
# article=Article.objects.all()
return render(request,'category_list.html',{'categories':categories})
def category_view(request,category_slug):
category=get_object_or_404(CategoryList,slug=category_slug)
article=Article.objects.filter(category=category)
return render(request,'category_view.html',{'category':category},{'article':article})
My url.py contains the following urls:
path("",views.index),
path('category',views.category_list, name='category_list'),
path('<slug:category_slug>',views.category_view, name='story_by_category'),
This is link in category_list.html which takes me to category_view.html
<div class="row py-3">
{%for c in categories%}
<div class="col-lg col-12 homeimg" id="img1">
<h5>{{c.name}}</h5>
<img class="secimg border-info" src="{{c.image_url|default_if_none:'#'}}" alt="gameplay
videos">
</div>
{%endfor%}
</div>

Django - add link with custom admin page href

In my Django project, I have created a custom admin page for an app via the get_urls() method. I'd like to add a link to the app's main model index view that will take users to this custom page - however, I'm having some trouble creating this link element correctly and I don't seem to be able to piece together the right way to do it - I'm just left with a Reverse for 'export' not found. 'export' is not a valid view function or pattern name. error.
I've set up the admin for the app like so:
# my_project/observations/admin.py
from django.template.response import TemplateResponse
from django.urls import path
class ObservationAdmin(SimpleHistoryAdmin, SoftDeletionModelAdmin):
change_list_template = 'export_link.html'
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('export/', self.admin_site.admin_view(self.export_view), name='export')
]
return custom_urls + urls
def export_view(self, request):
context = dict(
self.admin_site.each_context(request),
)
return TemplateResponse(request, 'export.html', context)
and the two templates that are referenced:
# my_project/observations/templates/export.html
{% extends "admin/base_site.html" %}
{% block content %}
<div>
Some custom content
</div>
{% endblock %}
# my_project/observations/templates/export_link.html
{% extends 'admin/change_list.html' %}
{% block object-tools-items %}
<li>
Export
</li>
{{ block.super }}
{% endblock %}
Navigating directly to http://localhost:8000/admin/observations/observation/export/ works perfectly, I see the custom content page exactly as I want it... so the issue I'm striking is with the link template - I get the Reverse... error when I navigate to the model index page.
Perhaps the argument I'm passing to url is incorrect, or I need to register that URL elsewhere - but I don't quite know. The other examples of link elements like this that I've been able to find don't reference URLs created via the admin class' get_urls() method - so any guidance on this would be greatly appreciated.
Thanks very much, let me know if there's any other info that I can provide to help sort this out.
I think the problems is in missing namespace in your export_link.html template. Instead of:
Export
try:
Export

How can I use the blog index within the homepage?

I'm new to wagtail and pretty new to django. I'm wondering how to implement the blog that's documented here:
https://docs.wagtail.io/en/stable/getting_started/tutorial.html
but directly within the home page. Meaning, I'd like the blog index to be the root of the site (like most blogging sites have).
Thanks in advance!
You could just add your blog 'posts' (e.g. BlogPage) as direct children under the home page.
This will mean your blog page URLs will be directly under the root URL.
e.g. mydomain.com/my-cool-post/.
Note: Other pages under the home page will share this route area (e.g. /contact-us/).
Essentially just follow the steps in the tutorial but ignore the part about the BlogIndex. Keep your BlogPage model as is and when adding children in the admin UI add them under the home page.
If you want to list all the posts on your HomePage template, you can revise the template context to return blog_pages similar to the documentation.
You can filter a page queryset by type using exact_type. Or, as shown below, you can use BlogPage.childOf(...) to query the other way.
Django documentation about the queryset api.
my-app/models.py
class HomePage(Page):
body = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('body', classname="full"),
]
def get_context(self, request):
context = super().get_context(request)
# Add extra variables and return the updated context
# note: be sure to get the `live` pages so you do not show draft pages
context['blog_pages'] = BlogPage.objects.child_of(self).live()
return context
my-app/templates/home_page.html
{{ page.title }}
{% for blog_page in blog_pages %}
{{ blog_page.title }}
{% endfor %}
Simple, using redirect in urls.py like below code:
from django.views.generic import RedirectView
urlpatterns = [
path(r'^$', RedirectView.as_view(url='/blog/', permanent=False)),
# pass other paths
]

How can django-cms plugins be displayed using pagination

I written two django-cms plugins to display image galleries and videos.
These are attached to CMS pages at /gallery/ and /videos/ where each template has a placeholder allowing the corresponding plugin to be included.
At that base level where I have gallery.html and video.html rendering all plugin instances to the page I would like to be able to attach endless for pagination.
This was a really simple task to achive pagination on images within a gallery because I have an apphook view in the gallery to collect a list of all the images and then it's as simple as {% pagingate images %} {% for image in images %} etc in the template.
However in a template where django-cms controls the collection & rendering of all the plugin instances and I lose that control, how can I paginate the plugins?
I've started down a route of using an apphook on the /gallery/ index, but to acomplish this I can imagine I'll need to stop django-cms doing what it does by default and what it should be left to do. So I need some guidance/advise on the best method for the job. Anyway, here's some code;
# views.py
def gallery_index(request, *args, **kwargs):
template = request.current_page.template
placeholder_id = request.current_page.placeholders.get(slot='gallery').id
gallery = Placeholder.objects.get(id=placeholder_id)
galleries = gallery.cmsplugin_set.all()
return render_to_response(
template,
{'galleries': galleries},
context_instance=RequestContext(request)
)
# gallery.html
<h3>GALLERIES</h3>
<div id="panel-area" class="gallery_grid">
<ul id="galleries" class="gridview" style="width: 800px;">
{% paginate galleries %}
{% for gallery in galleries %}
<li>{{ gallery }}</li> <!-- testing the pagination -->
{% endfor %}
{% placeholder "gallery" %}
</ul>
{% show_pages %}
# cms_plugins.py
class ImageGalleryPlugin(CMSPluginBase):
name = _('Image Gallery')
model = ImageGalleryPlugin
form = ImageGalleryAdminForm
render_template = 'single_gallery.html'
def render(self, context, instance, placeholder):
context.update({'gallery': instance,})
return context
# single_gallery.html
<li class="gallery_top">
<span class="title">
<a href="{% url 'image_gallery_page' page_id=request.current_page.id gallery_id=gallery.id %}">{{ gallery.event_date|date:"d/m/Y" }}<br/>
<p class="sub-text">View more of {{ gallery.event_name }}</p></a>
</span>
<img src="{{ gallery.display_random.gallery_display }}" alt="" border="0" />
</li>
My current solution is a jQuery solution, but I'd love it all to be controlled by django endless pagination for consistency in behaviour and design.
Been there. Pagination in Django CMS plugin is indeed problematic.
One possible way to solve the problem
Implement paginated API endpoint for fetching the objects. Use django.views.generic.list.ListView for example. It has nice built in pagination.
In CMS plugin, fetch objects from the API endpoint with AJAX. CMS plugin doesn't need to know which objects to render, it just needs to know where it can fetch the objects (the API endpoint).
This approach requires frontend code for fetching the correct objects, updating the UI, keeping track of the current page and so on, but can be (and has been) successfully used to implement paginated Django CMS plugins.
Here is my quick solution to add pagination into a django cms plugin
from cms.models.pagemodel import Page
from cms.plugin_base import CMSPluginBase
from django.http import Http404
from django.core.paginator import InvalidPage, Paginator
class MyPlugin(CMSPluginBase):
def render(self, context, instance, placeholder):
query_set = Page.objects.filter(is_page_type=False)
page = context['request'].GET.get('page') or 1
paginator, page, queryset, is_paginated = self.paginate_queryset(page, query_set, 6)
context.update({'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
})
return context
def paginate_queryset(self, page, queryset, page_size):
paginator = Paginator(queryset, page_size)
try:
page_number = int(page)
except ValueError:
if page == 'last':
page_number = paginator.num_pages
else:
raise Http404(_('Page is not “last”, nor can it be converted to an int.'))
try:
page = paginator.page(page_number)
return (paginator, page, page.object_list, page.has_other_pages())
except InvalidPage as e:
raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
'page_number': page_number,
'message': str(e)
})

Can Django Templates have Dynamic model content under the same "Template.html" name?

I have a main page with images of books that are href links in a template {% for %} statement that generates them. It roughly looks like this:
{% for book in books %}
<a href="/single_book/">
<img src="{{book.image}}">
</a>
I would like for once the image gets clicked on to redirect to a dynamically generated page about the book that uses info from the model to generate the content such as the book title, author, year published, and description. Is there a way to do this using an html file like "single_book.html" and it will show unique content when each book is clicked?
I looked up some URL patterns such as:
url(r'^single_book/(\w+)/$', single_book),
But does that mean each saved html file has to be saved for each book, so book one is: "single_book/book1.html" and book two is "single_book/book2.html" ? I would like to just have one one html template file of "single_book.html" that does it for each book.
If there is a way to do this, what would also be the view.py code, etc...? I am running Django 1.6 and Python 2.7. Using a MySQL DB. I'm just practicing using the "manage.py runserver" for the time being.
Thank you in advance.
You just need to create single html file for the details of a particular book:
book_detail.html
{{book.title}}
{{book.author}}
{{book.description}}
You just need to pass the instance of a book to the template book_detail.html which is requested by the user:
The url:
url(r'^single_book/(\d+)/$', single_book),
The View:
def single_book(request, pk):
book = Book.objects.get(pk=pk)
ctx = {'book': book}
return render_to_response('book_detail.html', RequestContext(request, ctx))
Yes, that's how templates are supposed to be used. If you read this link about Django's Class Based Views (specifically, DetailView) you can get a rough outline of what you should do:
# views.py, using the generic CBV DetailView
class SingleBookView(DetailView):
object = Book
singlebook = SingleBookView.as_view()
# url.py change slightly so you can use named urls in templates
url(r'^single_book/(?P<pk>\w+)/$', singlebook, name='singlebook')
Then the template can be the same single_book.html for every book, since the book object will be automatically detected by the view using the pk param from your url
<p>{{ object.title }}</p>
Plus, you can now use named urls:
{% for book in books %}
{# see how your original loop changes with named urls #}
<a href="{% url singlebook book.pk %}">
<img src="{{book.image}}">
</a>