Wagtail Show latest blog posts on Homepage through a streamfield - wagtail

I have 3 mains sections in my site, homepage, blog index, and blog specific. I am using the streamfield function in wagtail to order various sections in the homepage. One of those sections is for the latest three blog posts.
I have done this for the blog index page, but can't grab the latest blog posts in the streamfield.
My model looks like this
class CaseStudiesIndex(Page):
def casestudies(pages):
casestudies = CaseStudyPage.objects.all().order_by('-first_published_at')
return casestudies
intro = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('intro', classname="full")
]
class LatestPosts(blocks.StructBlock):
static = blocks.StaticBlock(admin_text='Latest posts: no configuration needed.',)
def casestudies(pages):
casestudies = CaseStudyPage.objects.all().order_by('-first_published_at')[:3]
return casestudies
class Meta:
icon = 'doc-full'
label = 'Latest Posts'
template = 'blocks/_latestPosts.html'
class HomePage(Page):
blocksbody = StreamField([
('lead_banner', LeadBanner()),
('latest_posts', LatestPosts()),
('team', Team())
],null=True,blank=True)
content_panels = Page.content_panels + [
StreamFieldPanel('blocksbody'),
]
In my block folder I am calling the file fine and it renders the wrapper fine but I can't grab any of the data, I have tried a bunch of ways but nothing returns.
{% load wagtailcore_tags wagtailimages_tags %}
{% load static %}
<section>
<div class="wrapper__inner">
<ul>
{% for case in self.casestudies %}
{{case.title}}
{% endfor %}
{% for case in self.case_studies %}
{{case.title}}
{% endfor %}
{% for case in self.latest_posts %}
{{case.title}}
{% endfor %}
{% for case in page.casestudies %}
{{case.title}}
{% endfor %}
{% for case in page.case_studies %}
{{case.title}}
{% endfor %}
{% for case in page.latest_posts %}
{{case.title}}
{% endfor %}
</ul>
</div>
</section>
For the Blog Index page that does work I do the following.
{% extends "inner_base.html" %}
{% load wagtailcore_tags %}
{% block body_class %}template-case-studies{% endblock %}
{% block content %}
<section>
<div class="wrapper__inner">
<h1>{{self.title}}</h1>
<ul>
{% include "blocks/CaseStudiesLatestBlock.html" %}
</ul>
</div>
</section>
{% endblock %}
And the CaseStudiesLatestBlock.html which works fine looks like
{% load wagtailcore_tags wagtailimages_tags %}
{% load static %}
{% for case in self.casestudies %}
<li>
<strong>{{ case.title }}</strong>
</li>
{% endfor %}

Defining your own methods on a StructBlock won't work - the self (or value) variable you receive on the template is just a plain dict, not the StructBlock object itself. (This might seem counter-intuitive, but it's consistent with how blocks work in general: just as a CharBlock gives you a string value to work with and not a CharBlock instance, StructBlock gives you a dict rather than a StructBlock instance.)
Instead, you can define a get_context method (as documented here) to provide additional variables to the template:
class LatestPosts(blocks.StructBlock):
static = blocks.StaticBlock(admin_text='Latest posts: no configuration needed.',)
def get_context(self, value, parent_context=None):
context = super(LatestPosts, self).get_context(value, parent_context=parent_context)
context['casestudies'] = CaseStudyPage.objects.all().order_by('-first_published_at')[:3]
return context
You can then access the casestudies variable in the template, e.g. {% for case in casestudies %}.

Related

How can you loop through all of the children of a model?

In Django I am trying to loop through all of the children of my Todo model. But whenever I try to run it, it gives me an AttributeError that says "Manager isn't accessible via Todo instances". My code looks like this:
{% extends 'base.html' %}
{% block content %}
<h3>Tasks</h3>
{% for t in model.objects.all %} <!-- Error -->
<p>{{t.name}}</p>
{% endfor %}
{% endblock %}
{% block options %}
<li class="bg-light py-3 w-100 px-4 rounded text-nowrap fs-4">
<button class="text-decoration-none text-dark">Save</button>
</li>
{% endblock %}
I tried to just put the model in the context, and then I got an error in the HTML, so I figured out that it happened when I tried to reference 'model.objects.all'.
Inside of your view you need to specify what django should pass to the template. It does not serve the entire Database; therefore queries like in model.objects.all inside of your templates are not allowed.
Specify the queryset inside your views.py:
def todo_view(request):
context = {}
context['my_todos'] = my_todo_model.objects.all()
context['most_important_todo'] = my_todo_model.objects.get(pk=1)
# put your own logic inside the `.get` method above
return render(request, 'my_template.html' context)
And then access it inside of your template like so:
{% for t in my_todos %}
<p>{{ t.name }}</p>
{% endfor %}
{{ most_important_todo.name }}
Because we put a queryset inside of my_todos we can loop over it in the template. most_important_todo ist just a single object passed to the template, so we can access its properties (e.g. the name) directly.
Let me know how it goes

How can i retrieve context from for loop in views django and send it to template

I need to do one page with topic and all entries about this topic. And for each entries I need all comments shown up below each entry. Comments should be for exact entry. Cant find it solution and decided ask. Thank you.
models.py
--snip--
class Comment(models.Model):
date_added = models.DateTimeField(auto_now_add=True)
text = models.TextField()
owner = models.ForeignKey(User, on_delete=models.CASCADE)
entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
def __str__(self):
return f"{self.text[:50]}..."
views.py
--snip--
def topic(request, topic_id):
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.all()
comments = []
for entry in entries:
c = entry.comment_set.all()
comments.append(c)
context = {'topic': topic, 'entries': entries,
'comments': comments}
return render(request, 'learning_logs/topic.html'
,context)
topic.html
{% extends 'learning_logs/base.html' %}
{% block content %}
<p>{{ topic.title }}</p>
<p>{{ topic.description }}</p><br>
<p>Entries:</p><br>
<ul>
{% for entry in entries %}
<li>{{ entry.text }}</li>
<ul>
<p>Comments:</p>
{% for comment in comments %}
<li>{{ comment.text }}</li>
{% endfor %}<br>
</ul>
{% empty %}
<li>Not entries</li>
{% endfor %}
</ul>
{% endblock content %}
You can do this with just topic. No need to get entries and comments:
{% extends 'learning_logs/base.html' %}
{% block content %}
<p>{{ topic.title }}</p>
<p>{{ topic.description }}</p><br>
<p>Entries:</p><br>
<ul>
{% for entry in topic.entry_set.all %}
<li>{{ entry.text }}</li>
<ul>
<p>Comments:</p>
{% for comment in entry.comment_set.all %}
<li>{{ comment.text }}</li>
{% endfor %}<br>
</ul>
{% empty %}
<li>Not entries</li>
{% endfor %}
</ul>
{% endblock content %}
You will also want to optimise the query using prefetch_related so you don't hit the db per entry per comment:
topic = Topic.objects.prefetch_related('entry_set__comment_set').get(id=topic_id))
context = {'topic': topic}
return render(request, 'learning_logs/topic.html', context)

Twig check if value is in array

I'm having trouble to check if a value is present in an array with Twig.
I want to hide a shipping method in a checkout if there's a certain product in the cart.
I can only use Twig code so I have to find a logic in that.
So let's say when product ID 1234 is in cart then I want to hide #certain_div
So what I have is this ->
{% if checkout %}
{% set array = theme.sku_shipping_rule | split(',') %}
// theme.sku_shipping_rule = a text string like 1234, 4321, 5478
{% if checkout.products %}
{% for product in checkout.products %}
{% if product.sku in array %}
<style>
#certain_div {
display: none;
}
</style>
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
The problem I'm facing is that it seems my code always returns true. So even if the product.sku doens't match a value in the array it still hides #certain_div. I've tested that with placing {{ product.sku }} just before <style>.
What do I wrong?
Any help greatly appreciated!
UPDATE:
I've updated the question/code to show what's happening
{% if checkout %}
{% set skuToCheck = theme.sku_shipping_rule | split(',') %}
{% set skuInCart = [] %}
{% if checkout.quote.products %}
{% for product in checkout.quote.products %}
{% set skuInCart = skuInCart | merge([product.sku]) %}
{% endfor %}
{% endif %}
{% for myVar in skuInCart %}
{{ myVar }}<br/>
{% endfor %}
// this prints
PSYGA1 // where this sku should NOT match
FP32MA4
{% for myVar in skuToCheck %}
{{ myVar }}<br/>
// this prints
FP32LY4
FP32STR4
FP32MA4
{% if myVar in skuInCart %} // also tried with | keys filter
{{ myVar }} is found
{% endif %}
{% endfor %}
{% endif %}
So what I did is placing the sku's from the products which are in the cart in an array skuInCart. Next I want to check if myVar is present in the skuInCart array. If so print myVar is found.
What happens is that you should expect that it prints only the matching results. However it actually prints all values present skuInCart (using keys filter) or completely blank without using keys filter.
What you are doing in theory should work, have a look a this fiddle example to show you a working demonstration:
https://twigfiddle.com/yvpbac
Basically:
<div id="certain_div">
This should not show up
</div>
{% set searchForSku = "890" %}
{% set productSkuArrayString = "1234,4567,890" %}
{% set productSkuArray = productSkuArrayString|split(',') %}
{% if searchForSku in productSkuArray %}
<style>
#certain_div {
display: none;
}
</style>
{% endif %}
<!-- New Trial -->
<div id="certain_div">
This should show up
</div>
{% set searchForSku = "891" %}
{% set productSkuArrayString = "1234,4567,890" %}
{% set productSkuArray = productSkuArrayString|split(',') %}
{% if searchForSku in productSkuArray %}
<style>
#certain_div {
display: none;
}
</style>
{% endif %}
Will result in:
<div id="certain_div">
This should not show up
</div>
<style>
#certain_div {
display: none;
}
</style>
<!-- New Trial -->
<div id="certain_div">
This should show up
</div>
You can use iterable to check if a variable is an array or a traversable object:
{% if items is iterable %}
{# stuff #}
{% endif %}

Wrong parent loop context in twig

I'm using Symfony 2.3 and Twig 1.15. I've got a nested foreach twig loop and I'm trying to get a different result for the last iteration of the outer loop.
I've seen this: http://twig.sensiolabs.org/doc/recipes.html#accessing-the-parent-context-in-nested-loops
However I'm getting a different result - an error in the line where I access the parent context:
Key "loop" for array with keys "groups, scores, type, user, assetic, app, avatarsDir, sonata_block, _parent, _seq, group, _key, subgroup" does not exist in "(...)"
the relevant code, stripped of classes, ids and unnecessary tags:
{% for group in groups %}
<div>
{% for subgroup in group.subgroups %}
{% for test in subgroup.tests %}
{% block test_block_box %}
{% if not loop.parent.loop.last %}
(html follows...)
{% else %}
(some different html follows...)
{% endif %}
{% endblock %}
{% endfor %}
{% endfor %}
</div>
{% endfor %}
I have made sure that the error does not refer to the inner loop call, i.e. I replaced loop.parent.loop.last with loop.last and the page rendered successfully (contents obviously wrong, but it didn't crash).
What am I doing wrong when accessing the parent context??
Simply remove {% block test_block_box %} and {% endblock %}, the loop parent shoud be accessible.
You can try to define a value outside of the {% block %} and see if you have access to it in the {% block %}:
{% for group in groups %}
<div>
{% for subgroup in group.subgroups %}
{% for test in subgroup.tests %}
{% set test_loop_is_last = loop.last %} {# define the value #}
{% block test_block_box %}
{% if not test_loop_is_last %}{#test the value #}
(html follows...)
{% else %}
(some different html follows...)
{% endif %}
{% endblock %}
{% endfor %}
{% endfor %}
</div>
{% endfor %}

Hyde copy code from content straight in to deploy

I'm using hyde (http://hyde.github.io) and everything is working great. Then I needed a page that is not static. I wrote it in php. Is there a way to have hyde just copy the content straight in to the deploy page from the content page?
{% extends "topbar.j2" %}
{% block container %}
{% block ignore %} *Hyde don't try to process just copy as is*
<h2> Search </h2>
<?php
... php code ...
echo "Stuff"
?>
{% endblock ignore %}
{% endblock container %}
So with some playing and deeper reading of the documentation.
The raw tag is for jinja syntax only (as shown)
{% raw %}
<ul>
{% for item in seq %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endraw %}
Thought about turning off the markdown filter for the search page, but didn't want to create jinja page rules
Learned that one line of php does not seem to break the page.
<?php phpinfo(); ?>
Our solution
Move all php code back to its own file searcher.php
<?php
if(url is searcher.php redirect to search.php);
//code and stuff ... ;
echo "results";
?>
Keep the search.php page simple
{% extends "topbar.j2" %}
{% block container %}
<h2> Search </h2>
{% raw %}
<?php include_once("searcher.php"); ?>
{% endraw %}
{% endblock container %}

Resources