diff --git a/docs/installation/netbox.md b/docs/installation/netbox.md index 0e116a29f..7f0046231 100644 --- a/docs/installation/netbox.md +++ b/docs/installation/netbox.md @@ -6,6 +6,7 @@ Python 3: ```no-highlight # 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: @@ -22,6 +23,7 @@ Python 3: # yum install -y epel-release # yum install -y gcc python34 python34-devel python34-setuptools libxml2-devel libxslt-devel libffi-devel graphviz openssl-devel # easy_install-3.4 pip +# ln -s -f python3.4 /usr/bin/python ``` 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.) +Python 3: + +```no-highlight +# pip3 install -r requirements.txt +``` + +Python 2: + ```no-highlight # pip install -r requirements.txt ``` @@ -173,7 +183,7 @@ Superuser created successfully. # Collect Static Files ```no-highlight -# ./manage.py collectstatic +# ./manage.py collectstatic --no-input You have requested to collect static files at the destination location as specified in your settings: diff --git a/docs/installation/postgresql.md b/docs/installation/postgresql.md index 39a8f05cb..543a0a2cf 100644 --- a/docs/installation/postgresql.md +++ b/docs/installation/postgresql.md @@ -5,13 +5,14 @@ NetBox requires a PostgreSQL database to store data. (Please note that MySQL is **Debian/Ubuntu** ```no-highlight -# apt-get install -y postgresql libpq-dev python-psycopg2 +# apt-get update +# apt-get install -y postgresql libpq-dev ``` **CentOS/RHEL** ```no-highlight -# yum install -y postgresql postgresql-server postgresql-devel python-psycopg2 +# yum install -y postgresql postgresql-server postgresql-devel # postgresql-setup initdb ``` diff --git a/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py b/netbox/circuits/migrations/0008_circuittermination_interface_protect_on_delete.py new file mode 100644 index 000000000..14ee6686d --- /dev/null +++ b/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'), + ), + ] diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 5dc55d503..04d9b3e13 100644 --- a/netbox/circuits/models.py +++ b/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') site = models.ForeignKey('dcim.Site', related_name='circuit_terminations', on_delete=models.PROTECT) 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)') - 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') pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)') diff --git a/netbox/generate_secret_key.py b/netbox/generate_secret_key.py index 0e0214dc4..3c88aa710 100755 --- a/netbox/generate_secret_key.py +++ b/netbox/generate_secret_key.py @@ -1,8 +1,7 @@ #!/usr/bin/env python # This script will generate a random 50-character string suitable for use as a SECRET_KEY. -import os import random 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))) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index c1b0d0c98..33cfc2211 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -418,12 +418,15 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm): 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') - 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): diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index ae06418ba..87d2636d8 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -588,7 +588,7 @@ class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView): class IPAddressBulkAddView(PermissionRequiredMixin, BulkAddView): permission_required = 'ipam.add_ipaddress' form = forms.IPAddressBulkAddForm - model = IPAddress + model_form = forms.IPAddressForm template_name = 'ipam/ipaddress_bulk_add.html' default_return_url = 'ipam:ipaddress_list' diff --git a/netbox/templates/ipam/ipaddress_bulk_add.html b/netbox/templates/ipam/ipaddress_bulk_add.html index 1599ee900..d53f73bd5 100644 --- a/netbox/templates/ipam/ipaddress_bulk_add.html +++ b/netbox/templates/ipam/ipaddress_bulk_add.html @@ -10,13 +10,21 @@ {% block form %}
-
IP Address
+
IP Addresses
- {% render_field form.address %} + {% render_field form.address_pattern %} {% render_field form.vrf %} {% render_field form.tenant %} {% render_field form.status %} {% render_field form.description %}
+ {% if form.custom_fields %} +
+
Custom Fields
+
+ {% render_custom_fields form %} +
+
+ {% endif %} {% endblock %} diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 14d1bfbdb..0b328ecb5 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -296,12 +296,12 @@ class BulkAddView(View): Create new objects in bulk. 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 default_return_url: Name of the URL to which the user is redirected after creating the objects """ form = None - model = None + model_form = None template_name = None default_return_url = 'home' @@ -310,47 +310,44 @@ class BulkAddView(View): form = self.form() 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, 'return_url': reverse(self.default_return_url), }) def post(self, request): + model = self.model_form._meta.model form = self.form(request.POST) 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] - - # 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 = [] try: with transaction.atomic(): 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) except ValidationError as e: form.add_error(None, e) 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) - 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: return redirect(request.path) return redirect(self.default_return_url) return render(request, self.template_name, { 'form': form, - 'obj_type': self.model._meta.verbose_name, + 'obj_type': model._meta.verbose_name, 'return_url': reverse(self.default_return_url), }) diff --git a/upgrade.sh b/upgrade.sh index 1d604a624..76ed4a57c 100755 --- a/upgrade.sh +++ b/upgrade.sh @@ -15,8 +15,12 @@ if [ "$(whoami)" = "root" ]; then PREFIX="" fi +# Fall back to pip3 if pip is missing +PIP="pip" +type $PIP >/dev/null 2>&1 || PIP="pip3" + # 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)..." eval $COMMAND @@ -24,7 +28,7 @@ eval $COMMAND ./netbox/manage.py migrate # Collect static files -./netbox/manage.py collectstatic --noinput +./netbox/manage.py collectstatic --no-input # Delete old bytecode find . -name "*.pyc" -delete