diff --git a/docs/data-model/extras.md b/docs/data-model/extras.md index d6c86398308..9d69af40a1e 100644 --- a/docs/data-model/extras.md +++ b/docs/data-model/extras.md @@ -35,6 +35,8 @@ Each export template is associated with a certain type of object. For instance, Export templates are written in [Django's template language](https://docs.djangoproject.com/en/1.9/ref/templates/language/), which is very similar to Jinja2. The list of objects returned from the database is stored in the `queryset` variable. Typically, you'll want to iterate through this list using a for loop. +To access custom fields of an object within a template, use the `cf` attribute. For example, `{{ obj.cf.color }}` will return the value (if any) for a custom field named `color` on `obj`. + A MIME type and file extension can optionally be defined for each export template. The default MIME type is `text/plain`. ## Example diff --git a/netbox/extras/models.py b/netbox/extras/models.py index ce5b1d43fb7..4bc9f35500a 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -67,7 +67,18 @@ class CustomFieldModel(object): - def custom_fields(self): + def cf(self): + """ + Name-based CustomFieldValue accessor for use in templates + """ + if not hasattr(self, 'custom_fields'): + return dict() + return {field.name: value for field, value in self.custom_fields.items()} + + def get_custom_fields(self): + """ + Return a dictionary of custom fields for a single object in the form {: value}. + """ # Find all custom fields applicable to this type of object content_type = ContentType.objects.get_for_model(self) diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 742dbcc3ee5..c59858626e3 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -105,7 +105,7 @@

{{ provider }}

- {% with provider.custom_fields as custom_fields %} + {% with provider.get_custom_fields as custom_fields %} {% include 'inc/custom_fields_panel.html' %} {% endwith %}
diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 754c9c2a809..551241715a8 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -144,7 +144,7 @@
- {% with device.custom_fields as custom_fields %} + {% with device.get_custom_fields as custom_fields %} {% include 'inc/custom_fields_panel.html' %} {% endwith %} {% if request.user.is_authenticated %} diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index 11d7fb41171..4c2aef15dff 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -132,7 +132,7 @@

Rack {{ rack.name }}

- {% with rack.custom_fields as custom_fields %} + {% with rack.get_custom_fields as custom_fields %} {% include 'inc/custom_fields_panel.html' %} {% endwith %}
diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index ffcf382ab08..ccf2c96739b 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -111,7 +111,7 @@

{{ site.name }}

- {% with site.custom_fields as custom_fields %} + {% with site.get_custom_fields as custom_fields %} {% include 'inc/custom_fields_panel.html' %} {% endwith %}
diff --git a/netbox/templates/ipam/aggregate.html b/netbox/templates/ipam/aggregate.html index e2b4086b103..d95494c678f 100644 --- a/netbox/templates/ipam/aggregate.html +++ b/netbox/templates/ipam/aggregate.html @@ -82,7 +82,7 @@

{{ aggregate }}

{% include 'inc/created_updated.html' with obj=aggregate %}
- {% with aggregate.custom_fields as custom_fields %} + {% with aggregate.get_custom_fields as custom_fields %} {% include 'inc/custom_fields_panel.html' %} {% endwith %}
diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index 6fe140e535c..c3e640de1ea 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -65,7 +65,7 @@

{{ tenant }}

- {% with tenant.custom_fields as custom_fields %} + {% with tenant.get_custom_fields as custom_fields %} {% include 'inc/custom_fields_panel.html' %} {% endwith %}
diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index cc888ad104d..5e8b01d6f87 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from django_tables2 import RequestConfig from django.contrib import messages @@ -14,13 +15,26 @@ from django.views.generic import View from extras.forms import CustomFieldForm -from extras.models import CustomFieldValue, ExportTemplate, UserAction +from extras.models import CustomField, CustomFieldValue, ExportTemplate, UserAction from .error_handlers import handle_protectederror from .forms import ConfirmationForm from .paginator import EnhancedPaginator +class annotate_custom_fields: + + def __init__(self, queryset, custom_fields): + self.queryset = queryset + self.custom_fields = custom_fields + + def __iter__(self): + for obj in self.queryset: + values_dict = {cfv.field_id: cfv.value for cfv in obj.custom_field_values.all()} + obj.custom_fields = OrderedDict([(field, values_dict.get(field.pk)) for field in self.custom_fields]) + yield obj + + class ObjectListView(View): queryset = None filter = None @@ -38,19 +52,26 @@ def get(self, request, *args, **kwargs): if self.filter: self.queryset = self.filter(request.GET, self.queryset).qs + # If this type of object has one or more custom fields, prefetch any relevant custom field values + custom_fields = CustomField.objects.filter(obj_type=ContentType.objects.get_for_model(model))\ + .prefetch_related('choices') + if custom_fields: + self.queryset = self.queryset.prefetch_related('custom_field_values') + # Check for export template rendering if request.GET.get('export'): et = get_object_or_404(ExportTemplate, content_type=object_ct, name=request.GET.get('export')) + queryset = annotate_custom_fields(self.queryset, custom_fields) if custom_fields else self.queryset try: - response = et.to_response(context_dict={'queryset': self.queryset.all()}, - filename='netbox_{}'.format(self.queryset.model._meta.verbose_name_plural)) + response = et.to_response(context_dict={'queryset': queryset}, + filename='netbox_{}'.format(model._meta.verbose_name_plural)) return response except TemplateSyntaxError: messages.error(request, "There was an error rendering the selected export template ({})." .format(et.name)) # Fall back to built-in CSV export elif 'export' in request.GET and hasattr(model, 'to_csv'): - output = '\n'.join([obj.to_csv() for obj in self.queryset.all()]) + output = '\n'.join([obj.to_csv() for obj in self.queryset]) response = HttpResponse( output, content_type='text/csv'