Skip to content

Commit 3ad0cc9

Browse files
Generate ZIP file for download with QR code button
1 parent a7d0c9e commit 3ad0cc9

File tree

6 files changed

+193
-5
lines changed

6 files changed

+193
-5
lines changed

changelog.d/+qr-code-bulk.added.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add button to SeedDB that generates a ZIP file to download with QR Codes linking to the selected netboxes/rooms

python/nav/web/seeddb/page/__init__.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,19 @@ def not_implemented(*_args, **_kwargs):
4242
raise NotImplementedError()
4343

4444

45-
def view_switcher(request, list_view=None, move_view=None, delete_view=None):
45+
def view_switcher(
46+
request,
47+
list_view=None,
48+
move_view=None,
49+
delete_view=None,
50+
generate_qr_codes_view=None,
51+
):
4652
"""Selects appropriate view depending on POST data."""
4753
if request.method == 'POST':
4854
if 'move' in request.POST:
4955
return move_view(request)
5056
elif 'delete' in request.POST:
5157
return delete_view(request)
58+
elif 'qr_code' in request.POST:
59+
return generate_qr_codes_view(request)
5260
return list_view(request)

python/nav/web/seeddb/page/netbox/__init__.py

+63-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
import datetime
1919
from django.db import transaction
2020
from django.contrib.postgres.aggregates import ArrayAgg
21-
from django.urls import reverse_lazy
21+
from django.http import HttpResponseRedirect
22+
from django.urls import reverse, reverse_lazy
2223

2324
from nav.models.manage import Netbox
2425
from nav.bulkparse import NetboxBulkParser
2526
from nav.bulkimport import NetboxImporter
2627

28+
from nav.web.message import new_message, Messages
2729
from nav.web.seeddb import SeeddbInfo
2830
from nav.web.seeddb.constants import SEEDDB_EDITABLE_MODELS
2931
from nav.web.seeddb.page import view_switcher
@@ -32,6 +34,9 @@
3234
from nav.web.seeddb.utils.move import move
3335
from nav.web.seeddb.utils.bulk import render_bulkimport
3436
from nav.web.seeddb.page.netbox.forms import NetboxFilterForm, NetboxMoveForm
37+
from nav.web.utils import (
38+
generate_qr_codes_as_zip_file,
39+
)
3540

3641

3742
class NetboxInfo(SeeddbInfo):
@@ -55,7 +60,11 @@ class NetboxInfo(SeeddbInfo):
5560
def netbox(request):
5661
"""Controller for landing page for netboxes"""
5762
return view_switcher(
58-
request, list_view=netbox_list, move_view=netbox_move, delete_view=netbox_delete
63+
request,
64+
list_view=netbox_list,
65+
move_view=netbox_move,
66+
delete_view=netbox_delete,
67+
generate_qr_codes_view=netbox_generate_qr_codes,
5968
)
6069

6170

@@ -113,6 +122,58 @@ def netbox_pre_deletion_mark(queryset):
113122
queryset.update(deleted_at=datetime.datetime.now(), up_to_date=False)
114123

115124

125+
def netbox_generate_qr_codes(request):
126+
"""Controller for generating qr codes for netboxes"""
127+
if not request.POST.getlist('object'):
128+
new_message(
129+
request,
130+
"You need to select at least one object to generate qr codes for",
131+
Messages.ERROR,
132+
)
133+
return HttpResponseRedirect(reverse('seeddb-room'))
134+
135+
url_dict = dict()
136+
netboxes = Netbox.objects.filter(id__in=request.POST.getlist('object'))
137+
138+
for netbox in netboxes:
139+
url = request.build_absolute_uri(
140+
reverse('ipdevinfo-details-by-id', kwargs={'netbox_id': netbox.id})
141+
)
142+
url_dict[str(netbox)] = url
143+
144+
qr_codes_zip_file = generate_qr_codes_as_zip_file(url_dict=url_dict)
145+
146+
info = NetboxInfo()
147+
query = (
148+
Netbox.objects.select_related("room", "category", "type", "organization")
149+
.prefetch_related("profiles")
150+
.annotate(profile=ArrayAgg("profiles__name"))
151+
)
152+
filter_form = NetboxFilterForm(request.GET)
153+
value_list = (
154+
'sysname',
155+
'room',
156+
'ip',
157+
'category',
158+
'organization',
159+
'profile',
160+
'type__name',
161+
)
162+
return render_list(
163+
request,
164+
query,
165+
value_list,
166+
'seeddb-netbox-edit',
167+
edit_url_attr='pk',
168+
filter_form=filter_form,
169+
template='seeddb/list_netbox.html',
170+
extra_context={
171+
**info.template_context,
172+
**{"qr_codes_zip_file": qr_codes_zip_file},
173+
},
174+
)
175+
176+
116177
def netbox_move(request):
117178
"""Controller for handling a move request"""
118179
info = NetboxInfo()

python/nav/web/seeddb/page/room.py

+48-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
#
1818
"""Forms and view functions for SeedDB's Room view"""
1919

20-
from django.urls import reverse_lazy
20+
from django.http import HttpResponseRedirect
21+
from django.urls import reverse, reverse_lazy
2122

2223
from nav.models.manage import Room
2324
from nav.bulkparse import RoomBulkParser
2425
from nav.bulkimport import RoomImporter
2526

27+
from nav.web.message import new_message, Messages
2628
from nav.web.seeddb import SeeddbInfo
2729
from nav.web.seeddb.constants import SEEDDB_EDITABLE_MODELS
2830
from nav.web.seeddb.page import view_switcher
@@ -31,6 +33,9 @@
3133
from nav.web.seeddb.utils.delete import render_delete
3234
from nav.web.seeddb.utils.move import move
3335
from nav.web.seeddb.utils.bulk import render_bulkimport
36+
from nav.web.utils import (
37+
generate_qr_codes_as_zip_file,
38+
)
3439

3540
from ..forms import RoomForm, RoomFilterForm, RoomMoveForm
3641

@@ -56,7 +61,11 @@ class RoomInfo(SeeddbInfo):
5661
def room(request):
5762
"""Controller for listing, moving and deleting rooms"""
5863
return view_switcher(
59-
request, list_view=room_list, move_view=room_move, delete_view=room_delete
64+
request,
65+
list_view=room_list,
66+
move_view=room_move,
67+
delete_view=room_delete,
68+
generate_qr_codes_view=room_generate_qr_codes,
6069
)
6170

6271

@@ -84,6 +93,43 @@ def room_move(request):
8493
)
8594

8695

96+
def room_generate_qr_codes(request):
97+
"""Controller for generating qr codes for rooms"""
98+
if not request.POST.getlist('object'):
99+
new_message(
100+
request,
101+
"You need to select at least one object to generate qr codes for",
102+
Messages.ERROR,
103+
)
104+
return HttpResponseRedirect(reverse('seeddb-room'))
105+
106+
url_dict = dict()
107+
ids = request.POST.getlist('object')
108+
109+
for id in ids:
110+
url = request.build_absolute_uri(reverse('room-info', kwargs={'roomid': id}))
111+
url_dict[id] = url
112+
113+
qr_codes_zip_file = generate_qr_codes_as_zip_file(url_dict=url_dict)
114+
115+
info = RoomInfo()
116+
value_list = ('id', 'location', 'description', 'position', 'data')
117+
query = Room.objects.select_related("location").all()
118+
filter_form = RoomFilterForm(request.GET)
119+
# When we drop Python 3.7 we can use extra_context = info.template_context | {"qr_codes": qr_codes}
120+
return render_list(
121+
request,
122+
query,
123+
value_list,
124+
'seeddb-room-edit',
125+
filter_form=filter_form,
126+
extra_context={
127+
**info.template_context,
128+
**{"qr_codes_zip_file": qr_codes_zip_file},
129+
},
130+
)
131+
132+
87133
def room_delete(request, object_id=None):
88134
"""Controller for deleting rooms. Used in room()"""
89135
info = RoomInfo()

python/nav/web/templates/seeddb/list.html

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
</div>
3030
{% endif %}
3131

32+
{% if qr_codes_zip_file %}
33+
<a src="{{ qr_codes_zip_file }}" download="nav_qr_codes">Download generated QR Codes</a>
34+
{% endif %}
35+
3236
<div id="tablewrapper" class="notvisible" data-forpage="{{ request.path }}" data-page="{{ active_page }}">
3337
<table id="seeddb-content" class="listtable" width="100%">
3438
<caption>

tests/integration/seeddb_test.py

+68
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,71 @@ def post(func):
146146
)
147147
post("")
148148
assert len(NetboxInfo.objects.filter(netbox=netbox, variable='function')) == 0
149+
150+
151+
def test_generating_qr_codes_for_netboxes_should_succeed(client, netbox):
152+
url = reverse('seeddb-netbox')
153+
154+
response = client.post(
155+
url,
156+
follow=True,
157+
data={
158+
"qr_code": "Generate+QR+codes+for+selected",
159+
"object": [netbox.id],
160+
},
161+
)
162+
163+
assert response.status_code == 200
164+
assert 'Download generated QR Codes' in smart_str(response.content)
165+
166+
167+
def test_generating_qr_codes_for_no_selected_netboxes_should_show_error(client, netbox):
168+
url = reverse('seeddb-netbox')
169+
170+
response = client.post(
171+
url,
172+
follow=True,
173+
data={
174+
"qr_code": "Generate+QR+codes+for+selected",
175+
},
176+
)
177+
178+
assert response.status_code == 200
179+
assert (
180+
'You need to select at least one object to generate qr codes for'
181+
in smart_str(response.content)
182+
)
183+
184+
185+
def test_generating_qr_codes_for_rooms_should_succeed(client):
186+
url = reverse('seeddb-room')
187+
188+
response = client.post(
189+
url,
190+
follow=True,
191+
data={
192+
"qr_code": "Generate+QR+codes+for+selected",
193+
"object": ["myroom"],
194+
},
195+
)
196+
197+
assert response.status_code == 200
198+
assert 'Download generated QR Codes' in smart_str(response.content)
199+
200+
201+
def test_generating_qr_codes_for_no_selected_rooms_should_show_error(client, netbox):
202+
url = reverse('seeddb-room')
203+
204+
response = client.post(
205+
url,
206+
follow=True,
207+
data={
208+
"qr_code": "Generate+QR+codes+for+selected",
209+
},
210+
)
211+
212+
assert response.status_code == 200
213+
assert (
214+
'You need to select at least one object to generate qr codes for'
215+
in smart_str(response.content)
216+
)

0 commit comments

Comments
 (0)