Django Version: 2.1.5
Python Version: 3.6.8
Wagtail Version: 2.4
I have a template with four columns of links in the footer. I have set up the following models which consist of a BaseSetting object and footer link objects for each column of links. The footer link objects each ForeignKey to the TemplateItems object.
#register_setting
class TemplateItems(BaseSetting):
page_banner = models.OneToOneField('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Banner image that shows below menu on pages other than home page')
footer_link_col1_header = models.CharField(max_length=25, default='', verbose_name='Footer Link Column 1 Header')
footer_link_col2_header = models.CharField(max_length=25, blank=True, default='', verbose_name='Footer Link Column 2 Header')
footer_link_col3_header = models.CharField(max_length=25, blank=True, default='', verbose_name='Footer Link Column 3 Header')
footer_link_col4_header = models.CharField(max_length=25, blank=True, default='', verbose_name='Footer Link Column 4 Header')
panels = [
ImageChooserPanel('page_banner'),
MultiFieldPanel([
FieldPanel('footer_link_col1_header'),
InlinePanel('footer_links_col_1', label='Column 1 Links'),
FieldPanel('footer_link_col2_header'),
InlinePanel('footer_links_col_2', label='Column 2 Links'),
FieldPanel('footer_link_col3_header'),
InlinePanel('footer_links_col_3', label='Column 3 Links'),
FieldPanel('footer_link_col4_header'),
InlinePanel('footer_links_col_4', label='Column 4 Links'),
], heading='Footer Links'),
InlinePanel('social_media_links', label="Social Media Links"),
]
class FooterLink(Orderable):
name = models.CharField(max_length=60, default='')
url = models.CharField(max_length=200, default='')
panels = [
FieldRowPanel([
FieldPanel('name'),
FieldPanel('url'),
])
]
class Meta:
abstract = True
def __str__(self):
return f'{self.name}'
class FooterLinkCol1(FooterLink):
template_items = ForeignKey('TemplateItems', related_name='footer_links_col_1', null=True, on_delete=models.SET_NULL)
class FooterLinkCol2(FooterLink):
template_items = ForeignKey('TemplateItems', related_name='footer_links_col_2', null=True, on_delete=models.SET_NULL)
class FooterLinkCol3(FooterLink):
template_items = ForeignKey('TemplateItems', related_name='footer_links_col_3', null=True, on_delete=models.SET_NULL)
class FooterLinkCol4(FooterLink):
template_items = ForeignKey('TemplateItems', related_name='footer_links_col_4', null=True, on_delete=models.SET_NULL)
Migrations are created and migrated successfully, but when I go to the TemplateItems settings object in the Wagtail admin in order to add footer links, I receive the following error:
KeyError at /admin/settings/main/templateitems/2/
'footer_links_col_1'
If I comment out any of the footer_links_col_X items, then I receive the error for the first one that is not commented out. There are no existing footer links in the database for any of the columns. I wondered if the problem was coming because the ForeignKey is to a BaseSetting object, but when I declare these models in the Django admin (including the inlines for each of the column links), it displays and allows me to add links just fine.
Traceback:
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/django/core/handlers/exception.py"
in inner
34. response = get_response(request)
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/django/core/handlers/base.py"
in _get_response
126. response = self.process_exception_by_middleware(e, request)
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/django/core/handlers/base.py"
in _get_response
124. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/django/views/decorators/cache.py"
in _wrapped_view_func
44. response = view_func(request, *args, **kwargs)
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/urls/init.py"
in wrapper
102. return view_func(request, *args, **kwargs)
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/decorators.py"
in decorated_view
34. return view_func(request, *args, **kwargs)
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/contrib/settings/views.py"
in edit
83. instance=instance, form=form, request=request)
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py"
in bind_to_instance
153. new.on_instance_bound()
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py"
in on_instance_bound
295. request=self.request))
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py"
in bind_to_instance
153. new.on_instance_bound()
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py"
in on_instance_bound
295. request=self.request))
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py"
in bind_to_instance
153. new.on_instance_bound()
File
"/opt/virtualenvs/MY_SITE-a0hNfZxl/lib/python3.6/site-packages/wagtail/admin/edit_handlers.py"
in on_instance_bound
692. self.formset = self.form.formsets[self.relation_name]
Exception Type: KeyError at /admin/settings/main/templateitems/2/
Exception Value: 'footer_links_col_1'
InlinePanel requires the corresponding foreign key to be a ParentalKey:
from modelcluster.fields import ParentalKey
class FooterLinkCol1(FooterLink):
template_items = ParentalKey('TemplateItems', related_name='footer_links_col_1', null=True, on_delete=models.SET_NULL)
In turn, ParentalKey requires the parent model to inherit from ClusterableModel (which is automatically true for Wagtail Page models):
from modelcluster.models import ClusterableModel
class TemplateItems(BaseSetting, ClusterableModel):
(There's some explanation of the motivation for ClusterableModel / ParentalKey in the readme for django-modelcluster.)
Related
I want to automatically create a Collection when a Page instance is saved and link it to the page using a FK, and it needs to be deleted if the page instance is deleted.
class CustomProject(BasePage):
description = models.TextField(_("description"), default="", blank=True)
main_section_image = models.ForeignKey(
"wagtailimages.Image",
verbose_name=_("Main section image"),
on_delete=models.PROTECT,
related_name="+",
)
images_collection = models.ForeignKey(
Collection,
on_delete=models.PROTECT,
null=True,
blank=True,
editable=False,
)
content_panels = BasePage.content_panels + [
FieldPanel("description", classname="full"),
FieldPanel("main_section_image"),
HelpPanel(_(
"To manage the project's photos, firstly save this page's changes "
"(either as Draft or Published) and then go to the "
"%(url_label)s, "
"select the collection with the same namne as this project and "
"there add the images."
) % {"url_label": "Images section"}),
]
parent_page_types = ["cms_site.CustomProjectsPage"]
page_description = _("Custom project page.")
template = "pages/custom_projects/custom_project.html"
def save(self, clean=True, user=None, log_action=False, **kwargs):
self.create_or_update_collection()
return super().save(clean, user, log_action, **kwargs)
def delete(self, *args, **kwargs):
print("calling delete")
self.delete_collection()
super().delete(self, *args, **kwargs)
def create_or_update_collection(self):
collection_name = f"[Projecte a mida] {self.title}"
if not self.images_collection:
root_node = Collection.objects.get(id=get_root_collection_id())
new_collection = root_node.add_child(name=collection_name)
self.images_collection = new_collection
else:
self.images_collection.name = collection_name
self.images_collection.save()
def delete_collection(self):
if self.images_collection:
Collection.objects.get(id=self.images_collection.id).delete()
The delete() method is not called at all, neither deleting a Draft or a Published instance.
The save() is working perfectly fine in both cases.
Is that the expected behavior for some reason?
Should I rely only in something like signals or hooks for this purpose? (I imagine that's the answer, but I still don't get why the save is called and the delete is not)
BassePage is not messing with it, I don't think it's relevant but i paste it here to share the full code:
class BasePage(Page):
header_image = models.ForeignKey(
"wagtailimages.Image",
verbose_name=_("Header image"),
on_delete=models.PROTECT,
related_name="+",
null=True,
blank=False,
)
content_panels = Page.content_panels + [
FieldPanel("header_image"),
]
show_in_menus_default = False
class Meta:
abstract = True
Thanks a lot!
Edit: in case someone wants an example on how to implement it using hooks, is really simple and well documented.
Just create a wagtail_hooks.py file at the root of your app with:
from wagtail import hooks
from wagtail.models import Collection
from apps.cms_site.models import CustomProject
#hooks.register("after_delete_page")
def after_delete_custom_project(request, page):
if (
request.method == 'POST'
and page.specific_class is CustomProject
and page.images_collection
):
Collection.objects.get(id=page.images_collection.id).delete()
I find stack overflow very difficult to use and I am probably going to be slammed for trying but here goes.
I am trying to get an image field to bring up the standard wagtail image chooser dialog but it's displaying in wagtail admin as a Select widget with no option to upload new image.
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.core.models import Page, Orderable
from modelcluster.models import ClusterableModel
from wagtail.admin.edit_handlers import (
FieldPanel,
MultiFieldPanel,
InlinePanel,
PageChooserPanel,
)
from wagtail.images.edit_handlers import ImageChooserPanel
class HomePage(Page):
def get_context(self, request):
context = super().get_context(request)
# Add extra variables and return the updated context
context['sections'] = Sections.objects.all()
return context
class Sections(ClusterableModel):
title = models.CharField(max_length = 60, blank = False, null= True)
section_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=False,
on_delete=models.SET_NULL,
related_name="+",
)
panels = [
FieldPanel("title"),
ImageChooserPanel("section_image"),
InlinePanel("albums"),
]
class Albums(ClusterableModel):
title = models.CharField(max_length = 60, blank = False, null= True)
section = ParentalKey("Sections", related_name="albums")
panels = [
FieldPanel("title"),
InlinePanel("images"),
]
class GalleryImage(Orderable):
album = ParentalKey("Albums", related_name="images")
galleryimage = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=False,
on_delete=models.SET_NULL,
related_name="+",
)
panels = [
ImageChooserPanel("galleryimage"),
]
This is probably a rookie mistake and would appreciate if someone can show me why gallery image is not rendering correctly.
Unfortunately this is an open bug in Wagtail: https://github.com/wagtail/wagtail/issues/5126
Historically, nesting InlinePanels has not been well-supported in Wagtail - there are some improvements in progress which will hopefully make it into the forthcoming 2.7 release, but this particular issue is still outstanding.
I need to render a form, with pre-filled data but it seems to not be possible in WagTail. I'm trying to do it the "Django way" using MyForm(initial={"field_name": value}) (MyForm inherits WagtailAdminModelForm if this is necessary), but this is not possible since MyForm doesn't have a Meta.model field and, as I understand, it cannot be specified due to how WagTail is designed.
Are there any work-arounds or alternative ways to render a pre-filled form?
# models.py
from django.db import models
from wagtail.core.models import Page
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.admin.forms import WagtailAdminPageForm
class BlogForm(WagtailAdminPageForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
my_field = self.fields["my_field"]
my_field.widget.attrs["value"] = "My new initial data"
class Blog(Page):
my_field = models.CharField(max_length=255, default='', blank=True)
content_panels = Page.content_panels + [
FieldPanel("my_field"),
]
base_form_class = BlogForm
I have a few Django models that I display in the admin using wagtail’s modelAdmin. few of the fields in the models are referencing the user model. Since I’m not creating the form/page and it’s handled by wagtail, how do I pass the current user object to the field when it’s saved? That is, the created and updated by fields should have the current user object assigned to them.
See code snippet below, currently I'm assigning the user manually by querying, instead I'd like to get the current user.
from django.db import models
from django.conf import settings
from django.contrib.auth import get_user_model
from wagtail.admin.forms import WagtailAdminPageForm
STATUS_CHOICES = (
(1, 'Active'),
(2, 'Inactive')
)
class BasePageForm(WagtailAdminPageForm):
def save(self, commit=True):
auth_user_model = get_user_model()
default_user = auth_user_model.objects.get(username='admin')
page = super().save(commit=False)
if not page.pk:
page.created_by = default_user
page.updated_by = default_user
else:
page.updated_by = default_user
if commit:
page.save()
return page
class BaseModel(models.Model):
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='created%(app_label)s_%(class)s_related'
)
created_at = models.DateTimeField(auto_now_add=True)
updated_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='updated%(app_label)s_%(class)s_related'
)
updated_at = models.DateTimeField(auto_now=True)
status = models.IntegerField(choices=STATUS_CHOICES, default=1)
class Meta:
abstract = True # Set this model as Abstract
base_form_class = BasePageForm
every one ,,I have
models.py
......
class Place(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
release = models.DateTimeField(blank=True, null=True)
def __unicode__(self):
return self.name
class ProductsTbl(models.Model):
......
place = models.ManyToManyField(Place)
......
def __unicode__(self):
return self.name
and I have forms.py here
forms.py
from django.forms import ModelForm
from .models import *
class ProductsTblForm(ModelForm):
class Meta:
model = ProductsTbl
fields = ('place',)
......
however,,I need let my templates shows the 'release' in form,,right now,,it only shows the 'name' which under the Class place(models.Model): but no 'release',,how can I let the 'release' can show up?
You can access model instance variables in the form by using form.instance.<field_name>.