Repository: wagtail/wagtail

Issue:
Snippet chooser panel broken
Snippet chooser panel broken in latest master. commit from a couple of weeks ago works.
.
Sorry I can't give many details at the moment. I'll investigate this further and replace this description later

Snippet chooser panel broken
Snippet chooser panel broken in latest master. commit from a couple of weeks ago works.
.
Sorry I can't give many details at the moment. I'll investigate this further and replace this description later



Discussion:
git bisect shows that the offending commit is d31e4cd032d38f6c816cf3c56115297c3c66d234

This is not specific to SnippetChooserPanel - it occurs on any initially-empty chooser for a non-nullable field. It seems that `get_related_model` returns the model that the foreign key is on, rather than the one the foreign key is pointing to, which means the related_model.DoesNotExist exception at https://github.com/torchbox/wagtail/commit/d31e4cd032d38f6c816cf3c56115297c3c66d234#diff-3eb06cf22d9b9742ad0aa1a58c9d76f5R533 is not caught.

git bisect shows that the offending commit is d31e4cd032d38f6c816cf3c56115297c3c66d234

This is not specific to SnippetChooserPanel - it occurs on any initially-empty chooser for a non-nullable field. It seems that `get_related_model` returns the model that the foreign key is on, rather than the one the foreign key is pointing to, which means the related_model.DoesNotExist exception at https://github.com/torchbox/wagtail/commit/d31e4cd032d38f6c816cf3c56115297c3c66d234#diff-3eb06cf22d9b9742ad0aa1a58c9d76f5R533 is not caught.


Resolving patch:
diff --git a/wagtail/utils/compat.py b/wagtail/utils/compat.py
--- a/wagtail/utils/compat.py
+++ b/wagtail/utils/compat.py
@@ -12,6 +12,15 @@ def get_related_model(rel):
         return rel.model
 
 
+def get_related_parent_model(rel):
+    # In Django 1.7 and under, the parent model is accessed by doing: rel.parent_model
+    # This was renamed in Django 1.8 to rel.model.
+    if django.VERSION >= (1, 8):
+        return rel.model
+    else:
+        return rel.parent_model
+
+
 def render_to_string(template_name, context=None, request=None, **kwargs):
     if django.VERSION >= (1, 8):
         return loader.render_to_string(
diff --git a/wagtail/wagtailadmin/edit_handlers.py b/wagtail/wagtailadmin/edit_handlers.py
--- a/wagtail/wagtailadmin/edit_handlers.py
+++ b/wagtail/wagtailadmin/edit_handlers.py
@@ -19,7 +19,7 @@
 from wagtail.wagtailadmin import widgets
 from wagtail.wagtailcore.models import Page
 from wagtail.wagtailcore.utils import camelcase_to_underscore, resolve_model_string
-from wagtail.utils.compat import get_related_model
+from wagtail.utils.compat import get_related_model, get_related_parent_model
 
 
 # Form field properties to override whenever we encounter a model field
@@ -525,7 +525,7 @@ class BaseChooserPanel(BaseFieldPanel):
 
     def get_chosen_item(self):
         field = self.instance._meta.get_field(self.field_name)
-        related_model = get_related_model(field.related)
+        related_model = get_related_parent_model(field.related)
         try:
             return getattr(self.instance, self.field_name)
         except related_model.DoesNotExist:


Test patch:
diff --git a/wagtail/wagtailadmin/tests/test_edit_handlers.py b/wagtail/wagtailadmin/tests/test_edit_handlers.py
--- a/wagtail/wagtailadmin/tests/test_edit_handlers.py
+++ b/wagtail/wagtailadmin/tests/test_edit_handlers.py
@@ -382,6 +382,16 @@ def test_render_as_field(self):
             '<a href="/admin/pages/%d/edit/" class="edit-link button button-small button-secondary" target="_blank">Edit this page</a>' % self.christmas_page.id,
             result)
 
+    def test_render_as_empty_field(self):
+        test_instance = PageChooserModel()
+        form = self.PageChooserForm(instance=test_instance)
+        page_chooser_panel = self.MyPageChooserPanel(instance=test_instance, form=form)
+        result = page_chooser_panel.render_as_field()
+
+        self.assertIn('<p class="help">help text</p>', result)
+        self.assertIn('<span class="title"></span>', result)
+        self.assertIn('Choose a page', result)
+
     def test_render_error(self):
         form = self.PageChooserForm({'page': ''}, instance=self.test_instance)
         self.assertFalse(form.is_valid())
diff --git a/wagtail/wagtailsnippets/tests.py b/wagtail/wagtailsnippets/tests.py
--- a/wagtail/wagtailsnippets/tests.py
+++ b/wagtail/wagtailsnippets/tests.py
@@ -255,10 +255,10 @@ def setUp(self):
         test_snippet = model.objects.create(
             advert=Advert.objects.create(text=self.advert_text))
 
-        edit_handler_class = get_snippet_edit_handler(model)
-        form_class = edit_handler_class.get_form_class(model)
-        form = form_class(instance=test_snippet)
-        edit_handler = edit_handler_class(instance=test_snippet, form=form)
+        self.edit_handler_class = get_snippet_edit_handler(model)
+        self.form_class = self.edit_handler_class.get_form_class(model)
+        form = self.form_class(instance=test_snippet)
+        edit_handler = self.edit_handler_class(instance=test_snippet, form=form)
 
         self.snippet_chooser_panel = [
             panel for panel in edit_handler.children
@@ -274,6 +274,19 @@ def test_render_as_field(self):
         self.assertIn("Choose advert", field_html)
         self.assertIn("Choose another advert", field_html)
 
+    def test_render_as_empty_field(self):
+        test_snippet = SnippetChooserModel()
+        form = self.form_class(instance=test_snippet)
+        edit_handler = self.edit_handler_class(instance=test_snippet, form=form)
+
+        snippet_chooser_panel = [
+            panel for panel in edit_handler.children
+            if getattr(panel, 'field_name', None) == 'advert'][0]
+
+        field_html = snippet_chooser_panel.render_as_field()
+        self.assertIn("Choose advert", field_html)
+        self.assertIn("Choose another advert", field_html)
+
     def test_render_js(self):
         self.assertIn('createSnippetChooser("id_advert", "tests/advert");',
                       self.snippet_chooser_panel.render_as_field())