Repository: learningequality/kolibri

Issue:
Crash in setchannelorder when channel order is None
### Observed behavior

The `setchannelorder` management command crashes when used with a newly imported channel.

### Expected behavior

Instead, the command should run as usual.

### User-facing consequences

This makes it difficult to use `setchannelorder` in a script that uses Kolibri's management commands to set up Kolibri.

### Errors and logs

This is happening because the `ChannelMetadata` record for the newly created channel has `order = None` (the field is nullable: https://github.com/learningequality/kolibri/blob/release-v0.14.x/kolibri/core/content/models.py#L345), but the code referencing `target_channel.order` in `setchannelposition` (https://github.com/learningequality/kolibri/blob/release-v0.14.x/kolibri/core/content/management/commands/setchannelposition.py#L39) is expecting it to always be a numerical value.

```
$ kolibri manage setchannelposition 000409f81dbe5d1ba67101cb9fed4530 1
Neither `ifconfig` (`ifconfig -a`) nor `ip` (`ip address show`) commands are available, listing network interfaces is likely to fail
INFO     Running Kolibri with the following settings: kolibri_gnome.kolibri_settings
INFO     Invoking command setchannelposition 000409f81dbe5d1ba67101cb9fed4530 7
Traceback (most recent call last):
  File "/app/bin/kolibri", line 10, in <module>
    sys.exit(main())
  File "/app/lib/python3.7/site-packages/kolibri/dist/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/app/lib/python3.7/site-packages/kolibri/dist/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/app/lib/python3.7/site-packages/kolibri/dist/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/app/lib/python3.7/site-packages/kolibri/utils/cli.py", line 277, in invoke
    return super(KolibriDjangoCommand, self).invoke(ctx)
  File "/app/lib/python3.7/site-packages/kolibri/dist/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/app/lib/python3.7/site-packages/kolibri/dist/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/app/lib/python3.7/site-packages/kolibri/dist/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/app/lib/python3.7/site-packages/kolibri/utils/cli.py", line 727, in manage
    execute_from_command_line(["kolibri manage"] + ctx.args)
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/core/management/__init__.py", line 364, in execute_from_command_line
    utility.execute()
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/core/management/__init__.py", line 356, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/core/management/base.py", line 283, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/core/management/base.py", line 330, in execute
    output = self.handle(*args, **options)
  File "/app/lib/python3.7/site-packages/kolibri/core/content/management/commands/setchannelposition.py", line 39, in handle
    order__lt=target_channel.order, order__gte=position
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/db/models/query.py", line 787, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/db/models/query.py", line 805, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/db/models/sql/query.py", line 1250, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/db/models/sql/query.py", line 1276, in _add_q
    allow_joins=allow_joins, split_subq=split_subq,
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/db/models/sql/query.py", line 1160, in build_filter
    value, lookups, used_joins = self.prepare_lookup_value(value, lookups, can_reuse, allow_joins)
  File "/app/lib/python3.7/site-packages/kolibri/dist/django/db/models/sql/query.py", line 989, in prepare_lookup_value
    raise ValueError("Cannot use None as a query value")
ValueError: Cannot use None as a query value
```

### Steps to reproduce

With an empty KOLIBRI_HOME:

```
$ kolibri manage importchannel 000409f81dbe5d1ba67101cb9fed4530
$ kolibri setchannelposition 000409f81dbe5d1ba67101cb9fed4530 1
```

### Context

 * Kolibri version: 0.14.3
 * Operating system: Linux
 * Browser: N/A



Discussion:

Resolving patch:
diff --git a/kolibri/core/content/management/commands/setchannelposition.py b/kolibri/core/content/management/commands/setchannelposition.py
--- a/kolibri/core/content/management/commands/setchannelposition.py
+++ b/kolibri/core/content/management/commands/setchannelposition.py
@@ -36,10 +36,10 @@ def handle(self, *args, **options):
             sys.exit(1)
 
         ChannelMetadata.objects.filter(
-            order__lt=target_channel.order, order__gte=position
+            order__lt=(target_channel.order or 0), order__gte=position
         ).update(order=F("order") + 1)
         ChannelMetadata.objects.filter(
-            order__gt=target_channel.order, order__lte=position
+            order__gt=(target_channel.order or 0), order__lte=position
         ).update(order=F("order") - 1)
         target_channel.order = position
         target_channel.save()


Test patch: