mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-15 11:42:52 -06:00
Closes #1283: Added a time zone field to the site model
This commit is contained in:
parent
9984238f2a
commit
b20258c66e
@ -20,7 +20,7 @@ from extras.api.customfields import CustomFieldModelSerializer
|
|||||||
from ipam.models import IPAddress, VLAN
|
from ipam.models import IPAddress, VLAN
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from users.api.serializers import NestedUserSerializer
|
from users.api.serializers import NestedUserSerializer
|
||||||
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
from utilities.api import ChoiceFieldSerializer, TimeZoneField, ValidatedModelSerializer
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
|
|
||||||
|
|
||||||
@ -58,13 +58,14 @@ class WritableRegionSerializer(ValidatedModelSerializer):
|
|||||||
class SiteSerializer(CustomFieldModelSerializer):
|
class SiteSerializer(CustomFieldModelSerializer):
|
||||||
region = NestedRegionSerializer()
|
region = NestedRegionSerializer()
|
||||||
tenant = NestedTenantSerializer()
|
tenant = NestedTenantSerializer()
|
||||||
|
time_zone = TimeZoneField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
|
'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
|
||||||
'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields', 'count_prefixes',
|
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
|
||||||
'count_vlans', 'count_racks', 'count_devices', 'count_circuits',
|
'count_prefixes', 'count_vlans', 'count_racks', 'count_devices', 'count_circuits',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -77,12 +78,13 @@ class NestedSiteSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class WritableSiteSerializer(CustomFieldModelSerializer):
|
class WritableSiteSerializer(CustomFieldModelSerializer):
|
||||||
|
time_zone = TimeZoneField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
|
'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
|
||||||
'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
|
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.contrib.postgres.forms.array import SimpleArrayField
|
from django.contrib.postgres.forms.array import SimpleArrayField
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from mptt.forms import TreeNodeChoiceField
|
from mptt.forms import TreeNodeChoiceField
|
||||||
|
from timezone_field import TimeZoneFormField
|
||||||
|
|
||||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from ipam.models import IPAddress, VLAN, VLANGroup
|
from ipam.models import IPAddress, VLAN, VLANGroup
|
||||||
@ -96,7 +97,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'region', 'tenant_group', 'tenant', 'facility', 'asn', 'physical_address',
|
'name', 'slug', 'region', 'tenant_group', 'tenant', 'facility', 'asn', 'physical_address',
|
||||||
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments',
|
'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'time_zone', 'comments',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'physical_address': SmallTextarea(attrs={'rows': 3}),
|
'physical_address': SmallTextarea(attrs={'rows': 3}),
|
||||||
@ -135,7 +136,7 @@ class SiteCSVForm(forms.ModelForm):
|
|||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
|
'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address',
|
||||||
'contact_name', 'contact_phone', 'contact_email', 'comments',
|
'contact_name', 'contact_phone', 'contact_email', 'time_zone', 'comments',
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'name': 'Site name',
|
'name': 'Site name',
|
||||||
@ -149,9 +150,10 @@ class SiteBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
|||||||
region = TreeNodeChoiceField(queryset=Region.objects.all(), required=False)
|
region = TreeNodeChoiceField(queryset=Region.objects.all(), required=False)
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
||||||
asn = forms.IntegerField(min_value=1, max_value=4294967295, required=False, label='ASN')
|
asn = forms.IntegerField(min_value=1, max_value=4294967295, required=False, label='ASN')
|
||||||
|
time_zone = TimeZoneFormField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = ['region', 'tenant', 'asn']
|
nullable_fields = ['region', 'tenant', 'asn', 'time_zone']
|
||||||
|
|
||||||
|
|
||||||
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class SiteFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
|
21
netbox/dcim/migrations/0054_site_time_zone.py
Normal file
21
netbox/dcim/migrations/0054_site_time_zone.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2017-12-19 21:53
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import timezone_field.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0053_platform_manufacturer'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='site',
|
||||||
|
name='time_zone',
|
||||||
|
field=timezone_field.fields.TimeZoneField(blank=True),
|
||||||
|
),
|
||||||
|
]
|
@ -14,6 +14,7 @@ from django.db.models import Count, Q, ObjectDoesNotExist
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
from timezone_field import TimeZoneField
|
||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from extras.models import CustomFieldModel, CustomFieldValue, ImageAttachment
|
from extras.models import CustomFieldModel, CustomFieldValue, ImageAttachment
|
||||||
@ -86,6 +87,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
tenant = models.ForeignKey(Tenant, related_name='sites', blank=True, null=True, on_delete=models.PROTECT)
|
tenant = models.ForeignKey(Tenant, related_name='sites', blank=True, null=True, on_delete=models.PROTECT)
|
||||||
facility = models.CharField(max_length=50, blank=True)
|
facility = models.CharField(max_length=50, blank=True)
|
||||||
asn = ASNField(blank=True, null=True, verbose_name='ASN')
|
asn = ASNField(blank=True, null=True, verbose_name='ASN')
|
||||||
|
time_zone = TimeZoneField(blank=True)
|
||||||
physical_address = models.CharField(max_length=200, blank=True)
|
physical_address = models.CharField(max_length=200, blank=True)
|
||||||
shipping_address = models.CharField(max_length=200, blank=True)
|
shipping_address = models.CharField(max_length=200, blank=True)
|
||||||
contact_name = models.CharField(max_length=50, blank=True)
|
contact_name = models.CharField(max_length=50, blank=True)
|
||||||
@ -98,7 +100,8 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
objects = SiteManager()
|
objects = SiteManager()
|
||||||
|
|
||||||
csv_headers = [
|
csv_headers = [
|
||||||
'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'contact_name', 'contact_phone', 'contact_email',
|
'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'contact_name', 'contact_phone',
|
||||||
|
'contact_email',
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -118,6 +121,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
self.tenant.name if self.tenant else None,
|
self.tenant.name if self.tenant else None,
|
||||||
self.facility,
|
self.facility,
|
||||||
self.asn,
|
self.asn,
|
||||||
|
self.time_zone,
|
||||||
self.contact_name,
|
self.contact_name,
|
||||||
self.contact_phone,
|
self.contact_phone,
|
||||||
self.contact_email,
|
self.contact_email,
|
||||||
|
@ -134,6 +134,7 @@ INSTALLED_APPS = (
|
|||||||
'mptt',
|
'mptt',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework_swagger',
|
'rest_framework_swagger',
|
||||||
|
'timezone_field',
|
||||||
'circuits',
|
'circuits',
|
||||||
'dcim',
|
'dcim',
|
||||||
'ipam',
|
'ipam',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends '_base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load static from staticfiles %}
|
{% load static from staticfiles %}
|
||||||
|
{% load tz %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -105,6 +106,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Time Zone</td>
|
||||||
|
<td>
|
||||||
|
{% if site.time_zone %}
|
||||||
|
{{ site.time_zone }} (UTC {{ site.time_zone|tzoffset }})<br />
|
||||||
|
<small class="text-muted">Site time: {% timezone site.time_zone %}{% now "SHORT_DATETIME_FORMAT" %}{% endtimezone %}</small>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">N/A</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
{% render_field form.region %}
|
{% render_field form.region %}
|
||||||
{% render_field form.facility %}
|
{% render_field form.facility %}
|
||||||
{% render_field form.asn %}
|
{% render_field form.asn %}
|
||||||
|
{% render_field form.time_zone %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
import pytz
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -97,6 +98,23 @@ class ContentTypeFieldSerializer(Field):
|
|||||||
raise ValidationError("Invalid content type")
|
raise ValidationError("Invalid content type")
|
||||||
|
|
||||||
|
|
||||||
|
class TimeZoneField(Field):
|
||||||
|
"""
|
||||||
|
Represent a pytz time zone.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_representation(self, obj):
|
||||||
|
return obj.zone if obj else None
|
||||||
|
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
if not data:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
return pytz.timezone(str(data))
|
||||||
|
except pytz.exceptions.UnknownTimeZoneError:
|
||||||
|
raise ValidationError('Invalid time zone "{}"'.format(data))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Viewsets
|
# Viewsets
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import pytz
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
@ -117,6 +120,14 @@ def example_choices(field, arg=3):
|
|||||||
return ', '.join(examples) or 'None'
|
return ', '.join(examples) or 'None'
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def tzoffset(value):
|
||||||
|
"""
|
||||||
|
Returns the hour offset of a given time zone using the current time.
|
||||||
|
"""
|
||||||
|
return datetime.datetime.now(value).strftime('%z')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
|
@ -5,6 +5,7 @@ django-filter>=1.1.0
|
|||||||
django-mptt==0.8.7
|
django-mptt==0.8.7
|
||||||
django-rest-swagger>=2.1.0
|
django-rest-swagger>=2.1.0
|
||||||
django-tables2>=1.10.0
|
django-tables2>=1.10.0
|
||||||
|
django-timezone-field>=2.0
|
||||||
djangorestframework>=3.6.4
|
djangorestframework>=3.6.4
|
||||||
graphviz>=0.6
|
graphviz>=0.6
|
||||||
Markdown>=2.6.7
|
Markdown>=2.6.7
|
||||||
|
Loading…
Reference in New Issue
Block a user