Render a form with initial values in WagTail - wagtail

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

Related

Wagtail: delete() method not called in Page instance

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

How to combine django crispy form layout and quill JS?

I am trying to combine crispy form layout with quill js that I am using for my description field but in the dev tools it does render the quill classes and stuff but the problem is, it is not showing on the page itself. I want to add the safe filter(guess that's what is missing to it) but I don't know how. I looked at the documentation but found no answer to that particular issue.
class CreateProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = "__all__"
def __init__(self, *args, **kwargs):
super(CreateProjectForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout()
Not using any layout for now.
class Project(models.Model):
typeChoices = (
("Software Development", "Software Development"),
("Task Management", "Task Management"),
("Business Management", "Business Management"),
)
project_type = models.CharField(max_length=25, choices=typeChoices)
slug = models.SlugField(max_length=30)
key = models.CharField(max_length=7, unique=True)
project_description = QuillField(blank=True, null=True)
def __str__(self):
return self.project_name
I want to have something like this {{description|safe}} in the description field itself. Any help would be appreciated. Thanks

ImageChooserPanel wrongfully showing as Select widget

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.

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.

Adding tags with Django-Taggit

I have tried different tutorials for Django-taggit, but for some reason they all show how to add tags through Admin. I was wondering can I add tags using View and template while creating an instance of Model? or should I add tags to existing items only? Is there any recent tutorials for Django-Taggit or my be better app for Tags?
Their documentation is pretty swell. Once you have your model set up, you can use the tag field just like any other field in a form. It will automatically be set up to parse the tags.
Here is a very basic working example.
views.py
from django.shortcuts import render
from .models import NewspaperIndex
from .forms import NewIndexForm
def overview(request):
if request.method == "POST":
form = NewIndexForm(request.POST)
if form.is_valid():
form.save()
else:
form = NewIndexForm()
indexes = NewspaperIndex.objects.all()
context = {'form': form,
'indexes': indexes
}
return render(request, 'newsindex/overview.html', context)
models.py
from django.db import models
from taggit.managers import TaggableManager
class NewspaperIndex(models.Model):
title = models.CharField(max_length=200)
date = models.DateField()
abstract = models.TextField()
tags = TaggableManager()
def __str__(self):
return self.title
forms.py
import datetime
from django import forms
from django.forms import ModelForm
from .models import NewspaperIndex
class NewIndexForm(forms.ModelForm):
class Meta:
model = NewspaperIndex
fields = ['title', 'date', 'abstract', 'tags']
templates/newsindex/overview.html
<form class="" action="./" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" name="submit" value="Submit">
</form>
If you would like to add a tag from a shell, try:
tag='tag to add'
post=NewspaperIndex.objects.all()[0] #pick an object, to add tag to
post.tags.add(tag)

Resources