mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-13 20:09:37 -06:00
Limit object assignment to object panels
This commit is contained in:
parent
17429c4257
commit
c05106f9b2
@ -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)
|
||||
|
||||
@ -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'],
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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):
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user