Repository: netbox-community/netbox

Issue:
Move to Django 3.0
### Proposed Changes
Update NetBox to move from Django 2.2 to [Django 3.0](https://docs.djangoproject.com/en/3.0/releases/3.0/).

**Note:** This will drop support for Python 3.5.

### Justification
General maintenance


Discussion:
Are there any plans to bring in MariaDB as a new backend along with Django 3.0? Django 3.0 will no longer support PG 9.4 and this is the last version with free BDR ver 1 support, MariaDB has a very mature and free clustering builtin (Galera)

It seems that problem mentioned [here](https://groups.google.com/d/msg/netbox-discuss/gq4iPk_T2lw/8Nzc7-_wAAAJ) needs to be fixed for the transition.

Since we don't have django 2.x in Fedora anymore, I'd love to help with testing netbox under django 3.x
From a maintenance perspective, does it make more sense to wait to upgrade to the 3.2 LTS rather than 3.0? That would give the maintainer team a stable API (currently on 2.2 LTS and [3.2 LTS in 2021](https://www.djangoproject.com/download/)) to work with until 2022, rather than chasing the `X.0` and `x.1` releases. That also gives Django features plenty of time to bake and receive patches.

I know it's tempting to target the handful of things in the new releases that have use cases in Netbox - but from a longevity and sanity perspective, perhaps waiting for the next LTS is a win for the project. Food for thought.
> Are there any plans to bring in MariaDB as a new backend along with Django 3.0? Django 3.0 will no longer support PG 9.4 and this is the last version with free BDR ver 1 support, MariaDB has a very mature and free clustering builtin (Galera)

@gdmk [Netbox relies on specific data type](https://netbox.readthedocs.io/en/stable/installation/1-postgresql/) handling in Postgresql so adding support for other database solutions isn't so easy without doing work on lots of core areas.

Resolving patch:
diff --git a/netbox/dcim/migrations/0097_mptt_remove_indexes.py b/netbox/dcim/migrations/0097_mptt_remove_indexes.py
new file mode 100644
--- /dev/null
+++ b/netbox/dcim/migrations/0097_mptt_remove_indexes.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.0.3 on 2020-02-18 21:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0096_interface_ordering'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='region',
+            name='level',
+            field=models.PositiveIntegerField(editable=False),
+        ),
+        migrations.AlterField(
+            model_name='region',
+            name='lft',
+            field=models.PositiveIntegerField(editable=False),
+        ),
+        migrations.AlterField(
+            model_name='region',
+            name='rght',
+            field=models.PositiveIntegerField(editable=False),
+        ),
+    ]
diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py
--- a/netbox/extras/forms.py
+++ b/netbox/extras/forms.py
@@ -467,7 +467,8 @@ def __init__(self, vars, *args, commit_default=True, **kwargs):
             self.fields['_commit'].initial = False
 
         # Move _commit to the end of the form
-        self.fields.move_to_end('_commit', True)
+        commit = self.fields.pop('_commit')
+        self.fields['_commit'] = commit
 
     @property
     def requires_input(self):
diff --git a/netbox/secrets/__init__.py b/netbox/secrets/__init__.py
--- a/netbox/secrets/__init__.py
+++ b/netbox/secrets/__init__.py
@@ -0,0 +1,11 @@
+# TODO: Rename the secrets app, probably
+# Python 3.6 introduced a standard library named "secrets," which obviously conflicts with this Django app. To avoid
+# renaming the app, we hotwire the components of the standard library that Django calls. (I don't like this any more
+# than you do, but it works for now.) The only references to the secrets modules are in django/utils/crypto.py.
+#
+# First, we copy secrets.compare_digest, which comes from the hmac module:
+from hmac import compare_digest
+
+# Then, we instantiate SystemRandom and map its choice() function:
+from random import SystemRandom
+choice = SystemRandom().choice


Test patch:
diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py
--- a/netbox/dcim/tests/test_api.py
+++ b/netbox/dcim/tests/test_api.py
@@ -40,7 +40,7 @@ def test_choices(self):
         self.assertEqual(choices_to_dict(response.data.get('cable:status')), CableStatusChoices.as_dict())
         content_types = ContentType.objects.filter(CABLE_TERMINATION_MODELS)
         cable_termination_choices = {
-            "{}.{}".format(ct.app_label, ct.model): ct.name for ct in content_types
+            "{}.{}".format(ct.app_label, ct.model): str(ct) for ct in content_types
         }
         self.assertEqual(choices_to_dict(response.data.get('cable:termination_a_type')), cable_termination_choices)
         self.assertEqual(choices_to_dict(response.data.get('cable:termination_b_type')), cable_termination_choices)
diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py
--- a/netbox/extras/tests/test_api.py
+++ b/netbox/extras/tests/test_api.py
@@ -37,7 +37,7 @@ def test_choices(self):
         # Graph
         content_types = ContentType.objects.filter(GRAPH_MODELS)
         graph_type_choices = {
-            "{}.{}".format(ct.app_label, ct.model): ct.name for ct in content_types
+            "{}.{}".format(ct.app_label, ct.model): str(ct) for ct in content_types
         }
         self.assertEqual(choices_to_dict(response.data.get('graph:type')), graph_type_choices)
         self.assertEqual(choices_to_dict(response.data.get('graph:template_language')), TemplateLanguageChoices.as_dict())