Browse Source

Merge branch 'develop' into api2

Jeremy Stretch 8 years ago
parent
commit
0dd857f7a2

+ 5 - 7
netbox/ipam/views.py

@@ -635,16 +635,14 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView):
 
     def save_obj(self, obj):
         obj.save()
-        # Update primary IP for device if needed
+
+        # Update primary IP for device if needed. The Device must be updated directly in the database; otherwise we risk
+        # overwriting a previous IP assignment from the same import (see #861).
         try:
             if obj.family == 4 and obj.primary_ip4_for:
-                device = obj.primary_ip4_for
-                device.primary_ip4 = obj
-                device.save()
+                Device.objects.filter(pk=obj.primary_ip4_for.pk).update(primary_ip4=obj)
             elif obj.family == 6 and obj.primary_ip6_for:
-                device = obj.primary_ip6_for
-                device.primary_ip6 = obj
-                device.save()
+                Device.objects.filter(pk=obj.primary_ip6_for.pk).update(primary_ip6=obj)
         except Device.DoesNotExist:
             pass
 

+ 3 - 2
netbox/templates/dcim/device.html

@@ -548,9 +548,10 @@
 {% block javascript %}
 <script type="text/javascript">
 function toggleConnection(elem, api_url) {
+    var url = netbox_api_path + api_url + elem.attr('data') + "/";
     if (elem.hasClass('connected')) {
         $.ajax({
-            url: netbox_api_path + api_url + elem.attr('data') + "/",
+            url: url,
             method: 'PATCH',
             dataType: 'json',
             beforeSend: function(xhr, settings) {
@@ -569,7 +570,7 @@ function toggleConnection(elem, api_url) {
         });
     } else {
         $.ajax({
-            url: api_url + elem.attr('data') + "/",
+            url: url,
             method: 'PATCH',
             dataType: 'json',
             beforeSend: function(xhr, settings) {

+ 6 - 7
netbox/utilities/error_handlers.py

@@ -5,20 +5,19 @@ def handle_protectederror(obj, request, e):
     """
     Generate a user-friendly error message in response to a ProtectedError exception.
     """
-    dependent_objects = e[1]
     try:
-        dep_class = dependent_objects[0]._meta.verbose_name_plural
+        dep_class = e.protected_objects[0]._meta.verbose_name_plural
     except IndexError:
         raise e
 
     # Grammar for single versus multiple triggering objects
     if type(obj) in (list, tuple):
-        err_message = "Unable to delete the requested {}. The following dependent {} were found: ".format(
+        err_message = u"Unable to delete the requested {}. The following dependent {} were found: ".format(
             obj[0]._meta.verbose_name_plural,
             dep_class,
         )
     else:
-        err_message = "Unable to delete {} {}. The following dependent {} were found: ".format(
+        err_message = u"Unable to delete {} {}. The following dependent {} were found: ".format(
             obj._meta.verbose_name,
             obj,
             dep_class,
@@ -26,11 +25,11 @@ def handle_protectederror(obj, request, e):
 
     # Append dependent objects to error message
     dependent_objects = []
-    for o in e[1]:
+    for o in e.protected_objects:
         if hasattr(o, 'get_absolute_url'):
-            dependent_objects.append('<a href="{}">{}</a>'.format(o.get_absolute_url(), str(o)))
+            dependent_objects.append(u'<a href="{}">{}</a>'.format(o.get_absolute_url(), o))
         else:
             dependent_objects.append(str(o))
-    err_message += ', '.join(dependent_objects)
+    err_message += u', '.join(dependent_objects)
 
     messages.error(request, err_message)

+ 8 - 6
netbox/utilities/forms.py

@@ -236,14 +236,15 @@ class CSVDataField(forms.CharField):
         if not self.help_text:
             self.help_text = 'Enter one line per record in CSV format.'
 
-    def utf_8_encoder(self, unicode_csv_data):
-        for line in unicode_csv_data:
-            yield line.encode('utf-8')
-
     def to_python(self, value):
-        # Return a list of dictionaries, each representing an individual record
+        """
+        Return a list of dictionaries, each representing an individual record
+        """
+        # Python 2's csv module has problems with Unicode
+        if not isinstance(value, str):
+            value = value.encode('utf-8')
         records = []
-        reader = csv.reader(self.utf_8_encoder(value.splitlines()))
+        reader = csv.reader(value.splitlines())
         for i, row in enumerate(reader, start=1):
             if row:
                 if len(row) < len(self.columns):
@@ -252,6 +253,7 @@ class CSVDataField(forms.CharField):
                 elif len(row) > len(self.columns):
                     raise forms.ValidationError("Line {}: Too many fields (found {}; expected {})"
                                                 .format(i, len(row), len(self.columns)))
+                row = [col.strip() for col in row]
                 record = dict(zip(self.columns, row))
                 records.append(record)
         return records

+ 18 - 7
netbox/utilities/utils.py

@@ -1,15 +1,26 @@
+import six
+
+
 def csv_format(data):
     """
     Encapsulate any data which contains a comma within double quotes.
     """
     csv = []
-    for d in data:
-        if d in [None, False]:
+    for value in data:
+
+        # Represent None or False with empty string
+        if value in [None, False]:
             csv.append(u'')
-        elif type(d) not in (str, unicode):
-            csv.append(u'{}'.format(d))
-        elif u',' in d:
-            csv.append(u'"{}"'.format(d))
+            continue
+
+        # Force conversion to string first so we can check for any commas
+        if not isinstance(value, six.string_types):
+            value = u'{}'.format(value)
+
+        # Double-quote the value if it contains a comma
+        if u',' in value:
+            csv.append(u'"{}"'.format(value))
         else:
-            csv.append(d)
+            csv.append(u'{}'.format(value))
+
     return u','.join(csv)

+ 1 - 1
netbox/utilities/views.py

@@ -228,7 +228,7 @@ class ObjectDeleteView(View):
             return get_object_or_404(self.model, pk=kwargs['pk'])
 
     def get_return_url(self, obj):
-        if hasattr(obj, 'get_absolute_url'):
+        if obj.pk and hasattr(obj, 'get_absolute_url'):
             return obj.get_absolute_url()
         return reverse(self.default_return_url)