Просмотр исходного кода

Replace ButtonsColumn with ActionsColumn

jeremystretch 4 лет назад
Родитель
Сommit
aed23d61fc

+ 20 - 41
netbox/dcim/tables/devices.py

@@ -7,7 +7,7 @@ from dcim.models import (
 )
 from tenancy.tables import TenantColumn
 from utilities.tables import (
-    ActionsColumn, BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
+    ActionsColumn, BaseTable, BooleanColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
     MarkdownColumn, TagColumn, TemplateColumn, ToggleColumn,
 )
 from .template_code import *
@@ -322,10 +322,8 @@ class DeviceConsolePortTable(ConsolePortTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=ConsolePort,
-        buttons=('edit', 'delete'),
-        prepend_template=CONSOLEPORT_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=CONSOLEPORT_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -367,10 +365,8 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=ConsoleServerPort,
-        buttons=('edit', 'delete'),
-        prepend_template=CONSOLESERVERPORT_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=CONSOLESERVERPORT_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -412,10 +408,8 @@ class DevicePowerPortTable(PowerPortTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=PowerPort,
-        buttons=('edit', 'delete'),
-        prepend_template=POWERPORT_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=POWERPORT_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -461,10 +455,8 @@ class DevicePowerOutletTable(PowerOutletTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=PowerOutlet,
-        buttons=('edit', 'delete'),
-        prepend_template=POWEROUTLET_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=POWEROUTLET_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -551,10 +543,8 @@ class DeviceInterfaceTable(InterfaceTable):
         linkify=True,
         verbose_name='LAG'
     )
-    actions = ButtonsColumn(
-        model=Interface,
-        buttons=('edit', 'delete'),
-        prepend_template=INTERFACE_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=INTERFACE_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -614,10 +604,8 @@ class DeviceFrontPortTable(FrontPortTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=FrontPort,
-        buttons=('edit', 'delete'),
-        prepend_template=FRONTPORT_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=FRONTPORT_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -662,10 +650,8 @@ class DeviceRearPortTable(RearPortTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=RearPort,
-        buttons=('edit', 'delete'),
-        prepend_template=REARPORT_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=REARPORT_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -713,10 +699,8 @@ class DeviceDeviceBayTable(DeviceBayTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=DeviceBay,
-        buttons=('edit', 'delete'),
-        prepend_template=DEVICEBAY_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=DEVICEBAY_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -749,10 +733,8 @@ class ModuleBayTable(DeviceComponentTable):
 
 
 class DeviceModuleBayTable(ModuleBayTable):
-    actions = ButtonsColumn(
-        model=DeviceBay,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULEBAY_BUTTONS
+    actions = ActionsColumn(
+        extra_buttons=MODULEBAY_BUTTONS
     )
 
     class Meta(DeviceComponentTable.Meta):
@@ -803,10 +785,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
         order_by=Accessor('_name'),
         attrs={'td': {'class': 'text-nowrap'}}
     )
-    actions = ButtonsColumn(
-        model=InventoryItem,
-        buttons=('edit', 'delete')
-    )
+    actions = ActionsColumn()
 
     class Meta(BaseTable.Meta):
         model = InventoryItem

+ 28 - 39
netbox/dcim/tables/devicetypes.py

@@ -6,8 +6,7 @@ from dcim.models import (
     InventoryItemTemplate, Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
 )
 from utilities.tables import (
-    ActionsColumn, BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
-    ToggleColumn,
+    ActionsColumn, BaseTable, BooleanColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn, ToggleColumn,
 )
 from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS
 
@@ -113,10 +112,9 @@ class ComponentTemplateTable(BaseTable):
 
 
 class ConsolePortTemplateTable(ComponentTemplateTable):
-    actions = ButtonsColumn(
-        model=ConsolePortTemplate,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -126,10 +124,9 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
 
 
 class ConsoleServerPortTemplateTable(ComponentTemplateTable):
-    actions = ButtonsColumn(
-        model=ConsoleServerPortTemplate,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -139,10 +136,9 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
 
 
 class PowerPortTemplateTable(ComponentTemplateTable):
-    actions = ButtonsColumn(
-        model=PowerPortTemplate,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -152,10 +148,9 @@ class PowerPortTemplateTable(ComponentTemplateTable):
 
 
 class PowerOutletTemplateTable(ComponentTemplateTable):
-    actions = ButtonsColumn(
-        model=PowerOutletTemplate,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -168,10 +163,9 @@ class InterfaceTemplateTable(ComponentTemplateTable):
     mgmt_only = BooleanColumn(
         verbose_name='Management Only'
     )
-    actions = ButtonsColumn(
-        model=InterfaceTemplate,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -185,10 +179,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
         verbose_name='Position'
     )
     color = ColorColumn()
-    actions = ButtonsColumn(
-        model=FrontPortTemplate,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -199,10 +192,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
 
 class RearPortTemplateTable(ComponentTemplateTable):
     color = ColorColumn()
-    actions = ButtonsColumn(
-        model=RearPortTemplate,
-        buttons=('edit', 'delete'),
-        prepend_template=MODULAR_COMPONENT_TEMPLATE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -212,9 +204,8 @@ class RearPortTemplateTable(ComponentTemplateTable):
 
 
 class ModuleBayTemplateTable(ComponentTemplateTable):
-    actions = ButtonsColumn(
-        model=ModuleBayTemplate,
-        buttons=('edit', 'delete')
+    actions = ActionsColumn(
+        sequence=('edit', 'delete')
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -224,9 +215,8 @@ class ModuleBayTemplateTable(ComponentTemplateTable):
 
 
 class DeviceBayTemplateTable(ComponentTemplateTable):
-    actions = ButtonsColumn(
-        model=DeviceBayTemplate,
-        buttons=('edit', 'delete')
+    actions = ActionsColumn(
+        sequence=('edit', 'delete')
     )
 
     class Meta(ComponentTemplateTable.Meta):
@@ -236,9 +226,8 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
 
 
 class InventoryItemTemplateTable(ComponentTemplateTable):
-    actions = ButtonsColumn(
-        model=InventoryItemTemplate,
-        buttons=('edit', 'delete')
+    actions = ActionsColumn(
+        sequence=('edit', 'delete')
     )
     role = tables.Column(
         linkify=True

+ 4 - 5
netbox/dcim/tables/sites.py

@@ -3,9 +3,9 @@ import django_tables2 as tables
 from dcim.models import Location, Region, Site, SiteGroup
 from tenancy.tables import TenantColumn
 from utilities.tables import (
-    BaseTable, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, MarkdownColumn, MPTTColumn, TagColumn, ToggleColumn,
+    ActionsColumn, BaseTable, ChoiceFieldColumn, LinkedCountColumn, MarkdownColumn, MPTTColumn, TagColumn, ToggleColumn,
 )
-from .template_code import LOCATION_ELEVATIONS
+from .template_code import LOCATION_BUTTONS
 
 __all__ = (
     'LocationTable',
@@ -127,9 +127,8 @@ class LocationTable(BaseTable):
     tags = TagColumn(
         url_name='dcim:location_list'
     )
-    actions = ButtonsColumn(
-        model=Location,
-        prepend_template=LOCATION_ELEVATIONS
+    actions = ActionsColumn(
+        extra_buttons=LOCATION_BUTTONS
     )
 
     class Meta(BaseTable.Meta):

+ 3 - 3
netbox/dcim/tables/template_code.py

@@ -87,7 +87,7 @@ POWERFEED_CABLETERMINATION = """
 <a href="{{ value.get_absolute_url }}">{{ value }}</a>
 """
 
-LOCATION_ELEVATIONS = """
+LOCATION_BUTTONS = """
 <a href="{% url 'dcim:rack_elevation_list' %}?site={{ record.site.slug }}&location_id={{ record.pk }}" class="btn btn-sm btn-primary" title="View elevations">
     <i class="mdi mdi-server"></i>
 </a>
@@ -99,8 +99,8 @@ LOCATION_ELEVATIONS = """
 
 MODULAR_COMPONENT_TEMPLATE_BUTTONS = """
 {% load helpers %}
-{% if perms.dcim.add_invnetoryitemtemplate %}
-<a href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ record.device_type.pk }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={{ request.path }}" title="Add inventory item" class="btn btn-primary btn-sm">
+{% if perms.dcim.add_inventoryitemtemplate %}
+<a href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ record.device_type_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={{ request.path }}" title="Add inventory item" class="btn btn-primary btn-sm">
   <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
 </a>
 {% endif %}

+ 5 - 6
netbox/ipam/tables/vlans.py

@@ -5,8 +5,8 @@ from django_tables2.utils import Accessor
 from dcim.models import Interface
 from tenancy.tables import TenantColumn
 from utilities.tables import (
-    ActionsColumn, BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn,
-    TagColumn, TemplateColumn, ToggleColumn,
+    ActionsColumn, BaseTable, BooleanColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn,
+    TemplateColumn, ToggleColumn,
 )
 from virtualization.models import VMInterface
 from ipam.models import *
@@ -38,7 +38,7 @@ VLAN_PREFIXES = """
 {% endfor %}
 """
 
-VLANGROUP_ADD_VLAN = """
+VLANGROUP_BUTTONS = """
 {% with next_vid=record.get_next_available_vid %}
     {% if next_vid and perms.ipam.add_vlan %}
         <a href="{% url 'ipam:vlan_add' %}?group={{ record.pk }}&vid={{ next_vid }}" title="Add VLAN" class="btn btn-sm btn-success">
@@ -77,9 +77,8 @@ class VLANGroupTable(BaseTable):
     tags = TagColumn(
         url_name='ipam:vlangroup_list'
     )
-    actions = ButtonsColumn(
-        model=VLANGroup,
-        prepend_template=VLANGROUP_ADD_VLAN
+    actions = ActionsColumn(
+        extra_buttons=VLANGROUP_BUTTONS
     )
 
     class Meta(BaseTable.Meta):

+ 24 - 65
netbox/utilities/tables/columns.py

@@ -1,9 +1,10 @@
-from collections import namedtuple
 from dataclasses import dataclass
 from typing import Optional
 
 import django_tables2 as tables
 from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
+from django.template import Context, Template
 from django.urls import reverse
 from django.utils.safestring import mark_safe
 from django_tables2.utils import Accessor
@@ -14,7 +15,6 @@ from utilities.utils import content_type_identifier, content_type_name
 __all__ = (
     'ActionsColumn',
     'BooleanColumn',
-    'ButtonsColumn',
     'ChoiceFieldColumn',
     'ColorColumn',
     'ColoredLabelColumn',
@@ -100,7 +100,14 @@ class ActionsItem:
 
 
 class ActionsColumn(tables.Column):
-    attrs = {'td': {'class': 'text-end noprint'}}
+    """
+    A dropdown menu which provides edit, delete, and changelog links for an object. Can optionally include
+    additional buttons rendered from a template string.
+
+    :param sequence: The ordered list of dropdown menu items to include
+    :param extra_buttons: A Django template string which renders additional buttons preceding the actions dropdown
+    """
+    attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
     empty_values = ()
     actions = {
         'edit': ActionsItem('Edit', 'pencil', 'change'),
@@ -108,12 +115,10 @@ class ActionsColumn(tables.Column):
         'changelog': ActionsItem('Changelog', 'history'),
     }
 
-    def __init__(self, *args, extra_actions=None, sequence=('edit', 'delete', 'changelog'), **kwargs):
+    def __init__(self, *args, sequence=('edit', 'delete', 'changelog'), extra_buttons='', **kwargs):
         super().__init__(*args, **kwargs)
 
-        # Add/update any extra actions passed
-        if extra_actions:
-            self.actions.update(extra_actions)
+        self.extra_buttons = extra_buttons
 
         # Determine which actions to enable
         self.actions = {
@@ -134,9 +139,10 @@ class ActionsColumn(tables.Column):
         url_appendix = f'?return_url={request.path}' if request else ''
 
         links = []
+        user = getattr(request, 'user', AnonymousUser())
         for action, attrs in self.actions.items():
             permission = f'{model._meta.app_label}.{attrs.permission}_{model._meta.model_name}'
-            if attrs.permission is None or request.user.has_perm(permission):
+            if attrs.permission is None or user.has_perm(permission):
                 url = reverse(f'{viewname_base}_{action}', kwargs={'pk': record.pk})
                 links.append(f'<li><a class="dropdown-item" href="{url}{url_appendix}">'
                              f'<i class="mdi mdi-{attrs.icon}"></i> {attrs.title}</a></li>')
@@ -144,66 +150,19 @@ class ActionsColumn(tables.Column):
         if not links:
             return ''
 
-        menu = f'<div class="dropdown">' \
-               f'<a class="btn btn-sm btn-outline-secondary dropdown-toggle" href="#" type="button" data-bs-toggle="dropdown">' \
+        menu = f'<span class="dropdown">' \
+               f'<a class="btn btn-sm btn-secondary dropdown-toggle" href="#" type="button" data-bs-toggle="dropdown">' \
                f'<i class="mdi mdi-wrench"></i></a>' \
-               f'<ul class="dropdown-menu">{"".join(links)}</ul></div>'
-
-        return mark_safe(menu)
-
+               f'<ul class="dropdown-menu">{"".join(links)}</ul></span>'
 
-class ButtonsColumn(tables.TemplateColumn):
-    """
-    Render edit, delete, and changelog buttons for an object.
+        # Render any extra buttons from template code
+        if self.extra_buttons:
+            template = Template(self.extra_buttons)
+            context = getattr(table, "context", Context())
+            context.update({'record': record})
+            menu = template.render(context) + menu
 
-    :param model: Model class to use for calculating URL view names
-    :param prepend_content: Additional template content to render in the column (optional)
-    """
-    buttons = ('changelog', 'edit', 'delete')
-    attrs = {'td': {'class': 'text-end text-nowrap noprint'}}
-    # Note that braces are escaped to allow for string formatting prior to template rendering
-    template_code = """
-    {{% if "changelog" in buttons %}}
-        <a href="{{% url '{app_label}:{model_name}_changelog' pk=record.pk %}}" class="btn btn-outline-dark btn-sm" title="Change log">
-            <i class="mdi mdi-history"></i>
-        </a>
-    {{% endif %}}
-    {{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
-        <a href="{{% url '{app_label}:{model_name}_edit' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-sm btn-warning" title="Edit">
-            <i class="mdi mdi-pencil"></i>
-        </a>
-    {{% endif %}}
-    {{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}}
-        <a href="{{% url '{app_label}:{model_name}_delete' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-sm btn-danger" title="Delete">
-            <i class="mdi mdi-trash-can-outline"></i>
-        </a>
-    {{% endif %}}
-    """
-
-    def __init__(self, model, *args, buttons=None, prepend_template=None, **kwargs):
-        if prepend_template:
-            prepend_template = prepend_template.replace('{', '{{')
-            prepend_template = prepend_template.replace('}', '}}')
-            self.template_code = prepend_template + self.template_code
-
-        template_code = self.template_code.format(
-            app_label=model._meta.app_label,
-            model_name=model._meta.model_name,
-            buttons=buttons
-        )
-
-        super().__init__(template_code=template_code, *args, **kwargs)
-
-        # Exclude from export by default
-        if 'exclude_from_export' not in kwargs:
-            self.exclude_from_export = True
-
-        self.extra_context.update({
-            'buttons': buttons or self.buttons,
-        })
-
-    def header(self):
-        return ''
+        return mark_safe(menu)
 
 
 class ChoiceFieldColumn(tables.Column):

+ 2 - 1
netbox/utilities/tests/test_tables.py

@@ -30,7 +30,8 @@ class TagColumnTest(TestCase):
 
     def test_tagcolumn(self):
         template = Template('{% load render_table from django_tables2 %}{% render_table table %}')
+        table = TagColumnTable(Site.objects.all(), orderable=False)
         context = Context({
-            'table': TagColumnTable(Site.objects.all(), orderable=False)
+            'table': table
         })
         template.render(context)

+ 4 - 5
netbox/virtualization/tables.py

@@ -3,7 +3,7 @@ import django_tables2 as tables
 from dcim.tables.devices import BaseInterfaceTable
 from tenancy.tables import TenantColumn
 from utilities.tables import (
-    BaseTable, ButtonsColumn, ChoiceFieldColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
+    ActionsColumn, BaseTable, ChoiceFieldColumn, ColoredLabelColumn, LinkedCountColumn, MarkdownColumn, TagColumn,
     ToggleColumn,
 )
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@@ -183,10 +183,9 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
     bridge = tables.Column(
         linkify=True
     )
-    actions = ButtonsColumn(
-        model=VMInterface,
-        buttons=('edit', 'delete'),
-        prepend_template=VMINTERFACE_BUTTONS
+    actions = ActionsColumn(
+        sequence=('edit', 'delete'),
+        extra_buttons=VMINTERFACE_BUTTONS
     )
 
     class Meta(BaseTable.Meta):