diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html
index 52b5d4bfe..01aeacff1 100644
--- a/netbox/templates/dcim/rack.html
+++ b/netbox/templates/dcim/rack.html
@@ -101,6 +101,12 @@
Outer Width |
diff --git a/netbox/templates/dcim/rack_edit.html b/netbox/templates/dcim/rack_edit.html
index 4bbd72405..a1ebb7531 100644
--- a/netbox/templates/dcim/rack_edit.html
+++ b/netbox/templates/dcim/rack_edit.html
@@ -71,6 +71,7 @@
{% render_field form.mounting_depth %}
{% render_field form.desc_units %}
+ {% render_field form.starting_unit %}
{% if form.custom_fields %}
diff --git a/netbox/templates/extras/configrevision.html b/netbox/templates/extras/configrevision.html
new file mode 100644
index 000000000..1c7eeb2dd
--- /dev/null
+++ b/netbox/templates/extras/configrevision.html
@@ -0,0 +1,200 @@
+{% extends 'generic/object.html' %}
+{% load buttons %}
+{% load custom_links %}
+{% load helpers %}
+{% load perms %}
+{% load plugins %}
+{% load static %}
+
+{% block breadcrumbs %}
+{% endblock %}
+
+{% block controls %}
+
+
+ {% plugin_buttons object %}
+
+
+ {% custom_links object %}
+
+
+{% endblock controls %}
+
+{% block content %}
+
+
+
+
+
+
+
+ Rack elevation default unit height: |
+ {{ object.data.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT }} |
+
+
+ Rack elevation default unit width: |
+ {{ object.data.RACK_ELEVATION_DEFAULT_UNIT_WIDTH }} |
+
+
+
+
+
+
+
+
+
+
+ Powerfeed default voltage: |
+ {{ object.data.POWERFEED_DEFAULT_VOLTAGE }} |
+
+
+ Powerfeed default amperage: |
+ {{ object.data.POWERFEED_DEFAULT_AMPERAGE }} |
+
+
+ Powerfeed default max utilization: |
+ {{ object.data.POWERFEED_DEFAULT_MAX_UTILIZATION }} |
+
+
+
+
+
+
+
+
+
+
+ IPAM enforce global unique: |
+ {{ object.data.ENFORCE_GLOBAL_UNIQUE }} |
+
+
+ IPAM prefer IPV4: |
+ {{ object.data.PREFER_IPV4 }} |
+
+
+
+
+
+
+
+
+
+
+ Allowed URL schemes: |
+ {{ object.data.ALLOWED_URL_SCHEMES }} |
+
+
+
+
+
+
+
+
+
+
+ Login banner: |
+ {{ object.data.BANNER_LOGIN }} |
+
+
+ Maintenance banner: |
+ {{ object.data.BANNER_MAINTENANCE }} |
+
+
+ Top banner: |
+ {{ object.data.BANNER_TOP }} |
+
+
+ Bottom banner: |
+ {{ object.data.BANNER_BOTTOM }} |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Paginate count: |
+ {{ object.data.PAGINATE_COUNT }} |
+
+
+ Max page size: |
+ {{ object.data.MAX_PAGE_SIZE }} |
+
+
+
+
+
+
+
+
+
+
+ Custom validators: |
+ {{ object.data.CUSTOM_VALIDATORS }} |
+
+
+
+
+
+
+
+
+
+
+ Default user preferences: |
+ {{ object.data.DEFAULT_USER_PREFERENCES }} |
+
+
+
+
+
+
+
+
+
+
+ Maintenance mode: |
+ {{ object.data.MAINTENANCE_MODE }} |
+
+
+ GraphQL enabled: |
+ {{ object.data.GRAPHQL_ENABLED }} |
+
+
+ Changelog retention: |
+ {{ object.data.CHANGELOG_RETENTION }} |
+
+
+ Job retention: |
+ {{ object.data.JOB_RETENTION }} |
+
+
+ Maps URL: |
+ {{ object.data.MAPS_URL }} |
+
+
+
+
+
+
+
+
+
+
+ Comment: |
+ {{ object.comment }} |
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/netbox/templates/extras/configrevision_restore.html b/netbox/templates/extras/configrevision_restore.html
new file mode 100644
index 000000000..ac22f8cbd
--- /dev/null
+++ b/netbox/templates/extras/configrevision_restore.html
@@ -0,0 +1,88 @@
+{% extends 'base/layout.html' %}
+{% load helpers %}
+{% load buttons %}
+{% load perms %}
+{% load static %}
+
+{% block title %}Restore: {{ object }}{% endblock %}
+
+{% block subtitle %}
+
+ Created {{ object.created|annotated_date }}
+
+{% endblock %}
+
+{% block header %}
+
+ {{ block.super }}
+{% endblock header %}
+
+{% block controls %}
+
+
+ {% if request.user|can_delete:job %}
+ {% delete_button job %}
+ {% endif %}
+
+
+{% endblock controls %}
+
+{% block tabs %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+ Parameter |
+ Current Value |
+ New Value |
+ |
+
+
+
+ {% for param, current, new in params %}
+
+ {{ param }} |
+ {{ current }} |
+ {{ new }} |
+ {% if current != new %} {% endif %} |
+
+ {% endfor %}
+
+
+
+
+
+
+
+{% endblock content %}
+
+{% block modals %}
+{% endblock modals %}
diff --git a/netbox/templates/extras/dashboard/widgets/bookmarks.html b/netbox/templates/extras/dashboard/widgets/bookmarks.html
new file mode 100644
index 000000000..2189cc55f
--- /dev/null
+++ b/netbox/templates/extras/dashboard/widgets/bookmarks.html
@@ -0,0 +1,9 @@
+{% if bookmarks %}
+
+{% endif %}
diff --git a/netbox/templates/extras/tag.html b/netbox/templates/extras/tag.html
index 6e4c5aee9..e5aa5cc75 100644
--- a/netbox/templates/extras/tag.html
+++ b/netbox/templates/extras/tag.html
@@ -43,9 +43,23 @@
-
+
+
+
+ {% for ct in object.object_types.all %}
+
+ {{ ct }} |
+
+ {% empty %}
+
+ Any |
+
+ {% endfor %}
+
+
+
+
+
{% for object_type in object_types %}
diff --git a/netbox/templates/generic/object.html b/netbox/templates/generic/object.html
index d3a617455..76ceb9f35 100644
--- a/netbox/templates/generic/object.html
+++ b/netbox/templates/generic/object.html
@@ -38,7 +38,7 @@ Context:
{{ block.super }}
-{% endblock %}
+{% endblock header %}
{% block title %}{{ object }}{% endblock %}
@@ -48,7 +48,7 @@ Context:
·
Updated {{ object.last_updated|timesince }} ago
-{% endblock %}
+{% endblock subtitle %}
{% block controls %}
{# Clone/Edit/Delete Buttons #}
@@ -59,6 +59,9 @@ Context:
{# Extra buttons #}
{% block extra_controls %}{% endblock %}
+ {% if perms.extras.add_bookmark %}
+ {% bookmark_button object %}
+ {% endif %}
{% if request.user|can_add:object %}
{% clone_button object %}
{% endif %}
diff --git a/netbox/templates/inc/profile_button.html b/netbox/templates/inc/profile_button.html
index b63b25464..932b91275 100644
--- a/netbox/templates/inc/profile_button.html
+++ b/netbox/templates/inc/profile_button.html
@@ -23,6 +23,11 @@
Profile
+
+
+ Bookmarks
+
+
Preferences
diff --git a/netbox/templates/users/base.html b/netbox/templates/users/base.html
index 58861ee90..e07e28ced 100644
--- a/netbox/templates/users/base.html
+++ b/netbox/templates/users/base.html
@@ -5,6 +5,9 @@
Profile
+
+ Bookmarks
+
Preferences
diff --git a/netbox/templates/users/bookmarks.html b/netbox/templates/users/bookmarks.html
new file mode 100644
index 000000000..66f367a1c
--- /dev/null
+++ b/netbox/templates/users/bookmarks.html
@@ -0,0 +1,34 @@
+{% extends 'users/base.html' %}
+{% load buttons %}
+{% load helpers %}
+{% load render_table from django_tables2 %}
+
+{% block title %}Bookmarks{% endblock %}
+
+{% block content %}
+
+
+{% endblock %}
diff --git a/netbox/users/api/nested_serializers.py b/netbox/users/api/nested_serializers.py
index 3510184ae..5e15fa41a 100644
--- a/netbox/users/api/nested_serializers.py
+++ b/netbox/users/api/nested_serializers.py
@@ -1,4 +1,5 @@
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes
@@ -28,7 +29,7 @@ class NestedUserSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='users-api:user-detail')
class Meta:
- model = User
+ model = get_user_model()
fields = ['id', 'url', 'display', 'username']
@extend_schema_field(OpenApiTypes.STR)
diff --git a/netbox/users/api/serializers.py b/netbox/users/api/serializers.py
index 1b975791f..1f4bf4ea0 100644
--- a/netbox/users/api/serializers.py
+++ b/netbox/users/api/serializers.py
@@ -1,5 +1,6 @@
from django.conf import settings
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes
@@ -30,7 +31,7 @@ class UserSerializer(ValidatedModelSerializer):
)
class Meta:
- model = User
+ model = get_user_model()
fields = (
'id', 'url', 'display', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff', 'is_active',
'date_joined', 'groups',
@@ -124,7 +125,7 @@ class ObjectPermissionSerializer(ValidatedModelSerializer):
many=True
)
users = SerializedPKRelatedField(
- queryset=User.objects.all(),
+ queryset=get_user_model().objects.all(),
serializer=NestedUserSerializer,
required=False,
many=True
diff --git a/netbox/users/api/views.py b/netbox/users/api/views.py
index 04b3ae336..4a8e1b154 100644
--- a/netbox/users/api/views.py
+++ b/netbox/users/api/views.py
@@ -1,5 +1,6 @@
from django.contrib.auth import authenticate
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from django.db.models import Count
from drf_spectacular.utils import extend_schema
from drf_spectacular.types import OpenApiTypes
@@ -32,7 +33,7 @@ class UsersRootView(APIRootView):
#
class UserViewSet(NetBoxModelViewSet):
- queryset = RestrictedQuerySet(model=User).prefetch_related('groups').order_by('username')
+ queryset = RestrictedQuerySet(model=get_user_model()).prefetch_related('groups').order_by('username')
serializer_class = serializers.UserSerializer
filterset_class = filtersets.UserFilterSet
diff --git a/netbox/users/filtersets.py b/netbox/users/filtersets.py
index 4ae9df89a..44ad98cc2 100644
--- a/netbox/users/filtersets.py
+++ b/netbox/users/filtersets.py
@@ -1,5 +1,6 @@
import django_filters
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from django.db.models import Q
from django.utils.translation import gettext as _
@@ -47,7 +48,7 @@ class UserFilterSet(BaseFilterSet):
)
class Meta:
- model = User
+ model = get_user_model()
fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_staff', 'is_active']
def search(self, queryset, name, value):
@@ -68,12 +69,12 @@ class TokenFilterSet(BaseFilterSet):
)
user_id = django_filters.ModelMultipleChoiceFilter(
field_name='user',
- queryset=User.objects.all(),
+ queryset=get_user_model().objects.all(),
label=_('User'),
)
user = django_filters.ModelMultipleChoiceFilter(
field_name='user__username',
- queryset=User.objects.all(),
+ queryset=get_user_model().objects.all(),
to_field_name='username',
label=_('User (name)'),
)
@@ -116,12 +117,12 @@ class ObjectPermissionFilterSet(BaseFilterSet):
)
user_id = django_filters.ModelMultipleChoiceFilter(
field_name='users',
- queryset=User.objects.all(),
+ queryset=get_user_model().objects.all(),
label=_('User'),
)
user = django_filters.ModelMultipleChoiceFilter(
field_name='users__username',
- queryset=User.objects.all(),
+ queryset=get_user_model().objects.all(),
to_field_name='username',
label=_('User (name)'),
)
diff --git a/netbox/users/graphql/schema.py b/netbox/users/graphql/schema.py
index 3b04d8418..f033a535a 100644
--- a/netbox/users/graphql/schema.py
+++ b/netbox/users/graphql/schema.py
@@ -1,6 +1,7 @@
import graphene
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from netbox.graphql.fields import ObjectField, ObjectListField
from .types import *
from utilities.graphql_optimizer import gql_query_optimizer
@@ -17,4 +18,4 @@ class UsersQuery(graphene.ObjectType):
user_list = ObjectListField(UserType)
def resolve_user_list(root, info, **kwargs):
- return gql_query_optimizer(User.objects.all(), info)
+ return gql_query_optimizer(get_user_model().objects.all(), info)
diff --git a/netbox/users/graphql/types.py b/netbox/users/graphql/types.py
index d948686c6..4254f1791 100644
--- a/netbox/users/graphql/types.py
+++ b/netbox/users/graphql/types.py
@@ -1,4 +1,5 @@
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from graphene_django import DjangoObjectType
from users import filtersets
@@ -25,7 +26,7 @@ class GroupType(DjangoObjectType):
class UserType(DjangoObjectType):
class Meta:
- model = User
+ model = get_user_model()
fields = (
'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'date_joined',
'groups',
@@ -34,4 +35,4 @@ class UserType(DjangoObjectType):
@classmethod
def get_queryset(cls, queryset, info):
- return RestrictedQuerySet(model=User).restrict(info.context.user, 'view')
+ return RestrictedQuerySet(model=get_user_model()).restrict(info.context.user, 'view')
diff --git a/netbox/users/tests/test_api.py b/netbox/users/tests/test_api.py
index 281f656d2..2de243775 100644
--- a/netbox/users/tests/test_api.py
+++ b/netbox/users/tests/test_api.py
@@ -1,4 +1,5 @@
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
@@ -7,6 +8,9 @@ from utilities.testing import APIViewTestCases, APITestCase
from utilities.utils import deepmerge
+User = get_user_model()
+
+
class AppTest(APITestCase):
def test_root(self):
diff --git a/netbox/users/tests/test_filtersets.py b/netbox/users/tests/test_filtersets.py
index 33ed7e7ba..d632687ef 100644
--- a/netbox/users/tests/test_filtersets.py
+++ b/netbox/users/tests/test_filtersets.py
@@ -1,6 +1,7 @@
import datetime
-from django.contrib.auth.models import Group, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.utils.timezone import make_aware
@@ -10,6 +11,9 @@ from users.models import ObjectPermission, Token
from utilities.testing import BaseFilterSetTests
+User = get_user_model()
+
+
class UserTestCase(TestCase, BaseFilterSetTests):
queryset = User.objects.all()
filterset = filtersets.UserFilterSet
diff --git a/netbox/users/tests/test_models.py b/netbox/users/tests/test_models.py
index 7a2337f33..791ea8fb4 100644
--- a/netbox/users/tests/test_models.py
+++ b/netbox/users/tests/test_models.py
@@ -1,7 +1,10 @@
-from django.contrib.auth.models import User
+from django.contrib.auth import get_user_model
from django.test import TestCase
+User = get_user_model()
+
+
class UserConfigTest(TestCase):
@classmethod
diff --git a/netbox/users/tests/test_preferences.py b/netbox/users/tests/test_preferences.py
index f1e947d67..203a67bdd 100644
--- a/netbox/users/tests/test_preferences.py
+++ b/netbox/users/tests/test_preferences.py
@@ -1,4 +1,4 @@
-from django.contrib.auth.models import User
+from django.contrib.auth import get_user_model
from django.test import override_settings
from django.test.client import RequestFactory
from django.urls import reverse
@@ -16,6 +16,9 @@ DEFAULT_USER_PREFERENCES = {
}
+User = get_user_model()
+
+
class UserPreferencesTest(TestCase):
user_permissions = ['dcim.view_site']
diff --git a/netbox/users/urls.py b/netbox/users/urls.py
index ed1c21c02..7cb1f3435 100644
--- a/netbox/users/urls.py
+++ b/netbox/users/urls.py
@@ -8,6 +8,7 @@ urlpatterns = [
# User
path('profile/', views.ProfileView.as_view(), name='profile'),
+ path('bookmarks/', views.BookmarkListView.as_view(), name='bookmarks'),
path('preferences/', views.UserConfigView.as_view(), name='preferences'),
path('password/', views.ChangePasswordView.as_view(), name='change_password'),
diff --git a/netbox/users/views.py b/netbox/users/views.py
index 05648e2e3..ad80fdfe5 100644
--- a/netbox/users/views.py
+++ b/netbox/users/views.py
@@ -15,10 +15,11 @@ from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import View
from social_core.backends.utils import load_backends
-from extras.models import ObjectChange
-from extras.tables import ObjectChangeTable
+from extras.models import Bookmark, ObjectChange
+from extras.tables import BookmarkTable, ObjectChangeTable
from netbox.authentication import get_auth_backend_display, get_saml_idps
from netbox.config import get_config
+from netbox.views.generic import ObjectListView
from utilities.forms import ConfirmationForm
from utilities.views import register_model_view
from .forms import LoginForm, PasswordChangeForm, TokenForm, UserConfigForm
@@ -230,6 +231,23 @@ class ChangePasswordView(LoginRequiredMixin, View):
})
+#
+# Bookmarks
+#
+
+class BookmarkListView(LoginRequiredMixin, ObjectListView):
+ table = BookmarkTable
+ template_name = 'users/bookmarks.html'
+
+ def get_queryset(self, request):
+ return Bookmark.objects.filter(user=request.user)
+
+ def get_extra_context(self, request):
+ return {
+ 'active_tab': 'bookmarks',
+ }
+
+
#
# API tokens
#
diff --git a/netbox/utilities/forms/fields/fields.py b/netbox/utilities/forms/fields/fields.py
index cb8c14d6d..c1e1e481c 100644
--- a/netbox/utilities/forms/fields/fields.py
+++ b/netbox/utilities/forms/fields/fields.py
@@ -11,13 +11,11 @@ from utilities.forms import widgets
from utilities.validators import EnhancedURLValidator
__all__ = (
- 'ChoiceField',
'ColorField',
'CommentField',
'JSONField',
'LaxURLField',
'MACAddressField',
- 'MultipleChoiceField',
'SlugField',
'TagFilterField',
)
@@ -128,24 +126,3 @@ class MACAddressField(forms.Field):
raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
return value
-
-
-#
-# Choice fields
-#
-
-class ChoiceField(forms.ChoiceField):
- """
- Previously used to override Django's built-in `ChoiceField` to use NetBox's now-obsolete `StaticSelect` widget.
- """
- # TODO: Remove in v3.6
- pass
-
-
-class MultipleChoiceField(forms.MultipleChoiceField):
- """
- Previously used to override Django's built-in `MultipleChoiceField` to use NetBox's now-obsolete
- `StaticSelectMultiple` widget.
- """
- # TODO: Remove in v3.6
- pass
diff --git a/netbox/utilities/templates/buttons/bookmark.html b/netbox/utilities/templates/buttons/bookmark.html
new file mode 100644
index 000000000..b11d1e82e
--- /dev/null
+++ b/netbox/utilities/templates/buttons/bookmark.html
@@ -0,0 +1,15 @@
+
diff --git a/netbox/utilities/templatetags/buttons.py b/netbox/utilities/templatetags/buttons.py
index 1556b29a0..828af3b43 100644
--- a/netbox/utilities/templatetags/buttons.py
+++ b/netbox/utilities/templatetags/buttons.py
@@ -2,11 +2,12 @@ from django import template
from django.contrib.contenttypes.models import ContentType
from django.urls import NoReverseMatch, reverse
-from extras.models import ExportTemplate
+from extras.models import Bookmark, ExportTemplate
from utilities.utils import get_viewname, prepare_cloned_fields
__all__ = (
'add_button',
+ 'bookmark_button',
'bulk_delete_button',
'bulk_edit_button',
'clone_button',
@@ -24,6 +25,37 @@ register = template.Library()
# Instance buttons
#
+@register.inclusion_tag('buttons/bookmark.html', takes_context=True)
+def bookmark_button(context, instance):
+ # Check if this user has already bookmarked the object
+ content_type = ContentType.objects.get_for_model(instance)
+ bookmark = Bookmark.objects.filter(
+ object_type=content_type,
+ object_id=instance.pk,
+ user=context['request'].user
+ ).first()
+
+ # Compile form URL & data
+ if bookmark:
+ form_url = reverse('extras:bookmark_delete', kwargs={'pk': bookmark.pk})
+ form_data = {
+ 'confirm': 'true',
+ }
+ else:
+ form_url = reverse('extras:bookmark_add')
+ form_data = {
+ 'object_type': content_type.pk,
+ 'object_id': instance.pk,
+ }
+
+ return {
+ 'bookmark': bookmark,
+ 'form_url': form_url,
+ 'form_data': form_data,
+ 'return_url': instance.get_absolute_url(),
+ }
+
+
@register.inclusion_tag('buttons/clone.html')
def clone_button(instance):
url = reverse(get_viewname(instance, 'add'))
diff --git a/netbox/utilities/testing/api.py b/netbox/utilities/testing/api.py
index 7f24c86b8..8cfe1cdd7 100644
--- a/netbox/utilities/testing/api.py
+++ b/netbox/utilities/testing/api.py
@@ -2,7 +2,7 @@ import inspect
import json
from django.conf import settings
-from django.contrib.auth.models import User
+from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from django.test import override_settings
@@ -26,6 +26,9 @@ __all__ = (
)
+User = get_user_model()
+
+
#
# REST/GraphQL API Tests
#
diff --git a/netbox/utilities/testing/base.py b/netbox/utilities/testing/base.py
index 04ceca1e2..76a9fac06 100644
--- a/netbox/utilities/testing/base.py
+++ b/netbox/utilities/testing/base.py
@@ -1,6 +1,6 @@
import json
-from django.contrib.auth.models import User
+from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import FieldDoesNotExist
@@ -27,7 +27,7 @@ class TestCase(_TestCase):
def setUp(self):
# Create the test user and assign permissions
- self.user = User.objects.create_user(username='testuser')
+ self.user = get_user_model().objects.create_user(username='testuser')
self.add_permissions(*self.user_permissions)
# Initialize the test client
diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py
index 52ccd002d..87fc3319c 100644
--- a/netbox/utilities/testing/utils.py
+++ b/netbox/utilities/testing/utils.py
@@ -2,7 +2,8 @@ import logging
import re
from contextlib import contextmanager
-from django.contrib.auth.models import Permission, User
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Permission
from django.utils.text import slugify
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
@@ -63,7 +64,7 @@ def create_test_user(username='testuser', permissions=None):
"""
Create a User with the given permissions.
"""
- user = User.objects.create_user(username=username)
+ user = get_user_model().objects.create_user(username=username)
if permissions is None:
permissions = ()
for perm_name in permissions:
diff --git a/requirements.txt b/requirements.txt
index df729d30f..2ffcd852b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
bleach==6.0.0
-boto3==1.27.1
-Django==4.1.10
+boto3==1.26.156
+Django==4.2.2
django-cors-headers==4.1.0
django-debug-toolbar==4.1.0
django-filter==23.2
@@ -11,25 +11,25 @@ django-prometheus==2.3.1
django-redis==5.3.0
django-rich==1.6.0
django-rq==2.8.1
-django-tables2==2.6.0
+django-tables2==2.5.3
django-taggit==4.0.0
django-timezone-field==5.1
djangorestframework==3.14.0
-drf-spectacular==0.26.3
-drf-spectacular-sidecar==2023.7.1
+drf-spectacular==0.26.2
+drf-spectacular-sidecar==2023.6.1
dulwich==0.21.5
feedparser==6.0.10
graphene-django==3.0.0
gunicorn==20.1.0
Jinja2==3.1.2
Markdown==3.3.7
-mkdocs-material==9.1.18
+mkdocs-material==9.1.16
mkdocstrings[python-legacy]==0.22.0
netaddr==0.8.0
-Pillow==10.0.0
-psycopg2-binary==2.9.6
+Pillow==9.5.0
+psycopg[binary,pool]==3.1.9
PyYAML==6.0
-sentry-sdk==1.27.1
+sentry-sdk==1.25.1
social-auth-app-django==5.2.0
social-auth-core[openidconnect]==4.4.2
svgwrite==1.4.3
|