Add custom field support for device component models

This commit is contained in:
Jeremy Stretch 2021-03-01 13:07:25 -05:00
parent 6a9b50f95d
commit d6ee4d58ba
15 changed files with 93 additions and 37 deletions

View File

@ -49,7 +49,7 @@ class CableTerminationSerializer(serializers.ModelSerializer):
return None return None
class ConnectedEndpointSerializer(ValidatedModelSerializer): class ConnectedEndpointSerializer(CustomFieldModelSerializer):
connected_endpoint_type = serializers.SerializerMethodField(read_only=True) connected_endpoint_type = serializers.SerializerMethodField(read_only=True)
connected_endpoint = serializers.SerializerMethodField(read_only=True) connected_endpoint = serializers.SerializerMethodField(read_only=True)
connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True) connected_endpoint_reachable = serializers.SerializerMethodField(read_only=True)
@ -497,7 +497,7 @@ class ConsoleServerPortSerializer(TaggedObjectSerializer, CableTerminationSerial
model = ConsoleServerPort model = ConsoleServerPort
fields = [ fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type', 'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
] ]
@ -515,7 +515,7 @@ class ConsolePortSerializer(TaggedObjectSerializer, CableTerminationSerializer,
model = ConsolePort model = ConsolePort
fields = [ fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type', 'id', 'url', 'device', 'name', 'label', 'type', 'description', 'cable', 'cable_peer', 'cable_peer_type',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
] ]
@ -544,7 +544,7 @@ class PowerOutletSerializer(TaggedObjectSerializer, CableTerminationSerializer,
fields = [ fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable', 'id', 'url', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'cable',
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'connected_endpoint_reachable', 'tags', 'custom_fields',
] ]
@ -563,7 +563,7 @@ class PowerPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
fields = [ fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'id', 'url', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable',
'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'connected_endpoint_reachable', 'tags', 'custom_fields',
] ]
@ -588,7 +588,7 @@ class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
fields = [ fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'id', 'url', 'device', 'name', 'label', 'type', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only',
'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'cable', 'cable_peer', 'cable_peer_type', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'cable', 'cable_peer', 'cable_peer_type',
'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields',
'count_ipaddresses', 'count_ipaddresses',
] ]
@ -606,7 +606,7 @@ class InterfaceSerializer(TaggedObjectSerializer, CableTerminationSerializer, Co
return super().validate(data) return super().validate(data)
class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ValidatedModelSerializer): class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField(choices=PortTypeChoices) type = ChoiceField(choices=PortTypeChoices)
@ -616,7 +616,7 @@ class RearPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Val
model = RearPort model = RearPort
fields = [ fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer', 'id', 'url', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'cable_peer',
'cable_peer_type', 'tags', 'cable_peer_type', 'tags', 'custom_fields',
] ]
@ -631,7 +631,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'name', 'label'] fields = ['id', 'url', 'name', 'label']
class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, ValidatedModelSerializer): class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
type = ChoiceField(choices=PortTypeChoices) type = ChoiceField(choices=PortTypeChoices)
@ -642,25 +642,25 @@ class FrontPortSerializer(TaggedObjectSerializer, CableTerminationSerializer, Va
model = FrontPort model = FrontPort
fields = [ fields = [
'id', 'url', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'id', 'url', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable',
'cable_peer', 'cable_peer_type', 'tags', 'cable_peer', 'cable_peer_type', 'tags', 'custom_fields',
] ]
class DeviceBaySerializer(TaggedObjectSerializer, ValidatedModelSerializer): class DeviceBaySerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
installed_device = NestedDeviceSerializer(required=False, allow_null=True) installed_device = NestedDeviceSerializer(required=False, allow_null=True)
class Meta: class Meta:
model = DeviceBay model = DeviceBay
fields = ['id', 'url', 'device', 'name', 'label', 'description', 'installed_device', 'tags'] fields = ['id', 'url', 'device', 'name', 'label', 'description', 'installed_device', 'tags', 'custom_fields']
# #
# Inventory items # Inventory items
# #
class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer): class InventoryItemSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail') url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitem-detail')
device = NestedDeviceSerializer() device = NestedDeviceSerializer()
# Provide a default value to satisfy UniqueTogetherValidator # Provide a default value to satisfy UniqueTogetherValidator
@ -672,7 +672,7 @@ class InventoryItemSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
model = InventoryItem model = InventoryItem
fields = [ fields = [
'id', 'url', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'id', 'url', 'device', 'parent', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag',
'discovered', 'description', 'tags', '_depth', 'discovered', 'description', 'tags', 'custom_fields', '_depth',
] ]

View File

@ -1,6 +1,5 @@
import django_filters import django_filters
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Count
from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
@ -704,7 +703,7 @@ class DeviceFilterSet(
return queryset.exclude(devicebays__isnull=value) return queryset.exclude(devicebays__isnull=value)
class DeviceComponentFilterSet(django_filters.FilterSet): class DeviceComponentFilterSet(CustomFieldModelFilterSet):
q = django_filters.CharFilter( q = django_filters.CharFilter(
method='search', method='search',
label='Search', label='Search',

View File

@ -58,7 +58,7 @@ def get_device_by_name_or_pk(name):
return device return device
class DeviceComponentFilterForm(BootstrapMixin, forms.Form): class DeviceComponentFilterForm(BootstrapMixin, CustomFieldFilterForm):
field_order = [ field_order = [
'q', 'region', 'site' 'q', 'region', 'site'
] ]
@ -2274,6 +2274,7 @@ class ComponentCreateForm(ComponentForm):
""" """
Base form for the creation of device components (models subclassed from ComponentModel). Base form for the creation of device components (models subclassed from ComponentModel).
""" """
# TODO: Enable custom field support
device = DynamicModelChoiceField( device = DynamicModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
display_field='display_name' display_field='display_name'
@ -2289,6 +2290,7 @@ class ComponentCreateForm(ComponentForm):
class DeviceBulkAddComponentForm(ComponentForm): class DeviceBulkAddComponentForm(ComponentForm):
# TODO: Enable custom field support
pk = forms.ModelMultipleChoiceField( pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
@ -2318,7 +2320,7 @@ class ConsolePortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class ConsolePortForm(BootstrapMixin, forms.ModelForm): class ConsolePortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
required=False required=False
@ -2365,7 +2367,7 @@ class ConsolePortBulkEditForm(
nullable_fields = ('label', 'description') nullable_fields = ('label', 'description')
class ConsolePortCSVForm(CSVModelForm): class ConsolePortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -2396,7 +2398,7 @@ class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm): class ConsoleServerPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
required=False required=False
@ -2443,7 +2445,7 @@ class ConsoleServerPortBulkEditForm(
nullable_fields = ('label', 'description') nullable_fields = ('label', 'description')
class ConsoleServerPortCSVForm(CSVModelForm): class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -2474,7 +2476,7 @@ class PowerPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class PowerPortForm(BootstrapMixin, forms.ModelForm): class PowerPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
required=False required=False
@ -2533,7 +2535,7 @@ class PowerPortBulkEditForm(
nullable_fields = ('label', 'description') nullable_fields = ('label', 'description')
class PowerPortCSVForm(CSVModelForm): class PowerPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -2564,7 +2566,7 @@ class PowerOutletFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class PowerOutletForm(BootstrapMixin, forms.ModelForm): class PowerOutletForm(BootstrapMixin, CustomFieldModelForm):
power_port = forms.ModelChoiceField( power_port = forms.ModelChoiceField(
queryset=PowerPort.objects.all(), queryset=PowerPort.objects.all(),
required=False required=False
@ -2658,7 +2660,7 @@ class PowerOutletBulkEditForm(
self.fields['power_port'].widget.attrs['disabled'] = True self.fields['power_port'].widget.attrs['disabled'] = True
class PowerOutletCSVForm(CSVModelForm): class PowerOutletCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -2738,7 +2740,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class InterfaceForm(BootstrapMixin, InterfaceCommonForm, forms.ModelForm): class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
untagged_vlan = DynamicModelChoiceField( untagged_vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
required=False, required=False,
@ -2988,7 +2990,7 @@ class InterfaceBulkEditForm(
self.cleaned_data['tagged_vlans'] = [] self.cleaned_data['tagged_vlans'] = []
class InterfaceCSVForm(CSVModelForm): class InterfaceCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -3058,7 +3060,7 @@ class FrontPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class FrontPortForm(BootstrapMixin, forms.ModelForm): class FrontPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
required=False required=False
@ -3168,7 +3170,7 @@ class FrontPortBulkEditForm(
nullable_fields = ('label', 'description') nullable_fields = ('label', 'description')
class FrontPortCSVForm(CSVModelForm): class FrontPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -3227,7 +3229,7 @@ class RearPortFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class RearPortForm(BootstrapMixin, forms.ModelForm): class RearPortForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
required=False required=False
@ -3280,7 +3282,7 @@ class RearPortBulkEditForm(
nullable_fields = ('label', 'description') nullable_fields = ('label', 'description')
class RearPortCSVForm(CSVModelForm): class RearPortCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -3307,7 +3309,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
tag = TagFilterField(model) tag = TagFilterField(model)
class DeviceBayForm(BootstrapMixin, forms.ModelForm): class DeviceBayForm(BootstrapMixin, CustomFieldModelForm):
tags = DynamicModelMultipleChoiceField( tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(), queryset=Tag.objects.all(),
required=False required=False
@ -3367,7 +3369,7 @@ class DeviceBayBulkEditForm(
nullable_fields = ('label', 'description') nullable_fields = ('label', 'description')
class DeviceBayCSVForm(CSVModelForm): class DeviceBayCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'
@ -3417,7 +3419,7 @@ class DeviceBayCSVForm(CSVModelForm):
# Inventory items # Inventory items
# #
class InventoryItemForm(BootstrapMixin, forms.ModelForm): class InventoryItemForm(BootstrapMixin, CustomFieldModelForm):
device = DynamicModelChoiceField( device = DynamicModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
display_field='display_name' display_field='display_name'
@ -3477,7 +3479,7 @@ class InventoryItemCreateForm(ComponentCreateForm):
) )
class InventoryItemCSVForm(CSVModelForm): class InventoryItemCSVForm(CustomFieldModelCSVForm):
device = CSVModelChoiceField( device = CSVModelChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
to_field_name='name' to_field_name='name'

View File

@ -9,11 +9,41 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AddField(
model_name='consoleport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='consoleserverport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='devicebay',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField( migrations.AddField(
model_name='devicerole', model_name='devicerole',
name='custom_field_data', name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
), ),
migrations.AddField(
model_name='frontport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='interface',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='inventoryitem',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField( migrations.AddField(
model_name='manufacturer', model_name='manufacturer',
name='custom_field_data', name='custom_field_data',
@ -24,6 +54,16 @@ class Migration(migrations.Migration):
name='custom_field_data', name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
), ),
migrations.AddField(
model_name='poweroutlet',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField(
model_name='powerport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField( migrations.AddField(
model_name='rackgroup', model_name='rackgroup',
name='custom_field_data', name='custom_field_data',
@ -34,6 +74,11 @@ class Migration(migrations.Migration):
name='custom_field_data', name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder), field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
), ),
migrations.AddField(
model_name='rearport',
name='custom_field_data',
field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
migrations.AddField( migrations.AddField(
model_name='region', model_name='region',
name='custom_field_data', name='custom_field_data',

View File

@ -13,7 +13,7 @@ from dcim.constants import *
from dcim.fields import MACAddressField from dcim.fields import MACAddressField
from extras.models import ObjectChange, TaggedItem from extras.models import ObjectChange, TaggedItem
from extras.utils import extras_features from extras.utils import extras_features
from netbox.models import BigIDModel from netbox.models import BigIDModel, CustomFieldsMixin
from utilities.fields import NaturalOrderingField from utilities.fields import NaturalOrderingField
from utilities.mptt import TreeManager from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface from utilities.ordering import naturalize_interface
@ -38,7 +38,7 @@ __all__ = (
) )
class ComponentModel(BigIDModel): class ComponentModel(CustomFieldsMixin, BigIDModel):
""" """
An abstract model inherited by any model which has a parent Device. An abstract model inherited by any model which has a parent Device.
""" """

View File

@ -10,6 +10,7 @@ from utilities.utils import serialize_object
__all__ = ( __all__ = (
'BigIDModel', 'BigIDModel',
'CustomFieldsMixin',
'NestedGroupModel', 'NestedGroupModel',
'OrganizationalModel', 'OrganizationalModel',
'PrimaryModel', 'PrimaryModel',

View File

@ -34,6 +34,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -34,6 +34,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -30,6 +30,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -44,6 +44,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -66,6 +66,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -62,6 +62,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -42,6 +42,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -42,6 +42,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>

View File

@ -38,6 +38,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' %}
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %} {% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
{% plugin_left_page object %} {% plugin_left_page object %}
</div> </div>