Browse Source

Closes #1283: Added a time zone field to the site model

Jeremy Stretch 7 years ago
parent
commit
b20258c66e

+ 8 - 6
netbox/dcim/api/serializers.py

@@ -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',
-            'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields', 'count_prefixes',
-            'count_vlans', 'count_racks', 'count_devices', 'count_circuits',
+            'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
+            'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
+            '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',
-            'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
+            'id', 'name', 'slug', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'physical_address',
+            'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'custom_fields',
         ]
         ]
 
 
 
 

+ 5 - 3
netbox/dcim/forms.py

@@ -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 - 0
netbox/dcim/migrations/0054_site_time_zone.py

@@ -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),
+        ),
+    ]

+ 5 - 1
netbox/dcim/models.py

@@ -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,

+ 1 - 0
netbox/netbox/settings.py

@@ -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',

+ 12 - 0
netbox/templates/dcim/site.html

@@ -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">

+ 1 - 0
netbox/templates/dcim/site_edit.html

@@ -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">

+ 18 - 0
netbox/utilities/api.py

@@ -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
 #
 #

+ 11 - 0
netbox/utilities/templatetags/helpers.py

@@ -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
 #
 #

+ 1 - 0
requirements.txt

@@ -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