Merge pull request #736 from digitalocean/develop

Release v1.7.3
This commit is contained in:
Jeremy Stretch 2016-12-08 12:34:53 -05:00 committed by GitHub
commit 8eb140fd65
23 changed files with 91 additions and 72 deletions

View File

@ -42,7 +42,7 @@ class ProviderEditView(PermissionRequiredMixin, ObjectEditView):
model = Provider
form_class = forms.ProviderForm
template_name = 'circuits/provider_edit.html'
cancel_url = 'circuits:provider_list'
obj_list_url = 'circuits:provider_list'
class ProviderDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -88,8 +88,8 @@ class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.change_circuittype'
model = CircuitType
form_class = forms.CircuitTypeForm
success_url = 'circuits:circuittype_list'
cancel_url = 'circuits:circuittype_list'
obj_list_url = 'circuits:circuittype_list'
use_obj_view = False
class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -126,7 +126,7 @@ class CircuitEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.CircuitForm
fields_initial = ['site']
template_name = 'circuits/circuit_edit.html'
cancel_url = 'circuits:circuit_list'
obj_list_url = 'circuits:circuit_list'
class CircuitDeleteView(PermissionRequiredMixin, ObjectDeleteView):

View File

@ -183,10 +183,14 @@ class DeviceAdmin(admin.ModelAdmin):
DeviceBayAdmin,
ModuleAdmin,
]
list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
list_display = ['display_name', 'device_type_full_name', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
'serial']
list_filter = ['device_role']
def get_queryset(self, request):
qs = super(DeviceAdmin, self).get_queryset(request)
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip4', 'primary_ip6', 'rack')
def device_type_full_name(self, obj):
return obj.device_type.full_name
device_type_full_name.short_description = 'Device type'

View File

@ -260,8 +260,11 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
).distinct()
def _mac_address(self, queryset, value):
value = value.strip()
if not value:
return queryset
try:
return queryset.filter(interfaces__mac_address=value.strip()).distinct()
return queryset.filter(interfaces__mac_address=value).distinct()
except AddrFormatError:
return queryset.none()

View File

@ -612,7 +612,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
platform = FilterChoiceField(queryset=Platform.objects.annotate(filter_count=Count('devices')),
to_field_name='slug', null_option=(0, 'None'))
status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES))
mac_address = forms.CharField(label='MAC address')
mac_address = forms.CharField(required=False, label='MAC address')
#

View File

@ -561,7 +561,7 @@ class DeviceType(models.Model):
]
def __unicode__(self):
return u'{} {}'.format(self.manufacturer, self.model)
return self.model
def __init__(self, *args, **kwargs):
super(DeviceType, self).__init__(*args, **kwargs)
@ -617,6 +617,10 @@ class DeviceType(models.Model):
'u_height': "Child device types must be 0U."
})
@property
def full_name(self):
return u'{} {}'.format(self.manufacturer.name, self.model)
@property
def is_parent_device(self):
return bool(self.subdevice_role)

View File

@ -294,7 +294,8 @@ class PlatformTable(BaseTable):
name = tables.LinkColumn(verbose_name='Name')
device_count = tables.Column(verbose_name='Devices')
slug = tables.Column(verbose_name='Slug')
actions = tables.TemplateColumn(template_code=PLATFORM_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
actions = tables.TemplateColumn(template_code=PLATFORM_ACTIONS, attrs={'td': {'class': 'text-right'}},
verbose_name='')
class Meta(BaseTable.Meta):
model = Platform
@ -313,7 +314,8 @@ class DeviceTable(BaseTable):
site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
device_type = tables.Column(verbose_name='Type')
device_type = tables.LinkColumn('dcim:devicetype', args=[Accessor('device_type.pk')], verbose_name='Type',
text=lambda record: record.device_type.full_name)
primary_ip = tables.TemplateColumn(orderable=False, verbose_name='IP Address',
template_code="{{ record.primary_ip.address.ip }}")

View File

@ -8,7 +8,6 @@ from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Count
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
@ -99,7 +98,7 @@ class SiteEditView(PermissionRequiredMixin, ObjectEditView):
model = Site
form_class = forms.SiteForm
template_name = 'dcim/site_edit.html'
cancel_url = 'dcim:site_list'
obj_list_url = 'dcim:site_list'
class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -141,8 +140,8 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_rackgroup'
model = RackGroup
form_class = forms.RackGroupForm
success_url = 'dcim:rackgroup_list'
cancel_url = 'dcim:rackgroup_list'
obj_list_url = 'dcim:rackgroup_list'
use_obj_view = False
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -166,8 +165,8 @@ class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_rackrole'
model = RackRole
form_class = forms.RackRoleForm
success_url = 'dcim:rackrole_list'
cancel_url = 'dcim:rackrole_list'
obj_list_url = 'dcim:rackrole_list'
use_obj_view = False
class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -214,7 +213,7 @@ class RackEditView(PermissionRequiredMixin, ObjectEditView):
model = Rack
form_class = forms.RackForm
template_name = 'dcim/rack_edit.html'
cancel_url = 'dcim:rack_list'
obj_list_url = 'dcim:rack_list'
class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -260,8 +259,8 @@ class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_manufacturer'
model = Manufacturer
form_class = forms.ManufacturerForm
success_url = 'dcim:manufacturer_list'
cancel_url = 'dcim:manufacturer_list'
obj_list_url = 'dcim:manufacturer_list'
use_obj_view = False
class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -332,7 +331,7 @@ class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_devicetype'
model = DeviceType
form_class = forms.DeviceTypeForm
cancel_url = 'dcim:devicetype_list'
obj_list_url = 'dcim:devicetype_list'
class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -497,8 +496,8 @@ class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_devicerole'
model = DeviceRole
form_class = forms.DeviceRoleForm
success_url = 'dcim:devicerole_list'
cancel_url = 'dcim:devicerole_list'
obj_list_url = 'dcim:devicerole_list'
use_obj_view = False
class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -522,8 +521,8 @@ class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_platform'
model = Platform
form_class = forms.PlatformForm
success_url = 'dcim:platform_list'
cancel_url = 'dcim:platform_list'
obj_list_url = 'dcim:platform_list'
use_obj_view = False
class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -617,7 +616,7 @@ class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.DeviceForm
fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
template_name = 'dcim/device_edit.html'
cancel_url = 'dcim:device_list'
obj_list_url = 'dcim:device_list'
class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):

View File

@ -49,7 +49,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
# Select
elif cf.type == CF_TYPE_SELECT:
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
if bulk_edit or filterable_only:
if not cf.required or bulk_edit or filterable_only:
choices = [(None, '---------')] + choices
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required)

View File

@ -1,4 +1,3 @@
from collections import OrderedDict
from django_tables2 import RequestConfig
import netaddr
@ -117,7 +116,7 @@ class VRFEditView(PermissionRequiredMixin, ObjectEditView):
model = VRF
form_class = forms.VRFForm
template_name = 'ipam/vrf_edit.html'
cancel_url = 'ipam:vrf_list'
obj_list_url = 'ipam:vrf_list'
class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -241,8 +240,8 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_rir'
model = RIR
form_class = forms.RIRForm
success_url = 'ipam:rir_list'
cancel_url = 'ipam:rir_list'
obj_list_url = 'ipam:rir_list'
use_obj_view = False
class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -307,7 +306,7 @@ class AggregateEditView(PermissionRequiredMixin, ObjectEditView):
model = Aggregate
form_class = forms.AggregateForm
template_name = 'ipam/aggregate_edit.html'
cancel_url = 'ipam:aggregate_list'
obj_list_url = 'ipam:aggregate_list'
class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -353,8 +352,8 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_role'
model = Role
form_class = forms.RoleForm
success_url = 'ipam:role_list'
cancel_url = 'ipam:role_list'
obj_list_url = 'ipam:role_list'
use_obj_view = False
class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -438,7 +437,7 @@ class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.PrefixForm
template_name = 'ipam/prefix_edit.html'
fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan']
cancel_url = 'ipam:prefix_list'
obj_list_url = 'ipam:prefix_list'
class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -602,7 +601,7 @@ class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.IPAddressForm
fields_initial = ['address', 'vrf']
template_name = 'ipam/ipaddress_edit.html'
cancel_url = 'ipam:ipaddress_list'
obj_list_url = 'ipam:ipaddress_list'
class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -665,8 +664,8 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_vlangroup'
model = VLANGroup
form_class = forms.VLANGroupForm
success_url = 'ipam:vlangroup_list'
cancel_url = 'ipam:vlangroup_list'
obj_list_url = 'ipam:vlangroup_list'
use_obj_view = False
class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -705,7 +704,7 @@ class VLANEditView(PermissionRequiredMixin, ObjectEditView):
model = VLAN
form_class = forms.VLANForm
template_name = 'ipam/vlan_edit.html'
cancel_url = 'ipam:vlan_list'
obj_list_url = 'ipam:vlan_list'
class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView):

View File

@ -12,7 +12,7 @@ except ImportError:
"the documentation.")
VERSION = '1.7.2-r1'
VERSION = '1.7.3'
# Import local configuration
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
@ -185,6 +185,8 @@ SECRETS_MIN_PUBKEY_SIZE = 2048
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
}
if LOGIN_REQUIRED:
REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = ('rest_framework.permissions.IsAuthenticated',)
# Swagger settings (API docs)
SWAGGER_SETTINGS = {

View File

@ -30,8 +30,8 @@ class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'secrets.change_secretrole'
model = SecretRole
form_class = forms.SecretRoleForm
success_url = 'secrets:secretrole_list'
cancel_url = 'secrets:secretrole_list'
obj_list_url = 'secrets:secretrole_list'
use_obj_view = False
class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):

View File

View File

@ -56,7 +56,7 @@
<tr>
<td>Device Type</td>
<td>
<span><a href="{% url 'dcim:devicetype' pk=device.device_type.pk %}">{{ device.device_type }}</a> ({{ device.device_type.u_height }}U)</span>
<span><a href="{% url 'dcim:devicetype' pk=device.device_type.pk %}">{{ device.device_type.full_name }}</a> ({{ device.device_type.u_height }}U)</span>
</td>
</tr>
<tr>
@ -293,7 +293,7 @@
<td>
<a href="{% url 'dcim:rack' pk=rd.rack.pk %}">Rack {{ rd.rack }}</a>
</td>
<td>{{ rd.device_type }}</td>
<td>{{ rd.device_type.full_name }}</td>
</tr>
{% endfor %}
</table>

View File

@ -24,7 +24,7 @@
{% for device in selected_devices %}
<tr>
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
<td>{{ device.device_type }}</td>
<td>{{ device.device_type.full_name }}</td>
<td>{{ device.device_role }}</td>
</tr>
{% endfor %}

View File

@ -14,7 +14,7 @@
{% for device in selected_objects %}
<tr>
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
<td>{{ device.device_type }}</td>
<td>{{ device.device_type.full_name }}</td>
<td>{{ device.device_role }}</td>
<td>{{ device.tenant }}</td>
<td>{{ device.serial }}</td>

View File

@ -13,7 +13,7 @@
<table class="table table-hover panel-body">
<tr>
<td>Model</td>
<td>{{ device.device_type }}</td>
<td>{{ device.device_type.full_name }}</td>
</tr>
<tr>
<td>Serial Number</td>

View File

@ -2,7 +2,7 @@
{% load helpers %}
{% load render_table from django_tables2 %}
{% block title %}{{ devicetype }}{% endblock %}
{% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
{% block content %}
<div class="row">
@ -32,7 +32,7 @@
</div>
{% endif %}
<h1>{{ devicetype }}</h1>
<h1>{{ devicetype.manufacturer }} {{ devicetype.model }}</h1>
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">

View File

@ -12,7 +12,7 @@
<a href="{% url 'dcim:device' pk=devicebay.installed_device.pk %}">{{ devicebay.installed_device }}</a>
</td>
<td>
<span>{{ devicebay.installed_device.device_type }}</span>
<span>{{ devicebay.installed_device.device_type.full_name }}</span>
</td>
{% else %}
<td colspan="2">

View File

@ -24,7 +24,7 @@
<li class="occupied h{{ u.device.device_type.u_height }}u"{% ifequal u.device.face face_id %} style="background-color: #{{ u.device.device_role.color }}"{% endifequal %}>
{% ifequal u.device.face face_id %}
<a href="{% url 'dcim:device' pk=u.device.pk %}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
data-content="{{ u.device.device_role }}<br />{{ u.device.device_type }} ({{ u.device.device_type.u_height }}U)">
data-content="{{ u.device.device_role }}<br />{{ u.device.device_type.full_name }} ({{ u.device.device_type.u_height }}U)">
{{ u.device.name|default:u.device.device_role }}
{% if u.device.devicebay_count %}
({{ u.device.get_children.count }}/{{ u.device.devicebay_count }})

View File

@ -153,8 +153,14 @@
<a href="{% url 'dcim:device' pk=device.pk %}">{{ device.name }}</a>
</td>
<td>{{ device.device_role }}</td>
<td>{{ device.device_type }}</td>
<td>{% if device.parent_bay %}<a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>{% endif %}</td>
<td>{{ device.device_type.full_name }}</td>
<td>
{% if device.parent_bay %}
<a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
{% endfor %}
</table>

View File

@ -28,8 +28,8 @@ class TenantGroupEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'tenancy.change_tenantgroup'
model = TenantGroup
form_class = forms.TenantGroupForm
success_url = 'tenancy:tenantgroup_list'
cancel_url = 'tenancy:tenantgroup_list'
obj_list_url = 'tenancy:tenantgroup_list'
use_obj_view = False
class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -83,7 +83,7 @@ class TenantEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.TenantForm
fields_initial = ['group']
template_name = 'tenancy/tenant_edit.html'
cancel_url = 'tenancy:tenant_list'
obj_list_url = 'tenancy:tenant_list'
class TenantDeleteView(PermissionRequiredMixin, ObjectDeleteView):

View File

@ -2,6 +2,7 @@ from django.http import HttpResponseRedirect
from django.conf import settings
BASE_PATH = getattr(settings, 'BASE_PATH', False)
LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False)
@ -11,5 +12,8 @@ class LoginRequiredMiddleware:
"""
def process_request(self, request):
if LOGIN_REQUIRED and not request.user.is_authenticated():
if request.path_info != settings.LOGIN_URL:
# Redirect unauthenticated requests to the login page. API requests are exempt from redirection as the API
# performs its own authentication.
api_path = '/{}api/'.format(BASE_PATH)
if not request.path_info.startswith(api_path) and request.path_info != settings.LOGIN_URL:
return HttpResponseRedirect('{}?next={}'.format(settings.LOGIN_URL, request.path_info))

View File

@ -119,8 +119,8 @@ class ObjectEditView(View):
form_class = None
fields_initial = []
template_name = 'utilities/obj_edit.html'
success_url = None
cancel_url = None
obj_list_url = None
use_obj_view = True
def get_object(self, kwargs):
# Look up object by slug if one has been provided. Otherwise, use PK.
@ -129,12 +129,13 @@ class ObjectEditView(View):
else:
return get_object_or_404(self.model, pk=kwargs['pk'])
def get_cancel_url(self, obj):
if hasattr(obj, 'get_absolute_url'):
return obj.get_absolute_url()
if hasattr(obj, 'get_parent_url'):
return obj.get_parent_url()
return reverse(self.cancel_url)
def get_redirect_url(self, obj):
if obj and self.use_obj_view:
if hasattr(obj, 'get_absolute_url'):
return obj.get_absolute_url()
if hasattr(obj, 'get_parent_url'):
return obj.get_parent_url()
return reverse(self.obj_list_url)
def get(self, request, *args, **kwargs):
@ -149,7 +150,7 @@ class ObjectEditView(View):
'obj': obj,
'obj_type': self.model._meta.verbose_name,
'form': form,
'cancel_url': self.get_cancel_url(obj),
'cancel_url': self.get_redirect_url(obj),
})
def post(self, request, *args, **kwargs):
@ -179,18 +180,13 @@ class ObjectEditView(View):
if '_addanother' in request.POST:
return redirect(request.path)
elif self.success_url:
return redirect(self.success_url)
elif hasattr(obj, 'get_absolute_url'):
return redirect(obj.get_absolute_url())
elif hasattr(obj, 'get_parent_url'):
return redirect(obj.get_parent_url())
return redirect(self.get_redirect_url(obj))
return render(request, self.template_name, {
'obj': obj,
'obj_type': self.model._meta.verbose_name,
'form': form,
'cancel_url': self.get_cancel_url(obj),
'cancel_url': self.get_redirect_url(obj),
})