Explorar el Código

Merged release v2.3.7

Jeremy Stretch hace 7 años
padre
commit
40efa55ec7

+ 0 - 49
.github/ISSUE_TEMPLATE.md

@@ -1,49 +0,0 @@
-<!--
-    Before opening a new issue, please search through the existing issues to
-    see if your topic has already been addressed. Note that you may need to
-    remove the "is:open" filter from the search bar to include closed issues.
-
-    Check the appropriate type for your issue below by placing an x between the
-    brackets. For assistance with installation issues, or for any other issues
-    other than those listed below, please raise your topic for discussion on
-    our mailing list:
-
-        https://groups.google.com/forum/#!forum/netbox-discuss
-
-    Please note that issues which do not fall under any of the below categories
-    will be closed. Due to an excessive backlog of feature requests, we are
-    not currently accepting any proposals which extend NetBox's feature scope.
-
-    Do not prepend any sort of tag to your issue's title. An administrator will
-    review your issue and assign labels as appropriate.
---->
-### Issue type
-[ ] Feature request <!-- An enhancement of existing functionality -->
-[ ] Bug report      <!-- Unexpected or erroneous behavior -->
-[ ] Documentation   <!-- A modification to the documentation -->
-[ ] Housekeeping    <!-- Changes pertaining to the codebase itself -->
-
-<!--
-    Please describe the environment in which you are running NetBox. (Be sure
-    to verify that you are running the latest stable release of NetBox before
-    submitting a bug report.) If you are submitting a bug report and have made
-    any changes to the code base, please first validate that your bug can be
-    recreated while running an official release.
--->
-### Environment
-* Python version:  <!-- Example: 3.5.4 -->
-* NetBox version:  <!-- Example: 2.3.5 -->
-
-<!--
-    BUG REPORTS must include:
-        * A list of the steps needed for someone else to reproduce the bug
-        * A description of the expected and observed behavior
-        * Any relevant error messages (screenshots may also help)
-
-    FEATURE REQUESTS must include:
-        * A detailed description of the proposed functionality
-        * A use case for the new feature
-        * A rough description of any necessary changes to the database schema
-        * Any relevant third-party libraries which would be needed
--->
-### Description

+ 35 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -0,0 +1,35 @@
+---
+name: 🐛 Bug Report
+about: Report a reproducible bug in the current release of NetBox
+
+---
+
+<!--
+    NOTE: This form is only for reproducible bugs. If you need assistance with
+    NetBox installation, or if you have a general question, DO NOT open an
+    issue. Instead, post to our mailing list:
+
+        https://groups.google.com/forum/#!forum/netbox-discuss
+
+    Please describe the environment in which you are running NetBox. Be sure
+    that you are running an unmodified instance of the latest stable release
+    before submitting a bug report.
+-->
+### Environment
+* Python version:  <!-- Example: 3.5.4 -->
+* NetBox version:  <!-- Example: 2.3.6 -->
+
+<!--
+    Describe in detail the steps that someone else can take to reproduce this
+    bug using the current stable release of NetBox (or the current beta release
+    where applicable).
+-->
+### Steps to Reproduce
+
+
+<!-- What did you expect to happen? -->
+### Expected Behavior
+
+
+<!-- What happened instead? -->
+### Observed Behavior

+ 18 - 0
.github/ISSUE_TEMPLATE/documentation_change.md

@@ -0,0 +1,18 @@
+---
+name: 📖 Documentation Change
+about: Suggest an addition or modification to the NetBox documentation
+
+---
+
+<!--
+    Please indicate the nature of the change by placing an X in one of the
+    boxes below.
+-->
+### Change Type
+[ ] Addition
+[ ] Correction
+[ ] Deprecation
+[ ] Cleanup (formatting, typos, etc.)
+
+<!-- Describe the proposed change(s). -->
+### Proposed Changes

+ 54 - 0
.github/ISSUE_TEMPLATE/feature_request.md

@@ -0,0 +1,54 @@
+---
+name: ✨ Feature Request
+about: Propose a new NetBox feature or enhancement
+
+---
+
+<!--
+    NOTE: This form is only for proposing specific new features or enhancements.
+    If you have a general idea or question, please post to our mailing list
+    instead of opening an issue:
+
+        https://groups.google.com/forum/#!forum/netbox-discuss
+
+    NOTE: Due to an excessive backlog of feature requests, we are not currently
+    accepting any proposals which significantly extend NetBox's feature scope.
+
+    Please describe the environment in which you are running NetBox. Be sure
+    that you are running an unmodified instance of the latest stable release
+    before submitting a bug report.
+-->
+### Environment
+* Python version:  <!-- Example: 3.5.4 -->
+* NetBox version:  <!-- Example: 2.3.6 -->
+
+<!--
+    Describe in detail the new functionality you are proposing. Include any
+    specific changes to work flows, data models, or the user interface.
+-->
+### Proposed Functionality
+
+
+<!--
+    Convey an example use case for your proposed feature. Write from the
+    perspective of a NetBox user who would benefit from the proposed
+    functionality and describe how.
+--->
+### Use Case
+
+
+<!--
+    Note any changes to the database schema necessary to support the new
+    feature. For example, does the proposal require adding a new model or
+    field? (Not all new features require database changes.)
+--->
+### Database Changes
+
+
+<!--
+    List any new dependencies on external libraries or services that this new
+    feature would introduce. For example, does the proposal require the
+    installation of a new Python package? (Not all new features introduce new
+    dependencies.)
+-->
+### External Dependencies

+ 17 - 0
.github/ISSUE_TEMPLATE/housekeeping.md

@@ -0,0 +1,17 @@
+---
+name: 🏡 Housekeeping
+about: A change pertaining to the codebase itself
+
+---
+
+<!--
+    NOTE: This type of issue should be opened only by those reasonably familiar
+    with NetBox's code base and interested in contributing to its development.
+
+    Describe the proposed change(s) in detail.
+-->
+### Proposed Changes
+
+
+<!-- Provide justification for the proposed change(s). -->
+### Justification

+ 29 - 11
netbox/ipam/tables.py

@@ -203,17 +203,35 @@ class RIRTable(BaseTable):
 
 
 
 
 class RIRDetailTable(RIRTable):
 class RIRDetailTable(RIRTable):
-    stats_total = tables.Column(accessor='stats.total', verbose_name='Total',
-                                footer=lambda table: sum(r.stats['total'] for r in table.data))
-    stats_active = tables.Column(accessor='stats.active', verbose_name='Active',
-                                 footer=lambda table: sum(r.stats['active'] for r in table.data))
-    stats_reserved = tables.Column(accessor='stats.reserved', verbose_name='Reserved',
-                                   footer=lambda table: sum(r.stats['reserved'] for r in table.data))
-    stats_deprecated = tables.Column(accessor='stats.deprecated', verbose_name='Deprecated',
-                                     footer=lambda table: sum(r.stats['deprecated'] for r in table.data))
-    stats_available = tables.Column(accessor='stats.available', verbose_name='Available',
-                                    footer=lambda table: sum(r.stats['available'] for r in table.data))
-    utilization = tables.TemplateColumn(template_code=RIR_UTILIZATION, verbose_name='Utilization')
+    stats_total = tables.Column(
+        accessor='stats.total',
+        verbose_name='Total',
+        footer=lambda table: sum(r.stats['total'] for r in table.data)
+    )
+    stats_active = tables.Column(
+        accessor='stats.active',
+        verbose_name='Active',
+        footer=lambda table: sum(r.stats['active'] for r in table.data)
+    )
+    stats_reserved = tables.Column(
+        accessor='stats.reserved',
+        verbose_name='Reserved',
+        footer=lambda table: sum(r.stats['reserved'] for r in table.data)
+    )
+    stats_deprecated = tables.Column(
+        accessor='stats.deprecated',
+        verbose_name='Deprecated',
+        footer=lambda table: sum(r.stats['deprecated'] for r in table.data)
+    )
+    stats_available = tables.Column(
+        accessor='stats.available',
+        verbose_name='Available',
+        footer=lambda table: sum(r.stats['available'] for r in table.data)
+    )
+    utilization = tables.TemplateColumn(
+        template_code=RIR_UTILIZATION,
+        verbose_name='Utilization'
+    )
 
 
     class Meta(RIRTable.Meta):
     class Meta(RIRTable.Meta):
         fields = (
         fields = (

+ 14 - 22
netbox/ipam/views.py

@@ -189,9 +189,15 @@ class RIRListView(ObjectListView):
                 queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix))
                 queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix))
 
 
                 # Find all consumed space for each prefix status (we ignore containers for this purpose).
                 # Find all consumed space for each prefix status (we ignore containers for this purpose).
-                active_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)])
-                reserved_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)])
-                deprecated_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)])
+                active_prefixes = netaddr.cidr_merge(
+                    [p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)]
+                )
+                reserved_prefixes = netaddr.cidr_merge(
+                    [p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)]
+                )
+                deprecated_prefixes = netaddr.cidr_merge(
+                    [p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)]
+                )
 
 
                 # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix.
                 # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix.
                 available_prefixes = (
                 available_prefixes = (
@@ -202,11 +208,11 @@ class RIRListView(ObjectListView):
                 )
                 )
 
 
                 # Add the size of each metric to the RIR total.
                 # Add the size of each metric to the RIR total.
-                stats['total'] += aggregate.prefix.size / denominator
-                stats['active'] += netaddr.IPSet(active_prefixes).size / denominator
-                stats['reserved'] += netaddr.IPSet(reserved_prefixes).size / denominator
-                stats['deprecated'] += netaddr.IPSet(deprecated_prefixes).size / denominator
-                stats['available'] += available_prefixes.size / denominator
+                stats['total'] += int(aggregate.prefix.size / denominator)
+                stats['active'] += int(netaddr.IPSet(active_prefixes).size / denominator)
+                stats['reserved'] += int(netaddr.IPSet(reserved_prefixes).size / denominator)
+                stats['deprecated'] += int(netaddr.IPSet(deprecated_prefixes).size / denominator)
+                stats['available'] += int(available_prefixes.size / denominator)
 
 
             # Calculate the percentage of total space for each prefix status.
             # Calculate the percentage of total space for each prefix status.
             total = float(stats['total'])
             total = float(stats['total'])
@@ -226,20 +232,6 @@ class RIRListView(ObjectListView):
 
 
         return rirs
         return rirs
 
 
-    def extra_context(self):
-
-        totals = {
-            'total': sum([rir.stats['total'] for rir in self.queryset]),
-            'active': sum([rir.stats['active'] for rir in self.queryset]),
-            'reserved': sum([rir.stats['reserved'] for rir in self.queryset]),
-            'deprecated': sum([rir.stats['deprecated'] for rir in self.queryset]),
-            'available': sum([rir.stats['available'] for rir in self.queryset]),
-        }
-
-        return {
-            'totals': totals,
-        }
-
 
 
 class RIRCreateView(PermissionRequiredMixin, ObjectEditView):
 class RIRCreateView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'ipam.add_rir'
     permission_required = 'ipam.add_rir'

+ 2 - 0
netbox/netbox/urls.py

@@ -80,3 +80,5 @@ if settings.DEBUG:
 urlpatterns = [
 urlpatterns = [
     url(r'^{}'.format(settings.BASE_PATH), include(_patterns))
     url(r'^{}'.format(settings.BASE_PATH), include(_patterns))
 ]
 ]
+
+handler500 = 'utilities.views.server_error'

+ 8 - 1
netbox/project-static/css/base.css

@@ -376,12 +376,19 @@ table.reports td.method {
     font-family: monospace;
     font-family: monospace;
     padding-left: 30px;
     padding-left: 30px;
 }
 }
-table.reports td.stats label {
+td.report-stats label {
     display: inline-block;
     display: inline-block;
     line-height: 14px;
     line-height: 14px;
     margin-bottom: 0;
     margin-bottom: 0;
     min-width: 40px;
     min-width: 40px;
 }
 }
+table.report th {
+    position: relative;
+}
+table.report th a {
+    position: absolute;
+    top: -51px;
+}
 
 
 /* AJAX loader */
 /* AJAX loader */
 .loading {
 .loading {

+ 56 - 46
netbox/templates/extras/report.html

@@ -29,63 +29,73 @@
                 <p class="lead">{{ report.description }}</p>
                 <p class="lead">{{ report.description }}</p>
             {% endif %}
             {% endif %}
             {% if report.result %}
             {% if report.result %}
-                <p>Last run: {{ report.result.created }}</p>
-            {% else %}
-                <p class="text-muted">Last run: Never</p>
+                <p>Last run: <strong>{{ report.result.created }}</strong></p>
             {% endif %}
             {% endif %}
-        </div>
-        <div class="col-md-9">
             {% if report.result %}
             {% if report.result %}
-                <table class="table table-hover">
-                    <thead>
-                        <tr>
-                            <th>Time</th>
-                            <th>Level</th>
-                            <th>Object</th>
-                            <th>Message</th>
-                        </tr>
-                    </thead>
-                    {% for method, data in report.result.data.items %}
-                        <tr>
-                            <th colspan="4"><a name="{{ method }}"></a>{{ method }}</th>
-                        </tr>
-                        {% for time, level, obj, url, message in data.log %}
-                            <tr class="{% if level == 'failure' %}danger{% elif level %}{{ level }}{% endif %}">
-                                <td>{{ time }}</td>
-                                <td>
-                                    <label class="label label-{% if level == 'failure' %}danger{% else %}{{ level }}{% endif %}">{{ level|title }}</label>
-                                </td>
-                                <td>
-                                    {% if obj and url %}
-                                        <a href="{{ url }}">{{ obj }}</a>
-                                    {% elif obj %}
-                                        {{ obj }}
-                                    {% endif %}
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <strong>Report Methods</strong>
+                    </div>
+                    <table class="table table-hover panel-body">
+                        {% for method, data in report.result.data.items %}
+                            <tr>
+                                <td><code><a href="#{{ method }}">{{ method }}</a></code></td>
+                                <td class="text-right report-stats">
+                                    <label class="label label-success">{{ data.success }}</label>
+                                    <label class="label label-info">{{ data.info }}</label>
+                                    <label class="label label-warning">{{ data.warning }}</label>
+                                    <label class="label label-danger">{{ data.failure }}</label>
                                 </td>
                                 </td>
-                                <td>{{ message }}</td>
                             </tr>
                             </tr>
                         {% endfor %}
                         {% endfor %}
-                    {% endfor %}
-                </table>
+                    </table>
+                </div>
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <strong>Report Results</strong>
+                    </div>
+                    <table class="table table-hover panel-body report">
+                        <thead>
+                            <tr class="table-headings">
+                                <th>Time</th>
+                                <th>Level</th>
+                                <th>Object</th>
+                                <th>Message</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {% for method, data in report.result.data.items %}
+                                <tr>
+                                    <th colspan="4" style="font-family: monospace">
+                                        <a name="{{ method }}"></a>{{ method }}
+                                    </th>
+                                </tr>
+                                {% for time, level, obj, url, message in data.log %}
+                                    <tr class="{% if level == 'failure' %}danger{% elif level %}{{ level }}{% endif %}">
+                                        <td>{{ time }}</td>
+                                        <td>
+                                            <label class="label label-{% if level == 'failure' %}danger{% else %}{{ level }}{% endif %}">{{ level|title }}</label>
+                                        </td>
+                                        <td>
+                                            {% if obj and url %}
+                                                <a href="{{ url }}">{{ obj }}</a>
+                                            {% elif obj %}
+                                                {{ obj }}
+                                            {% endif %}
+                                        </td>
+                                        <td>{{ message }}</td>
+                                    </tr>
+                                {% endfor %}
+                            {% endfor %}
+                        </tbody>
+                    </table>
+                </div>
             {% else %}
             {% else %}
                 <div class="well">No results are available for this report. Please run the report first.</div>
                 <div class="well">No results are available for this report. Please run the report first.</div>
             {% endif %}
             {% endif %}
         </div>
         </div>
         <div class="col-md-3">
         <div class="col-md-3">
             {% if report.result %}
             {% if report.result %}
-                <div class="panel panel-default">
-                    <div class="panel-heading">
-                        <strong>Methods</strong>
-                    </div>
-                    <ul class="list-group">
-                        {% for method, data in report.result.data.items %}
-                            <li class="list-group-item">
-                                <a href="#{{ method }}">{{ method }}</a>
-                                <span class="badge">{{ data.log|length }}</span>
-                            </li>
-                        {% endfor %}
-                    </ul>
-                </div>
             {% endif %}
             {% endif %}
         </div>
         </div>
     </div>
     </div>

+ 2 - 2
netbox/templates/extras/report_list.html

@@ -38,7 +38,7 @@
                                         <td colspan="3" class="method">
                                         <td colspan="3" class="method">
                                             {{ method }}
                                             {{ method }}
                                         </td>
                                         </td>
-                                        <td class="text-right stats">
+                                        <td class="text-right report-stats">
                                             <label class="label label-success">{{ stats.success }}</label>
                                             <label class="label label-success">{{ stats.success }}</label>
                                             <label class="label label-info">{{ stats.info }}</label>
                                             <label class="label label-info">{{ stats.info }}</label>
                                             <label class="label label-warning">{{ stats.warning }}</label>
                                             <label class="label label-warning">{{ stats.warning }}</label>
@@ -69,7 +69,7 @@
                                 <a href="#report.{{ report.name }}" class="list-group-item">
                                 <a href="#report.{{ report.name }}" class="list-group-item">
                                     <i class="fa fa-list-alt"></i> {{ report.name }}
                                     <i class="fa fa-list-alt"></i> {{ report.name }}
                                     <div class="pull-right">
                                     <div class="pull-right">
-                                        {% include 'extras/inc/report_label.html' %}
+                                        {% include 'extras/inc/report_label.html' with result=report.result %}
                                     </div>
                                     </div>
                                 </a>
                                 </a>
                             {% endfor %}
                             {% endfor %}

+ 11 - 14
netbox/utilities/middleware.py

@@ -5,9 +5,10 @@ import sys
 from django.conf import settings
 from django.conf import settings
 from django.db import ProgrammingError
 from django.db import ProgrammingError
 from django.http import Http404, HttpResponseRedirect
 from django.http import Http404, HttpResponseRedirect
-from django.shortcuts import render
 from django.urls import reverse
 from django.urls import reverse
 
 
+from .views import server_error
+
 BASE_PATH = getattr(settings, 'BASE_PATH', False)
 BASE_PATH = getattr(settings, 'BASE_PATH', False)
 LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False)
 LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False)
 
 
@@ -65,23 +66,19 @@ class ExceptionHandlingMiddleware(object):
         if isinstance(exception, Http404):
         if isinstance(exception, Http404):
             return
             return
 
 
-        # Determine the type of exception
+        # Determine the type of exception. If it's a common issue, return a custom error page with instructions.
+        custom_template = None
         if isinstance(exception, ProgrammingError):
         if isinstance(exception, ProgrammingError):
-            template_name = 'exceptions/programming_error.html'
+            custom_template = 'exceptions/programming_error.html'
         elif isinstance(exception, ImportError):
         elif isinstance(exception, ImportError):
-            template_name = 'exceptions/import_error.html'
+            custom_template = 'exceptions/import_error.html'
         elif (
         elif (
             sys.version_info[0] >= 3 and isinstance(exception, PermissionError)
             sys.version_info[0] >= 3 and isinstance(exception, PermissionError)
         ) or (
         ) or (
             isinstance(exception, OSError) and exception.errno == 13
             isinstance(exception, OSError) and exception.errno == 13
         ):
         ):
-            template_name = 'exceptions/permission_error.html'
-        else:
-            template_name = '500.html'
-
-        # Return an error message
-        type_, error, traceback = sys.exc_info()
-        return render(request, template_name, {
-            'exception': str(type_),
-            'error': error,
-        }, status=500)
+            custom_template = 'exceptions/permission_error.html'
+
+        # Return a custom error message, or fall back to Django's default 500 error handling
+        if custom_template:
+            return server_error(request, template_name=custom_template)

+ 23 - 1
netbox/utilities/views.py

@@ -2,6 +2,7 @@ from __future__ import unicode_literals
 
 
 from collections import OrderedDict
 from collections import OrderedDict
 from copy import deepcopy
 from copy import deepcopy
+import sys
 
 
 from django.conf import settings
 from django.conf import settings
 from django.contrib import messages
 from django.contrib import messages
@@ -10,12 +11,16 @@ from django.core.exceptions import ValidationError
 from django.db import transaction, IntegrityError
 from django.db import transaction, IntegrityError
 from django.db.models import Count, ProtectedError
 from django.db.models import Count, ProtectedError
 from django.forms import CharField, Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
 from django.forms import CharField, Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
+from django.http import HttpResponseServerError
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
-from django.template.exceptions import TemplateSyntaxError
+from django.template import loader
+from django.template.exceptions import TemplateDoesNotExist, TemplateSyntaxError
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.html import escape
 from django.utils.html import escape
 from django.utils.http import is_safe_url
 from django.utils.http import is_safe_url
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
+from django.views.decorators.csrf import requires_csrf_token
+from django.views.defaults import ERROR_500_TEMPLATE_NAME
 from django.views.generic import View
 from django.views.generic import View
 from django_tables2 import RequestConfig
 from django_tables2 import RequestConfig
 
 
@@ -844,3 +849,20 @@ class BulkComponentCreateView(GetReturnURLMixin, View):
             'table': table,
             'table': table,
             'return_url': self.get_return_url(request),
             'return_url': self.get_return_url(request),
         })
         })
+
+
+@requires_csrf_token
+def server_error(request, template_name=ERROR_500_TEMPLATE_NAME):
+    """
+    Custom 500 handler to provide additional context when rendering 500.html.
+    """
+    try:
+        template = loader.get_template(template_name)
+    except TemplateDoesNotExist:
+        return HttpResponseServerError('<h1>Server Error (500)</h1>', content_type='text/html')
+    type_, error, traceback = sys.exc_info()
+
+    return HttpResponseServerError(template.render({
+        'exception': str(type_),
+        'error': error,
+    }))