Snippet title problem after upgrade from wagtail 1.13 to 2.0 - wagtail

I am working on wagtail upgrade on a site from version 1.13.4 . Was mostly following this post https://wagtail.org/blog/upgrading-wagtail/ . However, since the first update to wagtail 2.0 snippet titles are wrong. I could sort it out on the templated front end pages, but they still appear wrong on the admin panel.
How titles are displayed on the admin panel
And this same issue is with fields that link to those snippets
I got as far as updating wagtail to 2.4, since the blog post said that it should support python 3.7, but issue still persists. As far as I can tell, there are no error messages on the terminal output, migrations run without any issues. Any clue where I should start looking?
This should be the relevant artist snippet declaration from the project
#register_snippet
class Artist(index.Indexed, models.Model):
class Meta:
ordering = ['name']
def __unicode__(self):
return unicode(self.name)
objects = ArtistQuerySet.as_manager()
name = models.CharField(max_length=512)
name_encoded = models.CharField(max_length=512)
image_url = models.URLField(blank=True, max_length=512)
image = models.ForeignKey(
'wagtailimages.Image',
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='+',
)
#property
def status(self):
# If there are no events currently attached to this artist
if not self.artistatevent_set.all():
return NO_CURRENT_DATES
# This artist does have some events, next see if there's any tickets
if self.total_ticket_count is not 0:
# Are they all the same type?
if self.available_ticket_count == self.total_ticket_count:
return AVAILABLE
if self.sold_out_ticket_count == self.total_ticket_count:
return SOLD_OUT
if self.cancelled_ticket_count == self.total_ticket_count:
return CANCELLED
if self.unavailable_ticket_count == self.total_ticket_count:
return UNAVAILABLE
# See if we have ANY of the following
if self.available_ticket_count:
return AVAILABLE
if self.coming_soon_ticket_count:
return COMING_SOON
if self.sold_out_ticket_count:
return SOLD_OUT
# Nothing matched, so default
return UNAVAILABLE
panels = [
FieldPanel('name'),
ImageChooserPanel('image'),
]
search_fields = [
index.SearchField('name', partial_match=True),
]
EDIT:
This is a snippet from admin.py declaration
...
class NameIDAdmin(admin.ModelAdmin):
list_display = ('name', 'id')
...
admin.site.register(Artist, NameIDAdmin)

When upgrading from Python 2 to 3, you should replace the __unicode__ method with __str__: https://docs.djangoproject.com/en/1.10/topics/python3/#str-and-unicode-methods

Related

Wagtail streamfield inside a snippet / setting not working as expected

Edit: Seems like I can't add images :(, added links to imgur instead.
I'm trying to implement settings in order to enter social media accounts.
#register_setting
class SocialMediaSettings(BaseGenericSetting):
discord_url = models.URLField(
help_text="Link used."
)
discord_name = models.CharField(
max_length=100,
help_text="Name to be displayed."
)
gitlab_url = models.URLField(
help_text="Link used."
)
gitlab_name = models.CharField(
max_length=100,
help_text="Name to be displayed."
)
panels = [
FieldPanel("discord_url"),
FieldPanel("discord_name"),
FieldPanel("gitlab_url"),
FieldPanel("gitlab_name")
]
Which works totally fine, but as it feels like a super bad idea to do it this way, I wanted to use a streamfield like this:
#register_setting
class SocialMediaSettings(BaseGenericSetting):
body = StreamField([
("social_account", blocks.SocialBlock()),
], null=True, blank=True, use_json_field=True)
panels = [
FieldPanel("body")
]
class SocialBlock(blocks.StructBlock):
name = blocks.CharBlock(
max_length=100,
help_text="Name used for tooltips."
)
url = blocks.URLBlock(
help_text="URL"
)
Image of wagtail admin (image)
This way it shows up partly. In the admin panel I can only enter the name, not the url. And saving doesn't work eighter (saving will reset what I entered in the admin panel).
Adding the SocialBlock to a normal page works as expected:
SocialBlock working in another Page (image)
Which is why I ran out of ideas. Are streamfields not supported in snippets / settings? (the above exapmles are done with #register_setting but I tried #register_snippet with the same result.)
Any idea how I could get this up and running?
Wagtail 4.0
Django 4.1
Well, wagtail itself just showed me the solution :x
It was a bug in Wagtail 4.0, which is solved in 4.0.1
Release Notes Wagtail 4.0.1

Slug translation issue in wagtail

I've been around the Internet the whole day reading about the following issue, in wagtail if I registered the following model for translation like this:
class RecipientsPage(Page):
intro = RichTextField(null=True, blank=True)
banner_image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.SET_NULL,
related_name="+",
null=True,
blank=False,
help_text=_("the Image shouldn't exceed ") + "1350 * 210",
)
content_panels = Page.content_panels + [
FieldPanel("intro"),
ImageChooserPanel("image"),
]
this is how I registered the model:
#register(RecipientsCountriesPage)
class RecipientsCountriesPage(TranslationOptions):
fields = ("intro",)
It causes a problem, because like this I'll have two slugs following the two titles (The original English one and the Arabic translated one), if I change the Arabic slug manually to equal the English one it'll work, but it's not efficient to do so for each page manually
I've read about the issue a lot like in here:
https://github.com/infoportugal/wagtail-modeltranslation/issues/195
I've found also the following question with no answer
How do you translate the slug value of a page?
I've also read that I can override some of the wagtail Page methods but without further explanation and I'm a bit lost, what's the best way to overcome this issue?
I did it using Django signals
#receiver(pre_save)
def set_arabic_slug_on_new_instance(sender, instance, **kwargs):
if isinstance(instance, Page):
instance.slug_ar = instance.slug_en

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()

Why is the SnippetChooserPanel not opening in Wagtail?

Some time ago I stopped using #register_snippet to decorate Snippets. This takes the Snippet out of the snippets section of admin.
Instead I used the wagtail_hooks.py to show the snippet directly in the left admin panel for user convenience. See below. This works nicely as the user can go directly to the snippet and you can also alter the displayed fields and ordering of fields - nice.
So in the below example I removed the line that says #register_snippet. What's the catch? The SnippetChooserPanel does not work! Later I was building a complex model and the SnippetChooserPanel did not work. I wasted quite a bit of time thinking the problem was in the complexity of my model. I want to save others' time!
wagtail_hooks.py:
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
from wagtail.wagtailsnippets.models import register_snippet
from demo.models import Advert
class AdvertAdmin(ModelAdmin):
model = Advert
modeladmin_register(AdvertAdmin)
Here is the snippet example from Wagtail: snippets
#register_snippet #<------- Source of issue (I removed this line!)
#python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Python 2
class Advert(models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
def __str__(self):
return self.text
class BookPage(Page):
advert = models.ForeignKey(
'demo.Advert',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
content_panels = Page.content_panels + [
SnippetChooserPanel('advert'),
# ...
]
If you make your Snippets editable via modelAdmin you still need to apply the decorator #register_snippet. Otherwise the chooser panel route/view won't be available. This view is requested by the ajax request fired on SnippetChooser modal open. Missing #register snippet will trow a 404.
You can register menu items via construct_main_menu hook. You can use the same hook to remove exiting menu-items. If you do not want the 'Snippets' menu item remove it. In wagtail_hooks.py:
#hooks.register('construct_main_menu')
def hide_snippet(request, menu_items):
menu_items[:] = [item for item in menu_items if item.name != 'snippets']
The solution is always use #register_snippet decorator otherwise the SnippetChooserPanel doesn't work!
#register_snippet
#python_2_unicode_compatible
class Advert(models.Model):
url = models.URLField(null=True, blank=True)
text = models.CharField(max_length=255)
panels = [
FieldPanel('url'),
FieldPanel('text'),
]
def __str__(self):
return self.text

Whats the most Django/pythonic way to create or overwrite a record?

Working with Django 1.2 I am making a wine review site. A user should only be able to review each wine once, but should be able to go back and re-review a wine without raising an error.
Using the get_or_create method seems the most rational solution but I have been running into various problems implementing it. Searching I found this article which looked promising:
Correct way to use get_or_create?
and of course the django documentation on it:
http://docs.djangoproject.com/en/1.2/ref/models/querysets/#get-or-create
But didn't seem to answer my question. Here is my code:
Views.py
#login_required
def wine_review_page(request, wine_id):
wine = get_object_or_404(Wine, pk=wine_id)
if request.method == 'POST':
form = WineReviewForm(request.POST)
if form.is_valid():
review, created = Review.objects.get_or_create(
user = request.user,
wine = wine,
like_dislike = form.cleaned_data['like_dislike'],
...
)
variables = RequestContext(request, {
'wine': wine
})
review.save()
return HttpResponseRedirect(
'/detail/%s/' % wine_id
)
else:
form = WineReviewForm()
variables = RequestContext(request, {
'form': form,
'wine': wine
})
return render_to_response('wine_review_page.html', variables)
Models.py
class Review(models.Model):
wine = models.ForeignKey(Wine, unique=True)
user = models.ForeignKey(User, unique=True)
like_dislike = models.CharField(max_length=7, unique=True)
...
If I understand how to use get_or_create correctly, since I am not matching on all the values like_dislike, etc... then django perceives it to be unique. I tried removing the other form parameters, but then they are not submitted with the post request.
Suggestions would be greatly appreciated.
I came across this too when making a CRUD based app. I'm not sure if there's a better way but the way I ended up getting doing was using a exists() to check if an entry ... exists.
You can use get_or_create within the is_valid() scope, however, you need to check if the review exists before displaying your form in order to load instance data into the form in the case that the review already exists.
Your models.py might look like this:
from django.db import models
from django.contrib.auth.models import User
class Wine(models.Model):
name = models.CharField()
class Review(models.Model):
wine = models.ForeignKey(Wine)
user = models.ForeignKey(User)
like = models.BooleanField(null=True, blank=True) # if null, unrated
Your forms.py might look like this:
from django import forms
class WineReviewForm(forms.ModelForm):
class Meta:
model = Review
fields = ['like',] # excludes the user and wine field from the form
Using get_or_create will let you do this if used like so:
#login_required
def wine_review_page(request, wine_id):
wine = get_object_or_404(Wine, pk=wine_id)
review, created = Review.objects.get_or_create(user=request.user, wine=wine)
if request.method == 'POST':
form = WineReviewForm(request.POST, instance=review)
if form.is_valid():
form.save()
return HttpResponseRedirect('/detail/%s/' % wine_id )
else:
form = WineReviewForm(instance=review)
variables = RequestContext(request, {'form': form, 'wine': wine })
return render_to_response('wine_review_page.html', variables)
Doing creates a review just by visiting the page and requires that the other information either have a default or are allowed to be blank at the model level.
With exists(), you get two db hits if the review exists, however you don't create an object unless the user submits a valid form:
#login_required
def wine_review_page(request, wine_id):
wine = get_object_or_404(Wine, pk=wine_id)
review = None
if Review.objects.filter(user=request.user, wine=wine).exists():
review = Review.objects.get(user=request.user, wine=wine)
if request.method == 'POST':
form = WineReviewForm(request.POST, instance=review)
if form.is_valid():
form.save()
return HttpResponseRedirect('/detail/%s/' % wine_id )
else:
form = WineReviewForm(instance=review)
variables = RequestContext(request, {'form': form, 'wine': wine })
return render_to_response('wine_review_page.html', variables)
I used exists() but I think that this might be better?
try:
review = Review.objects.get(user=request.user, wine=wine)
except Review.DoesNotExist:
review = None
Hopefully someone with more experience will chime in.
Edit:
Here is a fairly old post from Daniel Roseman's Blog. I don't know if it is still applicable, but might be relevant to your question.

Resources