Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reinstate image upload #60

Merged
merged 15 commits into from
Dec 8, 2024
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
max-line-length = 140
ignore = E126, E722
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Enable Migrations:
cd /opt/netbox
sudo ./venv/bin/python3 netbox/manage.py makemigrations netbox_floorplan_plugin
sudo ./venv/bin/python3 netbox/manage.py migrate
sudo ./venv/bin/python3 netbox/manage.py collectstatic
```

Restart NetBox and add `netbox-floorplan-plugin` to your local_requirements.txt
Expand Down
7 changes: 4 additions & 3 deletions netbox_floorplan/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from netbox.plugins import PluginConfig
from .version import __version__


class FloorplanConfig(PluginConfig):

name = "netbox_floorplan"
verbose_name = "Netbox Floorplan"
description = ""
version = "0.4.1"
version = __version__
base_url = "floorplan"
min_version = "4.0.2"
max_version = "4.0.11"
min_version = "4.1.0"
max_version = "4.1.99"


config = FloorplanConfig
9 changes: 9 additions & 0 deletions netbox_floorplan/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.contrib import admin
from .models import Floorplan


@admin.register(Floorplan)
class FloorplanAdmin(admin.ModelAdmin):
list_display = (
"pk",
)
15 changes: 13 additions & 2 deletions netbox_floorplan/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
from rest_framework import serializers
from netbox.api.serializers import NetBoxModelSerializer
from ..models import Floorplan
from ..models import Floorplan, FloorplanImage


class FloorplanImageSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_floorplan-api:floorplanimage-detail')

class Meta:
model = FloorplanImage
fields = ['id', 'url', 'name', 'file', 'external_url', 'filename', 'comments', 'tags', 'custom_fields', 'created', 'last_updated']
brief_fields = ['id', 'url', 'name', 'file', 'filename', 'external_url']


class FloorplanSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='plugins-api:netbox_floorplan-api:floorplan-detail')
assigned_image = FloorplanImageSerializer(nested=True, required=False, allow_null=True)

class Meta:
model = Floorplan
fields = ['id', 'url', 'site', 'location', 'background_image',
fields = ['id', 'url', 'site', 'location', 'assigned_image',
'width', 'height', 'tags', 'custom_fields', 'created',
'last_updated', 'canvas', 'measurement_unit']
2 changes: 1 addition & 1 deletion netbox_floorplan/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@

router = NetBoxRouter()
router.register('floorplans', views.FloorplanViewSet)

router.register('floorplanimages', views.FloorplanImageViewSet)
urlpatterns = router.urls
7 changes: 6 additions & 1 deletion netbox_floorplan/api/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from netbox.api.viewsets import NetBoxModelViewSet

from .. import filtersets, models
from .serializers import FloorplanSerializer
from .serializers import FloorplanSerializer, FloorplanImageSerializer


class FloorplanViewSet(NetBoxModelViewSet):
queryset = models.Floorplan.objects.all()
serializer_class = FloorplanSerializer
filterset_class = filtersets.FloorplanFilterSet


class FloorplanImageViewSet(NetBoxModelViewSet):
queryset = models.FloorplanImage.objects.prefetch_related('tags')
serializer_class = FloorplanImageSerializer
24 changes: 22 additions & 2 deletions netbox_floorplan/forms.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
from netbox.forms import NetBoxModelForm
from .models import Floorplan
from .models import Floorplan, FloorplanImage
from dcim.models import Rack, Device
from utilities.forms.rendering import FieldSet
from utilities.forms.fields import CommentField


class FloorplanImageForm(NetBoxModelForm):

comments = CommentField()

fieldsets = (
FieldSet(('name', 'file', 'external_url', 'comments'), name='General'),
FieldSet(('comments', 'tags'), name='')
)

class Meta:
model = FloorplanImage
fields = [
'name',
'file',
'external_url'
]


class FloorplanForm(NetBoxModelForm):
class Meta:
model = Floorplan
fields = ['site', 'location', 'background_image', 'width', 'height']
fields = ['site', 'location', 'assigned_image', 'width', 'height']


class FloorplanRackFilterForm(NetBoxModelForm):
Expand Down
30 changes: 30 additions & 0 deletions netbox_floorplan/migrations/0008_floorplanimage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 5.0.7 on 2024-07-19 23:49

import taggit.managers
import utilities.json
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('extras', '0115_convert_dashboard_widgets'),
('netbox_floorplan', '0007_alter_floorplan_options_and_more'),
]

operations = [
migrations.CreateModel(
name='FloorplanImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('name', models.CharField(max_length=128)),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'ordering': ('name',),
},
),
]
19 changes: 19 additions & 0 deletions netbox_floorplan/migrations/0009_floorplanimage_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.7 on 2024-07-20 19:02

import netbox_floorplan.utils
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('netbox_floorplan', '0008_floorplanimage'),
]

operations = [
migrations.AddField(
model_name='floorplanimage',
name='file',
field=models.FileField(blank=True, upload_to=netbox_floorplan.utils.file_upload),
),
]
18 changes: 18 additions & 0 deletions netbox_floorplan/migrations/0010_floorplanimage_external_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-07-20 19:57

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('netbox_floorplan', '0009_floorplanimage_file'),
]

operations = [
migrations.AddField(
model_name='floorplanimage',
name='external_url',
field=models.URLField(blank=True, max_length=255),
),
]
18 changes: 18 additions & 0 deletions netbox_floorplan/migrations/0011_floorplanimage_comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-07-20 20:34

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('netbox_floorplan', '0010_floorplanimage_external_url'),
]

operations = [
migrations.AddField(
model_name='floorplanimage',
name='comments',
field=models.TextField(blank=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.0.7 on 2024-07-20 20:53

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('netbox_floorplan', '0011_floorplanimage_comments'),
]

operations = [
migrations.AlterModelOptions(
name='floorplan',
options={'ordering': ('site', 'location', 'assigned_image', 'width', 'height', 'measurement_unit')},
),
migrations.RemoveField(
model_name='floorplan',
name='background_image',
),
migrations.AddField(
model_name='floorplan',
name='assigned_image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='netbox_floorplan.floorplanimage'),
),
]
101 changes: 95 additions & 6 deletions netbox_floorplan/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,98 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse

from netbox.models import NetBoxModel
from dcim.models import Rack, Device
from .utils import file_upload

from dcim.models import Rack, Device

class FloorplanImage(NetBoxModel):
"""
A Floorplan Image is effectively a background image
"""
name = models.CharField(
help_text='Can be used to quickly identify a particular image',
max_length=128,
blank=False,
null=False
)

file = models.FileField(
upload_to=file_upload,
blank=True
)

external_url = models.URLField(
blank=True,
max_length=255
)

comments = models.TextField(
blank=True
)

def get_absolute_url(self):
return reverse('plugins:netbox_floorplan:floorplanimage', args=[self.pk])

def __str__(self):
return f'{self.name}'

class Meta:
ordering = ('name',)

@property
def size(self):
"""
Wrapper around `document.size` to suppress an OSError in case the file is inaccessible. Also opportunistically
catch other exceptions that we know other storage back-ends to throw.
"""
expected_exceptions = [OSError]

try:
from botocore.exceptions import ClientError
expected_exceptions.append(ClientError)
except ImportError:
pass

try:
return self.file.size
except NameError:
return None

@property
def filename(self):
filename = self.file.name.rsplit('/', 1)[-1]
return filename

def clean(self):
super().clean()

# Must have an uploaded document or an external URL. cannot have both
if not self.file and self.external_url == '':
raise ValidationError("A document must contain an uploaded file or an external URL.")
if self.file and self.external_url:
raise ValidationError("A document cannot contain both an uploaded file and an external URL.")

def delete(self, *args, **kwargs):

# Check if its a document or a URL
if self.external_url == '':

_name = self.file.name

# Delete file from disk
super().delete(*args, **kwargs)
self.file.delete(save=False)

# Restore the name of the document as it's re-used in the notifications later
self.file.name = _name
else:
# Straight delete of external URL
super().delete(*args, **kwargs)


class Floorplan(NetBoxModel):

site = models.ForeignKey(
to='dcim.Site',
blank=True,
Expand All @@ -20,17 +105,21 @@ class Floorplan(NetBoxModel):
null=True,
on_delete=models.PROTECT
)
background_image = models.ImageField(
upload_to=file_upload,

assigned_image = models.ForeignKey(
to='FloorplanImage',
blank=True,
null=True
null=True,
on_delete=models.SET_NULL
)

width = models.DecimalField(
max_digits=10,
decimal_places=2,
blank=True,
null=True
)

height = models.DecimalField(
max_digits=10,
decimal_places=2,
Expand All @@ -50,7 +139,7 @@ class Floorplan(NetBoxModel):
canvas = models.JSONField(default=dict)

class Meta:
ordering = ('site', 'location', 'background_image',
ordering = ('site', 'location', 'assigned_image',
'width', 'height', 'measurement_unit')

def __str__(self):
Expand Down
5 changes: 3 additions & 2 deletions netbox_floorplan/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
#
menu_buttons = (
PluginMenuItem(
link="plugins:netbox_floorplan:floorplan_list",
link_text="Floorplans",
link="plugins:netbox_floorplan:floorplanimage_list",
link_text="Floorplan Images",
),

)


Expand Down
Loading
Loading