mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-04 06:16:23 -06:00
Limit object assignment to object panels
This commit is contained in:
+42
-20
@@ -247,9 +247,9 @@ class RegionView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
ObjectsTablePanel(
|
ObjectsTablePanel(
|
||||||
model='dcim.Region',
|
model='dcim.Region',
|
||||||
title=_('Child Regions'),
|
title=_('Child Regions'),
|
||||||
filters={'parent_id': lambda obj: obj.pk},
|
filters={'parent_id': lambda ctx: ctx['object'].pk},
|
||||||
actions=[
|
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'),
|
PluginContentPanel('full_width_page'),
|
||||||
@@ -386,9 +386,9 @@ class SiteGroupView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
ObjectsTablePanel(
|
ObjectsTablePanel(
|
||||||
model='dcim.SiteGroup',
|
model='dcim.SiteGroup',
|
||||||
title=_('Child Groups'),
|
title=_('Child Groups'),
|
||||||
filters={'parent_id': lambda obj: obj.pk},
|
filters={'parent_id': lambda ctx: ctx['object'].pk},
|
||||||
actions=[
|
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'),
|
PluginContentPanel('full_width_page'),
|
||||||
@@ -543,21 +543,21 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
layout.Column(
|
layout.Column(
|
||||||
ObjectsTablePanel(
|
ObjectsTablePanel(
|
||||||
model='dcim.Location',
|
model='dcim.Location',
|
||||||
filters={'site_id': lambda obj: obj.pk},
|
filters={'site_id': lambda ctx: ctx['object'].pk},
|
||||||
actions=[
|
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(
|
ObjectsTablePanel(
|
||||||
model='dcim.Device',
|
model='dcim.Device',
|
||||||
title=_('Non-Racked Devices'),
|
title=_('Non-Racked Devices'),
|
||||||
filters={
|
filters={
|
||||||
'site_id': lambda obj: obj.pk,
|
'site_id': lambda ctx: ctx['object'].pk,
|
||||||
'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
|
'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
|
||||||
'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
|
'parent_bay_id': settings.FILTERS_NULL_CHOICE_VALUE,
|
||||||
},
|
},
|
||||||
actions=[
|
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'),
|
PluginContentPanel('full_width_page'),
|
||||||
@@ -684,13 +684,13 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
ObjectsTablePanel(
|
ObjectsTablePanel(
|
||||||
model='dcim.Location',
|
model='dcim.Location',
|
||||||
title=_('Child Locations'),
|
title=_('Child Locations'),
|
||||||
filters={'parent_id': lambda obj: obj.pk},
|
filters={'parent_id': lambda ctx: ctx['object'].pk},
|
||||||
actions=[
|
actions=[
|
||||||
actions.AddObject(
|
actions.AddObject(
|
||||||
'dcim.Location',
|
'dcim.Location',
|
||||||
url_params={
|
url_params={
|
||||||
'site': lambda obj: obj.site.pk if obj.site else None,
|
'site': lambda ctx: ctx['object'].site_id,
|
||||||
'parent': lambda obj: obj.pk,
|
'parent': lambda ctx: ctx['object'].pk,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -699,7 +699,7 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
model='dcim.Device',
|
model='dcim.Device',
|
||||||
title=_('Non-Racked Devices'),
|
title=_('Non-Racked Devices'),
|
||||||
filters={
|
filters={
|
||||||
'location_id': lambda obj: obj.pk,
|
'location_id': lambda ctx: ctx['object'].pk,
|
||||||
'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
|
'rack_id': settings.FILTERS_NULL_CHOICE_VALUE,
|
||||||
'parent_bay_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(
|
actions.AddObject(
|
||||||
'dcim.Device',
|
'dcim.Device',
|
||||||
url_params={
|
url_params={
|
||||||
'site': lambda obj: obj.site.pk if obj.site else None,
|
'site': lambda ctx: ctx['object'].site_id,
|
||||||
'parent': lambda obj: obj.pk,
|
'parent': lambda ctx: ctx['object'].pk,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -907,14 +907,14 @@ class RackTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
layout.Row(
|
layout.Row(
|
||||||
layout.Column(
|
layout.Column(
|
||||||
panels.RackTypePanel(),
|
panels.RackTypePanel(),
|
||||||
panels.RackDimensionsPanel(_('Dimensions')),
|
panels.RackDimensionsPanel(title=_('Dimensions')),
|
||||||
TagsPanel(),
|
TagsPanel(),
|
||||||
CommentsPanel(),
|
CommentsPanel(),
|
||||||
PluginContentPanel('left_page'),
|
PluginContentPanel('left_page'),
|
||||||
),
|
),
|
||||||
layout.Column(
|
layout.Column(
|
||||||
panels.RackNumberingPanel(_('Numbering')),
|
panels.RackNumberingPanel(title=_('Numbering')),
|
||||||
panels.RackWeightPanel(_('Weight')),
|
panels.RackWeightPanel(title=_('Weight'), exclude=['total_weight']),
|
||||||
CustomFieldsPanel(),
|
CustomFieldsPanel(),
|
||||||
RelatedObjectsPanel(),
|
RelatedObjectsPanel(),
|
||||||
PluginContentPanel('right_page'),
|
PluginContentPanel('right_page'),
|
||||||
@@ -1047,9 +1047,9 @@ class RackView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
layout.Row(
|
layout.Row(
|
||||||
layout.Column(
|
layout.Column(
|
||||||
panels.RackPanel(),
|
panels.RackPanel(),
|
||||||
panels.RackDimensionsPanel(_('Dimensions')),
|
panels.RackDimensionsPanel(title=_('Dimensions')),
|
||||||
panels.RackNumberingPanel(_('Numbering')),
|
panels.RackNumberingPanel(title=_('Numbering')),
|
||||||
panels.RackWeightPanel(_('Weight')),
|
panels.RackWeightPanel(title=_('Weight')),
|
||||||
CustomFieldsPanel(),
|
CustomFieldsPanel(),
|
||||||
TagsPanel(),
|
TagsPanel(),
|
||||||
CommentsPanel(),
|
CommentsPanel(),
|
||||||
@@ -1199,6 +1199,28 @@ class RackReservationListView(generic.ObjectListView):
|
|||||||
@register_model_view(RackReservation)
|
@register_model_view(RackReservation)
|
||||||
class RackReservationView(generic.ObjectView):
|
class RackReservationView(generic.ObjectView):
|
||||||
queryset = RackReservation.objects.all()
|
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)
|
@register_model_view(RackReservation, 'add', detail=False)
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ class CustomFieldsPanel(panels.Panel):
|
|||||||
template_name = 'ui/panels/custom_fields.html'
|
template_name = 'ui/panels/custom_fields.html'
|
||||||
title = _('Custom Fields')
|
title = _('Custom Fields')
|
||||||
|
|
||||||
def get_context(self, obj):
|
def get_context(self, context):
|
||||||
|
obj = context['object']
|
||||||
return {
|
return {
|
||||||
|
**super().get_context(context),
|
||||||
'custom_fields': obj.get_custom_fields_by_group(),
|
'custom_fields': obj.get_custom_fields_by_group(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,9 +27,9 @@ class ImageAttachmentsPanel(panels.ObjectsTablePanel):
|
|||||||
actions.AddObject(
|
actions.AddObject(
|
||||||
'extras.imageattachment',
|
'extras.imageattachment',
|
||||||
url_params={
|
url_params={
|
||||||
'object_type': lambda obj: ContentType.objects.get_for_model(obj).pk,
|
'object_type': lambda ctx: ContentType.objects.get_for_model(ctx['object']).pk,
|
||||||
'object_id': lambda obj: obj.pk,
|
'object_id': lambda ctx: ctx['object'].pk,
|
||||||
'return_url': lambda obj: obj.get_absolute_url(),
|
'return_url': lambda ctx: ctx['object'].get_absolute_url(),
|
||||||
},
|
},
|
||||||
label=_('Attach an image'),
|
label=_('Attach an image'),
|
||||||
),
|
),
|
||||||
@@ -40,3 +42,9 @@ class ImageAttachmentsPanel(panels.ObjectsTablePanel):
|
|||||||
class TagsPanel(panels.Panel):
|
class TagsPanel(panels.Panel):
|
||||||
template_name = 'ui/panels/tags.html'
|
template_name = 'ui/panels/tags.html'
|
||||||
title = _('Tags')
|
title = _('Tags')
|
||||||
|
|
||||||
|
def get_context(self, context):
|
||||||
|
return {
|
||||||
|
**super().get_context(context),
|
||||||
|
'object': context['object'],
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,20 +26,20 @@ class PanelAction:
|
|||||||
if label is not None:
|
if label is not None:
|
||||||
self.label = label
|
self.label = label
|
||||||
|
|
||||||
def get_url(self, obj):
|
def get_url(self, context):
|
||||||
url = reverse(self.view_name, kwargs=self.view_kwargs or {})
|
url = reverse(self.view_name, kwargs=self.view_kwargs or {})
|
||||||
if self.url_params:
|
if self.url_params:
|
||||||
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:
|
if 'return_url' not in url_params and 'object' in context:
|
||||||
url_params['return_url'] = obj.get_absolute_url()
|
url_params['return_url'] = context['object'].get_absolute_url()
|
||||||
url = f'{url}?{urlencode(url_params)}'
|
url = f'{url}?{urlencode(url_params)}'
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_context(self, obj):
|
def get_context(self, context):
|
||||||
return {
|
return {
|
||||||
'url': self.get_url(obj),
|
'url': self.get_url(context),
|
||||||
'label': self.label,
|
'label': self.label,
|
||||||
'button_class': self.button_class,
|
'button_class': self.button_class,
|
||||||
'button_icon': self.button_icon,
|
'button_icon': self.button_icon,
|
||||||
|
|||||||
+50
-28
@@ -34,18 +34,15 @@ class Panel(ABC):
|
|||||||
if actions is not None:
|
if actions is not None:
|
||||||
self.actions = actions
|
self.actions = actions
|
||||||
|
|
||||||
def get_context(self, obj):
|
def get_context(self, context):
|
||||||
return {}
|
return {
|
||||||
|
'request': context.get('request'),
|
||||||
|
'title': self.title,
|
||||||
|
'actions': [action.get_context(context) for action in self.actions],
|
||||||
|
}
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
obj = context.get('object')
|
return render_to_string(self.template_name, self.get_context(context))
|
||||||
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),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectPanelMeta(ABCMeta):
|
class ObjectPanelMeta(ABCMeta):
|
||||||
@@ -76,17 +73,39 @@ class ObjectPanelMeta(ABCMeta):
|
|||||||
|
|
||||||
|
|
||||||
class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
class ObjectPanel(Panel, metaclass=ObjectPanelMeta):
|
||||||
|
accessor = None
|
||||||
template_name = 'ui/panels/object.html'
|
template_name = 'ui/panels/object.html'
|
||||||
|
|
||||||
def get_context(self, obj):
|
def __init__(self, accessor=None, only=None, exclude=None, **kwargs):
|
||||||
attrs = [
|
super().__init__(**kwargs)
|
||||||
{
|
if accessor is not None:
|
||||||
'label': attr.label or title(name),
|
self.accessor = accessor
|
||||||
'value': attr.render(obj, {'name': name}),
|
|
||||||
} for name, attr in self._attrs.items()
|
# 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 {
|
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'
|
template_name = 'ui/panels/related_objects.html'
|
||||||
title = _('Related Objects')
|
title = _('Related Objects')
|
||||||
|
|
||||||
# TODO: Handle related_models from context
|
def get_context(self, context):
|
||||||
def render(self, context):
|
return {
|
||||||
return render_to_string(self.template_name, {
|
**super().get_context(context),
|
||||||
'title': self.title,
|
|
||||||
'object': context.get('object'),
|
|
||||||
'related_models': context.get('related_models'),
|
'related_models': context.get('related_models'),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class ObjectsTablePanel(Panel):
|
class ObjectsTablePanel(Panel):
|
||||||
@@ -131,13 +148,14 @@ class ObjectsTablePanel(Panel):
|
|||||||
if self.title is None:
|
if self.title is None:
|
||||||
self.title = title(self.model._meta.verbose_name_plural)
|
self.title = title(self.model._meta.verbose_name_plural)
|
||||||
|
|
||||||
def get_context(self, obj):
|
def get_context(self, context):
|
||||||
url_params = {
|
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:
|
if 'return_url' not in url_params and 'object' in context:
|
||||||
url_params['return_url'] = obj.get_absolute_url()
|
url_params['return_url'] = context['object'].get_absolute_url()
|
||||||
return {
|
return {
|
||||||
|
**super().get_context(context),
|
||||||
'viewname': get_viewname(self.model, 'list'),
|
'viewname': get_viewname(self.model, 'list'),
|
||||||
'url_params': dict_to_querydict(url_params),
|
'url_params': dict_to_querydict(url_params),
|
||||||
}
|
}
|
||||||
@@ -149,6 +167,10 @@ class TemplatePanel(Panel):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.template_name = template_name
|
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):
|
class PluginContentPanel(Panel):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user