Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f75b9eb

Browse files
committedMay 16, 2024·
Generate ZIP file for download with QR code button
1 parent 05f30dd commit f75b9eb

File tree

6 files changed

+194
-3
lines changed

6 files changed

+194
-3
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

+64-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818
import datetime
1919
from django.db import transaction
2020
from django.contrib.postgres.aggregates import ArrayAgg
21+
from django.http import HttpResponseRedirect
22+
from django.urls import reverse
2123

2224
from nav.models.manage import Netbox
2325
from nav.bulkparse import NetboxBulkParser
2426
from nav.bulkimport import NetboxImporter
2527

28+
from nav.web.message import new_message, Messages
2629
from nav.web.seeddb import SeeddbInfo, reverse_lazy
2730
from nav.web.seeddb.constants import SEEDDB_EDITABLE_MODELS
2831
from nav.web.seeddb.page import view_switcher
@@ -31,6 +34,10 @@
3134
from nav.web.seeddb.utils.move import move
3235
from nav.web.seeddb.utils.bulk import render_bulkimport
3336
from nav.web.seeddb.page.netbox.forms import NetboxFilterForm, NetboxMoveForm
37+
from nav.web.utils import (
38+
generate_qr_codes_as_byte_strings,
39+
generate_qr_codes_as_zip_file,
40+
)
3441

3542

3643
class NetboxInfo(SeeddbInfo):
@@ -54,7 +61,11 @@ class NetboxInfo(SeeddbInfo):
5461
def netbox(request):
5562
"""Controller for landing page for netboxes"""
5663
return view_switcher(
57-
request, list_view=netbox_list, move_view=netbox_move, delete_view=netbox_delete
64+
request,
65+
list_view=netbox_list,
66+
move_view=netbox_move,
67+
delete_view=netbox_delete,
68+
generate_qr_codes_view=netbox_generate_qr_codes,
5869
)
5970

6071

@@ -112,6 +123,58 @@ def netbox_pre_deletion_mark(queryset):
112123
queryset.update(deleted_at=datetime.datetime.now(), up_to_date=False)
113124

114125

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

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

+48-1
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.http import HttpResponseRedirect
2021
from django.urls import reverse
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, reverse_lazy
2729
from nav.web.seeddb.constants import SEEDDB_EDITABLE_MODELS
2830
from nav.web.seeddb.page import view_switcher
@@ -31,6 +33,10 @@
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_byte_strings,
38+
generate_qr_codes_as_zip_file,
39+
)
3440

3541
from ..forms import RoomForm, RoomFilterForm, RoomMoveForm
3642

@@ -56,7 +62,11 @@ class RoomInfo(SeeddbInfo):
5662
def room(request):
5763
"""Controller for listing, moving and deleting rooms"""
5864
return view_switcher(
59-
request, list_view=room_list, move_view=room_move, delete_view=room_delete
65+
request,
66+
list_view=room_list,
67+
move_view=room_move,
68+
delete_view=room_delete,
69+
generate_qr_codes_view=room_generate_qr_codes,
6070
)
6171

6272

@@ -84,6 +94,43 @@ def room_move(request):
8494
)
8595

8696

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

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

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

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

‎tests/integration/seeddb_test.py

+68
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,71 @@ def test_log_netbox_change_should_not_crash(admin_account, netbox):
110110
new.category_id = "OTHER"
111111

112112
assert log_netbox_change(admin_account, old, new) is None
113+
114+
115+
def test_generating_qr_codes_for_netboxes_should_succeed(client, netbox):
116+
url = reverse('seeddb-netbox')
117+
118+
response = client.post(
119+
url,
120+
follow=True,
121+
data={
122+
"qr_code": "Generate+QR+codes+for+selected",
123+
"object": [netbox.id],
124+
},
125+
)
126+
127+
assert response.status_code == 200
128+
assert 'Download generated QR Codes' in smart_str(response.content)
129+
130+
131+
def test_generating_qr_codes_for_no_selected_netboxes_should_show_error(client, netbox):
132+
url = reverse('seeddb-netbox')
133+
134+
response = client.post(
135+
url,
136+
follow=True,
137+
data={
138+
"qr_code": "Generate+QR+codes+for+selected",
139+
},
140+
)
141+
142+
assert response.status_code == 200
143+
assert (
144+
'You need to select at least one object to generate qr codes for'
145+
in smart_str(response.content)
146+
)
147+
148+
149+
def test_generating_qr_codes_for_rooms_should_succeed(client):
150+
url = reverse('seeddb-room')
151+
152+
response = client.post(
153+
url,
154+
follow=True,
155+
data={
156+
"qr_code": "Generate+QR+codes+for+selected",
157+
"object": ["myroom"],
158+
},
159+
)
160+
161+
assert response.status_code == 200
162+
assert 'Download generated QR Codes' in smart_str(response.content)
163+
164+
165+
def test_generating_qr_codes_for_no_selected_rooms_should_show_error(client, netbox):
166+
url = reverse('seeddb-room')
167+
168+
response = client.post(
169+
url,
170+
follow=True,
171+
data={
172+
"qr_code": "Generate+QR+codes+for+selected",
173+
},
174+
)
175+
176+
assert response.status_code == 200
177+
assert (
178+
'You need to select at least one object to generate qr codes for'
179+
in smart_str(response.content)
180+
)

0 commit comments

Comments
 (0)
Please sign in to comment.