Template and model reuse in Wagtail - wagtail

I am building a fairly basic Wagtail site and have run into an issue regarding the reuse of models and templates.
Say my site has two kinds of entries:
blog posts and
events.
Both pages look the same and share many model fields (e.g., author, category, intro, etc.). However, there are some model fields that only make sense for the event entry type (e.g., event_date, event_venue).
What would be the ideal way of creating templates and models for this use-case without repeating myself in the code?
Right now, both blog and event entries use the same HTML template and the same model. However, when the user creates a blog post in the Wagtail admin, he or she has to "ignore" the event-specific fields (which may become even more in the future).
Do I have to create two separate template files and two separate models despite both blogs and events being 95% the same code? What would be the correct way to solve this in Wagtail?

If you want to maintain it the way it is, contained within one model and template, you could create separate model admins for each pseudo-type (Blogs and Events), and override the queryset function to make each separate modeladmin only show the ones you're looking for, and then edit the panels that are shown on create/edit/delete.
class EventAdmin(ModelAdmin):
...
panels = [
FieldPanel('your_field'),
...
]
def get_queryset(self, request):
qs = super().get_queryset(request)
events = qs.filter(your_field__isnull=False)
return events
More information at https://docs.wagtail.io/en/stable/reference/contrib/modeladmin/index.html

Related

Search/Filter Dropdown in Django Admin Panel for standard CharField

I am using Django to build data models including a model Company. Some of the fields belonging to this model are limited to set choices using the choices='' argument. Some of these fields have a large number of choices, for example a country CharField which lists all countries. Finding the right value among the long list can be tedious so I want to be able to search across the given choice values. This is easy to do for ForeignKey/ManytoMany fields using autocomplete_fields = [] as seen in the attached screenshot (from a different model) but can't seem to find a method for implementing this with a normal CharField with lots of choices. This seems like something that should be built into the Django for the admin panel but I can't find anything within the docs. Please how can I implement a search/filter dropdown for any given (non FK/m2m) fields? Thanks in advance. If there's anymore information I can provide let me know and I will.
country = models.CharField(max_length=128, choices=COUNTRY_CHOICES, null=True)
Model code added. This COUNTRY_CHOICES array is what I would like to be able to select from in a searchable dropdown list.

Is there a way to show images in a Wagtail Model Admin Record Listing page?

I have reviewed the question on Is there any way to show a field on a listing page in Wagtail admin? but my situation seems to similar but also different enough that that particular solution won't work for me. Instead of on the Page listing I wish to achieve a similar thing on the Model Admin listing and I would think this should be such a common requirement that I am picking that someone must have done this before I have attempted it.
I haven't really figured out how to even try anything to get started but what I have looked at is the modeladmin template tags under wagtail.contrib.modeladmin on GitHub but I am completely guessing.
Can anyone point me to which templates I need to modify and whether I need to modify any of the template tags and how to override anything I need to override?
There's no need to override templates for this - this is standard functionality in ModelAdmin. Adding extra fields to the listing is done by setting list_display on the ModelAdmin class:
class BookAdmin(ModelAdmin):
model = Book
list_display = ('title', 'author')
For displaying images, ModelAdmin provides ThumbnailMixin:
from wagtail.contrib.modeladmin.mixins import ThumbnailMixin
from wagtail.contrib.modeladmin.options import ModelAdmin
class BookAdmin(ThumbnailMixin, ModelAdmin):
model = Book
thumb_image_field_name = 'cover_image'
list_display = ('title', 'author', 'admin_thumb')
('admin_thumb' is a special-purpose field name provided by ThumbnailMixin, and should be used rather than the actual image field on your model - cover_image in this example.)

Single piece of content, multiple URLs?

I have a use case that I could use some advice on.
We publish multiple products, each of which has it's own subtree on the site. Generally, a piece of content gets published to just a single product, e.g. a news article gets published to product A and can be accessed at one URL.
However, sometimes we have content that we want to publish to multiple products, e.g. a single news article gets published to products A, B, and C and will be available at 3 different URLs.
With our current CMS we end up doing this by copying and pasting the content, which is a hassle for editors, especially if the content needs to be updated.
An ideal scenario would be where and editor edits the content in one place, specifies the products to publish to, and the content is served by more than one URL and with a template that is product-specific.
It seems that RoutablePageMixin could be useful here, but I'm not sure how to handle letting the editor specify the destination products and making the routing aware of that choice.
Has anyone solved a similar problem using Wagtail?
I have solved a similar problem in Wagtail, the RoutablePageMixin is the key to solving this problem.
If you have /blog/A/slug-product/, /blog/B/slug-product/, /blog/C/slug-product/ , then you can get the slug value slug-product here, then use this value to search the distinct content in your db.
class BlogPage(RoutablePageMixin, Page):
def get_posts(self):
return PostPage.objects.descendant_of(self).live()
#route(r'^(\d{4})/(\d{2})/(\d{2})/(.+)/$')
def post_by_date_slug(self, request, year, month, day, slug, *args, **kwargs):
post_page = self.get_posts().filter(slug=slug).first()
return Page.serve(post_page, request, *args, **kwargs)
As you can see, I did not use the date info of the url but the slug value to get the blog post object, you can follow the pattern here to use regex to match the url you want.
If the slug values in urls are also different, this solution might not work very well, but in most cases, this solution can work fine.
I have written a blog post talking about how to use RoutablePageMixin to make the page routable, you can check this link if you wang to get more about RoutablePageMixin.
Routable Page
Rather than thinking of your news articles as being child objects of one or more products, it might help to think of them as one big pool of news articles which are categorised by product. Your product page will then effectively be a filtered index page of news articles.
Here's how I'd model it:
If you want your news articles to exist at a canonical URL that's independent of any particular category, or you want to make use of page moderation and/or previewing, then define NewsArticle as a page model; otherwise, define it as a snippet or a ModelAdmin-managed model.
On the NewsArticle model, have an InlinePanel where editors can associate as many related products as required:
class NewsArticle(Page):
body = RichTextField()
date = models.DateField()
content_panels = Page.content_panels + [
FieldPanel('body'),
FieldPanel('date'),
InlinePanel('related_products', label="Related products"),
]
class NewsArticleRelatedProduct(Orderable):
news_article = ParentalKey(NewsArticle, related_name='related_products')
product = models.ForeignKey(ProductPage, on_delete=models.CASCADE, related_name='news_articles')
panels = [
PageChooserPanel('product'),
]
On your ProductPage model, add a method that returns a queryset of news items, filtered and sorted appropriately:
class ProductPage(Page):
# ...
def get_news_articles(self):
return self.news_articles.live().order_by('-date')
You can then loop over the news articles in your product page template, using a tag like {% for news_article in page.get_news_articles %}.

Backbone.js page layouts & having model collection available globally

I'm working on a simple Backbone.js app for practice - a people quote database. (Person has many Quotes and Comments)
Questions
I would like to have two layouts.
One is a standard two column layout, with one column as a sidebar and the other as the content area. (I would like to embed the view into that column, and keep the sidebar static.)
The other would be a simple one-column layout for authentication purposes (I want to add authentication as well, since this is a practice project). A simple page with a login form. Obviously, this layout would only be used for one view.
How can I do this? Is there a plug-in that will make this possible/easy? (Essentially, is there an equivalent to the Rails layout system?)
On that sidebar, I would like to have a list of Person model objects, so a list of all Person objects must be available on each page. In Rails, I would accomplish this with a simple before_filter in the ApplicationController.
What is the best way to accomplish this?
I worked on a project which also required a similar layout structure. We had a rails app with two backbone instances on the frontend. To get our layout we used jQuery-UI-Layout. This will allow you to create multiple 'panels' which represent your sidebar and column. Then you can simply render your views into each panel, and they will be very nicely separated.
when you create your quotes and comments views, you can pass them the people collection so they have access to the person model objects.
so...
Say you have a 'main_view', this main view will initialize your jQuery ui layout.
$(this.el).layout({options})
where options will set the sizing on your Quotes and Comments panels. Then you create your views and pass them the 'people' collection, which is a collection of your 'person' models.
new App.Views.QuotePanelView({
el: $(this.el).find('#quote_panel'),
collection: people
})
Here people is a collection of people. And the same goes for the comments panel.
var people = new People([
{"name" : "James Cameron"},
{"name" : "Bat Man"},
{"name" : "Cool Guy"}
]);

cakePHP: how to combine two or more application views on one cakePHP layout page?

Using cakePHP my goal is to combine the index view of two or more controllers in one layout page.
Example:
I have controllers for: news, events, links.
I want to show the last five entries from each table in one layout page.
Also, when one of the links from the views is selected it should take the user to the respective view for that record.
I have read through the books section on views but don't see how making a view into an element would accomplish this.
What confuses me is how to combine from three separate controller/views into one layout?
Thanks
Create methods in your News, Event and Link models for fetching the last 5 records. Then in your controller either include the models in the Controller::uses property, or in the action use ClassRegistry::init() to get access to the model, e.g.
function my_action() {
$news = ClassRegistry::init('News')->getRecent();
$events = ClassRegistry::init('Event')->getRecent();
$links = ClassRegistry::init('Link')->getRecent();
$this->set(compact('news', 'events', 'links'));
}
You can then call these model methods from any controller action, keeping your application DRY.
In your my_action.ctp view, and indeed many other views, just render the elements e.g.
// my_action.ctp
<?php
echo $this->element('recent_news');
echo $this->element('recent_events');
echo $this->element('recent_links');
?>
Your elements can then just iterate over the $news (or whatever) variable displaying the items with links to the 'view' actions in their respective controllers.
Just because a controller matches a model, doesn't mean you can't use other models in it.
First I would say that views and controllers are not necessarily tied together -- Cake will implicitly add the view specified by the file heirarchy / naming convention, but this doesn't necessarily have to be the case. So try to think of the views as decoupled from the controller (which is one of the main purposes for using the MVC architecture).
Assuming your three views (A,B,C) are exactly how you want them copied, put them into an element (which is just a view file located in the special APP/views/elements/ directory). Now you can use them in either layouts or other views, just by making a call to $this->element( 'elementName', array( 'options' ) ).
Basically, just abstract the code you want to display into elements, then insert those elements into the desired layouts.
You can set up your controller to use the RequestHandler and then make your view elements capable of fetching their own data from separate controllers in your application.
This link explains it better than I can
http://bakery.cakephp.org/articles/view/creating-reusable-elements-with-requestaction
One thing to keep in mind is that the controller actions you are calling should account for caching their own data so you don't do unnecessary database queries.

Resources