How to combine django crispy form layout and quill JS? - django-models

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

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

React / DRF: How to handle Many to Many relationship in POST request?

How do I make sure that Many-To-Many relationships are considered in my POST-request to a Django Rest Framework API?
I have the following models:
models.py
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Blog(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
tags = models.ManyToManyField(Tag, blank=True, related_name="blogs")
url = models.URLField(max_length=250, unique=True)
owner = models.ForeignKey(User, related_name="blogs", on_delete=models.CASCADE)
slug = models.CharField(max_length=20, default="blogs")
def __str__(self):
return self.name
And I am making the request like:
Frontend (don't mind the missing brackets)
const addContent = (content) => {
axiosInstance
.post(`/content/blogs/`, content, tokenConfig(auth.token))
.then((res) => {
dispatchMessages(
createMessage({ contentAdded: "Submitted successfully" })
);
The content object I am passing in looks like:
const content = {
name: "content title",
description: "content description",
url: "content URL",
tags: ["tag1", "tag2", "tag3"],
};
The POST request itself is going through and all the fields are posted correctly except for the tags, which appear empty.
Example Response:
{
"id": 2,
"tags": [],
"name": "Blog #1",
"description": "Its the best",
"url": "https://website.com",
},
My serializer looks like:
serializers.py
class BlogSerializer(serializers.ModelSerializer):
tags = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")
owner = CustomOwnerField(read_only=True)
class Meta:
model = Blog
fields = "__all__"
And the viewset:
api.py
class BlogViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
serializer_class = BlogSerializer
def get_queryset(self):
return Blog.objects.all()
Thank you for any tips
you have done all the tedious work. The only thing that is not allowing the tags to get saved is the read_only=True in the SlugRelatedField argument. This argument ignores the field when it is posted. So you have to remove read_only=True so that tags get parsed. I would go a little further and add queryset in the slugrelatedfield as queryset=Tags.objects.all()
This would only work if you have already created tags in your db and then you add the same names in your list. If you want to create them dynamically when you post them you have to modify the default create method in your serializer(check here)

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.

Render a form with initial values in 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

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.

Resources