Repository: wagtail/wagtail

Issue:
It would be nice if InlinePanel definitions required less model name repetition
In the following example model code

```
class BlogPageRelatedLink(Whatever):
    page = ParentalKey('myapp.BlogPage', related_name='related_links')

class BlogPage(Page):
    pass     

BlogPage.content_panels = [
    InlinePanel(BlogPage, 'related_links', label="Related links"),
]
```

... it would be nice if the `'myapp.BlogPage'` param in `ParentalKey` and the `BlogPage` param in `InlinePanel` weren't necessary. They're easily forgotten if you're copy-pasting models and appear avoidably repetitive. 

I don't understand a situation in which either param would _not_ point to the page model you're creating, although I expect situations exist.



Discussion:
The param in ParentalKey can't be avoided - that's the one thing that defines the parent/child relation between BlogPage and BlogPageRelatedLink.

The param in InlinePanel is annoyingly redundant, yep - but I can't see an easy way around it. The InlinePanel object doesn't know that it's being defined inside BlogPage's panels, so it needs to be explicitly passed BlogPage so that it knows what `related_links` refers to.


Resolving patch:
diff --git a/runtests.py b/runtests.py
--- a/runtests.py
+++ b/runtests.py
@@ -15,6 +15,7 @@
 def runtests():
     # Don't ignore DeprecationWarnings
     warnings.simplefilter('default', DeprecationWarning)
+    warnings.simplefilter('default', PendingDeprecationWarning)
 
     argv = sys.argv[:1] + ['test'] + sys.argv[1:]
     try:
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
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 import copy
+import warnings
 
 from six import text_type
 
@@ -21,6 +22,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.deprecation import RemovedInWagtail11Warning
 
 
 FORM_FIELD_OVERRIDES = {
@@ -640,13 +642,29 @@ def render_js_init(self):
 
 
 class InlinePanel(object):
-    def __init__(self, base_model, relation_name, panels=None, label='', help_text=''):
-        # the base_model param is now redundant; we set up relations based on the model passed to
+    def __init__(self, *args, **kwargs):
+        # prior to Wagtail 0.9, InlinePanel required two params, base_model and relation_name.
+        # base_model is no longer required; we set up relations based on the model passed to
         # bind_to_model instead
-        self.relation_name = relation_name
-        self.panels = panels
-        self.label = label
-        self.help_text = help_text
+        if len(args) == 1:  # new-style: InlinePanel(relation_name)
+            self.relation_name = args[0]
+        elif len(args) == 2:  # old-style: InlinePanel(base_model, relation_name)
+            self.relation_name = args[1]
+
+            warnings.warn(
+                "InlinePanel no longer needs to be passed a model parameter. "
+                "InlinePanel({classname}, '{relname}') should be changed to InlinePanel('{relname}')".format(
+                    classname=args[0].__name__, relname=self.relation_name
+                ), RemovedInWagtail11Warning, stacklevel=2)
+        else:
+            raise TypeError("InlinePanel() takes exactly 1 argument (%d given)" % len(args))
+
+        self.panels = kwargs.pop('panels', None)
+        self.label = kwargs.pop('label', '')
+        self.help_text = kwargs.pop('help_text', '')
+
+        if kwargs:
+            raise TypeError("InlinePanel got an unexpected keyword argument '%s'" % kwargs.keys()[0])
 
     def bind_to_model(self, model):
         return type(str('_InlinePanel'), (BaseInlinePanel,), {


Test patch:
diff --git a/wagtail/tests/models.py b/wagtail/tests/models.py
--- a/wagtail/tests/models.py
+++ b/wagtail/tests/models.py
@@ -247,10 +247,10 @@ class EventPage(Page):
     FieldPanel('audience'),
     FieldPanel('cost'),
     FieldPanel('signup_link'),
-    InlinePanel(EventPage, 'carousel_items', label="Carousel items"),
+    InlinePanel('carousel_items', label="Carousel items"),
     FieldPanel('body', classname="full"),
-    InlinePanel(EventPage, 'speakers', label="Speakers"),
-    InlinePanel(EventPage, 'related_links', label="Related links"),
+    InlinePanel('speakers', label="Speakers"),
+    InlinePanel('related_links', label="Related links"),
 ]
 
 EventPage.promote_panels = [
@@ -329,7 +329,7 @@ class FormPage(AbstractEmailForm):
 
 FormPage.content_panels = [
     FieldPanel('title', classname="full title"),
-    InlinePanel(FormPage, 'form_fields', label="Form fields"),
+    InlinePanel('form_fields', label="Form fields"),
     MultiFieldPanel([
         FieldPanel('to_address', classname="full"),
         FieldPanel('from_address', classname="full"),
@@ -392,7 +392,7 @@ class StandardIndex(Page):
 
 StandardIndex.content_panels = [
     FieldPanel('title', classname="full title"),
-    InlinePanel(StandardIndex, 'advert_placements', label="Adverts"),
+    InlinePanel('advert_placements', label="Adverts"),
 ]
 
 
diff --git a/wagtail/tests/utils.py b/wagtail/tests/utils.py
--- a/wagtail/tests/utils.py
+++ b/wagtail/tests/utils.py
@@ -1,5 +1,6 @@
 from contextlib import contextmanager
 import warnings
+import sys
 
 from django.contrib.auth import get_user_model
 from django.utils import six
@@ -28,3 +29,18 @@ def ignore_deprecation_warnings():
         for w in warning_list:
             if not issubclass(w.category, DeprecationWarning):
                 warnings.showwarning(message=w.message, category=w.category, filename=w.filename, lineno=w.lineno, file=w.file, line=w.line)
+
+    # borrowed from https://github.com/django/django/commit/9f427617e4559012e1c2fd8fce46cbe225d8515d
+    @staticmethod
+    def reset_warning_registry():
+        """
+        Clear warning registry for all modules. This is required in some tests
+        because of a bug in Python that prevents warnings.simplefilter("always")
+        from always making warnings appear: http://bugs.python.org/issue4180
+
+        The bug was fixed in Python 3.4.2.
+        """
+        key = "__warningregistry__"
+        for mod in sys.modules.values():
+            if hasattr(mod, key):
+                getattr(mod, key).clear()
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
@@ -1,4 +1,5 @@
 from datetime import date
+import warnings
 
 from django.core.exceptions import ImproperlyConfigured
 from django.test import TestCase
@@ -19,6 +20,8 @@
 from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
 from wagtail.wagtailcore.models import Page, Site
 from wagtail.tests.models import PageChooserModel, EventPage, EventPageSpeaker
+from wagtail.tests.utils import WagtailTestUtils
+from wagtail.utils.deprecation import RemovedInWagtail11Warning
 
 
 class TestGetFormForModel(TestCase):
@@ -129,7 +132,7 @@ def setUp(self):
                 FieldPanel('date_to'),
             ], heading='Event details', classname="shiny"),
             ObjectList([
-                InlinePanel(EventPage, 'speakers', label="Speakers"),
+                InlinePanel('speakers', label="Speakers"),
             ], heading='Speakers'),
         ]).bind_to_model(EventPage)
 
@@ -200,7 +203,7 @@ def setUp(self):
             FieldPanel('title', widget=forms.Textarea),
             FieldPanel('date_from'),
             FieldPanel('date_to'),
-            InlinePanel(EventPage, 'speakers', label="Speakers"),
+            InlinePanel('speakers', label="Speakers"),
         ], heading='Event details', classname="shiny").bind_to_model(EventPage)
 
     def test_get_form_class(self):
@@ -391,7 +394,7 @@ def test_target_content_type_nonexistent_type(self):
                           result.target_content_type)
 
 
-class TestInlinePanel(TestCase):
+class TestInlinePanel(TestCase, WagtailTestUtils):
     fixtures = ['test.json']
 
     def test_render(self):
@@ -399,7 +402,7 @@ def test_render(self):
         Check that the inline panel renders the panels set on the model
         when no 'panels' parameter is passed in the InlinePanel definition
         """
-        SpeakerInlinePanel = InlinePanel(EventPage, 'speakers', label="Speakers").bind_to_model(EventPage)
+        SpeakerInlinePanel = InlinePanel('speakers', label="Speakers").bind_to_model(EventPage)
         EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
 
         # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
@@ -434,7 +437,7 @@ def test_render_with_panel_overrides(self):
         Check that inline panel renders the panels listed in the InlinePanel definition
         where one is specified
         """
-        SpeakerInlinePanel = InlinePanel(EventPage, 'speakers', label="Speakers", panels=[
+        SpeakerInlinePanel = InlinePanel('speakers', label="Speakers", panels=[
             FieldPanel('first_name', widget=forms.Textarea),
             ImageChooserPanel('image'),
         ]).bind_to_model(EventPage)
@@ -471,3 +474,37 @@ def test_render_with_panel_overrides(self):
 
         # render_js_init must provide the JS initializer
         self.assertIn('var panel = InlinePanel({', panel.render_js_init())
+
+    def test_old_style_inlinepanel_declaration(self):
+        """
+        Check that the deprecated form of InlinePanel declaration (where the base model is passed
+        as the first arg) still works
+        """
+        self.reset_warning_registry()
+        with warnings.catch_warnings(record=True) as w:
+            SpeakerInlinePanelDef = InlinePanel(EventPage, 'speakers', label="Speakers")
+
+            # Check that a RemovedInWagtail11Warning has been triggered
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[-1].category, RemovedInWagtail11Warning))
+            self.assertTrue("InlinePanel(EventPage, 'speakers') should be changed to InlinePanel('speakers')" in str(w[-1].message))
+
+        SpeakerInlinePanel = SpeakerInlinePanelDef.bind_to_model(EventPage)
+        EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
+
+        # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
+        self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
+
+        event_page = EventPage.objects.get(slug='christmas')
+        form = EventPageForm(instance=event_page)
+        panel = SpeakerInlinePanel(instance=event_page, form=form)
+
+        result = panel.render_as_field()
+        self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result)
+        self.assertIn('value="Father"', result)
+
+    def test_invalid_inlinepanel_declaration(self):
+        with self.ignore_deprecation_warnings():
+            self.assertRaises(TypeError, lambda: InlinePanel(label="Speakers"))
+            self.assertRaises(TypeError, lambda: InlinePanel(EventPage, 'speakers', 'bacon', label="Speakers"))
+            self.assertRaises(TypeError, lambda: InlinePanel(EventPage, 'speakers', label="Speakers", bacon="chunky"))