mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-24 20:39:59 -06:00
Introduce OwnerGroup model
This commit is contained in:
6108
contrib/openapi.json
6108
contrib/openapi.json
File diff suppressed because it is too large
Load Diff
@@ -468,6 +468,7 @@ ADMIN_MENU = Menu(
|
|||||||
MenuGroup(
|
MenuGroup(
|
||||||
label=_('Ownership'),
|
label=_('Ownership'),
|
||||||
items=(
|
items=(
|
||||||
|
get_model_item('users', 'ownergroup', _('Owner Groups')),
|
||||||
get_model_item('users', 'owner', _('Owners')),
|
get_model_item('users', 'owner', _('Owners')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
{% extends 'generic/object.html' %}
|
{% extends 'generic/object.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% if object.group %}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'users:owner_list' %}?group_id={{ object.group_id }}">{{ object.group }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block subtitle %}{% endblock %}
|
{% block subtitle %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -13,6 +22,10 @@
|
|||||||
<th scope="row">{% trans "Name" %}</th>
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
<td>{{ object.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Group" %}</th>
|
||||||
|
<td>{{ object.group|linkify|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
@@ -22,7 +35,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Groups" %}</h2>
|
<h2 class="card-header">{% trans "Groups" %}</h2>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
{% for group in object.groups.all %}
|
{% for group in object.user_groups.all %}
|
||||||
<a href="{% url 'users:group' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
|
<a href="{% url 'users:group' pk=group.pk %}" class="list-group-item list-group-item-action">{{ group }}</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
||||||
|
|||||||
38
netbox/templates/users/ownergroup.html
Normal file
38
netbox/templates/users/ownergroup.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% block subtitle %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Group" %}</h2>
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Members" %}</h2>
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
{% for owner in object.members.all %}
|
||||||
|
<a href="{% url 'users:owner' pk=user.pk %}" class="list-group-item list-group-item-action">{{ owner }}</a>
|
||||||
|
{% empty %}
|
||||||
|
<div class="list-group-item text-muted">{% trans "None" %}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,15 +1,30 @@
|
|||||||
from netbox.api.fields import SerializedPKRelatedField
|
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
|
||||||
from netbox.api.serializers import ValidatedModelSerializer
|
from netbox.api.serializers import ValidatedModelSerializer
|
||||||
from users.models import Group, Owner, User
|
from users.models import Group, Owner, OwnerGroup, User
|
||||||
from .users import GroupSerializer, UserSerializer
|
from .users import GroupSerializer, UserSerializer
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'OwnerGroupSerializer',
|
||||||
'OwnerSerializer',
|
'OwnerSerializer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerGroupSerializer(ValidatedModelSerializer):
|
||||||
|
# Related object counts
|
||||||
|
member_count = RelatedObjectCountField('members')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OwnerGroup
|
||||||
|
fields = ('id', 'url', 'display_url', 'display', 'name', 'description', 'member_count')
|
||||||
|
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||||
|
|
||||||
|
|
||||||
class OwnerSerializer(ValidatedModelSerializer):
|
class OwnerSerializer(ValidatedModelSerializer):
|
||||||
groups = SerializedPKRelatedField(
|
group = OwnerGroupSerializer(
|
||||||
|
nested=True,
|
||||||
|
allow_null=True,
|
||||||
|
)
|
||||||
|
user_groups = SerializedPKRelatedField(
|
||||||
queryset=Group.objects.all(),
|
queryset=Group.objects.all(),
|
||||||
serializer=GroupSerializer,
|
serializer=GroupSerializer,
|
||||||
nested=True,
|
nested=True,
|
||||||
@@ -26,5 +41,5 @@ class OwnerSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Owner
|
model = Owner
|
||||||
fields = ('id', 'url', 'display_url', 'display', 'name', 'description', 'groups', 'users')
|
fields = ('id', 'url', 'display_url', 'display', 'name', 'group', 'description', 'user_groups', 'users')
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ router.register('users', views.UserViewSet)
|
|||||||
router.register('groups', views.GroupViewSet)
|
router.register('groups', views.GroupViewSet)
|
||||||
router.register('tokens', views.TokenViewSet)
|
router.register('tokens', views.TokenViewSet)
|
||||||
router.register('permissions', views.ObjectPermissionViewSet)
|
router.register('permissions', views.ObjectPermissionViewSet)
|
||||||
|
router.register('owner-groups', views.OwnerGroupViewSet)
|
||||||
router.register('owners', views.OwnerViewSet)
|
router.register('owners', views.OwnerViewSet)
|
||||||
router.register('config', views.UserConfigViewSet, basename='userconfig')
|
router.register('config', views.UserConfigViewSet, basename='userconfig')
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from rest_framework.viewsets import ViewSet
|
|||||||
|
|
||||||
from netbox.api.viewsets import NetBoxModelViewSet
|
from netbox.api.viewsets import NetBoxModelViewSet
|
||||||
from users import filtersets
|
from users import filtersets
|
||||||
from users.models import Group, ObjectPermission, Owner, Token, User, UserConfig
|
from users.models import Group, ObjectPermission, Owner, OwnerGroup, Token, User, UserConfig
|
||||||
from utilities.data import deepmerge
|
from utilities.data import deepmerge
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from . import serializers
|
from . import serializers
|
||||||
@@ -92,6 +92,12 @@ class ObjectPermissionViewSet(NetBoxModelViewSet):
|
|||||||
# Owners
|
# Owners
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class OwnerGroupViewSet(NetBoxModelViewSet):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
serializer_class = serializers.OwnerGroupSerializer
|
||||||
|
filterset_class = filtersets.OwnerGroupFilterSet
|
||||||
|
|
||||||
|
|
||||||
class OwnerViewSet(NetBoxModelViewSet):
|
class OwnerViewSet(NetBoxModelViewSet):
|
||||||
queryset = Owner.objects.all()
|
queryset = Owner.objects.all()
|
||||||
serializer_class = serializers.OwnerSerializer
|
serializer_class = serializers.OwnerSerializer
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ from django.utils.translation import gettext as _
|
|||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
from extras.models import NotificationGroup
|
from extras.models import NotificationGroup
|
||||||
from netbox.filtersets import BaseFilterSet
|
from netbox.filtersets import BaseFilterSet
|
||||||
from users.models import Group, ObjectPermission, Owner, Token, User
|
from users.models import Group, ObjectPermission, Owner, OwnerGroup, Token, User
|
||||||
from utilities.filters import ContentTypeFilter
|
from utilities.filters import ContentTypeFilter
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupFilterSet',
|
'GroupFilterSet',
|
||||||
'ObjectPermissionFilterSet',
|
'ObjectPermissionFilterSet',
|
||||||
'OwnerFilterSet',
|
'OwnerFilterSet',
|
||||||
|
'OwnerGroupFilterSet',
|
||||||
'TokenFilterSet',
|
'TokenFilterSet',
|
||||||
'UserFilterSet',
|
'UserFilterSet',
|
||||||
)
|
)
|
||||||
@@ -246,22 +247,51 @@ class ObjectPermissionFilterSet(BaseFilterSet):
|
|||||||
return queryset.exclude(actions__contains=[action])
|
return queryset.exclude(actions__contains=[action])
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerGroupFilterSet(BaseFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label=_('Search'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OwnerGroup
|
||||||
|
fields = ('id', 'name', 'description')
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(name__icontains=value) |
|
||||||
|
Q(description__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OwnerFilterSet(BaseFilterSet):
|
class OwnerFilterSet(BaseFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
group_id = django_filters.ModelMultipleChoiceFilter(
|
group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='groups',
|
queryset=OwnerGroup.objects.all(),
|
||||||
queryset=Group.objects.all(),
|
|
||||||
label=_('Group (ID)'),
|
label=_('Group (ID)'),
|
||||||
)
|
)
|
||||||
group = django_filters.ModelMultipleChoiceFilter(
|
group = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='groups__name',
|
field_name='group__name',
|
||||||
queryset=Group.objects.all(),
|
queryset=OwnerGroup.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
label=_('Group (name)'),
|
label=_('Group (name)'),
|
||||||
)
|
)
|
||||||
|
user_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='user_groups',
|
||||||
|
queryset=Group.objects.all(),
|
||||||
|
label=_('User group (ID)'),
|
||||||
|
)
|
||||||
|
user_group = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='user_groups__name',
|
||||||
|
queryset=Group.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('User group (name)'),
|
||||||
|
)
|
||||||
user_id = django_filters.ModelMultipleChoiceFilter(
|
user_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='users',
|
field_name='users',
|
||||||
queryset=User.objects.all(),
|
queryset=User.objects.all(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from ipam.formfields import IPNetworkFormField
|
|||||||
from ipam.validators import prefix_validator
|
from ipam.validators import prefix_validator
|
||||||
from users.models import *
|
from users.models import *
|
||||||
from utilities.forms import BulkEditForm
|
from utilities.forms import BulkEditForm
|
||||||
|
from utilities.forms.fields import DynamicModelChoiceField
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, DateTimePicker
|
from utilities.forms.widgets import BulkEditNullBooleanSelect, DateTimePicker
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ __all__ = (
|
|||||||
'GroupBulkEditForm',
|
'GroupBulkEditForm',
|
||||||
'ObjectPermissionBulkEditForm',
|
'ObjectPermissionBulkEditForm',
|
||||||
'OwnerBulkEditForm',
|
'OwnerBulkEditForm',
|
||||||
|
'OwnerGroupBulkEditForm',
|
||||||
'UserBulkEditForm',
|
'UserBulkEditForm',
|
||||||
'TokenBulkEditForm',
|
'TokenBulkEditForm',
|
||||||
)
|
)
|
||||||
@@ -127,11 +129,34 @@ class TokenBulkEditForm(BulkEditForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerGroupBulkEditForm(BulkEditForm):
|
||||||
|
pk = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=OwnerGroup.objects.all(),
|
||||||
|
widget=forms.MultipleHiddenInput
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
label=_('Description'),
|
||||||
|
max_length=200,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
model = OwnerGroup
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('description',),
|
||||||
|
)
|
||||||
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
|
|
||||||
class OwnerBulkEditForm(BulkEditForm):
|
class OwnerBulkEditForm(BulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
pk = forms.ModelMultipleChoiceField(
|
||||||
queryset=Owner.objects.all(),
|
queryset=Owner.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput
|
widget=forms.MultipleHiddenInput
|
||||||
)
|
)
|
||||||
|
group = DynamicModelChoiceField(
|
||||||
|
label=_('Group'),
|
||||||
|
queryset=OwnerGroup.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
label=_('Description'),
|
||||||
max_length=200,
|
max_length=200,
|
||||||
@@ -140,6 +165,6 @@ class OwnerBulkEditForm(BulkEditForm):
|
|||||||
|
|
||||||
model = Owner
|
model = Owner
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('description',),
|
FieldSet('group', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description',)
|
nullable_fields = ('group', 'description',)
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ from django.utils.translation import gettext as _
|
|||||||
from users.models import *
|
from users.models import *
|
||||||
from users.choices import TokenVersionChoices
|
from users.choices import TokenVersionChoices
|
||||||
from utilities.forms import CSVModelForm
|
from utilities.forms import CSVModelForm
|
||||||
from utilities.forms.fields import CSVModelMultipleChoiceField
|
from utilities.forms.fields import CSVModelChoiceField, CSVModelMultipleChoiceField
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupImportForm',
|
'GroupImportForm',
|
||||||
|
'OwnerGroupImportForm',
|
||||||
'OwnerImportForm',
|
'OwnerImportForm',
|
||||||
'UserImportForm',
|
'UserImportForm',
|
||||||
'TokenImportForm',
|
'TokenImportForm',
|
||||||
@@ -54,8 +55,22 @@ class TokenImportForm(CSVModelForm):
|
|||||||
fields = ('user', 'version', 'token', 'write_enabled', 'expires', 'description',)
|
fields = ('user', 'version', 'token', 'write_enabled', 'expires', 'description',)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerGroupImportForm(CSVModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OwnerGroup
|
||||||
|
fields = (
|
||||||
|
'name', 'description',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OwnerImportForm(CSVModelForm):
|
class OwnerImportForm(CSVModelForm):
|
||||||
groups = CSVModelMultipleChoiceField(
|
group = CSVModelChoiceField(
|
||||||
|
queryset=OwnerGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
to_field_name='name',
|
||||||
|
)
|
||||||
|
user_groups = CSVModelMultipleChoiceField(
|
||||||
queryset=Group.objects.all(),
|
queryset=Group.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
@@ -69,5 +84,5 @@ class OwnerImportForm(CSVModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Owner
|
model = Owner
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'description', 'groups', 'users',
|
'group', 'name', 'description', 'user_groups', 'users',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from netbox.forms.mixins import SavedFiltersMixin
|
from netbox.forms.mixins import SavedFiltersMixin
|
||||||
from users.choices import TokenVersionChoices
|
from users.choices import TokenVersionChoices
|
||||||
from users.models import Group, ObjectPermission, Owner, Token, User
|
from users.models import Group, ObjectPermission, Owner, OwnerGroup, Token, User
|
||||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
|
||||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
@@ -15,6 +15,7 @@ __all__ = (
|
|||||||
'GroupFilterForm',
|
'GroupFilterForm',
|
||||||
'ObjectPermissionFilterForm',
|
'ObjectPermissionFilterForm',
|
||||||
'OwnerFilterForm',
|
'OwnerFilterForm',
|
||||||
|
'OwnerGroupFilterForm',
|
||||||
'TokenFilterForm',
|
'TokenFilterForm',
|
||||||
'UserFilterForm',
|
'UserFilterForm',
|
||||||
)
|
)
|
||||||
@@ -143,19 +144,32 @@ class TokenFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerGroupFilterForm(NetBoxModelFilterSetForm):
|
||||||
|
model = OwnerGroup
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('q', 'filter_id',),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OwnerFilterForm(NetBoxModelFilterSetForm):
|
class OwnerFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = Owner
|
model = Owner
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id',),
|
FieldSet('q', 'filter_id',),
|
||||||
FieldSet('group_id', 'user_id', name=_('Members')),
|
FieldSet('group_id', name=_('Group')),
|
||||||
|
FieldSet('user_group_id', 'user_id', name=_('Membership')),
|
||||||
)
|
)
|
||||||
group_id = DynamicModelMultipleChoiceField(
|
group_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Group.objects.all(),
|
queryset=OwnerGroup.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Group')
|
label=_('Group')
|
||||||
)
|
)
|
||||||
|
user_group_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Group.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Groups')
|
||||||
|
)
|
||||||
user_id = DynamicModelMultipleChoiceField(
|
user_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=User.objects.all(),
|
queryset=User.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('User')
|
label=_('Users')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ from users.choices import TokenVersionChoices
|
|||||||
from users.constants import *
|
from users.constants import *
|
||||||
from users.models import *
|
from users.models import *
|
||||||
from utilities.data import flatten_dict
|
from utilities.data import flatten_dict
|
||||||
from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, JSONField
|
from utilities.forms.fields import (
|
||||||
|
ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
|
||||||
|
)
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import DateTimePicker, SplitMultiSelectWidget
|
from utilities.forms.widgets import DateTimePicker, SplitMultiSelectWidget
|
||||||
from utilities.permissions import qs_filter_from_constraints
|
from utilities.permissions import qs_filter_from_constraints
|
||||||
@@ -24,6 +26,7 @@ __all__ = (
|
|||||||
'GroupForm',
|
'GroupForm',
|
||||||
'ObjectPermissionForm',
|
'ObjectPermissionForm',
|
||||||
'OwnerForm',
|
'OwnerForm',
|
||||||
|
'OwnerGroupForm',
|
||||||
'TokenForm',
|
'TokenForm',
|
||||||
'UserConfigForm',
|
'UserConfigForm',
|
||||||
'UserForm',
|
'UserForm',
|
||||||
@@ -433,16 +436,35 @@ class ObjectPermissionForm(forms.ModelForm):
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class OwnerForm(forms.ModelForm):
|
class OwnerGroupForm(forms.ModelForm):
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('name', 'description', name=_('Owner')),
|
FieldSet('name', 'description', name=_('Owner Group')),
|
||||||
FieldSet('groups', name=_('Groups')),
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OwnerGroup
|
||||||
|
fields = [
|
||||||
|
'name', 'description',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerForm(forms.ModelForm):
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('name', 'group', 'description', name=_('Owner')),
|
||||||
|
FieldSet('user_groups', name=_('Groups')),
|
||||||
FieldSet('users', name=_('Users')),
|
FieldSet('users', name=_('Users')),
|
||||||
)
|
)
|
||||||
|
group = DynamicModelChoiceField(
|
||||||
|
label=_('Group'),
|
||||||
|
queryset=OwnerGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
selector=True,
|
||||||
|
quick_add=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Owner
|
model = Owner
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'description', 'groups', 'users',
|
'name', 'group', 'description', 'user_groups', 'users',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from users import models
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupFilter',
|
'GroupFilter',
|
||||||
'OwnerFilter',
|
'OwnerFilter',
|
||||||
|
'OwnerGroupFilter',
|
||||||
'UserFilter',
|
'UserFilter',
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,5 +39,16 @@ class UserFilter(BaseObjectTypeFilterMixin):
|
|||||||
class OwnerFilter(BaseObjectTypeFilterMixin):
|
class OwnerFilter(BaseObjectTypeFilterMixin):
|
||||||
name: FilterLookup[str] | None = strawberry_django.filter_field()
|
name: FilterLookup[str] | None = strawberry_django.filter_field()
|
||||||
description: FilterLookup[str] | None = strawberry_django.filter_field()
|
description: FilterLookup[str] | None = strawberry_django.filter_field()
|
||||||
groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
|
group: Annotated['OwnerGroupFilter', strawberry.lazy('users.graphql.filters')] | None = (
|
||||||
|
strawberry_django.filter_field()
|
||||||
|
)
|
||||||
|
user_groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = (
|
||||||
|
strawberry_django.filter_field()
|
||||||
|
)
|
||||||
users: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
|
users: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.filter_type(models.OwnerGroup, lookups=True)
|
||||||
|
class OwnerGroupFilter(BaseObjectTypeFilterMixin):
|
||||||
|
name: FilterLookup[str] | None = strawberry_django.filter_field()
|
||||||
|
description: FilterLookup[str] | None = strawberry_django.filter_field()
|
||||||
|
|||||||
@@ -14,5 +14,8 @@ class UsersQuery:
|
|||||||
user: UserType = strawberry_django.field()
|
user: UserType = strawberry_django.field()
|
||||||
user_list: List[UserType] = strawberry_django.field()
|
user_list: List[UserType] = strawberry_django.field()
|
||||||
|
|
||||||
|
owner_group: OwnerGroupType = strawberry_django.field()
|
||||||
|
owner_group_list: List[OwnerGroupType] = strawberry_django.field()
|
||||||
|
|
||||||
owner: OwnerType = strawberry_django.field()
|
owner: OwnerType = strawberry_django.field()
|
||||||
owner_list: List[OwnerType] = strawberry_django.field()
|
owner_list: List[OwnerType] = strawberry_django.field()
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ from typing import List
|
|||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
|
||||||
from netbox.graphql.types import BaseObjectType
|
from netbox.graphql.types import BaseObjectType
|
||||||
from users.models import Group, Owner, User
|
from users.models import Group, Owner, OwnerGroup, User
|
||||||
from .filters import *
|
from .filters import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupType',
|
'GroupType',
|
||||||
|
'OwnerGroupType',
|
||||||
'OwnerType',
|
'OwnerType',
|
||||||
'UserType',
|
'UserType',
|
||||||
)
|
)
|
||||||
@@ -35,11 +36,21 @@ class UserType(BaseObjectType):
|
|||||||
groups: List[GroupType]
|
groups: List[GroupType]
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.type(
|
||||||
|
OwnerGroup,
|
||||||
|
fields=['id', 'name', 'description'],
|
||||||
|
filters=OwnerGroupFilter,
|
||||||
|
pagination=True
|
||||||
|
)
|
||||||
|
class OwnerGroupType(BaseObjectType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
Owner,
|
Owner,
|
||||||
fields=['id', 'name', 'description', 'groups', 'users'],
|
fields=['id', 'group', 'name', 'description', 'user_groups', 'users'],
|
||||||
filters=OwnerFilter,
|
filters=OwnerFilter,
|
||||||
pagination=True
|
pagination=True
|
||||||
)
|
)
|
||||||
class OwnerType(BaseObjectType):
|
class OwnerType(BaseObjectType):
|
||||||
pass
|
group: OwnerGroupType
|
||||||
|
|||||||
@@ -1,28 +1,51 @@
|
|||||||
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('users', '0014_users_token_v2'),
|
('users', '0014_users_token_v2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OwnerGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'owner group',
|
||||||
|
'verbose_name_plural': 'owner groups',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Owner',
|
name='Owner',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
('name', models.CharField(max_length=150, unique=True)),
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
('description', models.CharField(blank=True, max_length=200)),
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
(
|
(
|
||||||
'groups',
|
'group',
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name='members',
|
||||||
|
to='users.ownergroup',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'user_groups',
|
||||||
models.ManyToManyField(
|
models.ManyToManyField(
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name='owners',
|
related_name='owners',
|
||||||
related_query_name='owner',
|
related_query_name='owner',
|
||||||
to='users.group',
|
to='users.group',
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'users',
|
'users',
|
||||||
@@ -31,7 +54,7 @@ class Migration(migrations.Migration):
|
|||||||
related_name='owners',
|
related_name='owners',
|
||||||
related_query_name='owner',
|
related_query_name='owner',
|
||||||
to=settings.AUTH_USER_MODEL,
|
to=settings.AUTH_USER_MODEL,
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
|||||||
@@ -7,16 +7,47 @@ from utilities.querysets import RestrictedQuerySet
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Owner',
|
'Owner',
|
||||||
|
'OwnerGroup',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerGroup(AdminModel):
|
||||||
|
"""
|
||||||
|
An arbitrary grouping of Owners.
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
|
max_length=100,
|
||||||
|
unique=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
verbose_name = _('owner group')
|
||||||
|
verbose_name_plural = _('owner groups')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('users:ownergroup', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
class Owner(AdminModel):
|
class Owner(AdminModel):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
verbose_name=_('name'),
|
||||||
max_length=150,
|
max_length=100,
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
groups = models.ManyToManyField(
|
group = models.ForeignKey(
|
||||||
|
to='users.OwnerGroup',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='members',
|
||||||
|
verbose_name=_('group'),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
user_groups = models.ManyToManyField(
|
||||||
to='users.Group',
|
to='users.Group',
|
||||||
verbose_name=_('groups'),
|
verbose_name=_('groups'),
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -32,7 +63,7 @@ class Owner(AdminModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
objects = RestrictedQuerySet.as_manager()
|
||||||
clone_fields = ('groups', 'users')
|
clone_fields = ('user_groups', 'users')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import django_tables2 as tables
|
|||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import NetBoxTable, columns
|
||||||
from users.models import Group, ObjectPermission, Owner, Token, User
|
from users.models import Group, ObjectPermission, Owner, OwnerGroup, Token, User
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'GroupTable',
|
'GroupTable',
|
||||||
'ObjectPermissionTable',
|
'ObjectPermissionTable',
|
||||||
|
'OwnerGroupTable',
|
||||||
'OwnerTable',
|
'OwnerTable',
|
||||||
'TokenTable',
|
'TokenTable',
|
||||||
'UserTable',
|
'UserTable',
|
||||||
@@ -146,12 +147,33 @@ class ObjectPermissionTable(NetBoxTable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerGroupTable(NetBoxTable):
|
||||||
|
name = tables.Column(
|
||||||
|
verbose_name=_('Name'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
actions = columns.ActionsColumn(
|
||||||
|
actions=('edit', 'delete'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = OwnerGroup
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'name', 'description',
|
||||||
|
)
|
||||||
|
default_columns = ('pk', 'name', 'description')
|
||||||
|
|
||||||
|
|
||||||
class OwnerTable(NetBoxTable):
|
class OwnerTable(NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
verbose_name=_('Name'),
|
verbose_name=_('Name'),
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
groups = columns.ManyToManyColumn(
|
group = tables.Column(
|
||||||
|
verbose_name=_('Group'),
|
||||||
|
linkify=True,
|
||||||
|
)
|
||||||
|
user_groups = columns.ManyToManyColumn(
|
||||||
verbose_name=_('Groups'),
|
verbose_name=_('Groups'),
|
||||||
linkify_item=('users:group', {'pk': tables.A('pk')})
|
linkify_item=('users:group', {'pk': tables.A('pk')})
|
||||||
)
|
)
|
||||||
@@ -166,6 +188,6 @@ class OwnerTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Owner
|
model = Owner
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'description', 'groups', 'users',
|
'pk', 'id', 'name', 'group', 'description', 'user_groups', 'users',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'description', 'groups', 'users')
|
default_columns = ('pk', 'name', 'group', 'description', 'user_groups', 'users')
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ urlpatterns = [
|
|||||||
path('permissions/', include(get_model_urls('users', 'objectpermission', detail=False))),
|
path('permissions/', include(get_model_urls('users', 'objectpermission', detail=False))),
|
||||||
path('permissions/<int:pk>/', include(get_model_urls('users', 'objectpermission'))),
|
path('permissions/<int:pk>/', include(get_model_urls('users', 'objectpermission'))),
|
||||||
|
|
||||||
|
path('owner-groups/', include(get_model_urls('users', 'ownergroup', detail=False))),
|
||||||
|
path('owner-groups/<int:pk>/', include(get_model_urls('users', 'ownergroup'))),
|
||||||
|
|
||||||
path('owners/', include(get_model_urls('users', 'owner', detail=False))),
|
path('owners/', include(get_model_urls('users', 'owner', detail=False))),
|
||||||
path('owners/<int:pk>/', include(get_model_urls('users', 'owner'))),
|
path('owners/<int:pk>/', include(get_model_urls('users', 'owner'))),
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, B
|
|||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.views import GetRelatedModelsMixin, register_model_view
|
from utilities.views import GetRelatedModelsMixin, register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .models import Group, User, ObjectPermission, Owner, Token
|
from .models import Group, User, ObjectPermission, Owner, OwnerGroup, Token
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -233,6 +233,67 @@ class ObjectPermissionBulkDeleteView(generic.BulkDeleteView):
|
|||||||
table = tables.ObjectPermissionTable
|
table = tables.ObjectPermissionTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Owner groups
|
||||||
|
#
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup, 'list', path='', detail=False)
|
||||||
|
class OwnerGroupListView(generic.ObjectListView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
filterset = filtersets.OwnerGroupFilterSet
|
||||||
|
filterset_form = forms.OwnerGroupFilterForm
|
||||||
|
table = tables.OwnerGroupTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup)
|
||||||
|
class OwnerGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
template_name = 'users/ownergroup.html'
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
return {
|
||||||
|
'related_models': self.get_related_models(request, instance),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup, 'add', detail=False)
|
||||||
|
@register_model_view(OwnerGroup, 'edit')
|
||||||
|
class OwnerGroupEditView(generic.ObjectEditView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
form = forms.OwnerGroupForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup, 'delete')
|
||||||
|
class OwnerGroupDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup, 'bulk_import', path='import', detail=False)
|
||||||
|
class OwnerGroupBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
model_form = forms.OwnerGroupImportForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup, 'bulk_edit', path='edit', detail=False)
|
||||||
|
class OwnerGroupBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
filterset = filtersets.OwnerGroupFilterSet
|
||||||
|
table = tables.OwnerGroupTable
|
||||||
|
form = forms.OwnerGroupBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup, 'bulk_rename', path='rename', detail=False)
|
||||||
|
class OwnerGroupBulkRenameView(generic.BulkRenameView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(OwnerGroup, 'bulk_delete', path='delete', detail=False)
|
||||||
|
class OwnerGroupBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = OwnerGroup.objects.all()
|
||||||
|
filterset = filtersets.OwnerGroupFilterSet
|
||||||
|
table = tables.OwnerGroupTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Owners
|
# Owners
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user