diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
index 7d2da2996..da58b0dd6 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -469,6 +469,7 @@ EXEMPT_EXCLUDE_MODELS = (
('auth', 'group'),
('auth', 'user'),
('users', 'objectpermission'),
+ ('users', 'token'),
)
# All URLs starting with a string listed here are exempt from login enforcement
diff --git a/netbox/templates/inc/profile_button.html b/netbox/templates/inc/profile_button.html
index 932b91275..a5d8cef61 100644
--- a/netbox/templates/inc/profile_button.html
+++ b/netbox/templates/inc/profile_button.html
@@ -34,7 +34,7 @@
diff --git a/netbox/templates/users/account/base.html b/netbox/templates/users/account/base.html
index f492f89ec..9ac61bced 100644
--- a/netbox/templates/users/account/base.html
+++ b/netbox/templates/users/account/base.html
@@ -18,7 +18,7 @@
{% endif %}
- {% trans "API Tokens" %}
+ {% trans "API Tokens" %}
{% endblock %}
diff --git a/netbox/users/filtersets.py b/netbox/users/filtersets.py
index 100b2ea03..0f590e012 100644
--- a/netbox/users/filtersets.py
+++ b/netbox/users/filtersets.py
@@ -12,7 +12,6 @@ __all__ = (
'ObjectPermissionFilterSet',
'TokenFilterSet',
'UserFilterSet',
- 'UserTokenFilterSet',
)
@@ -112,54 +111,6 @@ class TokenFilterSet(BaseFilterSet):
)
-class UserTokenFilterSet(BaseFilterSet):
- q = django_filters.CharFilter(
- method='search',
- label=_('Search'),
- )
- user_id = django_filters.ModelMultipleChoiceFilter(
- field_name='user',
- queryset=get_user_model().objects.all(),
- label=_('User'),
- )
- user = django_filters.ModelMultipleChoiceFilter(
- field_name='user__username',
- queryset=get_user_model().objects.all(),
- to_field_name='username',
- label=_('User (name)'),
- )
- created = django_filters.DateTimeFilter()
- created__gte = django_filters.DateTimeFilter(
- field_name='created',
- lookup_expr='gte'
- )
- created__lte = django_filters.DateTimeFilter(
- field_name='created',
- lookup_expr='lte'
- )
- expires = django_filters.DateTimeFilter()
- expires__gte = django_filters.DateTimeFilter(
- field_name='expires',
- lookup_expr='gte'
- )
- expires__lte = django_filters.DateTimeFilter(
- field_name='expires',
- lookup_expr='lte'
- )
-
- class Meta:
- model = Token
- fields = ['id', 'key', 'write_enabled', 'description']
-
- def search(self, queryset, name, value):
- if not value.strip():
- return queryset
- return queryset.filter(
- Q(user__username__icontains=value) |
- Q(description__icontains=value)
- )
-
-
class ObjectPermissionFilterSet(BaseFilterSet):
q = django_filters.CharFilter(
method='search',
diff --git a/netbox/users/forms/bulk_edit.py b/netbox/users/forms/bulk_edit.py
index 2eaac4ef9..50f31ae04 100644
--- a/netbox/users/forms/bulk_edit.py
+++ b/netbox/users/forms/bulk_edit.py
@@ -8,7 +8,7 @@ from utilities.forms.widgets import BulkEditNullBooleanSelect
__all__ = (
'ObjectPermissionBulkEditForm',
'UserBulkEditForm',
- 'UserTokenBulkEditForm',
+ 'TokenBulkEditForm',
)
@@ -73,7 +73,7 @@ class ObjectPermissionBulkEditForm(BootstrapMixin, forms.Form):
nullable_fields = ('description',)
-class UserTokenBulkEditForm(BulkEditForm):
+class TokenBulkEditForm(BulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=Token.objects.all(),
widget=forms.MultipleHiddenInput
diff --git a/netbox/users/forms/bulk_import.py b/netbox/users/forms/bulk_import.py
index c50ab463a..dc0185403 100644
--- a/netbox/users/forms/bulk_import.py
+++ b/netbox/users/forms/bulk_import.py
@@ -7,7 +7,7 @@ from utilities.forms import CSVModelForm
__all__ = (
'GroupImportForm',
'UserImportForm',
- 'UserTokenImportForm',
+ 'TokenImportForm',
)
@@ -36,7 +36,7 @@ class UserImportForm(CSVModelForm):
return super().save(*args, **kwargs)
-class UserTokenImportForm(CSVModelForm):
+class TokenImportForm(CSVModelForm):
key = forms.CharField(
label=_('Key'), required=False, help_text=_("If no key is provided, one will be generated automatically.")
)
diff --git a/netbox/users/forms/filtersets.py b/netbox/users/forms/filtersets.py
index 121a7ab0b..2232a7986 100644
--- a/netbox/users/forms/filtersets.py
+++ b/netbox/users/forms/filtersets.py
@@ -16,7 +16,7 @@ __all__ = (
'GroupFilterForm',
'ObjectPermissionFilterForm',
'UserFilterForm',
- 'UserTokenFilterForm',
+ 'TokenFilterForm',
)
@@ -116,7 +116,7 @@ class ObjectPermissionFilterForm(NetBoxModelFilterSetForm):
)
-class UserTokenFilterForm(SavedFiltersMixin, FilterForm):
+class TokenFilterForm(SavedFiltersMixin, FilterForm):
model = Token
fieldsets = (
(None, ('q', 'filter_id',)),
diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py
index ba0b11c5c..6ca050110 100644
--- a/netbox/users/forms/model_forms.py
+++ b/netbox/users/forms/model_forms.py
@@ -20,12 +20,13 @@ from utilities.permissions import qs_filter_from_constraints
from utilities.utils import flatten_dict
__all__ = (
+ 'UserTokenForm',
'GroupForm',
'ObjectPermissionForm',
'TokenForm',
'UserConfigForm',
'UserForm',
- 'UserTokenForm',
+ 'TokenForm',
)
@@ -108,9 +109,11 @@ class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMe
]
-class TokenForm(BootstrapMixin, forms.ModelForm):
+class UserTokenForm(BootstrapMixin, forms.ModelForm):
key = forms.CharField(
- label=_('Key'), required=False, help_text=_("If no key is provided, one will be generated automatically.")
+ label=_('Key'),
+ required=False,
+ help_text=_("If no key is provided, one will be generated automatically.")
)
allowed_ips = SimpleArrayField(
base_field=IPNetworkFormField(validators=[prefix_validator]),
@@ -139,25 +142,13 @@ class TokenForm(BootstrapMixin, forms.ModelForm):
del self.fields['key']
-class UserTokenForm(BootstrapMixin, forms.ModelForm):
- key = forms.CharField(
- label=_('Key'), required=False, help_text=_("If no key is provided, one will be generated automatically.")
- )
+class TokenForm(UserTokenForm):
user = forms.ModelChoiceField(
queryset=get_user_model().objects.order_by(
'username'
),
required=False
)
- allowed_ips = SimpleArrayField(
- base_field=IPNetworkFormField(validators=[prefix_validator]),
- required=False,
- label=_('Allowed IPs'),
- help_text=_(
- 'Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. '
- 'Example:
10.1.1.0/24,192.168.10.16/32,2001:db8:1::/64
'
- ),
- )
class Meta:
model = Token
@@ -168,13 +159,6 @@ class UserTokenForm(BootstrapMixin, forms.ModelForm):
'expires': DateTimePicker(),
}
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- # Omit the key field if token retrieval is not permitted
- if self.instance.pk and not settings.ALLOW_TOKEN_RETRIEVAL:
- del self.fields['key']
-
class UserForm(BootstrapMixin, forms.ModelForm):
password = forms.CharField(
diff --git a/netbox/users/tables.py b/netbox/users/tables.py
index 6ba1b2add..86ca838e0 100644
--- a/netbox/users/tables.py
+++ b/netbox/users/tables.py
@@ -8,7 +8,6 @@ __all__ = (
'ObjectPermissionTable',
'TokenTable',
'UserTable',
- 'UserTokenTable',
)
@@ -32,31 +31,6 @@ class TokenActionsColumn(columns.ActionsColumn):
class TokenTable(NetBoxTable):
- key = columns.TemplateColumn(
- template_code=TOKEN
- )
- write_enabled = columns.BooleanColumn(
- verbose_name='Write'
- )
- created = columns.DateColumn()
- expired = columns.DateColumn()
- last_used = columns.DateTimeColumn()
- allowed_ips = columns.TemplateColumn(
- template_code=ALLOWED_IPS
- )
- actions = TokenActionsColumn(
- actions=('edit', 'delete'),
- extra_buttons=COPY_BUTTON
- )
-
- class Meta(NetBoxTable.Meta):
- model = Token
- fields = (
- 'pk', 'description', 'key', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips',
- )
-
-
-class UserTokenTable(NetBoxTable):
key = columns.TemplateColumn(
verbose_name='Key',
template_code=TOKEN,
diff --git a/netbox/users/urls.py b/netbox/users/urls.py
index bb21f72c1..496201b92 100644
--- a/netbox/users/urls.py
+++ b/netbox/users/urls.py
@@ -11,16 +11,17 @@ urlpatterns = [
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'),
- path('api-tokens/', views.TokenListView.as_view(), name='token_list'),
- path('api-tokens/add/', views.TokenEditView.as_view(), name='token_add'),
- path('api-tokens/
/', include(get_model_urls('users', 'token'))),
+ path('api-tokens/', views.UserTokenListView.as_view(), name='usertoken_list'),
+ path('api-tokens/add/', views.UserTokenEditView.as_view(), name='usertoken_add'),
+ path('api-tokens//edit/', views.UserTokenEditView.as_view(), name='usertoken_edit'),
+ path('api-tokens//delete/', views.UserTokenDeleteView.as_view(), name='usertoken_delete'),
# Tokens
- path('tokens/', views.UserTokenListView.as_view(), name='token_list'),
- path('tokens/add/', views.UserTokenEditView.as_view(), name='token_add'),
- path('tokens/import/', views.UserTokenBulkImportView.as_view(), name='token_import'),
- path('tokens/edit/', views.UserTokenBulkEditView.as_view(), name='token_bulk_edit'),
- path('tokens/delete/', views.UserTokenBulkDeleteView.as_view(), name='token_bulk_delete'),
+ path('tokens/', views.TokenListView.as_view(), name='token_list'),
+ path('tokens/add/', views.TokenEditView.as_view(), name='token_add'),
+ path('tokens/import/', views.TokenBulkImportView.as_view(), name='token_import'),
+ path('tokens/edit/', views.TokenBulkEditView.as_view(), name='token_bulk_edit'),
+ path('tokens/delete/', views.TokenBulkDeleteView.as_view(), name='token_bulk_delete'),
path('tokens//', include(get_model_urls('users', 'token'))),
# Users
diff --git a/netbox/users/views.py b/netbox/users/views.py
index f77e8503d..14c8a7d5c 100644
--- a/netbox/users/views.py
+++ b/netbox/users/views.py
@@ -252,7 +252,7 @@ class BookmarkListView(LoginRequiredMixin, generic.ObjectListView):
# API tokens
#
-class TokenListView(LoginRequiredMixin, View):
+class UserTokenListView(LoginRequiredMixin, View):
def get(self, request):
@@ -267,8 +267,7 @@ class TokenListView(LoginRequiredMixin, View):
})
-@register_model_view(Token, 'edit')
-class TokenEditView(LoginRequiredMixin, View):
+class UserTokenEditView(LoginRequiredMixin, View):
def get(self, request, pk=None):
@@ -277,7 +276,7 @@ class TokenEditView(LoginRequiredMixin, View):
else:
token = Token(user=request.user)
- form = forms.TokenForm(instance=token)
+ form = forms.UserTokenForm(instance=token)
return render(request, 'generic/object_edit.html', {
'object': token,
@@ -289,10 +288,10 @@ class TokenEditView(LoginRequiredMixin, View):
if pk:
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
- form = forms.TokenForm(request.POST, instance=token)
+ form = forms.UserTokenForm(request.POST, instance=token)
else:
token = Token(user=request.user)
- form = forms.TokenForm(request.POST)
+ form = forms.UserTokenForm(request.POST)
if form.is_valid():
@@ -322,8 +321,7 @@ class TokenEditView(LoginRequiredMixin, View):
})
-@register_model_view(Token, 'delete')
-class TokenDeleteView(LoginRequiredMixin, View):
+class UserTokenDeleteView(LoginRequiredMixin, View):
def get(self, request, pk):
@@ -356,18 +354,18 @@ class TokenDeleteView(LoginRequiredMixin, View):
#
-# User Token
+# Tokens
#
-class UserTokenListView(generic.ObjectListView):
+class TokenListView(generic.ObjectListView):
queryset = Token.objects.all()
- filterset = filtersets.UserTokenFilterSet
- filterset_form = forms.UserTokenFilterForm
- table = tables.UserTokenTable
+ filterset = filtersets.TokenFilterSet
+ filterset_form = forms.TokenFilterForm
+ table = tables.TokenTable
@register_model_view(Token)
-class UserTokenView(generic.ObjectView):
+class TokenView(generic.ObjectView):
queryset = Token.objects.all()
def get_extra_context(self, request, instance):
@@ -375,30 +373,30 @@ class UserTokenView(generic.ObjectView):
@register_model_view(Token, 'edit')
-class UserTokenEditView(generic.ObjectEditView):
+class TokenEditView(generic.ObjectEditView):
queryset = Token.objects.all()
- form = forms.UserTokenForm
+ form = forms.TokenForm
@register_model_view(Token, 'delete')
-class UserTokenDeleteView(generic.ObjectDeleteView):
+class TokenDeleteView(generic.ObjectDeleteView):
queryset = Token.objects.all()
-class UserTokenBulkImportView(generic.BulkImportView):
+class TokenBulkImportView(generic.BulkImportView):
queryset = Token.objects.all()
- model_form = forms.UserTokenImportForm
+ model_form = forms.TokenImportForm
-class UserTokenBulkEditView(generic.BulkEditView):
+class TokenBulkEditView(generic.BulkEditView):
queryset = Token.objects.all()
table = tables.TokenTable
- form = forms.UserTokenBulkEditForm
+ form = forms.TokenBulkEditForm
-class UserTokenBulkDeleteView(generic.BulkDeleteView):
+class TokenBulkDeleteView(generic.BulkDeleteView):
queryset = Token.objects.all()
- table = tables.UserTokenTable
+ table = tables.TokenTable
#