Repository: wagtail/wagtail

Issue:
min_num validation crash on new pages
### Issue Summary

When setting `min_num` on related InlinePanels, if the user fails to select any related objects when creating a  new page, wagtail doesn't throw a validation warning. Instead it crashes with:

`unorderable types: NoneType() < NoneType()`

This only seems to happen on new pages. If I have a page that previously met validation, then I remove those relations and save again, I do get  a proper validation warning.


### Steps to Reproduce

Code:

```
class ShopPageManager(models.Model):
    def get_shop_managers():
        g = Group.objects.get(name='Shop Managers')
        return {'groups__in': [g, ]}

# [and we have a nearly identical class for ShopPageMonitor]

    person = models.ForeignKey('people.UserProfile',  limit_choices_to=get_shop_managers)
    page = ParentalKey('ShopPage', related_name='shoppage_managers')

...

class ShopPage(RoutablePageMixin, BaseAsset):
     ...
    content_panels = BaseAsset.content_panels + [
        FieldPanel('hours_of_access'),
        MultiFieldPanel(
            [
                InlinePanel('shoppage_managers', label="Shop Managers", min_num=1),
                InlinePanel('shoppage_monitors', label="Shop Monitors", min_num=1)
            ],
            heading="Managers and Monitors",
            classname="collapsible collapsed"
        ),
    .....
```
### Technical details

python 3.5
wagtail 1.7 AND wagtail 1.8
django 1.10




Discussion:
Hi @shacker - we're having trouble replicating this issue as described. Would you be able to come up with a standalone code example based on a fresh `wagtail start` project that demonstrates this bug, please?
Closing for now as unable to replicate - will be happy to reopen if you can provide a test case.
Apologies for not responding sooner. Multiple simultaneous deadlines. Will try to reproduce and follow up soon. 
Same issue happened multiple times to one of my clients with Wagtail 1.11.1 when creating a page.

It appears that for some reason, the javascript is not filling correctly one of the `ORDER` fields. Here is the data I got from the report for a `carousel_images` inline panel.
```
carousel_images-MIN_NUM_FORMS = '1'
carousel_images-MAX_NUM_FORMS = '7'
carousel_images-INITIAL_FORMS = '0'
carousel_images-TOTAL_FORMS = '3'
carousel_images-0-id = ''
carousel_images-0-image = '57'
carousel_images-0-ORDER = ''
carousel_images-0-DELETE = ''
carousel_images-1-id = ''
carousel_images-1-image = '56'
carousel_images-1-ORDER = '2'
carousel_images-1-DELETE = ''
carousel_images-2-id = ''
carousel_images-2-image = '55'
carousel_images-2-ORDER = '3'
carousel_images-2-DELETE = ''
```

Here’s the traceback:
```
Traceback:

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
  41.             response = get_response(request)

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/django/views/decorators/cache.py" in _cache_controlled
  43.             response = viewfunc(request, *args, **kw)

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/wagtail/wagtailadmin/decorators.py" in decorated_view
  31.             return view_func(request, *args, **kwargs)

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/wagtail/wagtailadmin/views/pages.py" in create
  273.             edit_handler = edit_handler_class(instance=page, form=form)

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/wagtail/wagtailadmin/edit_handlers.py" in __init__
  269.             self.children.append(child(instance=self.instance, form=self.form))

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/wagtail/wagtailadmin/edit_handlers.py" in __init__
  269.             self.children.append(child(instance=self.instance, form=self.form))

File "/home/my_client/.virtualenvs/my_client/lib/python3.5/site-packages/wagtail/wagtailadmin/edit_handlers.py" in __init__
  712.             self.children = sorted(self.children, key=lambda x: x.form.cleaned_data['ORDER'])

Exception Type: TypeError at /admin/pages/add/my_client/projet/5/
Exception Value: unorderable types: int() < NoneType()
```

Resolving patch:
diff --git a/wagtail/admin/edit_handlers.py b/wagtail/admin/edit_handlers.py
--- a/wagtail/admin/edit_handlers.py
+++ b/wagtail/admin/edit_handlers.py
@@ -3,6 +3,7 @@
 from django import forms
 from django.core.exceptions import ImproperlyConfigured
 from django.db.models.fields import FieldDoesNotExist
+from django.forms.formsets import DELETION_FIELD_NAME, ORDERING_FIELD_NAME
 from django.forms.models import fields_for_model
 from django.template.loader import render_to_string
 from django.utils.encoding import force_text
@@ -689,11 +690,11 @@ def on_instance_bound(self):
         self.children = []
         for subform in self.formset.forms:
             # override the DELETE field to have a hidden input
-            subform.fields['DELETE'].widget = forms.HiddenInput()
+            subform.fields[DELETION_FIELD_NAME].widget = forms.HiddenInput()
 
             # ditto for the ORDER field, if present
             if self.formset.can_order:
-                subform.fields['ORDER'].widget = forms.HiddenInput()
+                subform.fields[ORDERING_FIELD_NAME].widget = forms.HiddenInput()
 
             child_edit_handler = self.get_child_edit_handler()
             self.children.append(
@@ -703,12 +704,13 @@ def on_instance_bound(self):
         # if this formset is valid, it may have been re-ordered; respect that
         # in case the parent form errored and we need to re-render
         if self.formset.can_order and self.formset.is_valid():
-            self.children = sorted(self.children, key=lambda x: x.form.cleaned_data['ORDER'])
+            self.children.sort(
+                key=lambda child: child.form.cleaned_data[ORDERING_FIELD_NAME] or 1)
 
         empty_form = self.formset.empty_form
-        empty_form.fields['DELETE'].widget = forms.HiddenInput()
+        empty_form.fields[DELETION_FIELD_NAME].widget = forms.HiddenInput()
         if self.formset.can_order:
-            empty_form.fields['ORDER'].widget = forms.HiddenInput()
+            empty_form.fields[ORDERING_FIELD_NAME].widget = forms.HiddenInput()
 
         self.empty_child = self.get_child_edit_handler()
         self.empty_child = self.empty_child.bind_to_instance(


Test patch: