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

Limit object assignment to object panels

Jeremy Stretch 3 месяцев назад
Родитель
Сommit
c05106f9b2
4 измененных файлов с 110 добавлено и 58 удалено
  1. 42 20
      netbox/dcim/views.py
  2. 12 4
      netbox/extras/ui/panels.py
  3. 6 6
      netbox/netbox/ui/actions.py
  4. 50 28
      netbox/netbox/ui/panels.py

+ 42 - 20
netbox/dcim/views.py

@@ -247,9 +247,9 @@ class RegionView(GetRelatedModelsMixin, generic.ObjectView):
                 ObjectsTablePanel(
                     model='dcim.Region',
                     title=_('Child Regions'),
-                    filters={'parent_id': lambda obj: obj.pk},
+                    filters={'parent_id': lambda ctx: ctx['object'].pk},
                     actions=[
-                        actions.AddObject('dcim.Region', url_params={'parent': lambda obj: obj.pk}),
+                        actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
                     ],
                 ),
                 PluginContentPanel('full_width_page'),
@@ -386,9 +386,9 @@ class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
                 ObjectsTablePanel(
                     model='dcim.SiteGroup',
                     title=_('Child Groups'),
-                    filters={'parent_id': lambda obj: obj.pk},
+                    filters={'parent_id': lambda ctx: ctx['object'].pk},
                     actions=[
-                        actions.AddObject('dcim.Region', url_params={'parent': lambda obj: obj.pk}),
+                        actions.AddObject('dcim.Region', url_params={'parent': lambda ctx: ctx['object'].pk}),
                     ],
                 ),
                 PluginContentPanel('full_width_page'),
@@ -543,21 +543,21 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView):
             layout.Column(
                 ObjectsTablePanel(
                     model='dcim.Location',
-                    filters={'site_id': lambda obj: obj.pk},
+                    filters={'site_id': lambda ctx: ctx['object'].pk},
                     actions=[
-                        actions.AddObject('dcim.Location', url_params={'site': lambda obj: obj.pk}),
+                        actions.AddObject('dcim.Location', url_params={'site': lambda ctx: ctx['object'].pk}),
                     ],
                 ),
                 ObjectsTablePanel(
                     model='dcim.Device',
                     title=_('Non-Racked Devices'),
                     filters={
-                        'site_id': lambda obj: obj.pk,
+                        'site_id': lambda ctx: ctx['object'].pk,
                         'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
                         'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
                     },
                     actions=[
-                        actions.AddObject('dcim.Device', url_params={'site': lambda obj: obj.pk}),
+                        actions.AddObject('dcim.Device', url_params={'site': lambda ctx: ctx['object'].pk}),
                     ],
                 ),
                 PluginContentPanel('full_width_page'),
@@ -684,13 +684,13 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
                 ObjectsTablePanel(
                     model='dcim.Location',
                     title=_('Child Locations'),
-                    filters={'parent_id': lambda obj: obj.pk},
+                    filters={'parent_id': lambda ctx: ctx['object'].pk},
                     actions=[
                         actions.AddObject(
                             'dcim.Location',
                             url_params={
-                                'site': lambda obj: obj.site.pk if obj.site else None,
-                                'parent': lambda obj: obj.pk,
+                                'site': lambda ctx: ctx['object'].site_id,
+                                'parent': lambda ctx: ctx['object'].pk,
                             }
                         ),
                     ],
@@ -699,7 +699,7 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
                     model='dcim.Device',
                     title=_('Non-Racked Devices'),
                     filters={
-                        'location_id': lambda obj: obj.pk,
+                        'location_id': lambda ctx: ctx['object'].pk,
                         'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
                         'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
                     },
@@ -707,8 +707,8 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
                         actions.AddObject(
                             'dcim.Device',
                             url_params={
-                                'site': lambda obj: obj.site.pk if obj.site else None,
-                                'parent': lambda obj: obj.pk,
+                                'site': lambda ctx: ctx['object'].site_id,
+                                'parent': lambda ctx: ctx['object'].pk,
                             }
                         ),
                     ],
@@ -907,14 +907,14 @@ class RackTypeView(GetRelatedModelsMixin, generic.ObjectView):
         layout.Row(
             layout.Column(
                 panels.RackTypePanel(),
-                panels.RackDimensionsPanel(_('Dimensions')),
+                panels.RackDimensionsPanel(title=_('Dimensions')),
                 TagsPanel(),
                 CommentsPanel(),
                 PluginContentPanel('left_page'),
             ),
             layout.Column(
-                panels.RackNumberingPanel(_('Numbering')),
-                panels.RackWeightPanel(_('Weight')),
+                panels.RackNumberingPanel(title=_('Numbering')),
+                panels.RackWeightPanel(title=_('Weight'), exclude=['total_weight']),
                 CustomFieldsPanel(),
                 RelatedObjectsPanel(),
                 PluginContentPanel('right_page'),
@@ -1047,9 +1047,9 @@ class RackView(GetRelatedModelsMixin, generic.ObjectView):
         layout.Row(
             layout.Column(
                 panels.RackPanel(),
-                panels.RackDimensionsPanel(_('Dimensions')),
-                panels.RackNumberingPanel(_('Numbering')),
-                panels.RackWeightPanel(_('Weight')),
+                panels.RackDimensionsPanel(title=_('Dimensions')),
+                panels.RackNumberingPanel(title=_('Numbering')),
+                panels.RackWeightPanel(title=_('Weight')),
                 CustomFieldsPanel(),
                 TagsPanel(),
                 CommentsPanel(),
@@ -1199,6 +1199,28 @@ class RackReservationListView(generic.ObjectListView):
 @register_model_view(RackReservation)
 class RackReservationView(generic.ObjectView):
     queryset = RackReservation.objects.all()
+    layout = layout.Layout(
+        layout.Row(
+            layout.Column(
+                panels.RackPanel(accessor='rack', only=['region', 'site', 'location']),
+                CustomFieldsPanel(),
+                TagsPanel(),
+                CommentsPanel(),
+                ImageAttachmentsPanel(),
+                PluginContentPanel('left_page'),
+            ),
+            layout.Column(
+                TemplatePanel('dcim/panels/rack_elevations.html'),
+                RelatedObjectsPanel(),
+                PluginContentPanel('right_page'),
+            ),
+        ),
+        layout.Row(
+            layout.Column(
+                PluginContentPanel('full_width_page'),
+            ),
+        ),
+    )
 
 
 @register_model_view(RackReservation, 'add', detail=False)

+ 12 - 4
netbox/extras/ui/panels.py

@@ -14,8 +14,10 @@ class CustomFieldsPanel(panels.Panel):
     template_name = 'ui/panels/custom_fields.html'
     title = _('Custom Fields')
 
-    def get_context(self, obj):
+    def get_context(self, context):
+        obj = context['object']
         return {
+            **super().get_context(context),
             'custom_fields': obj.get_custom_fields_by_group(),
         }
 
@@ -25,9 +27,9 @@ class ImageAttachmentsPanel(panels.ObjectsTablePanel):
         actions.AddObject(
             'extras.imageattachment',
             url_params={
-                'object_type': lambda obj: ContentType.objects.get_for_model(obj).pk,
-                'object_id': lambda obj: obj.pk,
-                'return_url': lambda obj: obj.get_absolute_url(),
+                'object_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
+                'object_id': lambda ctx: ctx['object'].pk,
+                'return_url': lambda ctx: ctx['object'].get_absolute_url(),
             },
             label=_('Attach an image'),
         ),
@@ -40,3 +42,9 @@ class ImageAttachmentsPanel(panels.ObjectsTablePanel):
 class TagsPanel(panels.Panel):
     template_name = 'ui/panels/tags.html'
     title = _('Tags')
+
+    def get_context(self, context):
+        return {
+            **super().get_context(context),
+            'object': context['object'],
+        }

+ 6 - 6
netbox/netbox/ui/actions.py

@@ -26,20 +26,20 @@ class PanelAction:
         if label is not None:
             self.label = label
 
-    def get_url(self, obj):
+    def get_url(self, context):
         url = reverse(self.view_name, kwargs=self.view_kwargs or {})
         if self.url_params:
             url_params = {
-                k: v(obj) if callable(v) else v for k, v in self.url_params.items()
+                k: v(context) if callable(v) else v for k, v in self.url_params.items()
             }
-            if 'return_url' not in url_params:
-                url_params['return_url'] = obj.get_absolute_url()
+            if 'return_url' not in url_params and 'object' in context:
+                url_params['return_url'] = context['object'].get_absolute_url()
             url = f'{url}?{urlencode(url_params)}'
         return url
 
-    def get_context(self, obj):
+    def get_context(self, context):
         return {
-            'url': self.get_url(obj),
+            'url': self.get_url(context),
             'label': self.label,
             'button_class': self.button_class,
             'button_icon': self.button_icon,

+ 50 - 28
netbox/netbox/ui/panels.py

@@ -34,18 +34,15 @@ class Panel(ABC):
         if actions is not None:
             self.actions = actions
 
-    def get_context(self, obj):
-        return {}
+    def get_context(self, context):
+        return {
+            'request': context.get('request'),
+            'title': self.title,
+            'actions': [action.get_context(context) for action in self.actions],
+        }
 
     def render(self, context):
-        obj = context.get('object')
-        return render_to_string(self.template_name, {
-            'request': context.get('request'),
-            'object': obj,
-            'title': self.title or title(obj._meta.verbose_name),
-            'actions': [action.get_context(obj) for action in self.actions],
-            **self.get_context(obj),
-        })
+        return render_to_string(self.template_name, self.get_context(context))
 
 
 class ObjectPanelMeta(ABCMeta):
@@ -76,17 +73,39 @@ class ObjectPanelMeta(ABCMeta):
 
 
 class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
+    accessor = None
     template_name = 'ui/panels/object.html'
 
-    def get_context(self, obj):
-        attrs = [
-            {
-                'label': attr.label or title(name),
-                'value': attr.render(obj, {'name': name}),
-            } for name, attr in self._attrs.items()
-        ]
+    def __init__(self, accessor=None, only=None, exclude=None, **kwargs):
+        super().__init__(**kwargs)
+        if accessor is not None:
+            self.accessor = accessor
+
+        # Set included/excluded attributes
+        if only is not None and exclude is not None:
+            raise ValueError("attrs and exclude cannot both be specified.")
+        self.only = only or []
+        self.exclude = exclude or []
+
+    def get_context(self, context):
+        # Determine which attributes to display in the panel based on only/exclude args
+        attr_names = set(self._attrs.keys())
+        if self.only:
+            attr_names &= set(self.only)
+        elif self.exclude:
+            attr_names -= set(self.exclude)
+
+        obj = getattr(context['object'], self.accessor) if self.accessor else context['object']
+
         return {
-            'attrs': attrs,
+            **super().get_context(context),
+            'object': obj,
+            'attrs': [
+                {
+                    'label': attr.label or title(name),
+                    'value': attr.render(obj, {'name': name}),
+                } for name, attr in self._attrs.items() if name in attr_names
+            ],
         }
 
 
@@ -108,13 +127,11 @@ class RelatedObjectsPanel(Panel):
     template_name = 'ui/panels/related_objects.html'
     title = _('Related Objects')
 
-    # TODO: Handle related_models from context
-    def render(self, context):
-        return render_to_string(self.template_name, {
-            'title': self.title,
-            'object': context.get('object'),
+    def get_context(self, context):
+        return {
+            **super().get_context(context),
             'related_models': context.get('related_models'),
-        })
+        }
 
 
 class ObjectsTablePanel(Panel):
@@ -131,13 +148,14 @@ class ObjectsTablePanel(Panel):
         if self.title is None:
             self.title = title(self.model._meta.verbose_name_plural)
 
-    def get_context(self, obj):
+    def get_context(self, context):
         url_params = {
-            k: v(obj) if callable(v) else v for k, v in self.filters.items()
+            k: v(context) if callable(v) else v for k, v in self.filters.items()
         }
-        if 'return_url' not in url_params:
-            url_params['return_url'] = obj.get_absolute_url()
+        if 'return_url' not in url_params and 'object' in context:
+            url_params['return_url'] = context['object'].get_absolute_url()
         return {
+            **super().get_context(context),
             'viewname': get_viewname(self.model, 'list'),
             'url_params': dict_to_querydict(url_params),
         }
@@ -149,6 +167,10 @@ class TemplatePanel(Panel):
         super().__init__(**kwargs)
         self.template_name = template_name
 
+    def render(self, context):
+        # Pass the entire context to the template
+        return render_to_string(self.template_name, context.flatten())
+
 
 class PluginContentPanel(Panel):