Wagtail 2.11 translatable field with wagtai-localize - wagtail

Is it possible to translate a non snipppet/page model in Wagtail >= 2.11 and wagtail-localize >= 0.9.3 ?
I've set up my model as follows:
class TrainingPlace(TranslatableMixin, models.Model):
name = models.CharField()
description = models.TextField()
class Meta(TranslatableMixin.Meta):
verbose_name = "Training Places"
translatable_fields = [
TranslatableField("description")
]
I have a "Training" submenu in my wagtail admin page, with Trainings, Training Photos, Trainers, Training Places, where I can add Trainers, new Trainings etc.
If I register a TraningPlace with a #register_snipppet I can translate it, but at the moment there are some problems:
I see many Trainings (menu) -> TrainingPlace recoreds in admin listing with no "Translate" option
If I go to Snippets (menu) => Training Place snippets I can see snipppets and have option to translate/create/sync translations, but when I click on change language button in admin I am getting a blank page (url points to a snippet with different ID) which may be a bug.
Can non snippet/page models be translated in admin when using TranslatableMixin on model ?

Related

Wagtail many-to-many links between different Page models

Does anyone have or know of a recipe (sample code and/or instructions) on setting up many-to-many relationships between different Page models? If I have PersonPage and SitePage models, how do I connect the pages (a person can work at multiple sites and a site can have multiple people working there)?
Here's what I've found related to, but not directly on, this topic—
Wagtail docs: from a search for "many-to-many" the only hit is in the section on the taggit module (Recipes page).
Wagtail docs: the only reference to the ParentalManyToManyField is a demo of how it can be used to create M2Ms between pages and categories (Tutorial)
This 2015 post on M2M relationships in Wagtail (it's referenced in an SO 'answer' to basically the same question I'm asking here). Although it doesn't discuss page-page relationships the approach presented might be adapted to work. My modified imitation failed with various errors depending on how I tried to set up the InlinePanel call — but the sample code from the post fails in just the same ways, so either it wasn't tested or it's been made obsolete in 2.x.
class PersonPage(Page):
pass
PersonPage.content_panels = [
InlinePanel('ps_links', label='PS Links'),
]
class PersonSitePageLink():
spage = models.ForeignKey('SitePage', on_delete=models.SET_NULL, related_name='sites')
ppage = ParentalKey('PersonPage', related_name='ps_links', on_delete=models.SET_NULL,)
panels = [
FieldPanel('spage')
]
class SitePage(Page):
pass
This technique works fine for relating a Page model to itself, but expanding it to encompass two distinct models creates two parallel but unconnected sets of relationships (you can pick arbitrary Bug pages to link to any Plant page, or vice versa, but the Plants you picked don't show when you edit Bugs). I see why in the code, I think, but I don't see how to make a single M2M connection between the two pages.
class PlantPage(Page):
related_bugs = ParentalManyToManyField('BugPage', blank=True)
content_panels = Page.content_panels + [
FieldPanel('related_bugs'),
]
class BugPage(Page):
related_plants = ParentalManyToManyField('PlantPage', blank=True)
content_panels = Page.content_panels + [
FieldPanel('related_plants'),
]
This one also only talks about intra-page model (rather than inter-page model) M2Ms. (It is pre-ParentalManyToManyField and in fact only available from the Wayback Machine.)
I hope this helps, I took inspiration from this article about moving from ParentalManyToManyField to a central model that 'links' each page from this AccordBox article.
It turns out that InlinePanel does not fully support ParentalManyToManyField, hence the issues you were running into.
I was able to implement a refined approach to your option one above and it should solve your problem.
A reminder that all Page models already extend ClusterableModel so there is no need to add that to any models you create.
Overview
Create a new 'relation' that extends models.Model which will be the relation between these two page models.
Each field within this new model will be the two page types via the model-cluster ParentalKey each with a logical related_name set that is the OTHER side of the relationship.
No need to set panels on this model as we will declare the panels individually via the panels kwarg to InlinePanel - see the InlinePanel docs.
Finally, each individual Page's content_panels has an InlinePanel added that refers to the central relation model indirectly via that model's related_name, adding the other side reference to PageChooserPanel.
Example Code
from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, PageChooserPanel
class PersonPageSitePageRelation(models.Model):
person = ParentalKey('app.PersonPage', on_delete=models.CASCADE, related_name='sites')
site = ParentalKey('app.SitePage', on_delete=models.CASCADE, related_name='people')
# Optional: some additional fields (e.g. 'note') for this relation
# Important: NOT setting any `panels` here, will be set individually for each 'direction'
class Meta:
unique_together = ('person', 'site')
class PersonPage(Page):
# ... fields (note: `sites` does NOT need to be declared as a field)
# Now we add an `InlinePanel` that will connect to the parental connection to PersonPageSitePageRelation via the related name `sites`, but the panels available will be the PersonPageSitePageRelation's field `site`
content_panels = Page.content_panels + [
# ... other FieldPanel etc
InlinePanel('sites', label='Related Sites', [PageChooserPanel('site')]),
]
class SitePage(Page):
# ... fields (note: `people` does NOT need to be declared as a field)
# Now we add an `InlinePanel` that will connect to the parental connection to PersonPageSitePageRelation via the related name `people`, but the panels available will be the PersonPageSitePageRelation's field `person`
content_panels = Page.content_panels + [
# ... other FieldPanel etc
InlinePanel('people', label='Related People', panels=[PageChooserPanel('person')]),
]
Further Reading
Read about Django Modelcluster - which is the library that ParentalKey comes from.

Filtering on PageQuerySet (which is retuned by specific()) is returning FieldError in Wagtail

I am using subclassed model of Wagtail Page.
In below code you can see that PhoenixPage is base page which subclasses Wagtail Page model.
PhoenixArticlePage & PhoenixMealPrepPage subclasses PhoenixPage
PhoenixArticleIndexPage subclasses PhoenixBaseIndexPage which in turn subclasses PhoenixPage
Idea is to use PhoenixArticleIndexPage for all other article pages.
Problem is even after using the specific() method on queryset i am unable to use filter or any other operation on the queryset.
i tried using order_by() as well as filter()
Can someone share some insights here ? what might be wrong ?
Here is a model example:
class PhoenixPage(Page):
"""
General use page with caching, templating, and SEO functionality.
All pages should inherit from this.
"""
class Meta:
verbose_name = _("Phoenix Page")
# Do not allow this page type to be created in wagtail admin
is_creatable = False
tags = ClusterTaggableManager(
through=PhoenixBaseTag,
verbose_name="Tags",
blank=True,
related_name="phoenixpage_tags",
)
class PhoenixBaseIndexPage(PaginatedListPageMixin, PhoenixPage):
class meta:
verbose_name = "Phoenix Base Index Page"
app_label = "v1"
index_show_subpages_default = True
is_creatable = False
class PhoenixArticleIndexPage(PhoenixBaseIndexPage):
class Meta:
verbose_name = "Phoenix Article Index Page"
app_label = "v1"
class PhoenixArticlePage(PhoenixPage):
class Meta:
verbose_name = "Phoenix Article Page"
app_label = "v1"
subpage_types = []
parent_page_types = ["v1.PhoenixArticleIndexPage"]
class PhoenixMealPrepPage(PhoenixPage):
class Meta:
verbose_name = "Phoenix Meal Prep Page"
app_label = "v1"
subpage_types = []
parent_page_types = ["v1.PhoenixArticleIndexPage"]
Here are shell queries i tried.
Index page
In [4]: a = PhoenixArticleIndexPage.objects.all()[0]
In [5]: a
Out[5]: <PhoenixArticleIndexPage: articles>
As expected, get_children returning all instances of Wagtail Page.
In [6]: a.get_children()
Out[6]: <PageQuerySet [<Page: article title>, <Page: article title2>, <Page: Our 30-Day Reset Recipes Are So Easy AND Delicious>]>
Getting specific children from the Index page.
In [7]: a.get_children().specific()
Out[7]: <PageQuerySet [<PhoenixArticlePage: article title>, <PhoenixArticlePage: article title2>, <PhoenixMealPrepPage: Our 30-Day Reset Recipes Are So Easy AND Delicious>]>
Get Tag and try to filter the queryset
In [8]: q = a.get_children().specific()
In [12]: m = PhoenixTag.objects.get(slug='meal')
In [16]: k={"tags":m}
In [19]: q.filter(**k)
***FieldError: Cannot resolve keyword 'tags' into field. Choices are ...***
But if i go to particular entry in queryset then i can see tags field on it.
In [15]: q[2]
Out[15]: <PhoenixMealPrepPage: Our 30-Day Reset Recipes Are So Easy AND Delicious>
In [16]: q[2].tags
Out[16]: <modelcluster.contrib.taggit._ClusterTaggableManager at 0x1060832b0>
Could be different question all together but for reference adding it here.
Found the corner case of using difference() and specific() method on a queryset.
In [87]: q = PhoenixPage.objects.child_of(a).live()
In [89]: f = q.filter(featured=True)[:3]
In [91]: l = q.difference(f)
In [93]: l.order_by(a.index_order_by).specific() . <-- does not work
DatabaseError: ORDER BY term does not match any column in the result set.
The specific() method on PageQuerySet works by running the initial query on the basic Page model as normal, then running additional queries - one for each distinct page type found in the results - to retrieve the information from the specific page models. This means it's not possible to use fields from the specific model in filter or order_by clauses, because those have to be part of the initial query, and at that point Django has no way to know which page models are involved.
However, if you know that your query should only ever return pages of one particular type (PhoenixPage in this case) containing the field you want to filter/order on, you can reorganise your query expression so that the query happens on that model instead:
PhoenixPage.objects.child_of(a).filter(tags=m).specific()

Akeneo category tree UI locale override

I have an Akeneo 2.3 running with 10 locales. 1 of the locales is our customised one called ab_AB.
When viewing the category tree in Settings -> Categories UI or when assigning product to categories UI, the category's label is displayed according to the locale of the logged-in user.
I would like to display the category's label value from ab_AB locale instead of the logged-in user's locale.
I have looked into /vendor/akeneo/pim-community-dev/src/Pim/Bundle/EnrichBundle/Resources/views/CategoryTree for hints of what to extend/override but not quite sure what to make of it.
To sum up what happens: the tree is generated by calling CategoryTreeController::childrenAction. The rendered twig view will format the categories using the Twig function children_response, defined in the CategoryExtension.
To set your own locale, you need to override this extension in your project (extend the class and redefine the class parameter pim_enrich.twig.category_extension.class) and override the protected method getLabel as follows:
protected function getLabel(
CategoryInterface $category,
$withCount = false,
$includeSub = false,
$relatedEntity = 'product'
) {
$category->setLocale('ab_AB');
return parent::getLabel($category, $withCount, $includeSub, $relatedEntity);
}
I successfully tested it with locale fr_FR while my PIM was in English. Category labels were then in French, both in the Settings → Categories menu and the category filter of the product grid.

Storing lists of words in database - best practice

I'm implementing a user filter system on a website. Users are to be able to select 'categories' and 'packages' of interest to them and have the matching data presented when they log in. Both sets of data will come from HTML select forms eg. Categories: 'null pointers', 'dead code'... and packages 'package_x', 'package_y', 'package_z'...
My question is about the best way to store this list information in a database (I am using Django and PostgresSQL).
My initial thought is to have a table like this:
user_id - one to one field
categories - textfield - store json data
packages - textfield - store json data
Is there a better way to be doing this?
I would go the route of using a user profile with categories and packages being many to many fields.
In models.py
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=255)
class Package(models.Model):
name = models.CharField(max_length=255)
class UserProfile(models.Model):
user = models.ForiegnKey(User)
categories = models.ManyToManyField(Category)
packages = models.ManyToManyField(Package)
In admin.py
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class CustomUserAdmin(UserAdmin):
inlines = [UserProfile]
#filter_horizontal = ('',) #Makes the selection a bit more friendly
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
In views.py
user_with_profile = User.objects.get(pk=user_id).get_profile()
All that being said. Django 1.5 will replace the user profile with being able to use a configurable user model.

Favoriting system on Appengine

I have the following model structure
class User(db.Model) :
nickname = db.StringProperty(required=True)
fullname = db.StringProperty(required=True)
class Article(db.Model) :
title = db.StringProperty(required=True)
body = db.StringProperty(required=True)
author = db.ReferenceProperty(User, required=True)
class Favorite(db.Model) :
who = db.ReferenceProperty(User, required=True)
what = db.ReferenceProperty(Article, required=True)
I'd like to display 10 last articles according to this pattern: article.title, article.body, article.author(nickname), info if this article has been already favorited by the signed in user.
I have added a function which I use to get the authors of these articles using only one query (it is described here)
But I don't know what to do with the favorites (I'd like to know which of the displayed articles have been favorited by me using less than 10 queries (I want to display 10 articles)). Is it possible?
You can actually do this with an amortized cost of 0 queries if you denormalize your data more! Add a favorites property to Authors which stores a list of keys of articles which the user has favorited. Then you can determine if the article is the user's favorite by simply checking this list.
If you retrieve this list of favorites when the user first logs in and just store it in your user's session data (and update it when the user adds/removes a favorite), then you won't have to query the datastore to check to see if an item is a favorite.
Suggested update to the Authors model:
class Authors(db.Model): # I think this would be better named "User"
# same properties you already had ...
favorites = db.ListProperty(db.Key, required=True, default=[])
When the user logs in, just cache their list of favorites in your session data:
session['favs'] = user.favorites
Then when you show the latest articles, you can check if they are a favorite just by seeing if each article's key is in the favorites list you cached already (or you could dynamically query the favorites list but there is really no need to).
favs = session['favs']
articles = get_ten_latest_articles()
for article in articles:
if article.key() in favs:
# ...
I think there is one more solution.
Let's add 'auto increment' fields to the User and Article class.
Then, when we want to add an entry to the Favorite class, we will also add the key name in the format which we will be able to know having auto increment value of the signed in user and the article, like this 'UserId'+id_of_the_user+'ArticleId'+id_of_an_article.
Then, when it comes to display, we will easily predict key names of the favorites and would be able to use Favorite.get_by_key_name(key_names).
An alternative solution to dound's is to store the publication date of the favorited article on the Favorite entry. Then, simply sort by that when querying.

Resources