Unable to link blog post to its content page in Wagtail - blogs

I'm having a problem creating a link of a Blog Post to its own content page in wagtail. In my models I have two page classes, BlogPage and IndexPage. My BlogPage class is used to create the blog post, and IndexPage class is used to display a list of blog posts.
Please see models below:
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.wagtailcore.models import Page, Orderable
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index
class IndexPage(Page):
intro = RichTextField(blank=True)
def child_pages(self):
return BlogPage.objects.live()
content_panels = Page.content_panels + [
FieldPanel('intro', classname='full'),
]
subpage_types = ['blog.BlogPage']
class BlogPage(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
search_fields = Page.search_fields + (
index.SearchField('intro'),
index.SearchField('body'),
)
content_panels = Page.content_panels + [
FieldPanel('date'),
FieldPanel('intro'),
FieldPanel('body', classname="full")
]
My challenge is that I can't figure out how to link the blog post on the Index Page to its own page. Do I need to create a separate page model and html template to achieve this? or what could be the best approach to solve this problem?

You can create an include template (it doesn't need a model) - let's name it truncated_blog_post.html - which you can then invoke in your index_page.html template. This would be the recommended approach because using a include template for a post gives the possibility to use it anywhere you need to display a list of (truncated usually) posts: when you want the posts under a certain tag, for example.
truncated_blog_post.html
{% load wagtailcore_tags %}
<article>
<h2>{{ blog.title }}</h2>
<p>{{ blog.date }}</p>
<p>{{ blog.body|truncatewords:40 }}</p>
</article>
Using the pageurl tag from wagtailcore_tags you get the relative URL of that blog post. Obviously, if you don't want to create a include template for a truncated post, you can put the article code from blog_post.html directly in the for loop in the index_page.html template.
And your index_page.html template:
....
{% for blog in blogs %}
{% include "path/to/includes/truncated_blog_post.html" %}
{% empty %}
No posts found
{% endfor %}
....
For this to work you have to modify the IndexPage model:
class IndexPage(Page):
intro = RichTextField(blank=True)
#property
def blogs(self):
blogs = BlogPage.objects.live()
return blogs
def get_context(self, request):
# Get blogs
blogs = self.blogs
# Update template context
context = super(IndexPage, self).get_context(request)
context['blogs'] = blogs
return context
content_panels = Page.content_panels + [
FieldPanel('intro', classname='full'),
]
subpage_types = ['blog.BlogPage']

Related

Django ListView count all keys in a JSON variable

I would like to return in my Link.html the number of links contained in allLinks JSON variable.
So far I guess I misunderstand the use of get_context_data() and how to pass in context['CountLink'] the total count of links for each Post.
With the current code, I got:
Liste des recherches
terre : <QuerySet [<Post: terre>, <Post: océan>]> Links
océan : <QuerySet [<Post: terre>, <Post: océan>]> Links
models.py
class Post(models.Model):
title = models.CharField(max_length=255)
url = models.URLField(max_length=255)
allLinks = models.JSONField()
def __str__(self):
return self.title
views.py
class LinkView(ListView):
model = Post
template_name = 'link.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['CountLink'] = Post.objects.all()
return context
Link.html
{% for post in object_list %}
<li>
{{ post.title }} :
{{CountLink}} Links
</li>
{% endfor %}
Example of allLinks: {"0": "github.com/kubernetes/kubernetes/releases/tag/v1.26.0", "1": "kubernetes.io/docs/concepts/overview/what-is-kubernetes",}
Use the built-in length template filter:
{{ post.allLinks|length }} Links
You can use count()
In your case I think you want something like that, it will return count of Post model objects
context['CountLink'] = Post.objects.all().count()
For more info check this

Django,html template,for loop not working as expected

I am trying to apply a for loop to the following html (in a Django project) such that the 'Name' and the 'Comments' field are caused to repeat on the html view.
When I insert the templating code, that is:
{% for c in comments %}
{% endfor %}
on either side of the content i want to repeat, it simply makes the name and comments disappear altogether and does not have the desired result.
The relevant parts of the file are below:
index.html (the main html page)
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'guestbook/styles.css' %}">
</head>
<body>
<h1>The world's guestbook</h1>
<p>Sign the guestbook</p>
{% for c in comments %}
<h2>Name</h2>
<p>This the message that the user leaves.</p>
{% endfor %}
</body>
</html>
views.py (in the guestbook app)
from django.shortcuts import render
from .models import Comment
# Create your views here.
def index(request):
comments = Comment.objects.order_by('-date_added')
context ={'comments': comments}
#name=Name.objects.order_by('-date_added')
return render(request,'guestbook/index.html')
def sign(request):
return render(request,'guestbook/sign.html')
models.py file
from django.db import models
from django.utils import timezone
# Create your models here.
class Comment(models.Model):
name=models.CharField(max_length=20)
comment=models.TextField()
date_added=models.DateTimeField(default=timezone.now)
def __str__(self):
return self.name
I am working off a tutorial in which this is the recommended code and the desired result is as expected - I notice my html template does not have div tags and wonder if that could be an issue? If so, how can it be resolved?
You need to pass that context:
def index(request):
comments = Comment.objects.order_by('-date_added')
context ={'comments': comments}
return render(request,'guestbook/index.html', context=context)
^^^^^^^^^^^^^^^
From documentation of render:
Context: A dictionary of values to add to the template context. By default, this is an empty dictionary. If a value in the dictionary is
callable, the view will call it just before rendering the template.
Meaning, the values inside dictionary which is being used with known argument context of render function, these values will be sent to the template. Then you can access those values through {{ key }} of the dictionary(which is sent as context) in html template, or your case {{ comments }}. More information can be found regarding context in this SO Answer.

DJANGO template tags in plain text not displaying

I am making an app that displays questions. The question model has a text field and an image field. Each question has a template that is stored in my database in the text field. My problem is when I want to access images from the model, template tags are displayed as text and not rendering. My code:
# question model
class Question(models.Model):
question_text = models.TextField()
question_image = models.FileField(upload_to='static/images/questions', blank=true)
# question view
def question(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'questiontemplate.html', {'question': question})
# template
{% extends 'base.html %}
{% load static %}
{% autoscape off %}
{{ question.question_text }}
{% endautoscape %}
# in my database:
question.question_text = '<p> some html
{{ question.question_image.url }}
some html </p>'
question.question_image = 'image.png'
This works fine and renders the html perfectly except the template tag is not rendered and does not not give the image url
I want this to be the output:
Some html
static/images/questions/image.png
some html
But instead this is the output:
some html
{{ question.question_image.url }}
some html
Any suggestions to how the template tags could be render from the database text would be much appreciated.
Thanks for reading
Django doesn't know that the content in your model field is itself a model. The template can't know that. The only way to make this work is to treat that field itself as a template, and render it manually.
You could do that with a method on the model:
from django.template import Template, Context
class Question(models.Model):
...
def render_question(self):
template = Template(self.question_text)
context = Context({'question': self})
rendered = template.render(context)
return mark_safe(rendered)
Now you can call it in your template:
{{ question.render_question }}

Django 1.11 How to display template variable from within HTML saved in model for CMS Site

Within a template, I need to render a {{ variable }} from HTML content saved in a model instance.
Below are the stripped down parts of the code.
page.html
{% load static %}
<html>
<head>
<styles, links, etc.>
<title>{{ object.title }}</title>
</head>
<body>
<div>{{ object.html_content }}</div>
</body>
</html>
Model
class Page(models.Model):
title = models.CharField(max_length=30)
html_content = models.TextField()
GlobalMixin
# Used site wide for Global information.
class GlobalMixin(object):
def get_context_data(self, *args, **kwargs):
context = super(GlobalMixin, self).get_context_data(*args, **kwargs)
context['variable'] = "A Global piece of information"
return context
View
from .mixins import GlobalMixin
from .models import Page
PageView(GlobalMixin, generic.DetailView):
model = Page
template_name = "my_app/page.html"
def get_context_data(self, *args, **kwargs):
context = super(PageView, self).get_context_data(*args, **kwargs)
return context
Admin & HTML Content Field
I then enter admin, add new Page, enter my HTML content into html_content Field "Html Content" as per the following example.
<p>This is {{ variable }} that I need to display within my rendered page!</p>
Then SAVE.
BROWSER RESULTS
This is {{ variable }} that I need to display within my loaded page!
I know there is Django Flat Pages, but it doesn't look like it works for me as I require Global variables in my templates that flat pages doesn't offer.
The template is being rendered directly with the models content without looking at it.
I think I need to process the html_content field in the view and then add the required context variables to the context returned or save a temporary template file, append the html_content into the file, then render it.
How do I do make this work?
Is there a Django packaged interface that I can use to process the template from within my view?
I worked it out. Here's the code if anyone else comes across this or has a better way, please share.
Change View type to TemplateView, use slug in url to get model instance, use django.template.engines to convert the string to a template object then render the template object and return it in the context.
page.html
{% load static %}
<html>
<head>
<styles, links, etc.>
<title>{{ object.title }}</title>
</head>
<body>
<!-- <div>{{ object.html_content }}</div> -->
<div>{{ html_content }}</div>
</body>
</html>
views.py
from django.views.generic.base import TemplateView
from .mixins import GlobalMixin
from .models import Page
# NEW
from django.shortcuts import get_object_or_404
from django.template import engines
PageView(GlobalMixin, TemplateView): # generic.DetailView
# model = Page
template_name = "my_app/page.html"
def get_context_data(self, *args, **kwargs):
context = super(PageView, self).get_context_data(*args, **kwargs)
# NEW
context['object'] = get_object_or_404(Page, slug=self.kwargs['slug'])
t = engines['django'].from_string(context['object'].html_content)
context['html_content'] = t.render(context=context, request=None)
return context
I thought you could use custom filter like below.
file path
yourapp/templatetags/__init__.py
yourapp/templatetags/filters.py
filters.py
from django import template
import re
register = template.Library()
#register.filter(name="change_global_variable")
def change_global_variable(value):
return re.sub(r'\{\{(.*)\}\}', 'A global piece', value)
template
{% load static %}
{% load filters %} //filters.py//
<html>
<head>
<styles, links, etc.>
<title>{{ object.title }}</title>
</head>
<body>
<div>{{ object.html_content | change_global_variable}}</div>
</body>
</html>

Working with links in Django

I am working in a small blog application using Django. Sorry if the question is obvious, but I am a newbie. Actually it is my third since I started an online course. I have the following Queryset:
def all(request):
allTiles = Post.objects.values('title')
allPosts = Post.objects.all()[:3]
context = {'Posts': allPosts,"Titles":allTiles}
template = "home.html"
return render(request, template, context)
and the follwing html code:
<ol class="list-unstyled">
{% for singleTile in Titles %}
<li>{{singleTile.title}}</li>
{% endfor %}
</ol>
As you can see every title creates an link. Lets assume a person decides to read one of the posts. How can I use the title name and send a request back to the database to get the content of the post.
It is better to use the id or slug field for such task.
But if you surely want to use the title as the GET parameter then apply the urlencode filter to the field's value:
<a href="{% url 'post_detail' %}?title={{ singleTile.title|urlencode }}">
{{ singleTile.title }}
</a>
And the view will be something like this:
def post_detail(request):
post = get_object_or_404(Post, title=request.GET.get('title'))
return render(request, 'post_detail.html', {'post': post})
UPDATE: If you decide to go with the id/slug option then you can use the generic DetailView:
<a href="{% url 'post_detail' singleTile.id %}">
{{ singleTile.title }}
</a
urls.py:
from django.views.generic.detail import DetailView
from app.models import Post
url(r'^post/(?P<pk>\d+)/$', DetailView.as_view(model=Post),
name='post_detail')
You have to configure url first like
{% url 'app.views.post_id' singleTile.id %}</li>
In your urls
url(r'^post/(?P<post_id>\d+)/$', views.by_id, name='post_id'),
And in your views
def post_id(request, post_id):
allTiles = Post.objects.get(id=post_id)
return render(request, template, context)