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

View File

@ -183,10 +183,14 @@ class DeviceAdmin(admin.ModelAdmin):
DeviceBayAdmin, DeviceBayAdmin,
ModuleAdmin, 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'] 'serial']
list_filter = ['device_role'] list_filter = ['device_role']
def get_queryset(self, request): def get_queryset(self, request):
qs = super(DeviceAdmin, self).get_queryset(request) qs = super(DeviceAdmin, self).get_queryset(request)
return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip4', 'primary_ip6', 'rack') 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() ).distinct()
def _mac_address(self, queryset, value): def _mac_address(self, queryset, value):
value = value.strip()
if not value:
return queryset
try: try:
return queryset.filter(interfaces__mac_address=value.strip()).distinct() return queryset.filter(interfaces__mac_address=value).distinct()
except AddrFormatError: except AddrFormatError:
return queryset.none() return queryset.none()

View File

@ -612,7 +612,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
platform = FilterChoiceField(queryset=Platform.objects.annotate(filter_count=Count('devices')), platform = FilterChoiceField(queryset=Platform.objects.annotate(filter_count=Count('devices')),
to_field_name='slug', null_option=(0, 'None')) to_field_name='slug', null_option=(0, 'None'))
status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES)) 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): def __unicode__(self):
return u'{} {}'.format(self.manufacturer, self.model) return self.model
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(DeviceType, self).__init__(*args, **kwargs) super(DeviceType, self).__init__(*args, **kwargs)
@ -617,6 +617,10 @@ class DeviceType(models.Model):
'u_height': "Child device types must be 0U." 'u_height': "Child device types must be 0U."
}) })
@property
def full_name(self):
return u'{} {}'.format(self.manufacturer.name, self.model)
@property @property
def is_parent_device(self): def is_parent_device(self):
return bool(self.subdevice_role) return bool(self.subdevice_role)

View File

@ -294,7 +294,8 @@ class PlatformTable(BaseTable):
name = tables.LinkColumn(verbose_name='Name') name = tables.LinkColumn(verbose_name='Name')
device_count = tables.Column(verbose_name='Devices') device_count = tables.Column(verbose_name='Devices')
slug = tables.Column(verbose_name='Slug') 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): class Meta(BaseTable.Meta):
model = Platform model = Platform
@ -313,7 +314,8 @@ class DeviceTable(BaseTable):
site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site') site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack') rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role') 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', primary_ip = tables.TemplateColumn(orderable=False, verbose_name='IP Address',
template_code="{{ record.primary_ip.address.ip }}") 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.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Count from django.db.models import Count
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
@ -99,7 +98,7 @@ class SiteEditView(PermissionRequiredMixin, ObjectEditView):
model = Site model = Site
form_class = forms.SiteForm form_class = forms.SiteForm
template_name = 'dcim/site_edit.html' template_name = 'dcim/site_edit.html'
cancel_url = 'dcim:site_list' obj_list_url = 'dcim:site_list'
class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView): class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -141,8 +140,8 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_rackgroup' permission_required = 'dcim.change_rackgroup'
model = RackGroup model = RackGroup
form_class = forms.RackGroupForm form_class = forms.RackGroupForm
success_url = 'dcim:rackgroup_list' obj_list_url = 'dcim:rackgroup_list'
cancel_url = 'dcim:rackgroup_list' use_obj_view = False
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -166,8 +165,8 @@ class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_rackrole' permission_required = 'dcim.change_rackrole'
model = RackRole model = RackRole
form_class = forms.RackRoleForm form_class = forms.RackRoleForm
success_url = 'dcim:rackrole_list' obj_list_url = 'dcim:rackrole_list'
cancel_url = 'dcim:rackrole_list' use_obj_view = False
class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -214,7 +213,7 @@ class RackEditView(PermissionRequiredMixin, ObjectEditView):
model = Rack model = Rack
form_class = forms.RackForm form_class = forms.RackForm
template_name = 'dcim/rack_edit.html' template_name = 'dcim/rack_edit.html'
cancel_url = 'dcim:rack_list' obj_list_url = 'dcim:rack_list'
class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView): class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -260,8 +259,8 @@ class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_manufacturer' permission_required = 'dcim.change_manufacturer'
model = Manufacturer model = Manufacturer
form_class = forms.ManufacturerForm form_class = forms.ManufacturerForm
success_url = 'dcim:manufacturer_list' obj_list_url = 'dcim:manufacturer_list'
cancel_url = 'dcim:manufacturer_list' use_obj_view = False
class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -332,7 +331,7 @@ class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_devicetype' permission_required = 'dcim.change_devicetype'
model = DeviceType model = DeviceType
form_class = forms.DeviceTypeForm form_class = forms.DeviceTypeForm
cancel_url = 'dcim:devicetype_list' obj_list_url = 'dcim:devicetype_list'
class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView): class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -497,8 +496,8 @@ class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_devicerole' permission_required = 'dcim.change_devicerole'
model = DeviceRole model = DeviceRole
form_class = forms.DeviceRoleForm form_class = forms.DeviceRoleForm
success_url = 'dcim:devicerole_list' obj_list_url = 'dcim:devicerole_list'
cancel_url = 'dcim:devicerole_list' use_obj_view = False
class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -522,8 +521,8 @@ class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_platform' permission_required = 'dcim.change_platform'
model = Platform model = Platform
form_class = forms.PlatformForm form_class = forms.PlatformForm
success_url = 'dcim:platform_list' obj_list_url = 'dcim:platform_list'
cancel_url = 'dcim:platform_list' use_obj_view = False
class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -617,7 +616,7 @@ class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.DeviceForm form_class = forms.DeviceForm
fields_initial = ['site', 'rack', 'position', 'face', 'device_bay'] fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
template_name = 'dcim/device_edit.html' template_name = 'dcim/device_edit.html'
cancel_url = 'dcim:device_list' obj_list_url = 'dcim:device_list'
class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView): 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 # Select
elif cf.type == CF_TYPE_SELECT: elif cf.type == CF_TYPE_SELECT:
choices = [(cfc.pk, cfc) for cfc in cf.choices.all()] 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 choices = [(None, '---------')] + choices
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required) 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 from django_tables2 import RequestConfig
import netaddr import netaddr
@ -117,7 +116,7 @@ class VRFEditView(PermissionRequiredMixin, ObjectEditView):
model = VRF model = VRF
form_class = forms.VRFForm form_class = forms.VRFForm
template_name = 'ipam/vrf_edit.html' template_name = 'ipam/vrf_edit.html'
cancel_url = 'ipam:vrf_list' obj_list_url = 'ipam:vrf_list'
class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView): class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -241,8 +240,8 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_rir' permission_required = 'ipam.change_rir'
model = RIR model = RIR
form_class = forms.RIRForm form_class = forms.RIRForm
success_url = 'ipam:rir_list' obj_list_url = 'ipam:rir_list'
cancel_url = 'ipam:rir_list' use_obj_view = False
class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -307,7 +306,7 @@ class AggregateEditView(PermissionRequiredMixin, ObjectEditView):
model = Aggregate model = Aggregate
form_class = forms.AggregateForm form_class = forms.AggregateForm
template_name = 'ipam/aggregate_edit.html' template_name = 'ipam/aggregate_edit.html'
cancel_url = 'ipam:aggregate_list' obj_list_url = 'ipam:aggregate_list'
class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView): class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -353,8 +352,8 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_role' permission_required = 'ipam.change_role'
model = Role model = Role
form_class = forms.RoleForm form_class = forms.RoleForm
success_url = 'ipam:role_list' obj_list_url = 'ipam:role_list'
cancel_url = 'ipam:role_list' use_obj_view = False
class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -438,7 +437,7 @@ class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.PrefixForm form_class = forms.PrefixForm
template_name = 'ipam/prefix_edit.html' template_name = 'ipam/prefix_edit.html'
fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan'] fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan']
cancel_url = 'ipam:prefix_list' obj_list_url = 'ipam:prefix_list'
class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView): class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -602,7 +601,7 @@ class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
form_class = forms.IPAddressForm form_class = forms.IPAddressForm
fields_initial = ['address', 'vrf'] fields_initial = ['address', 'vrf']
template_name = 'ipam/ipaddress_edit.html' template_name = 'ipam/ipaddress_edit.html'
cancel_url = 'ipam:ipaddress_list' obj_list_url = 'ipam:ipaddress_list'
class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView): class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@ -665,8 +664,8 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_vlangroup' permission_required = 'ipam.change_vlangroup'
model = VLANGroup model = VLANGroup
form_class = forms.VLANGroupForm form_class = forms.VLANGroupForm
success_url = 'ipam:vlangroup_list' obj_list_url = 'ipam:vlangroup_list'
cancel_url = 'ipam:vlangroup_list' use_obj_view = False
class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@ -705,7 +704,7 @@ class VLANEditView(PermissionRequiredMixin, ObjectEditView):
model = VLAN model = VLAN
form_class = forms.VLANForm form_class = forms.VLANForm
template_name = 'ipam/vlan_edit.html' template_name = 'ipam/vlan_edit.html'
cancel_url = 'ipam:vlan_list' obj_list_url = 'ipam:vlan_list'
class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView): class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView):

View File

@ -12,7 +12,7 @@ except ImportError:
"the documentation.") "the documentation.")
VERSION = '1.7.2-r1' VERSION = '1.7.3'
# Import local configuration # Import local configuration
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']: for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
@ -185,6 +185,8 @@ SECRETS_MIN_PUBKEY_SIZE = 2048
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',) '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 (API docs)
SWAGGER_SETTINGS = { SWAGGER_SETTINGS = {

View File

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

View File

View File

@ -56,7 +56,7 @@
<tr> <tr>
<td>Device Type</td> <td>Device Type</td>
<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> </td>
</tr> </tr>
<tr> <tr>
@ -293,7 +293,7 @@
<td> <td>
<a href="{% url 'dcim:rack' pk=rd.rack.pk %}">Rack {{ rd.rack }}</a> <a href="{% url 'dcim:rack' pk=rd.rack.pk %}">Rack {{ rd.rack }}</a>
</td> </td>
<td>{{ rd.device_type }}</td> <td>{{ rd.device_type.full_name }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
{% load helpers %} {% load helpers %}
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% block title %}{{ devicetype }}{% endblock %} {% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
@ -32,7 +32,7 @@
</div> </div>
{% endif %} {% endif %}
<h1>{{ devicetype }}</h1> <h1>{{ devicetype.manufacturer }} {{ devicetype.model }}</h1>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="panel panel-default"> <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> <a href="{% url 'dcim:device' pk=devicebay.installed_device.pk %}">{{ devicebay.installed_device }}</a>
</td> </td>
<td> <td>
<span>{{ devicebay.installed_device.device_type }}</span> <span>{{ devicebay.installed_device.device_type.full_name }}</span>
</td> </td>
{% else %} {% else %}
<td colspan="2"> <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 %}> <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 %} {% 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" <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 }} {{ u.device.name|default:u.device.device_role }}
{% if u.device.devicebay_count %} {% if u.device.devicebay_count %}
({{ u.device.get_children.count }}/{{ 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> <a href="{% url 'dcim:device' pk=device.pk %}">{{ device.name }}</a>
</td> </td>
<td>{{ device.device_role }}</td> <td>{{ device.device_role }}</td>
<td>{{ device.device_type }}</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>{% endif %}</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> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

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

View File

@ -2,6 +2,7 @@ from django.http import HttpResponseRedirect
from django.conf import settings from django.conf import settings
BASE_PATH = getattr(settings, 'BASE_PATH', False)
LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False) LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False)
@ -11,5 +12,8 @@ class LoginRequiredMiddleware:
""" """
def process_request(self, request): def process_request(self, request):
if LOGIN_REQUIRED and not request.user.is_authenticated(): 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)) return HttpResponseRedirect('{}?next={}'.format(settings.LOGIN_URL, request.path_info))

View File

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