Browse Source

Merge branch 'develop' into v2-develop

Conflicts:
	netbox/circuits/models.py
	netbox/netbox/settings.py
	upgrade.sh
Jeremy Stretch 8 years ago
parent
commit
f73693206f

+ 11 - 1
docs/installation/netbox.md

@@ -6,6 +6,7 @@ Python 3:
 
 
 ```no-highlight
 ```no-highlight
 # apt-get install -y python3 python3-dev python3-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
 # apt-get install -y python3 python3-dev python3-pip libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev
+# update-alternatives --install /usr/bin/python python /usr/bin/python3 1
 ```
 ```
 
 
 Python 2:
 Python 2:
@@ -22,6 +23,7 @@ Python 3:
 # yum install -y epel-release
 # yum install -y epel-release
 # yum install -y gcc python34 python34-devel python34-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
 # yum install -y gcc python34 python34-devel python34-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel
 # easy_install-3.4 pip
 # easy_install-3.4 pip
+# ln -s -f python3.4 /usr/bin/python
 ```
 ```
 
 
 Python 2:
 Python 2:
@@ -84,6 +86,14 @@ Checking connectivity... done.
 
 
 Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
 Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
 
 
+Python 3:
+
+```no-highlight
+# pip3 install -r requirements.txt
+```
+
+Python 2:
+
 ```no-highlight
 ```no-highlight
 # pip install -r requirements.txt
 # pip install -r requirements.txt
 ```
 ```
@@ -173,7 +183,7 @@ Superuser created successfully.
 # Collect Static Files
 # Collect Static Files
 
 
 ```no-highlight
 ```no-highlight
-# ./manage.py collectstatic
+# ./manage.py collectstatic --no-input
 
 
 You have requested to collect static files at the destination
 You have requested to collect static files at the destination
 location as specified in your settings:
 location as specified in your settings:

+ 3 - 2
docs/installation/postgresql.md

@@ -5,13 +5,14 @@ NetBox requires a PostgreSQL database to store data. (Please note that MySQL is
 **Debian/Ubuntu**
 **Debian/Ubuntu**
 
 
 ```no-highlight
 ```no-highlight
-# apt-get install -y postgresql libpq-dev python-psycopg2
+# apt-get update
+# apt-get install -y postgresql libpq-dev
 ```
 ```
 
 
 **CentOS/RHEL**
 **CentOS/RHEL**
 
 
 ```no-highlight
 ```no-highlight
-# yum install -y postgresql postgresql-server postgresql-devel python-psycopg2
+# yum install -y postgresql postgresql-server postgresql-devel
 # postgresql-setup initdb
 # postgresql-setup initdb
 ```
 ```
 
 

+ 21 - 0
netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py

@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2017-04-19 17:17
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('circuits', '0007_circuit_add_description'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='circuittermination',
+            name='interface',
+            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_termination', to='dcim.Interface'),
+        ),
+    ]

+ 5 - 3
netbox/circuits/models.py

@@ -151,11 +151,13 @@ class CircuitTermination(models.Model):
     term_side = models.CharField(max_length=1, choices=TERM_SIDE_CHOICES, verbose_name='Termination')
     term_side = models.CharField(max_length=1, choices=TERM_SIDE_CHOICES, verbose_name='Termination')
     site = models.ForeignKey('dcim.Site', related_name='circuit_terminations', on_delete=models.PROTECT)
     site = models.ForeignKey('dcim.Site', related_name='circuit_terminations', on_delete=models.PROTECT)
     interface = models.OneToOneField(
     interface = models.OneToOneField(
-        'dcim.Interface', related_name='circuit_termination', blank=True, null=True, on_delete=models.CASCADE
+        'dcim.Interface', related_name='circuit_termination', blank=True, null=True, on_delete=models.PROTECT
     )
     )
     port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)')
     port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)')
-    upstream_speed = models.PositiveIntegerField(blank=True, null=True, verbose_name='Upstream speed (Kbps)',
-                                                 help_text='Upstream speed, if different from port speed')
+    upstream_speed = models.PositiveIntegerField(
+        blank=True, null=True, verbose_name='Upstream speed (Kbps)',
+        help_text='Upstream speed, if different from port speed'
+    )
     xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID')
     xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID')
     pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)')
     pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)')
 
 

+ 2 - 3
netbox/generate_secret_key.py

@@ -1,8 +1,7 @@
 #!/usr/bin/env python
 #!/usr/bin/env python
 # This script will generate a random 50-character string suitable for use as a SECRET_KEY.
 # This script will generate a random 50-character string suitable for use as a SECRET_KEY.
-import os
 import random
 import random
 
 
 charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*(-_=+)'
 charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*(-_=+)'
-random.seed = (os.urandom(2048))
-print(''.join(random.choice(charset) for c in range(50)))
+secure_random = random.SystemRandom()
+print(''.join(secure_random.sample(charset, 50)))

+ 8 - 5
netbox/ipam/forms.py

@@ -418,12 +418,15 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
                 self.fields['nat_inside'].choices = []
                 self.fields['nat_inside'].choices = []
 
 
 
 
-class IPAddressBulkAddForm(BootstrapMixin, forms.Form):
-    address = ExpandableIPAddressField()
+class IPAddressBulkAddForm(BootstrapMixin, CustomFieldForm):
+    address_pattern = ExpandableIPAddressField(label='Address Pattern')
     vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF', empty_label='Global')
     vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF', empty_label='Global')
-    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
-    status = forms.ChoiceField(choices=IPADDRESS_STATUS_CHOICES)
-    description = forms.CharField(max_length=100, required=False)
+
+    pattern_map = ('address_pattern', 'address')
+
+    class Meta:
+        model = IPAddress
+        fields = ['address_pattern', 'vrf', 'tenant', 'status', 'description']
 
 
 
 
 class IPAddressAssignForm(BootstrapMixin, forms.Form):
 class IPAddressAssignForm(BootstrapMixin, forms.Form):

+ 1 - 1
netbox/ipam/views.py

@@ -588,7 +588,7 @@ class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
 class IPAddressBulkAddView(PermissionRequiredMixin, BulkAddView):
 class IPAddressBulkAddView(PermissionRequiredMixin, BulkAddView):
     permission_required = 'ipam.add_ipaddress'
     permission_required = 'ipam.add_ipaddress'
     form = forms.IPAddressBulkAddForm
     form = forms.IPAddressBulkAddForm
-    model = IPAddress
+    model_form = forms.IPAddressForm
     template_name = 'ipam/ipaddress_bulk_add.html'
     template_name = 'ipam/ipaddress_bulk_add.html'
     default_return_url = 'ipam:ipaddress_list'
     default_return_url = 'ipam:ipaddress_list'
 
 

+ 10 - 2
netbox/templates/ipam/ipaddress_bulk_add.html

@@ -10,13 +10,21 @@
 
 
 {% block form %}
 {% block form %}
     <div class="panel panel-default">
     <div class="panel panel-default">
-        <div class="panel-heading"><strong>IP Address</strong></div>
+        <div class="panel-heading"><strong>IP Addresses</strong></div>
         <div class="panel-body">
         <div class="panel-body">
-            {% render_field form.address %}
+            {% render_field form.address_pattern %}
             {% render_field form.vrf %}
             {% render_field form.vrf %}
             {% render_field form.tenant %}
             {% render_field form.tenant %}
             {% render_field form.status %}
             {% render_field form.status %}
             {% render_field form.description %}
             {% render_field form.description %}
         </div>
         </div>
     </div>
     </div>
+    {% if form.custom_fields %}
+        <div class="panel panel-default">
+            <div class="panel-heading"><strong>Custom Fields</strong></div>
+            <div class="panel-body">
+                {% render_custom_fields form %}
+            </div>
+        </div>
+    {% endif %}
 {% endblock %}
 {% endblock %}

+ 13 - 16
netbox/utilities/views.py

@@ -296,12 +296,12 @@ class BulkAddView(View):
     Create new objects in bulk.
     Create new objects in bulk.
 
 
     form: Form class
     form: Form class
-    model: The model of the objects being created
+    model_form: The ModelForm used to create individual objects
     template_name: The name of the template
     template_name: The name of the template
     default_return_url: Name of the URL to which the user is redirected after creating the objects
     default_return_url: Name of the URL to which the user is redirected after creating the objects
     """
     """
     form = None
     form = None
-    model = None
+    model_form = None
     template_name = None
     template_name = None
     default_return_url = 'home'
     default_return_url = 'home'
 
 
@@ -310,47 +310,44 @@ class BulkAddView(View):
         form = self.form()
         form = self.form()
 
 
         return render(request, self.template_name, {
         return render(request, self.template_name, {
-            'obj_type': self.model._meta.verbose_name,
+            'obj_type': self.model_form._meta.model._meta.verbose_name,
             'form': form,
             'form': form,
             'return_url': reverse(self.default_return_url),
             'return_url': reverse(self.default_return_url),
         })
         })
 
 
     def post(self, request):
     def post(self, request):
 
 
+        model = self.model_form._meta.model
         form = self.form(request.POST)
         form = self.form(request.POST)
         if form.is_valid():
         if form.is_valid():
 
 
-            # The first field will be used as the pattern
-            field_names = list(form.fields.keys())
-            pattern_field = field_names[0]
+            # Read the pattern field and target from the form's pattern_map
+            pattern_field, pattern_target = form.pattern_map
             pattern = form.cleaned_data[pattern_field]
             pattern = form.cleaned_data[pattern_field]
-
-            # All other fields will be copied as object attributes
-            kwargs = {k: form.cleaned_data[k] for k in field_names[1:]}
+            model_form_data = form.cleaned_data
 
 
             new_objs = []
             new_objs = []
             try:
             try:
                 with transaction.atomic():
                 with transaction.atomic():
                     for value in pattern:
                     for value in pattern:
-                        obj = self.model(**kwargs)
-                        setattr(obj, pattern_field, value)
-                        obj.full_clean()
-                        obj.save()
+                        model_form_data[pattern_target] = value
+                        model_form = self.model_form(model_form_data)
+                        obj = model_form.save()
                         new_objs.append(obj)
                         new_objs.append(obj)
             except ValidationError as e:
             except ValidationError as e:
                 form.add_error(None, e)
                 form.add_error(None, e)
 
 
             if not form.errors:
             if not form.errors:
-                msg = u"Added {} {}".format(len(new_objs), self.model._meta.verbose_name_plural)
+                msg = u"Added {} {}".format(len(new_objs), model._meta.verbose_name_plural)
                 messages.success(request, msg)
                 messages.success(request, msg)
-                UserAction.objects.log_bulk_create(request.user, ContentType.objects.get_for_model(self.model), msg)
+                UserAction.objects.log_bulk_create(request.user, ContentType.objects.get_for_model(model), msg)
                 if '_addanother' in request.POST:
                 if '_addanother' in request.POST:
                     return redirect(request.path)
                     return redirect(request.path)
                 return redirect(self.default_return_url)
                 return redirect(self.default_return_url)
 
 
         return render(request, self.template_name, {
         return render(request, self.template_name, {
             'form': form,
             'form': form,
-            'obj_type': self.model._meta.verbose_name,
+            'obj_type': model._meta.verbose_name,
             'return_url': reverse(self.default_return_url),
             'return_url': reverse(self.default_return_url),
         })
         })
 
 

+ 6 - 2
upgrade.sh

@@ -15,8 +15,12 @@ if [ "$(whoami)" = "root" ]; then
 	PREFIX=""
 	PREFIX=""
 fi
 fi
 
 
+# Fall back to pip3 if pip is missing
+PIP="pip"
+type $PIP >/dev/null 2>&1 || PIP="pip3"
+
 # Install any new Python packages
 # Install any new Python packages
-COMMAND="${PREFIX}pip install -r requirements.txt --upgrade"
+COMMAND="${PREFIX}${PIP} install -r requirements.txt --upgrade"
 echo "Updating required Python packages ($COMMAND)..."
 echo "Updating required Python packages ($COMMAND)..."
 eval $COMMAND
 eval $COMMAND
 
 
@@ -24,7 +28,7 @@ eval $COMMAND
 ./netbox/manage.py migrate
 ./netbox/manage.py migrate
 
 
 # Collect static files
 # Collect static files
-./netbox/manage.py collectstatic --noinput
+./netbox/manage.py collectstatic --no-input
 
 
 # Delete old bytecode
 # Delete old bytecode
 find . -name "*.pyc" -delete
 find . -name "*.pyc" -delete