We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] Confirmation Email uses local ip for link URL
### Please verify that this bug has NOT been raised before.

- [X] I checked and didn't find similar issue

### Describe the bug*

When sending a verification email, it shows the local IP not the set base URL

Eg. mywebsite.com

Hello from InvenTree!

You're receiving this e-mail because user kyle has given your e-mail address to register an account on https://mywebsite.com

To confirm this is correct, go to http://192.168.1.111:83/accounts/confirm-email/MQ:1olnC0:UXQyFESkK7GxGnQOOEJcP8AXY1gkd-bbeIK02tDoxoo/

Thank you for using InvenTree!
https://mywebsite.com

### Steps to Reproduce

1. Set base URL
2. Add an email
3. Send a verification email

### Expected behavior

URL should respect the base URL setting

### Deployment Method

- [X] Docker
- [ ] Bare metal

### Version Information

# Version Information:
InvenTree-Version: 0.8.3
Django Version: 3.2.15
Commit Hash: f9d2b14
Commit Date: 2022-09-23
Database: postgresql
Debug-Mode: False
Deployed using Docker: True


### Relevant log output

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `InvenTree/InvenTree/forms.py`
Content:
```
1 """Helper forms which subclass Django forms to provide additional functionality."""
2 
3 import logging
4 from urllib.parse import urlencode
5 
6 from django import forms
7 from django.conf import settings
8 from django.contrib.auth.models import Group, User
9 from django.http import HttpResponseRedirect
10 from django.urls import reverse
11 from django.utils.translation import gettext_lazy as _
12 
13 from allauth.account.adapter import DefaultAccountAdapter
14 from allauth.account.forms import SignupForm, set_form_field_order
15 from allauth.exceptions import ImmediateHttpResponse
16 from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
17 from allauth_2fa.adapter import OTPAdapter
18 from allauth_2fa.utils import user_has_valid_totp_device
19 from crispy_forms.bootstrap import (AppendedText, PrependedAppendedText,
20                                     PrependedText)
21 from crispy_forms.helper import FormHelper
22 from crispy_forms.layout import Field, Layout
23 
24 from common.models import InvenTreeSetting
25 
26 logger = logging.getLogger('inventree')
27 
28 
29 class HelperForm(forms.ModelForm):
30     """Provides simple integration of crispy_forms extension."""
31 
32     # Custom field decorations can be specified here, per form class
33     field_prefix = {}
34     field_suffix = {}
35     field_placeholder = {}
36 
37     def __init__(self, *args, **kwargs):
38         """Setup layout."""
39         super(forms.ModelForm, self).__init__(*args, **kwargs)
40         self.helper = FormHelper()
41 
42         self.helper.form_tag = False
43         self.helper.form_show_errors = True
44 
45         """
46         Create a default 'layout' for this form.
47         Ref: https://django-crispy-forms.readthedocs.io/en/latest/layouts.html
48         This is required to do fancy things later (like adding PrependedText, etc).
49 
50         Simply create a 'blank' layout for each available field.
51         """
52 
53         self.rebuild_layout()
54 
55     def rebuild_layout(self):
56         """Build crispy layout out of current fields."""
57         layouts = []
58 
59         for field in self.fields:
60             prefix = self.field_prefix.get(field, None)
61             suffix = self.field_suffix.get(field, None)
62             placeholder = self.field_placeholder.get(field, '')
63 
64             # Look for font-awesome icons
65             if prefix and prefix.startswith('fa-'):
66                 prefix = r"<i class='fas {fa}'/>".format(fa=prefix)
67 
68             if suffix and suffix.startswith('fa-'):
69                 suffix = r"<i class='fas {fa}'/>".format(fa=suffix)
70 
71             if prefix and suffix:
72                 layouts.append(
73                     Field(
74                         PrependedAppendedText(
75                             field,
76                             prepended_text=prefix,
77                             appended_text=suffix,
78                             placeholder=placeholder
79                         )
80                     )
81                 )
82 
83             elif prefix:
84                 layouts.append(
85                     Field(
86                         PrependedText(
87                             field,
88                             prefix,
89                             placeholder=placeholder
90                         )
91                     )
92                 )
93 
94             elif suffix:
95                 layouts.append(
96                     Field(
97                         AppendedText(
98                             field,
99                             suffix,
100                             placeholder=placeholder
101                         )
102                     )
103                 )
104 
105             else:
106                 layouts.append(Field(field, placeholder=placeholder))
107 
108         self.helper.layout = Layout(*layouts)
109 
110 
111 class EditUserForm(HelperForm):
112     """Form for editing user information."""
113 
114     class Meta:
115         """Metaclass options."""
116 
117         model = User
118         fields = [
119             'first_name',
120             'last_name',
121         ]
122 
123 
124 class SetPasswordForm(HelperForm):
125     """Form for setting user password."""
126 
127     enter_password = forms.CharField(
128         max_length=100,
129         min_length=8,
130         required=True,
131         initial='',
132         widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
133         label=_('Enter password'),
134         help_text=_('Enter new password')
135     )
136 
137     confirm_password = forms.CharField(
138         max_length=100,
139         min_length=8,
140         required=True,
141         initial='',
142         widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
143         label=_('Confirm password'),
144         help_text=_('Confirm new password')
145     )
146 
147     old_password = forms.CharField(
148         label=_("Old password"),
149         strip=False,
150         widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}),
151     )
152 
153     class Meta:
154         """Metaclass options."""
155 
156         model = User
157         fields = [
158             'enter_password',
159             'confirm_password',
160             'old_password',
161         ]
162 
163 
164 # override allauth
165 class CustomSignupForm(SignupForm):
166     """Override to use dynamic settings."""
167 
168     def __init__(self, *args, **kwargs):
169         """Check settings to influence which fields are needed."""
170         kwargs['email_required'] = InvenTreeSetting.get_setting('LOGIN_MAIL_REQUIRED')
171 
172         super().__init__(*args, **kwargs)
173 
174         # check for two mail fields
175         if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):
176             self.fields["email2"] = forms.EmailField(
177                 label=_("Email (again)"),
178                 widget=forms.TextInput(
179                     attrs={
180                         "type": "email",
181                         "placeholder": _("Email address confirmation"),
182                     }
183                 ),
184             )
185 
186         # check for two password fields
187         if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'):
188             self.fields.pop("password2")
189 
190         # reorder fields
191         set_form_field_order(self, ["username", "email", "email2", "password1", "password2", ])
192 
193     def clean(self):
194         """Make sure the supllied emails match if enabled in settings."""
195         cleaned_data = super().clean()
196 
197         # check for two mail fields
198         if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):
199             email = cleaned_data.get("email")
200             email2 = cleaned_data.get("email2")
201             if (email and email2) and email != email2:
202                 self.add_error("email2", _("You must type the same email each time."))
203 
204         return cleaned_data
205 
206 
207 class RegistratonMixin:
208     """Mixin to check if registration should be enabled."""
209 
210     def is_open_for_signup(self, request, *args, **kwargs):
211         """Check if signup is enabled in settings."""
212         if settings.EMAIL_HOST and InvenTreeSetting.get_setting('LOGIN_ENABLE_REG', True):
213             return super().is_open_for_signup(request, *args, **kwargs)
214         return False
215 
216     def save_user(self, request, user, form, commit=True):
217         """Check if a default group is set in settings."""
218         user = super().save_user(request, user, form)
219         start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP')
220         if start_group:
221             try:
222                 group = Group.objects.get(id=start_group)
223                 user.groups.add(group)
224             except Group.DoesNotExist:
225                 logger.error('The setting `SIGNUP_GROUP` contains an non existant group', start_group)
226         user.save()
227         return user
228 
229 
230 class CustomAccountAdapter(RegistratonMixin, OTPAdapter, DefaultAccountAdapter):
231     """Override of adapter to use dynamic settings."""
232     def send_mail(self, template_prefix, email, context):
233         """Only send mail if backend configured."""
234         if settings.EMAIL_HOST:
235             return super().send_mail(template_prefix, email, context)
236         return False
237 
238 
239 class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter):
240     """Override of adapter to use dynamic settings."""
241 
242     def is_auto_signup_allowed(self, request, sociallogin):
243         """Check if auto signup is enabled in settings."""
244         if InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO', True):
245             return super().is_auto_signup_allowed(request, sociallogin)
246         return False
247 
248     # from OTPAdapter
249     def has_2fa_enabled(self, user):
250         """Returns True if the user has 2FA configured."""
251         return user_has_valid_totp_device(user)
252 
253     def login(self, request, user):
254         """Ensure user is send to 2FA before login if enabled."""
255         # Require two-factor authentication if it has been configured.
256         if self.has_2fa_enabled(user):
257             # Cast to string for the case when this is not a JSON serializable
258             # object, e.g. a UUID.
259             request.session['allauth_2fa_user_id'] = str(user.id)
260 
261             redirect_url = reverse('two-factor-authenticate')
262             # Add GET parameters to the URL if they exist.
263             if request.GET:
264                 redirect_url += '?' + urlencode(request.GET)
265 
266             raise ImmediateHttpResponse(
267                 response=HttpResponseRedirect(redirect_url)
268             )
269 
270         # Otherwise defer to the original allauth adapter.
271         return super().login(request, user)
272
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py
--- a/InvenTree/InvenTree/forms.py
+++ b/InvenTree/InvenTree/forms.py
@@ -6,6 +6,7 @@
 from django import forms
 from django.conf import settings
 from django.contrib.auth.models import Group, User
+from django.contrib.sites.models import Site
 from django.http import HttpResponseRedirect
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
@@ -227,7 +228,16 @@
         return user
 
 
-class CustomAccountAdapter(RegistratonMixin, OTPAdapter, DefaultAccountAdapter):
+class CustomUrlMixin:
+    """Mixin to set urls."""
+
+    def get_email_confirmation_url(self, request, emailconfirmation):
+        """Custom email confirmation (activation) url."""
+        url = reverse("account_confirm_email", args=[emailconfirmation.key])
+        return Site.objects.get_current().domain + url
+
+
+class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter):
     """Override of adapter to use dynamic settings."""
     def send_mail(self, template_prefix, email, context):
         """Only send mail if backend configured."""
@@ -236,7 +246,7 @@
         return False
 
 
-class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter):
+class CustomSocialAccountAdapter(CustomUrlMixin, RegistratonMixin, DefaultSocialAccountAdapter):
     """Override of adapter to use dynamic settings."""
 
     def is_auto_signup_allowed(self, request, sociallogin):

{"golden_diff": "diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py\n--- a/InvenTree/InvenTree/forms.py\n+++ b/InvenTree/InvenTree/forms.py\n@@ -6,6 +6,7 @@\n from django import forms\n from django.conf import settings\n from django.contrib.auth.models import Group, User\n+from django.contrib.sites.models import Site\n from django.http import HttpResponseRedirect\n from django.urls import reverse\n from django.utils.translation import gettext_lazy as _\n@@ -227,7 +228,16 @@\n         return user\n \n \n-class CustomAccountAdapter(RegistratonMixin, OTPAdapter, DefaultAccountAdapter):\n+class CustomUrlMixin:\n+    \"\"\"Mixin to set urls.\"\"\"\n+\n+    def get_email_confirmation_url(self, request, emailconfirmation):\n+        \"\"\"Custom email confirmation (activation) url.\"\"\"\n+        url = reverse(\"account_confirm_email\", args=[emailconfirmation.key])\n+        return Site.objects.get_current().domain + url\n+\n+\n+class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter):\n     \"\"\"Override of adapter to use dynamic settings.\"\"\"\n     def send_mail(self, template_prefix, email, context):\n         \"\"\"Only send mail if backend configured.\"\"\"\n@@ -236,7 +246,7 @@\n         return False\n \n \n-class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter):\n+class CustomSocialAccountAdapter(CustomUrlMixin, RegistratonMixin, DefaultSocialAccountAdapter):\n     \"\"\"Override of adapter to use dynamic settings.\"\"\"\n \n     def is_auto_signup_allowed(self, request, sociallogin):\n", "issue": "[BUG] Confirmation Email uses local ip for link URL\n### Please verify that this bug has NOT been raised before.\n\n- [X] I checked and didn't find similar issue\n\n### Describe the bug*\n\nWhen sending a verification email, it shows the local IP not the set base URL\r\n\r\nEg. mywebsite.com\r\n\r\nHello from InvenTree!\r\n\r\nYou're receiving this e-mail because user kyle has given your e-mail address to register an account on https://mywebsite.com\r\n\r\nTo confirm this is correct, go to http://192.168.1.111:83/accounts/confirm-email/MQ:1olnC0:UXQyFESkK7GxGnQOOEJcP8AXY1gkd-bbeIK02tDoxoo/\r\n\r\nThank you for using InvenTree!\r\nhttps://mywebsite.com\n\n### Steps to Reproduce\n\n1. Set base URL\r\n2. Add an email\r\n3. Send a verification email\n\n### Expected behavior\n\nURL should respect the base URL setting\n\n### Deployment Method\n\n- [X] Docker\n- [ ] Bare metal\n\n### Version Information\n\n# Version Information:\r\nInvenTree-Version: 0.8.3\r\nDjango Version: 3.2.15\r\nCommit Hash: f9d2b14\r\nCommit Date: 2022-09-23\r\nDatabase: postgresql\r\nDebug-Mode: False\r\nDeployed using Docker: True\r\n\n\n### Relevant log output\n\n_No response_\n", "before_files": [{"content": "\"\"\"Helper forms which subclass Django forms to provide additional functionality.\"\"\"\n\nimport logging\nfrom urllib.parse import urlencode\n\nfrom django import forms\nfrom django.conf import settings\nfrom django.contrib.auth.models import Group, User\nfrom django.http import HttpResponseRedirect\nfrom django.urls import reverse\nfrom django.utils.translation import gettext_lazy as _\n\nfrom allauth.account.adapter import DefaultAccountAdapter\nfrom allauth.account.forms import SignupForm, set_form_field_order\nfrom allauth.exceptions import ImmediateHttpResponse\nfrom allauth.socialaccount.adapter import DefaultSocialAccountAdapter\nfrom allauth_2fa.adapter import OTPAdapter\nfrom allauth_2fa.utils import user_has_valid_totp_device\nfrom crispy_forms.bootstrap import (AppendedText, PrependedAppendedText,\n                                    PrependedText)\nfrom crispy_forms.helper import FormHelper\nfrom crispy_forms.layout import Field, Layout\n\nfrom common.models import InvenTreeSetting\n\nlogger = logging.getLogger('inventree')\n\n\nclass HelperForm(forms.ModelForm):\n    \"\"\"Provides simple integration of crispy_forms extension.\"\"\"\n\n    # Custom field decorations can be specified here, per form class\n    field_prefix = {}\n    field_suffix = {}\n    field_placeholder = {}\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Setup layout.\"\"\"\n        super(forms.ModelForm, self).__init__(*args, **kwargs)\n        self.helper = FormHelper()\n\n        self.helper.form_tag = False\n        self.helper.form_show_errors = True\n\n        \"\"\"\n        Create a default 'layout' for this form.\n        Ref: https://django-crispy-forms.readthedocs.io/en/latest/layouts.html\n        This is required to do fancy things later (like adding PrependedText, etc).\n\n        Simply create a 'blank' layout for each available field.\n        \"\"\"\n\n        self.rebuild_layout()\n\n    def rebuild_layout(self):\n        \"\"\"Build crispy layout out of current fields.\"\"\"\n        layouts = []\n\n        for field in self.fields:\n            prefix = self.field_prefix.get(field, None)\n            suffix = self.field_suffix.get(field, None)\n            placeholder = self.field_placeholder.get(field, '')\n\n            # Look for font-awesome icons\n            if prefix and prefix.startswith('fa-'):\n                prefix = r\"<i class='fas {fa}'/>\".format(fa=prefix)\n\n            if suffix and suffix.startswith('fa-'):\n                suffix = r\"<i class='fas {fa}'/>\".format(fa=suffix)\n\n            if prefix and suffix:\n                layouts.append(\n                    Field(\n                        PrependedAppendedText(\n                            field,\n                            prepended_text=prefix,\n                            appended_text=suffix,\n                            placeholder=placeholder\n                        )\n                    )\n                )\n\n            elif prefix:\n                layouts.append(\n                    Field(\n                        PrependedText(\n                            field,\n                            prefix,\n                            placeholder=placeholder\n                        )\n                    )\n                )\n\n            elif suffix:\n                layouts.append(\n                    Field(\n                        AppendedText(\n                            field,\n                            suffix,\n                            placeholder=placeholder\n                        )\n                    )\n                )\n\n            else:\n                layouts.append(Field(field, placeholder=placeholder))\n\n        self.helper.layout = Layout(*layouts)\n\n\nclass EditUserForm(HelperForm):\n    \"\"\"Form for editing user information.\"\"\"\n\n    class Meta:\n        \"\"\"Metaclass options.\"\"\"\n\n        model = User\n        fields = [\n            'first_name',\n            'last_name',\n        ]\n\n\nclass SetPasswordForm(HelperForm):\n    \"\"\"Form for setting user password.\"\"\"\n\n    enter_password = forms.CharField(\n        max_length=100,\n        min_length=8,\n        required=True,\n        initial='',\n        widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),\n        label=_('Enter password'),\n        help_text=_('Enter new password')\n    )\n\n    confirm_password = forms.CharField(\n        max_length=100,\n        min_length=8,\n        required=True,\n        initial='',\n        widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),\n        label=_('Confirm password'),\n        help_text=_('Confirm new password')\n    )\n\n    old_password = forms.CharField(\n        label=_(\"Old password\"),\n        strip=False,\n        widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}),\n    )\n\n    class Meta:\n        \"\"\"Metaclass options.\"\"\"\n\n        model = User\n        fields = [\n            'enter_password',\n            'confirm_password',\n            'old_password',\n        ]\n\n\n# override allauth\nclass CustomSignupForm(SignupForm):\n    \"\"\"Override to use dynamic settings.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Check settings to influence which fields are needed.\"\"\"\n        kwargs['email_required'] = InvenTreeSetting.get_setting('LOGIN_MAIL_REQUIRED')\n\n        super().__init__(*args, **kwargs)\n\n        # check for two mail fields\n        if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):\n            self.fields[\"email2\"] = forms.EmailField(\n                label=_(\"Email (again)\"),\n                widget=forms.TextInput(\n                    attrs={\n                        \"type\": \"email\",\n                        \"placeholder\": _(\"Email address confirmation\"),\n                    }\n                ),\n            )\n\n        # check for two password fields\n        if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'):\n            self.fields.pop(\"password2\")\n\n        # reorder fields\n        set_form_field_order(self, [\"username\", \"email\", \"email2\", \"password1\", \"password2\", ])\n\n    def clean(self):\n        \"\"\"Make sure the supllied emails match if enabled in settings.\"\"\"\n        cleaned_data = super().clean()\n\n        # check for two mail fields\n        if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):\n            email = cleaned_data.get(\"email\")\n            email2 = cleaned_data.get(\"email2\")\n            if (email and email2) and email != email2:\n                self.add_error(\"email2\", _(\"You must type the same email each time.\"))\n\n        return cleaned_data\n\n\nclass RegistratonMixin:\n    \"\"\"Mixin to check if registration should be enabled.\"\"\"\n\n    def is_open_for_signup(self, request, *args, **kwargs):\n        \"\"\"Check if signup is enabled in settings.\"\"\"\n        if settings.EMAIL_HOST and InvenTreeSetting.get_setting('LOGIN_ENABLE_REG', True):\n            return super().is_open_for_signup(request, *args, **kwargs)\n        return False\n\n    def save_user(self, request, user, form, commit=True):\n        \"\"\"Check if a default group is set in settings.\"\"\"\n        user = super().save_user(request, user, form)\n        start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP')\n        if start_group:\n            try:\n                group = Group.objects.get(id=start_group)\n                user.groups.add(group)\n            except Group.DoesNotExist:\n                logger.error('The setting `SIGNUP_GROUP` contains an non existant group', start_group)\n        user.save()\n        return user\n\n\nclass CustomAccountAdapter(RegistratonMixin, OTPAdapter, DefaultAccountAdapter):\n    \"\"\"Override of adapter to use dynamic settings.\"\"\"\n    def send_mail(self, template_prefix, email, context):\n        \"\"\"Only send mail if backend configured.\"\"\"\n        if settings.EMAIL_HOST:\n            return super().send_mail(template_prefix, email, context)\n        return False\n\n\nclass CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter):\n    \"\"\"Override of adapter to use dynamic settings.\"\"\"\n\n    def is_auto_signup_allowed(self, request, sociallogin):\n        \"\"\"Check if auto signup is enabled in settings.\"\"\"\n        if InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO', True):\n            return super().is_auto_signup_allowed(request, sociallogin)\n        return False\n\n    # from OTPAdapter\n    def has_2fa_enabled(self, user):\n        \"\"\"Returns True if the user has 2FA configured.\"\"\"\n        return user_has_valid_totp_device(user)\n\n    def login(self, request, user):\n        \"\"\"Ensure user is send to 2FA before login if enabled.\"\"\"\n        # Require two-factor authentication if it has been configured.\n        if self.has_2fa_enabled(user):\n            # Cast to string for the case when this is not a JSON serializable\n            # object, e.g. a UUID.\n            request.session['allauth_2fa_user_id'] = str(user.id)\n\n            redirect_url = reverse('two-factor-authenticate')\n            # Add GET parameters to the URL if they exist.\n            if request.GET:\n                redirect_url += '?' + urlencode(request.GET)\n\n            raise ImmediateHttpResponse(\n                response=HttpResponseRedirect(redirect_url)\n            )\n\n        # Otherwise defer to the original allauth adapter.\n        return super().login(request, user)\n", "path": "InvenTree/InvenTree/forms.py"}], "after_files": [{"content": "\"\"\"Helper forms which subclass Django forms to provide additional functionality.\"\"\"\n\nimport logging\nfrom urllib.parse import urlencode\n\nfrom django import forms\nfrom django.conf import settings\nfrom django.contrib.auth.models import Group, User\nfrom django.contrib.sites.models import Site\nfrom django.http import HttpResponseRedirect\nfrom django.urls import reverse\nfrom django.utils.translation import gettext_lazy as _\n\nfrom allauth.account.adapter import DefaultAccountAdapter\nfrom allauth.account.forms import SignupForm, set_form_field_order\nfrom allauth.exceptions import ImmediateHttpResponse\nfrom allauth.socialaccount.adapter import DefaultSocialAccountAdapter\nfrom allauth_2fa.adapter import OTPAdapter\nfrom allauth_2fa.utils import user_has_valid_totp_device\nfrom crispy_forms.bootstrap import (AppendedText, PrependedAppendedText,\n                                    PrependedText)\nfrom crispy_forms.helper import FormHelper\nfrom crispy_forms.layout import Field, Layout\n\nfrom common.models import InvenTreeSetting\n\nlogger = logging.getLogger('inventree')\n\n\nclass HelperForm(forms.ModelForm):\n    \"\"\"Provides simple integration of crispy_forms extension.\"\"\"\n\n    # Custom field decorations can be specified here, per form class\n    field_prefix = {}\n    field_suffix = {}\n    field_placeholder = {}\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Setup layout.\"\"\"\n        super(forms.ModelForm, self).__init__(*args, **kwargs)\n        self.helper = FormHelper()\n\n        self.helper.form_tag = False\n        self.helper.form_show_errors = True\n\n        \"\"\"\n        Create a default 'layout' for this form.\n        Ref: https://django-crispy-forms.readthedocs.io/en/latest/layouts.html\n        This is required to do fancy things later (like adding PrependedText, etc).\n\n        Simply create a 'blank' layout for each available field.\n        \"\"\"\n\n        self.rebuild_layout()\n\n    def rebuild_layout(self):\n        \"\"\"Build crispy layout out of current fields.\"\"\"\n        layouts = []\n\n        for field in self.fields:\n            prefix = self.field_prefix.get(field, None)\n            suffix = self.field_suffix.get(field, None)\n            placeholder = self.field_placeholder.get(field, '')\n\n            # Look for font-awesome icons\n            if prefix and prefix.startswith('fa-'):\n                prefix = r\"<i class='fas {fa}'/>\".format(fa=prefix)\n\n            if suffix and suffix.startswith('fa-'):\n                suffix = r\"<i class='fas {fa}'/>\".format(fa=suffix)\n\n            if prefix and suffix:\n                layouts.append(\n                    Field(\n                        PrependedAppendedText(\n                            field,\n                            prepended_text=prefix,\n                            appended_text=suffix,\n                            placeholder=placeholder\n                        )\n                    )\n                )\n\n            elif prefix:\n                layouts.append(\n                    Field(\n                        PrependedText(\n                            field,\n                            prefix,\n                            placeholder=placeholder\n                        )\n                    )\n                )\n\n            elif suffix:\n                layouts.append(\n                    Field(\n                        AppendedText(\n                            field,\n                            suffix,\n                            placeholder=placeholder\n                        )\n                    )\n                )\n\n            else:\n                layouts.append(Field(field, placeholder=placeholder))\n\n        self.helper.layout = Layout(*layouts)\n\n\nclass EditUserForm(HelperForm):\n    \"\"\"Form for editing user information.\"\"\"\n\n    class Meta:\n        \"\"\"Metaclass options.\"\"\"\n\n        model = User\n        fields = [\n            'first_name',\n            'last_name',\n        ]\n\n\nclass SetPasswordForm(HelperForm):\n    \"\"\"Form for setting user password.\"\"\"\n\n    enter_password = forms.CharField(\n        max_length=100,\n        min_length=8,\n        required=True,\n        initial='',\n        widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),\n        label=_('Enter password'),\n        help_text=_('Enter new password')\n    )\n\n    confirm_password = forms.CharField(\n        max_length=100,\n        min_length=8,\n        required=True,\n        initial='',\n        widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),\n        label=_('Confirm password'),\n        help_text=_('Confirm new password')\n    )\n\n    old_password = forms.CharField(\n        label=_(\"Old password\"),\n        strip=False,\n        widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}),\n    )\n\n    class Meta:\n        \"\"\"Metaclass options.\"\"\"\n\n        model = User\n        fields = [\n            'enter_password',\n            'confirm_password',\n            'old_password',\n        ]\n\n\n# override allauth\nclass CustomSignupForm(SignupForm):\n    \"\"\"Override to use dynamic settings.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Check settings to influence which fields are needed.\"\"\"\n        kwargs['email_required'] = InvenTreeSetting.get_setting('LOGIN_MAIL_REQUIRED')\n\n        super().__init__(*args, **kwargs)\n\n        # check for two mail fields\n        if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):\n            self.fields[\"email2\"] = forms.EmailField(\n                label=_(\"Email (again)\"),\n                widget=forms.TextInput(\n                    attrs={\n                        \"type\": \"email\",\n                        \"placeholder\": _(\"Email address confirmation\"),\n                    }\n                ),\n            )\n\n        # check for two password fields\n        if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'):\n            self.fields.pop(\"password2\")\n\n        # reorder fields\n        set_form_field_order(self, [\"username\", \"email\", \"email2\", \"password1\", \"password2\", ])\n\n    def clean(self):\n        \"\"\"Make sure the supllied emails match if enabled in settings.\"\"\"\n        cleaned_data = super().clean()\n\n        # check for two mail fields\n        if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'):\n            email = cleaned_data.get(\"email\")\n            email2 = cleaned_data.get(\"email2\")\n            if (email and email2) and email != email2:\n                self.add_error(\"email2\", _(\"You must type the same email each time.\"))\n\n        return cleaned_data\n\n\nclass RegistratonMixin:\n    \"\"\"Mixin to check if registration should be enabled.\"\"\"\n\n    def is_open_for_signup(self, request, *args, **kwargs):\n        \"\"\"Check if signup is enabled in settings.\"\"\"\n        if settings.EMAIL_HOST and InvenTreeSetting.get_setting('LOGIN_ENABLE_REG', True):\n            return super().is_open_for_signup(request, *args, **kwargs)\n        return False\n\n    def save_user(self, request, user, form, commit=True):\n        \"\"\"Check if a default group is set in settings.\"\"\"\n        user = super().save_user(request, user, form)\n        start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP')\n        if start_group:\n            try:\n                group = Group.objects.get(id=start_group)\n                user.groups.add(group)\n            except Group.DoesNotExist:\n                logger.error('The setting `SIGNUP_GROUP` contains an non existant group', start_group)\n        user.save()\n        return user\n\n\nclass CustomUrlMixin:\n    \"\"\"Mixin to set urls.\"\"\"\n\n    def get_email_confirmation_url(self, request, emailconfirmation):\n        \"\"\"Custom email confirmation (activation) url.\"\"\"\n        url = reverse(\"account_confirm_email\", args=[emailconfirmation.key])\n        return Site.objects.get_current().domain + url\n\n\nclass CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter):\n    \"\"\"Override of adapter to use dynamic settings.\"\"\"\n    def send_mail(self, template_prefix, email, context):\n        \"\"\"Only send mail if backend configured.\"\"\"\n        if settings.EMAIL_HOST:\n            return super().send_mail(template_prefix, email, context)\n        return False\n\n\nclass CustomSocialAccountAdapter(CustomUrlMixin, RegistratonMixin, DefaultSocialAccountAdapter):\n    \"\"\"Override of adapter to use dynamic settings.\"\"\"\n\n    def is_auto_signup_allowed(self, request, sociallogin):\n        \"\"\"Check if auto signup is enabled in settings.\"\"\"\n        if InvenTreeSetting.get_setting('LOGIN_SIGNUP_SSO_AUTO', True):\n            return super().is_auto_signup_allowed(request, sociallogin)\n        return False\n\n    # from OTPAdapter\n    def has_2fa_enabled(self, user):\n        \"\"\"Returns True if the user has 2FA configured.\"\"\"\n        return user_has_valid_totp_device(user)\n\n    def login(self, request, user):\n        \"\"\"Ensure user is send to 2FA before login if enabled.\"\"\"\n        # Require two-factor authentication if it has been configured.\n        if self.has_2fa_enabled(user):\n            # Cast to string for the case when this is not a JSON serializable\n            # object, e.g. a UUID.\n            request.session['allauth_2fa_user_id'] = str(user.id)\n\n            redirect_url = reverse('two-factor-authenticate')\n            # Add GET parameters to the URL if they exist.\n            if request.GET:\n                redirect_url += '?' + urlencode(request.GET)\n\n            raise ImmediateHttpResponse(\n                response=HttpResponseRedirect(redirect_url)\n            )\n\n        # Otherwise defer to the original allauth adapter.\n        return super().login(request, user)\n", "path": "InvenTree/InvenTree/forms.py"}]}