From cb2cb4500d4b32678051ce54f15a74243af99441 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 6 Jul 2023 12:11:04 +0700 Subject: [PATCH] 13044 move token from admin to netbox ui --- netbox/netbox/navigation/menu.py | 10 +++ netbox/users/forms/__init__.py | 4 ++ netbox/users/forms/bulk_edit.py | 22 ++++++ netbox/users/forms/bulk_import.py | 14 ++++ netbox/users/forms/filtersets.py | 12 ++++ .../users/{forms.py => forms/model_forms.py} | 2 +- netbox/users/migrations/0004_usertoken.py | 23 ++++++ netbox/users/models.py | 12 ++++ netbox/users/urls.py | 8 +++ netbox/users/views.py | 70 ++++++++++++++++--- 10 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 netbox/users/forms/__init__.py create mode 100644 netbox/users/forms/bulk_edit.py create mode 100644 netbox/users/forms/bulk_import.py create mode 100644 netbox/users/forms/filtersets.py rename netbox/users/{forms.py => forms/model_forms.py} (98%) create mode 100644 netbox/users/migrations/0004_usertoken.py diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 100de16da..252dcdfd9 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -360,6 +360,16 @@ ADMIN_MENU = Menu( ), ), ), + MenuGroup( + label=_('Users'), + items=( + MenuItem( + link='users:user_token_list', + link_text=_('Tokens'), + permissions=['users.view_token'] + ), + ), + ), ), ) diff --git a/netbox/users/forms/__init__.py b/netbox/users/forms/__init__.py new file mode 100644 index 000000000..1499f98b2 --- /dev/null +++ b/netbox/users/forms/__init__.py @@ -0,0 +1,4 @@ +from .bulk_edit import * +from .bulk_import import * +from .filtersets import * +from .model_forms import * diff --git a/netbox/users/forms/bulk_edit.py b/netbox/users/forms/bulk_edit.py new file mode 100644 index 000000000..df2b418b0 --- /dev/null +++ b/netbox/users/forms/bulk_edit.py @@ -0,0 +1,22 @@ +from django import forms +from django.utils.translation import gettext as _ + +from users.models import * +from utilities.forms import BulkEditForm, add_blank_choice + +__all__ = ( + 'TokenBulkEditForm', +) + + +class TokenBulkEditForm(BulkEditForm): + pk = forms.ModelMultipleChoiceField( + queryset=Token.objects.all(), + widget=forms.MultipleHiddenInput + ) + description = forms.CharField( + max_length=200, + required=False + ) + + nullable_fields = ('description',) diff --git a/netbox/users/forms/bulk_import.py b/netbox/users/forms/bulk_import.py new file mode 100644 index 000000000..31283bd4a --- /dev/null +++ b/netbox/users/forms/bulk_import.py @@ -0,0 +1,14 @@ +from users.models import * +from utilities.forms import CSVModelForm + + +__all__ = ( + 'TokenImportForm', +) + + +class TokenImportForm(CSVModelForm): + + class Meta: + model = Token + fields = ('description', ) diff --git a/netbox/users/forms/filtersets.py b/netbox/users/forms/filtersets.py new file mode 100644 index 000000000..70c06828d --- /dev/null +++ b/netbox/users/forms/filtersets.py @@ -0,0 +1,12 @@ +from django import forms +from extras.forms.mixins import SavedFiltersMixin +from utilities.forms import FilterForm +from users.models import Token + +__all__ = ( + 'TokenFilterForm', +) + + +class TokenFilterForm(SavedFiltersMixin, FilterForm): + model = Token diff --git a/netbox/users/forms.py b/netbox/users/forms/model_forms.py similarity index 98% rename from netbox/users/forms.py rename to netbox/users/forms/model_forms.py index 027fa5327..83ca6f269 100644 --- a/netbox/users/forms.py +++ b/netbox/users/forms/model_forms.py @@ -11,7 +11,7 @@ from netbox.preferences import PREFERENCES from utilities.forms import BootstrapMixin from utilities.forms.widgets import DateTimePicker from utilities.utils import flatten_dict -from .models import Token, UserConfig +from users.models import Token, UserConfig class LoginForm(BootstrapMixin, AuthenticationForm): diff --git a/netbox/users/migrations/0004_usertoken.py b/netbox/users/migrations/0004_usertoken.py new file mode 100644 index 000000000..482cda193 --- /dev/null +++ b/netbox/users/migrations/0004_usertoken.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.2 on 2023-07-06 05:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('users', '0003_token_allowed_ips_last_used'), + ] + + operations = [ + migrations.CreateModel( + name='UserToken', + fields=[], + options={ + 'verbose_name': 'Token', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('users.token',), + ), + ] diff --git a/netbox/users/models.py b/netbox/users/models.py index 4e7d9ca52..57d7430e8 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -272,6 +272,18 @@ class Token(models.Model): return False +class UserToken(Token): + """ + Proxy Token for NetBox admin UI + """ + class Meta: + verbose_name = 'Token' + proxy = True + + def get_absolute_url(self): + return reverse('users:usertoken', args=[self.pk]) + + # # Permissions # diff --git a/netbox/users/urls.py b/netbox/users/urls.py index 7cb1f3435..354a04e1b 100644 --- a/netbox/users/urls.py +++ b/netbox/users/urls.py @@ -17,4 +17,12 @@ urlpatterns = [ path('api-tokens/add/', views.TokenEditView.as_view(), name='token_add'), path('api-tokens//', include(get_model_urls('users', 'token'))), + # Tokens + path('user-tokens/', views.UserTokenListView.as_view(), name='user_token_list'), + path('user-tokens/add/', views.UserTokenEditView.as_view(), name='user_token_add'), + path('user-tokens/import/', views.UserTokenBulkImportView.as_view(), name='user_token_import'), + path('user-tokens/edit/', views.UserTokenBulkEditView.as_view(), name='user_token_bulk_edit'), + path('user-tokens/delete/', views.UserTokenBulkDeleteView.as_view(), name='user_token_bulk_delete'), + path('user-tokens//', include(get_model_urls('users', 'user_token'))), + ] diff --git a/netbox/users/views.py b/netbox/users/views.py index 4dcdaebab..debfb8617 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -19,11 +19,12 @@ 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 netbox.views import generic from utilities.forms import ConfirmationForm from utilities.views import register_model_view -from .forms import LoginForm, PasswordChangeForm, TokenForm, UserConfigForm -from .models import Token, UserConfig +from .filtersets import TokenFilterSet +from . import forms +from .models import Token, UserConfig, UserToken from .tables import TokenTable @@ -70,7 +71,7 @@ class LoginView(View): return auth_backends def get(self, request): - form = LoginForm(request) + form = forms.LoginForm(request) if request.user.is_authenticated: logger = logging.getLogger('netbox.auth.login') @@ -83,7 +84,7 @@ class LoginView(View): def post(self, request): logger = logging.getLogger('netbox.auth.login') - form = LoginForm(request, data=request.POST) + form = forms.LoginForm(request, data=request.POST) if form.is_valid(): logger.debug("Login form validation was successful") @@ -208,7 +209,7 @@ class ChangePasswordView(LoginRequiredMixin, View): messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.") return redirect('users:profile') - form = PasswordChangeForm(user=request.user) + form = forms.PasswordChangeForm(user=request.user) return render(request, self.template_name, { 'form': form, @@ -216,7 +217,7 @@ class ChangePasswordView(LoginRequiredMixin, View): }) def post(self, request): - form = PasswordChangeForm(user=request.user, data=request.POST) + form = forms.PasswordChangeForm(user=request.user, data=request.POST) if form.is_valid(): form.save() update_session_auth_hash(request, form.user) @@ -233,7 +234,7 @@ class ChangePasswordView(LoginRequiredMixin, View): # Bookmarks # -class BookmarkListView(LoginRequiredMixin, ObjectListView): +class BookmarkListView(LoginRequiredMixin, generic.ObjectListView): table = BookmarkTable template_name = 'users/bookmarks.html' @@ -275,7 +276,7 @@ class TokenEditView(LoginRequiredMixin, View): else: token = Token(user=request.user) - form = TokenForm(instance=token) + form = forms.TokenForm(instance=token) return render(request, 'generic/object_edit.html', { 'object': token, @@ -287,10 +288,10 @@ class TokenEditView(LoginRequiredMixin, View): if pk: token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk) - form = TokenForm(request.POST, instance=token) + form = forms.TokenForm(request.POST, instance=token) else: token = Token(user=request.user) - form = TokenForm(request.POST) + form = forms.TokenForm(request.POST) if form.is_valid(): @@ -351,3 +352,50 @@ class TokenDeleteView(LoginRequiredMixin, View): 'form': form, 'return_url': reverse('users:token_list'), }) + +# +# User Token +# + + +class UserTokenListView(generic.ObjectListView): + queryset = UserToken.objects.all() + filterset = TokenFilterSet + filterset_form = forms.TokenFilterForm + table = TokenTable + + +@register_model_view(UserToken) +class UserTokenView(generic.ObjectView): + queryset = UserToken.objects.all() + + def get_extra_context(self, request, instance): + + return {} + + +@register_model_view(UserToken, 'edit') +class UserTokenEditView(generic.ObjectEditView): + queryset = UserToken.objects.all() + form = forms.TokenForm + + +@register_model_view(UserToken, 'delete') +class UserTokenDeleteView(generic.ObjectDeleteView): + queryset = UserToken.objects.all() + + +class UserTokenBulkImportView(generic.BulkImportView): + queryset = UserToken.objects.all() + model_form = forms.TokenImportForm + + +class UserTokenBulkEditView(generic.BulkEditView): + queryset = UserToken.objects.all() + table = TokenTable + form = forms.TokenBulkEditForm + + +class UserTokenBulkDeleteView(generic.BulkDeleteView): + queryset = UserToken.objects.all() + table = TokenTable