wagtail / wagtail

A Django content management system focused on flexibility and user experience
https://wagtail.org
BSD 3-Clause "New" or "Revised" License
18.41k stars 3.9k forks source link

SnippetChooserPanel is broken with UUID primary keys #10856

Open terabitti opened 1 year ago

terabitti commented 1 year ago

Issue Summary

SnippetChooserPanel doesn’t work with models having UUID primary keys. I believe this problem has always been there. Now that Wagtail has been greatly improving Snippets' features, perhaps this could be fixed too.

Steps to Reproduce

Here's a simple example working with wagtail start mysite.

Replace home/models.py with this:

import uuid
from django.db import models

from wagtail.admin.panels import FieldPanel
from wagtail.fields import StreamField
from wagtail.models import Page
from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.snippets.models import register_snippet

@register_snippet
class MySnippet(models.Model):
    id = models.UUIDField("UUID", primary_key=True, default=uuid.uuid4, editable=False)
    name = models.TextField("name")

class HomePage(Page):
    body = StreamField(
        [
            ("my_snippet", SnippetChooserBlock("home.MySnippet")),
        ],
        use_json_field=True,
    )

    content_panels = Page.content_panels + [FieldPanel("body")]

Add this to home/templates/home/home_page.html:

{% load wagtailcore_tags %}
{% include_block page.body %}

When editing a page, adding a snippet and then saving or publishing, the value of the snippet is correctly saved in to a database:

[{"id": "113a1b9b-d507-4d77-96ff-994fa6fb073f", "type": "my_snippet", "value": "c25b4ab3-d21b-4f6c-ag58-488ba1xae427"}]

Also the selected snippet is visible in a preview.

However, the selected snippet is not visible ("None" value printed) when viewing the published page and SnippetChooserBlock shows an empty value when editing the page again. The selected snippet is lost.

Technical details

olly-flowmoco commented 1 year ago

The issue here is with UUIDs as strings vs UUID objects.

When a snippet chooser block is deserialised, the ChooserBlock.bulk_to_python method is called with the values as a list of strings. It then calls the manager's in_bulk method. in_bulk returns a dict of primary keys to instances, and in the case of UUID primary keys, the primary key is a UUID object. That prevents the objects.get(id) lookup from working.

It's possible to work around the issue by subclassing SnippetChooserBlock and overriding bulk_to_python (and get_form_state to turn the ID back into a string for JSON serialisation):

from uuid import UUID

class UUIDSnippetChooserBlock(SnippetChooserBlock):
    def bulk_to_python(self, values):
        return super().bulk_to_python([
            UUID(value) for value in values
        ])

    def get_form_state(self, value):
        form_state = super().get_form_state(value)
        if form_state:
            form_state["id"] = str(form_state["id"])
        return form_state
OsafAliSayed commented 10 months ago

Hi, I will try to work on this. I will start by replicating this issue.

OsafAliSayed commented 9 months ago

Hi, I tried replicating the issue with the given technical details. It seems that after setting up the Wagtail project as shown the issue does exist. should I add any details related to the issue or start working on a fix?

lb- commented 9 months ago

Any information or notes for how you think this could be fixed would be helpful @OsafAliSayed

If you have a way to fix and can write unit tests also, a PR is welcome.

OsafAliSayed commented 9 months ago

I guess we can write a converter function for UUID obj to string in same class and then use it before passing the id in object.get(). I will try this and see if it works