Introduce OwnerGroup model
Some checks are pending
CI / build (20.x, 3.12) (push) Waiting to run
CI / build (20.x, 3.13) (push) Waiting to run

This commit is contained in:
Jeremy Stretch
2025-10-22 16:34:27 -04:00
parent 3ca2a18a3f
commit 1a6ea31538
20 changed files with 6486 additions and 52 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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')),
), ),
), ),

View File

@@ -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>

View 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 %}

View File

@@ -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')

View File

@@ -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')

View File

@@ -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

View File

@@ -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(),

View File

@@ -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',)

View File

@@ -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',
) )

View File

@@ -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')
) )

View File

@@ -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',
] ]

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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={

View File

@@ -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',)

View File

@@ -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')

View File

@@ -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'))),

View File

@@ -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
# #