Browse Source

Closes #561: Make custom fields accessible from within export templates

Jeremy Stretch 8 years ago
parent
commit
b10e29aaac

+ 2 - 0
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

+ 12 - 1
netbox/extras/models.py

@@ -67,7 +67,18 @@ ACTION_CHOICES = (
 
 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 {<field>: value}.
+        """
 
         # Find all custom fields applicable to this type of object
         content_type = ContentType.objects.get_for_model(self)

+ 1 - 1
netbox/templates/circuits/provider.html

@@ -105,7 +105,7 @@
                 </tr>
             </table>
         </div>
-        {% with provider.custom_fields as custom_fields %}
+        {% with provider.get_custom_fields as custom_fields %}
             {% include 'inc/custom_fields_panel.html' %}
         {% endwith %}
         <div class="panel panel-default">

+ 1 - 1
netbox/templates/dcim/device.html

@@ -144,7 +144,7 @@
                 </tr>
             </table>
         </div>
-        {% 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 %}

+ 1 - 1
netbox/templates/dcim/rack.html

@@ -132,7 +132,7 @@
                 </tr>
             </table>
         </div>
-        {% with rack.custom_fields as custom_fields %}
+        {% with rack.get_custom_fields as custom_fields %}
             {% include 'inc/custom_fields_panel.html' %}
         {% endwith %}
         <div class="panel panel-default">

+ 1 - 1
netbox/templates/dcim/site.html

@@ -111,7 +111,7 @@
                 </tr>
             </table>
         </div>
-        {% with site.custom_fields as custom_fields %}
+        {% with site.get_custom_fields as custom_fields %}
             {% include 'inc/custom_fields_panel.html' %}
         {% endwith %}
         <div class="panel panel-default">

+ 1 - 1
netbox/templates/ipam/aggregate.html

@@ -82,7 +82,7 @@
         {% include 'inc/created_updated.html' with obj=aggregate %}
     </div>
     <div class="col-md-6">
-        {% with aggregate.custom_fields as custom_fields %}
+        {% with aggregate.get_custom_fields as custom_fields %}
             {% include 'inc/custom_fields_panel.html' %}
         {% endwith %}
     </div>

+ 1 - 1
netbox/templates/tenancy/tenant.html

@@ -65,7 +65,7 @@
                 </tr>
             </table>
         </div>
-        {% with tenant.custom_fields as custom_fields %}
+        {% with tenant.get_custom_fields as custom_fields %}
             {% include 'inc/custom_fields_panel.html' %}
         {% endwith %}
         <div class="panel panel-default">

+ 25 - 4
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.utils.http import is_safe_url
 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 @@ class ObjectListView(View):
         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'