mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-22 13:22:24 -06:00
Merge branch 'develop-2.9' into 2006-scripts-reports-background
This commit is contained in:
@@ -110,7 +110,7 @@ class ExportTemplateViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class TagViewSet(ModelViewSet):
|
||||
queryset = Tag.restricted.annotate(
|
||||
queryset = Tag.objects.annotate(
|
||||
tagged_items=Count('extras_taggeditem_items', distinct=True)
|
||||
)
|
||||
serializer_class = serializers.TagSerializer
|
||||
|
||||
@@ -6,6 +6,7 @@ from django import get_version
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
APPS = ['circuits', 'dcim', 'extras', 'ipam', 'secrets', 'tenancy', 'users', 'virtualization']
|
||||
@@ -52,6 +53,7 @@ class Command(BaseCommand):
|
||||
pass
|
||||
|
||||
# Additional objects to include
|
||||
namespace['ContentType'] = ContentType
|
||||
namespace['User'] = User
|
||||
|
||||
# Load convenience commands
|
||||
|
||||
@@ -540,7 +540,7 @@ class ConfigContextModel(models.Model):
|
||||
|
||||
# Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
|
||||
data = OrderedDict()
|
||||
for context in ConfigContext.objects.get_for_object(self):
|
||||
for context in ConfigContext.objects.unrestricted().get_for_object(self):
|
||||
data = deepmerge(data, context.data)
|
||||
|
||||
# If the object has local config context data defined, merge it last
|
||||
|
||||
@@ -22,14 +22,10 @@ class Tag(TagBase, ChangeLoggedModel):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
restricted = RestrictedQuerySet.as_manager()
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
csv_headers = ['name', 'slug', 'color', 'description']
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('extras:tag', args=[self.slug])
|
||||
|
||||
def slugify(self, tag, i=None):
|
||||
# Allow Unicode in Tag slugs (avoids empty slugs for Tags with all-Unicode names)
|
||||
slug = slugify(tag, allow_unicode=True)
|
||||
|
||||
@@ -173,7 +173,7 @@ class ChoiceVar(ScriptVariable):
|
||||
|
||||
class ObjectVar(ScriptVariable):
|
||||
"""
|
||||
NetBox object representation. The provided QuerySet will determine the choices available.
|
||||
A single object within NetBox.
|
||||
"""
|
||||
form_field = DynamicModelChoiceField
|
||||
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
|
||||
from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, ToggleColumn
|
||||
from .models import ConfigContext, ObjectChange, JobResult, Tag, TaggedItem
|
||||
|
||||
TAG_ACTIONS = """
|
||||
<a href="{% url 'extras:tag_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Change log">
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% if perms.taggit.change_tag %}
|
||||
<a href="{% url 'extras:tag_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
{% if perms.taggit.delete_tag %}
|
||||
<a href="{% url 'extras:tag_delete' slug=record.slug %}" class="btn btn-xs btn-danger"><i class="glyphicon glyphicon-trash" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
TAGGED_ITEM = """
|
||||
{% if value.get_absolute_url %}
|
||||
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
|
||||
@@ -64,16 +52,8 @@ OBJECTCHANGE_REQUEST_ID = """
|
||||
|
||||
class TagTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn(
|
||||
viewname='extras:tag',
|
||||
args=[Accessor('slug')]
|
||||
)
|
||||
actions = tables.TemplateColumn(
|
||||
template_code=TAG_ACTIONS,
|
||||
attrs={'td': {'class': 'text-right noprint'}},
|
||||
verbose_name=''
|
||||
)
|
||||
color = ColorColumn()
|
||||
actions = ButtonsColumn(Tag, pk_field='slug')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Tag
|
||||
|
||||
@@ -10,7 +10,7 @@ from extras.models import ConfigContext, ObjectChange, Tag
|
||||
from utilities.testing import ViewTestCases, TestCase
|
||||
|
||||
|
||||
class TagTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
model = Tag
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -13,7 +13,6 @@ urlpatterns = [
|
||||
path('tags/import/', views.TagBulkImportView.as_view(), name='tag_import'),
|
||||
path('tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'),
|
||||
path('tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
|
||||
path('tags/<str:slug>/', views.TagView.as_view(), name='tag'),
|
||||
path('tags/<str:slug>/edit/', views.TagEditView.as_view(), name='tag_edit'),
|
||||
path('tags/<str:slug>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
|
||||
path('tags/<str:slug>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}),
|
||||
|
||||
@@ -4,13 +4,15 @@ from django import template
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Count, Q
|
||||
from django.db.models import Count, Prefetch, Q
|
||||
from django.http import Http404, HttpResponseForbidden
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.generic import View
|
||||
from django_tables2 import RequestConfig
|
||||
|
||||
from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.paginator import EnhancedPaginator
|
||||
from utilities.utils import copy_safe_request, shallow_compare_dict
|
||||
@@ -18,6 +20,7 @@ from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
ContentTypePermissionRequiredMixin,
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup
|
||||
from . import filters, forms, tables
|
||||
from .choices import JobResultStatusChoices
|
||||
from .models import ConfigContext, ImageAttachment, ObjectChange, Report, JobResult, Script, Tag, TaggedItem
|
||||
@@ -30,7 +33,7 @@ from .scripts import get_scripts, run_script
|
||||
#
|
||||
|
||||
class TagListView(ObjectListView):
|
||||
queryset = Tag.restricted.annotate(
|
||||
queryset = Tag.objects.annotate(
|
||||
items=Count('extras_taggeditem_items', distinct=True)
|
||||
).order_by(
|
||||
'name'
|
||||
@@ -40,71 +43,39 @@ class TagListView(ObjectListView):
|
||||
table = tables.TagTable
|
||||
|
||||
|
||||
class TagView(ObjectView):
|
||||
queryset = Tag.restricted.all()
|
||||
|
||||
def get(self, request, slug):
|
||||
|
||||
tag = get_object_or_404(self.queryset, slug=slug)
|
||||
tagged_items = TaggedItem.objects.filter(
|
||||
tag=tag
|
||||
).prefetch_related(
|
||||
'content_type', 'content_object'
|
||||
)
|
||||
|
||||
# Generate a table of all items tagged with this Tag
|
||||
items_table = tables.TaggedItemTable(tagged_items)
|
||||
paginate = {
|
||||
'paginator_class': EnhancedPaginator,
|
||||
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||
}
|
||||
RequestConfig(request, paginate).configure(items_table)
|
||||
|
||||
return render(request, 'extras/tag.html', {
|
||||
'tag': tag,
|
||||
'items_count': tagged_items.count(),
|
||||
'items_table': items_table,
|
||||
})
|
||||
|
||||
|
||||
class TagEditView(ObjectEditView):
|
||||
queryset = Tag.restricted.all()
|
||||
queryset = Tag.objects.all()
|
||||
model_form = forms.TagForm
|
||||
default_return_url = 'extras:tag_list'
|
||||
template_name = 'extras/tag_edit.html'
|
||||
|
||||
|
||||
class TagDeleteView(ObjectDeleteView):
|
||||
queryset = Tag.restricted.all()
|
||||
default_return_url = 'extras:tag_list'
|
||||
queryset = Tag.objects.all()
|
||||
|
||||
|
||||
class TagBulkImportView(BulkImportView):
|
||||
queryset = Tag.restricted.all()
|
||||
queryset = Tag.objects.all()
|
||||
model_form = forms.TagCSVForm
|
||||
table = tables.TagTable
|
||||
default_return_url = 'extras:tag_list'
|
||||
|
||||
|
||||
class TagBulkEditView(BulkEditView):
|
||||
queryset = Tag.restricted.annotate(
|
||||
queryset = Tag.objects.annotate(
|
||||
items=Count('extras_taggeditem_items', distinct=True)
|
||||
).order_by(
|
||||
'name'
|
||||
)
|
||||
table = tables.TagTable
|
||||
form = forms.TagBulkEditForm
|
||||
default_return_url = 'extras:tag_list'
|
||||
|
||||
|
||||
class TagBulkDeleteView(BulkDeleteView):
|
||||
queryset = Tag.restricted.annotate(
|
||||
queryset = Tag.objects.annotate(
|
||||
items=Count('extras_taggeditem_items')
|
||||
).order_by(
|
||||
'name'
|
||||
)
|
||||
table = tables.TagTable
|
||||
default_return_url = 'extras:tag_list'
|
||||
|
||||
|
||||
#
|
||||
@@ -123,6 +94,18 @@ class ConfigContextView(ObjectView):
|
||||
queryset = ConfigContext.objects.all()
|
||||
|
||||
def get(self, request, pk):
|
||||
# Extend queryset to prefetch related objects
|
||||
self.queryset = self.queryset.prefetch_related(
|
||||
Prefetch('regions', queryset=Region.objects.restrict(request.user)),
|
||||
Prefetch('sites', queryset=Site.objects.restrict(request.user)),
|
||||
Prefetch('roles', queryset=DeviceRole.objects.restrict(request.user)),
|
||||
Prefetch('platforms', queryset=Platform.objects.restrict(request.user)),
|
||||
Prefetch('clusters', queryset=Cluster.objects.restrict(request.user)),
|
||||
Prefetch('cluster_groups', queryset=ClusterGroup.objects.restrict(request.user)),
|
||||
Prefetch('tenants', queryset=Tenant.objects.restrict(request.user)),
|
||||
Prefetch('tenant_groups', queryset=TenantGroup.objects.restrict(request.user)),
|
||||
)
|
||||
|
||||
configcontext = get_object_or_404(self.queryset, pk=pk)
|
||||
|
||||
# Determine user's preferred output format
|
||||
@@ -144,7 +127,6 @@ class ConfigContextView(ObjectView):
|
||||
class ConfigContextEditView(ObjectEditView):
|
||||
queryset = ConfigContext.objects.all()
|
||||
model_form = forms.ConfigContextForm
|
||||
default_return_url = 'extras:configcontext_list'
|
||||
template_name = 'extras/configcontext_edit.html'
|
||||
|
||||
|
||||
@@ -153,18 +135,15 @@ class ConfigContextBulkEditView(BulkEditView):
|
||||
filterset = filters.ConfigContextFilterSet
|
||||
table = tables.ConfigContextTable
|
||||
form = forms.ConfigContextBulkEditForm
|
||||
default_return_url = 'extras:configcontext_list'
|
||||
|
||||
|
||||
class ConfigContextDeleteView(ObjectDeleteView):
|
||||
queryset = ConfigContext.objects.all()
|
||||
default_return_url = 'extras:configcontext_list'
|
||||
|
||||
|
||||
class ConfigContextBulkDeleteView(BulkDeleteView):
|
||||
queryset = ConfigContext.objects.all()
|
||||
table = tables.ConfigContextTable
|
||||
default_return_url = 'extras:configcontext_list'
|
||||
|
||||
|
||||
class ObjectConfigContextView(ObjectView):
|
||||
@@ -264,9 +243,11 @@ class ObjectChangeLogView(View):
|
||||
|
||||
def get(self, request, model, **kwargs):
|
||||
|
||||
# Get object my model and kwargs (e.g. slug='foo')
|
||||
queryset = model.objects.restrict(request.user, 'view')
|
||||
obj = get_object_or_404(queryset, **kwargs)
|
||||
# Handle QuerySet restriction of parent object if needed
|
||||
if hasattr(model.objects, 'restrict'):
|
||||
obj = get_object_or_404(model.objects.restrict(request.user, 'view'), **kwargs)
|
||||
else:
|
||||
obj = get_object_or_404(model, **kwargs)
|
||||
|
||||
# Gather all changes for this object (and its related objects)
|
||||
content_type = ContentType.objects.get_for_model(model)
|
||||
@@ -299,6 +280,7 @@ class ObjectChangeLogView(View):
|
||||
|
||||
return render(request, 'extras/object_changelog.html', {
|
||||
object_var: obj,
|
||||
'instance': obj, # We'll eventually standardize on 'instance` for the object variable name
|
||||
'table': objectchanges_table,
|
||||
'base_template': base_template,
|
||||
'active_tab': 'changelog',
|
||||
|
||||
Reference in New Issue
Block a user