How to create an invisible dummy page in Wagtail? - wagtail

How can I create an invisible dummy page in Wagtail?
I need a "virtual" page object within Wagtail to build a menu for non Wagtail based pages and also external resources. (See my entry post here)
class MenuDummyPage(Page):
menu_description = models.CharField(max_length=255, blank=True)
menu_icon = models.CharField(max_length=255, blank=True)
menu_link = models.CharField(max_length=255, blank=True)
settings_panels = [
FieldPanel('menu_description'),
FieldPanel('menu_icon'),
FieldPanel('menu_link'),
]
def get_sitemap_urls(self):
return []
def serve(self, request):
pass
If I create the above page object then it is not listed within the generated wagtail sitemap.
But if I navigate on my own to that page manually the object is called. How can I stop this?
Example:
If I create a MenuDummyPage with the title "This is a test" then the system will automatically generate a slug => "this-is-a-test".
If I call "/this-is-a-test"/ in my browser wagtail is answering because the slug exists. How can I remove this behavior for my "MenuDummyPage" objects?

If what you mean by a dummy page is a page under which other pages are kept in the page tree, then you could do the following:
from django.http import HttpResponseRedirect
class Node(Page):
subpage_types = [your subpage types]
parent_page_types = [your parent page types]
link = models.CharField(max_length=255, default='', blank='True')
content_panels = Page.content_panels + [
FieldPanel('link')
]
def serve(self, request):
if self.link is not None:
return HttpResponseRedirect(self.link)
# To handle the situation where someone inadvertantly lands on a parent page, do a redirect
first_descendant = self.get_descendants().first()
if first_descendant:
return HttpResponseRedirect(first_descendant.url)
else:
return HttpResponseRedirect(request.site.root_page.url)
The optional link field allows you to define a link if you wish for this position in the page tree structure. The above, again, assumes that you are using this Page-based item as a placeholder in the Page tree so that you can have other Pages under it. As long as you don't render the url of this page in your template, then users should never know how to get to the url for the Node, but if someone does get to the url for a Node-type page, then the first_descendant logic handles this situation by sending them to either the first descendant of the Node or to the home page if no descendants of Node exist.
In your template (note the use of specific_class):
{% for item in menu_items %}
<li>
<a href="{% if item.specific.link and item.specific.link != '' %}{{ item.specific.link }}{% elif item.specific_class == 'Node'%}#{% else %}{% pageurl item %}{% endif %}">{{ item.title }
</a>
</li>
{% endfor %}

Related

wagtail 4.0/ How return urls (templates) for preview in admin board from #path

class ProductIndexPage(RoutablePageMixin, Page):
subpage_types = ['ProductPage']
parent_page_types = ['home.HomePage']
def live_categories(self):
all_products_live_id = ProductPage.objects.live().values_list('categories_id', flat=True)
list_live_id_uniqe = list(set(all_products_live_id))
live_cat = ProductCategory.objects.filter(id__in=list_live_id_uniqe).order_by('-id')
return live_cat
#path('')
#path('all-categories/')
def all_category_page(self, request):
productpages = self.get_children().live().order_by('-first_published_at')
return self.render(request, context_overrides={
'title': self.title,
'productpages': productpages,
'live_categories': self.live_categories,
})
#path('<str:cat_name>/', name='cat_url')
def current_category_page(self, request, cat_name=None):
productpages = ProductPage.objects.live().filter(categories__slug__iexact=cat_name).order_by \
('-first_published_at')
current_cat = self.live_categories().get(slug=cat_name).name
return self.render(request, context_overrides={
'title': "%s" % current_cat,
'productpages': productpages,
'live_categories': self.live_categories,
})
#path('<str:cat_name>/<str:prod_name>/')
def product_page(self, request, cat_name=None, prod_name=None):
product = ProductPage.objects.get(categories__slug__iexact=cat_name, slug=prod_name)
return self.render(request, context_overrides={
'product': product},
template="products/product_page.html",)
I can't edit page in wagtail admin menu from path:
#path('<str:cat_name>/<str:prod_name>/')
My page tree in admin:
root/products/<prod_name>
How to change route in Admin for editing pages from wagtal button or for preview in Admin?
I am newbie, plz show me example.
sorry for English)
With RoutablePageMixin there is only one page - and it displays differently depending on the parameter sent on the url. Unfortunately, this means you can't preview the category version of your page, current_category_page.
But it looks like the product page you are routing too as #path('str:cat_name/str:prod_name/') should be displaying the same page as you serve from the ProductPage's own url. So the ProductPage you see in the preview ProductIndexPage + 'str:cat_name/str:prod_name/'

My urls no longer point to my pages Wagtail

On a wagtail site I have lost links to my blog pages (possibly as a result of moving to a newer version)
My blog listing page is still accessible with the url "http://localhost:8080/blog/"
I have the following models
class BlogListingPage(Page):
template = "home/blog_listing_page.html"
subpage_types = ['home.BlogDetailPage']
blog_listing_title = models.CharField(
max_length=100,
blank=False,
null=False,
help_text="Heading for the blog listing page",
)
content_panels = Page.content_panels + [
FieldPanel("blog_listing_title"),
]
class BlogDetailPage(Page):
"""Blog detail page."""
template = "home/blog_detail_page.html"
parent_page_types = ['home.BlogListingPage']
blog_title = models.CharField(
max_length=100,
blank=False,
null=True,
help_text="Blog title (100 chars max)",
)
content_panels = Page.content_panels + [
FieldPanel("blog_title"),
]
I can list the blog pages in my blog listing page:
{{ blog_listing.get_children.specific}}
{% for blog in blog_listing.get_children.live %}
<h5>{{ blog.specific.blog_title}}</h5>
<h5>{{ blog.slug }}</h5>
{% endfor %}
The slug displays correctly (e.g. /blog/xxx/) but the pageurl blog is None and if I click on it I get a 404 error (Request URL: http://localhost:8080/blog/xxx/)
Finally, even if I put http://localhost:8080/blog/xxx/ in the browser I get a 404 error
The line
{{ blog_listing.get_children.specific}}
displays the page queryset with the list of wagtail.core.models.Pages as I would expect
If I enter the following code into views
views.py
pqs = BlogListingPage.objects.all()
children = pqs.live()[0].get_children()
for child in children:
print('s', child.slug, '|', child.url_path)
I get the output
s xxx | /blog/xxx/
Which is what I expect.
Why are the pages/links no longer in sync?
I cannot post an MRE because, ironically, when I try to construct one, it works as expected. I cannot see what the difference is with my live site, but if someone could point to where I might look it would help.

Routablepage of the BlogPage/category

I fail to display the routable index page for categories/given category.
My code uses:
-BlogCategory(model)
-BlogPageBlogCategory (page) an intemediary structure that links to:
-PostPage(page)
I can pass the code to the post_page template, but when I click on a specific category link I get:
Reverse for 'post_by_category' with arguments '('',)' not found. 1 pattern(s) tried: ['category/(?P<category>[-\\w]+)/$']
In the following post #gasman wrote: "the routablepageurl tag has received an empty string". I couldn't find the 'empty' string/missing link.
I assume it's related to the route of my 'def post_by_category'. Any input that would help me deepen my learning woulg be great.
NB - in case it helps, when I run this procedure without the intemeiary page all's fine. I can display the BlogPage/given_category once I click on the category in the PostPage.
Here's my code:
Models
class BlogPage(RoutablePageMixin, Page):
...
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context["posts"] = posts
return context
def get_posts(self):
return PostPage.objects.descendant_of(self).live().order_by("-post_date")
#route(r'^category/(?P<category>[-\w]+)/$')
def post_by_category(self, request, category, *args, **kwargs):
self.posts = self.get_posts().filter(categories__blog_category__slug=category)
context["categories"] = BlogCategory.objects.all()
return self.render(request)
class PostPage(MetadataPageMixin, Page):
...
content_panels = Page.content_panels + [
...
InlinePanel("categories", label="category"),
...
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
return context
class PostPageBlogCategory(models.Model):
page = ParentalKey(
"blog.PostPage", on_delete=models.CASCADE, related_name="categories"
)
blog_category = models.ForeignKey(
"blog.BlogCategory", on_delete=models.CASCADE, related_name="post_pages"
)
panels = [
SnippetChooserPanel("blog_category"),
]
class Meta:
unique_together = ("page", "blog_category")
#register_snippet
class BlogCategory(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True, max_length=80)
panels = [
FieldPanel('name'),
FieldPanel("slug"),
]
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'categories'
The post_page.html:
{% extends "base.html" %}
{% load wagtailroutablepage_tags %}
...
{% for category in page.categories.all %}
<li>
<a href="{% routablepageurl blog_page "post_by_category" category.slug %}">
{{ blog_category.name }}
</a>
</li>
{% empty %}
No categories yet'
{% endfor %}
...
After debugging and testing, I didn't find any blank categories, (which doesn't mean there were none, but that I didn't find or deleted them Unintentionally).
I case it could help, what worked for me was adding the category_type at the beginning of my varaiable:
{{ category.blog_category.name }} rather than {{ blog_category.name }}
{% for category in page.categories.all %}
<a href="{% routablepageurl article_index_page "post_by_category" category.blog_category.slug %}" class="link-primary text-decoration-none">
{{ category.blog_category.name }}
</a>
{% empty %}
No categories yet'
{% endfor %}

Django - save form into database

Hey guy I need your help
so I have this model:
class PreferedShops(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
shop = models.ForeignKey(Shops, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.user.username, self.shop.name
and this is the form:
class LikeShopForm(forms.ModelForm):
class Meta:
model = PreferedShops
fields = ['date_posted']
and this is the view:
def shops(request):
if request.method == 'POST':
form = LikeShopForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.shop = request.shop
u.save()
return redirect('shops')
else:
form = LikeShopForm()
return render(request, "shops.html", {'form': form})
the probleme that I have is when I click on Like Button, I want that the form takes automatically the user and the name of the shop, and then save them into the DB
the user and the shop's name should be hidden
when I click submit I have this error 'WSGIRequest' object has no attribute 'shop'
please help me to take the shop's name automatically and save it in the db
Well the shop is not part of the request (strictly speaking, user is not either, but it is frequently added to the request object in the middelware by looking at the session).
You thus need to encode it into the URL, or in the POST parameters. For example with:
# app/urls.py
from django.urls import path
from app.views import like_shop
urlpatterns = [
path('shop/<int:shop_id>/like', like_shop, name='like_shop'),
]
Then in the view we obtain a parameter shop_id that contains the id of the related Shops object:
from django.shortcuts import get_object_or_404
from app.models import Shops
def like_shop(request, shop_id):
if request.method == 'POST':
shop = get_object_or_404(Shops, id=shop_id)
form = LikeShopForm(request.POST)
if form.is_valid():
u = form.save(commit=False)
u.user = request.user
u.shop = shop
u.save()
return redirect('shops')
else:
form = LikeShopForm()
return render(request, "shops.html", {'form': form, 'shop_id': shop_id})
Then the request in the POST should point to:
<!-- shops.html -->
<form action="{% url 'like_shop' shop_id=shop_id %}" method="post">
<!-- ... -->
</form>
The URL to this page then thus looks like /shops/123/like with 123 the id of the shop. If you thus want to pass the shop "implicitly", you need to encode it in the URL. Otherwise, you should make it a field of the form, such that the user can pick an option. Personally I find it very strange that you use date_posted as a form field, since typically this is an field that I would expect to be filled in with the timestamp when the user likes the shop.
Note: the name of the models is normally singular, so Shop instead of Shops.

Wagtail generic gallery implementation and OneToOneField

http://docs.wagtail.io/en/v1.13.1/getting_started/tutorial.html
The wagtail getting_started tutorial intros a blog gallery feature, implements as below:
class BlogPage(Page):
...
class BlogPageGalleryImage(Orderable)
page = ParentalKey(BlogPage, related_name='gallery_images')
image = ...
This way works, however BlogPageGalleryImage couples with BlogPage model. My intention is to make a generic gallery model which can be embbed with any model(page). The idea is using an intermediate Gallery model:
class BlogPage(Page):
gallery = models.OneToOneField(Gallery, on_delete=models.SET_NULL, null=True)
...
class Gallery(Page):
pass
class GalleryImage(Orderable):
gallery = ParentalKey(Gallery, related_name='images')
Then in code, we can get the images via blog.gallery.images.
My question is how to get it work with wagtail admin interface to inline create/edit the gallery object (OneToOneField) when editing the blog page object.
One way to do this is via a more generic relationship for your Page-Image connection, relating this to the Page model, rather than a specific BlogPage model.
This means that any page can have gallery images, you just need to expose the field to content panels via an InlinePanel.
You can also create a Mixin class to make some helpful methods available without rewriting them each time.
Here is an example:
from django.db import models
from wagtail.admin.edit_handlers import InlinePanel
from wagtail.core.models import Orderable, Page
from wagtail.images.edit_handlers import ImageChooserPanel
class ImageGalleryRelationship(Orderable, models.Model):
""" Relationship between any `Page` and `Image` for an image gallery."""
page = ParentalKey(Page, related_name='gallery_page')
image = models.ForeignKey('wagtailimages.Image', related_name='gallery_image')
panels = [ImageChooserPanel('image')]
class PageGalleryMixin():
def gallery_images(self):
images = [g.image for g in self.gallery_page.all()]
return images
class BlogPage(Page, PageGalleryMixin):
# all page fields, gallery does not need to be defined here
content_panels = Page.content_panels + [
InlinePanel('gallery_page', label='Image Gallery'),
#...
]
Note: This is not a OneToOne connection, InlinePanel requires a ParentalKey relationship. There is no real 'Gallery' model in this solution just a set of orderable relationships.
In general app (or wherever), models.py
class PageGalleryImage(Orderable):
page = ParentalKey(Page,
on_delete=models.CASCADE,
related_name='image_gallery')
image = models.ForeignKey('wagtailimages.Image',
on_delete=models.CASCADE,
related_name='page_gallery_image')
caption = models.CharField(blank=True, max_length=250)
panels = [
ImageChooserPanel('image'),
FieldPanel('caption'),
]
Other apps as blog, models.py:
class BlogPage(Page):
content_panels = Page.content_panels + [
...
InlinePanel('image_gallery', label="Gallery images"),
]
This provides one app = one gallery.

Resources