Repository: inventree/InvenTree

Issue:
[BUG] Duplication of "base URL" setting
Currently there are two distinct places to set the base URL for the server, and the two values are functionally distinct:

![image](https://user-images.githubusercontent.com/10080325/173743926-45aabbe6-5d53-42cc-ae02-657b67496642.png)

![image](https://user-images.githubusercontent.com/10080325/173743612-19d8c7ad-7d2e-4927-a74b-7f394fe8496c.png)

The second option was added when added support for django-allauth, but did not consider the interplay with the existing setting!

What we should do:

- Remove the custom global parameter `INVENTREE_BASE_URL` in favour of the option provided by django-allauth
- Expose the django-allauth settings to the "settings" page (rather than requiring the user to navigate to the admin section)
- Refactor any existing code that uses `INVENTREE_BASE_URL` to use the domain name setting configured in django-allauth

Ref: https://github.com/inventree/InvenTree/discussions/3195


Discussion:
@matmair thoughts on this? Feels like this has been an oversight more than anything else.
@SchrodingersGat this is not a duplication - you can have multiple domains pointing to the same service and name the sites different names in the interface.

Ex:
https://example.com -> Parts Registry
https://dep1.example.com -> Department 1 Parts Registry

This is the core functionality of the sites framework - this is used for auhtentication, registration, password forgetting.
I understand that the domain info is crucial - the needless duplication (as I understand it) is the `INVENTREE_BASE_URL` (which is an Inventree specific setting) could be refactored (removed) and we use the allauth setting in its place.
@SchrodingersGat that could be more difficult than you might think - if you have two or more domains the behaviour gets a bit trickier to test repetable.
I have not considered the use case where multiple domains are used. With your perspective what is the purpose of the `INVENTREE_BASE_URL` (separate from the django-allauth values)
@SchrodingersGat the 'site' stuff actually is a core part of [django](https://docs.djangoproject.com/en/4.0/ref/contrib/sites/). I am not really sure what the base_url is used for. Maybe for links?
@matmair here's a crazy thought, for a potential solution here. We currently have the `INVENTREE_BASE_URL` which is used in a bunch of places for generating URL links etc, and the "site domain name" 

We could implement a per-setting "on_save" callback for the InvenTreeSetting class, and then whenever `INVENTREE_BASE_URL` is updated, write the same value to the "site domain name". This means that:

- Domain name is exposed to the existing settings framework
- The two values do not get out of sync
- Users do not have to navigate to the admin site to change this value
@SchrodingersGat seems to be error prone. How do you select which site to update? There can be multiples.
The `INVENTREE_BASE_URL` in pratice sets the 'main' url for a project. We could add a link and note to the setting but I would not try to figure out the right `Site` instance. Maybe if we only have one it could work.
We know which one the "main" site is (by looking at the `SITE_ID` parameter in settings.py). 
@SchrodingersGat that is not always the case - if there is only 1 for sure. If you have multiple it most often is the first.

Resolving patch:
diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py
--- a/InvenTree/common/models.py
+++ b/InvenTree/common/models.py
@@ -21,6 +21,7 @@
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.humanize.templatetags.humanize import naturaltime
+from django.contrib.sites.models import Site
 from django.core.cache import cache
 from django.core.exceptions import AppRegistryNotReady, ValidationError
 from django.core.validators import MinValueValidator, URLValidator
@@ -82,6 +83,14 @@ def save(self, *args, **kwargs):
 
         super().save()
 
+        # Get after_save action
+        setting = self.get_setting_definition(self.key, *args, **kwargs)
+        after_save = setting.get('after_save', None)
+
+        # Execute if callable
+        if callable(after_save):
+            after_save(self)
+
     @property
     def cache_key(self):
         """Generate a unique cache key for this settings object"""
@@ -735,6 +744,20 @@ def settings_group_options():
     return [('', _('No group')), *[(str(a.id), str(a)) for a in Group.objects.all()]]
 
 
+def update_instance_url(setting):
+    """Update the first site objects domain to url."""
+    site_obj = Site.objects.all().order_by('id').first()
+    site_obj.domain = setting.value
+    site_obj.save()
+
+
+def update_instance_name(setting):
+    """Update the first site objects name to instance name."""
+    site_obj = Site.objects.all().order_by('id').first()
+    site_obj.name = setting.value
+    site_obj.save()
+
+
 class InvenTreeSetting(BaseInvenTreeSetting):
     """An InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values).
 
@@ -782,6 +805,7 @@ def save(self, *args, **kwargs):
             'name': _('Server Instance Name'),
             'default': 'InvenTree',
             'description': _('String descriptor for the server instance'),
+            'after_save': update_instance_name,
         },
 
         'INVENTREE_INSTANCE_TITLE': {
@@ -809,6 +833,7 @@ def save(self, *args, **kwargs):
             'description': _('Base URL for server instance'),
             'validator': EmptyURLValidator(),
             'default': '',
+            'after_save': update_instance_url,
         },
 
         'INVENTREE_DEFAULT_CURRENCY': {


Test patch:
diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py
--- a/InvenTree/InvenTree/tests.py
+++ b/InvenTree/InvenTree/tests.py
@@ -9,6 +9,7 @@
 import django.core.exceptions as django_exceptions
 from django.conf import settings
 from django.contrib.auth import get_user_model
+from django.contrib.sites.models import Site
 from django.core.exceptions import ValidationError
 from django.test import TestCase, override_settings
 
@@ -604,3 +605,16 @@ def test_instance_name(self):
         InvenTreeSetting.set_setting("INVENTREE_INSTANCE", "Testing title", self.user)
 
         self.assertEqual(version.inventreeInstanceTitle(), 'Testing title')
+
+        # The site should also be changed
+        site_obj = Site.objects.all().order_by('id').first()
+        self.assertEqual(site_obj.name, 'Testing title')
+
+    def test_instance_url(self):
+        """Test instance url settings."""
+        # Set up required setting
+        InvenTreeSetting.set_setting("INVENTREE_BASE_URL", "http://127.1.2.3", self.user)
+
+        # The site should also be changed
+        site_obj = Site.objects.all().order_by('id').first()
+        self.assertEqual(site_obj.domain, 'http://127.1.2.3')
diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py
--- a/InvenTree/common/tests.py
+++ b/InvenTree/common/tests.py
@@ -133,6 +133,7 @@ def run_settings_check(self, key, setting):
             'choices',
             'units',
             'requires_restart',
+            'after_save',
         ]
 
         for k in setting.keys():