Passed
Push — main ( ff306b...3f60e2 )
by Jochen
07:37
created

byceps.blueprints.admin.guest_server.views   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 481
Duplicated Lines 10.6 %

Test Coverage

Coverage 32.87%

Importance

Changes 0
Metric Value
eloc 295
dl 51
loc 481
ccs 71
cts 216
cp 0.3287
rs 8.96
c 0
b 0
f 0
wmc 43

22 Functions

Rating   Name   Duplication   Size   Complexity  
A server_create_form() 0 12 2
A server_index() 23 23 1
A server_view() 0 19 1
A address_index() 28 28 2
A server_update() 0 18 2
A server_create() 0 36 2
A server_update_form() 0 14 2
A server_delete() 0 14 1
A address_create() 0 22 2
A address_update() 0 23 2
A _get_full_hostname() 0 3 2
B address_export_netbox() 0 40 5
A setting_view() 0 12 1
A setting_update_form() 0 14 2
A _to_ip_address() 0 2 2
A _sort_addresses() 0 13 2
A address_create_form() 0 14 2
A _get_server_or_404() 0 7 2
A address_update_form() 0 15 2
A _get_address_or_404() 0 7 2
A _get_party_or_404() 0 7 2
A setting_update() 0 23 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like byceps.blueprints.admin.guest_server.views often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
byceps.blueprints.admin.guest_server.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2014-2022 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from __future__ import annotations
10 1
import ipaddress
11 1
from typing import Iterable, Optional
12
13 1
from flask import abort, g, request, url_for
14 1
from flask_babel import gettext
15
16 1
from ....services.guest_server import service as guest_server_service
17 1
from ....services.guest_server.transfer.models import (
18
    Address,
19
    IPAddress,
20
    Setting,
21
)
22 1
from ....services.party import service as party_service
23 1
from ....services.user import service as user_service
24 1
from ....signals import guest_server as guest_server_signals
25 1
from ....util.export import serialize_tuples_to_csv
26 1
from ....util.framework.blueprint import create_blueprint
27 1
from ....util.framework.flash import flash_success
28 1
from ....util.framework.templating import templated
29 1
from ....util.views import (
30
    permission_required,
31
    redirect_to,
32
    respond_no_content_with_location,
33
    textified,
34
)
35
36 1
from .forms import (
37
    AddressCreateForm,
38
    AddressUpdateForm,
39
    ServerCreateForm,
40
    ServerUpdateForm,
41
    SettingUpdateForm,
42
)
43
44
45 1
blueprint = create_blueprint('guest_server_admin', __name__)
46
47
48
# -------------------------------------------------------------------- #
49
# servers
50
51
52 1 View Code Duplication
@blueprint.get('/for_party/<party_id>/servers')
53 1
@permission_required('guest_server.view')
54 1
@templated
55
def server_index(party_id):
56
    """Show guest servers for a party."""
57
    party = _get_party_or_404(party_id)
58
59
    setting = guest_server_service.get_setting_for_party(party.id)
60
61
    servers = guest_server_service.get_all_servers_for_party(party.id)
62
63
    creator_ids = {server.creator_id for server in servers}
64
    owner_ids = {server.owner_id for server in servers}
65
    user_ids = creator_ids.union(owner_ids)
66
    users = user_service.get_users(user_ids, include_avatars=True)
67
    users_by_id = user_service.index_users_by_id(users)
68
69
    return {
70
        'party': party,
71
        'setting': setting,
72
        'servers': servers,
73
        'users_by_id': users_by_id,
74
        'sort_addresses': _sort_addresses,
75
    }
76
77
78 1
@blueprint.get('/servers/<server_id>')
79 1
@permission_required('guest_server.view')
80 1
@templated
81
def server_view(server_id):
82
    """Show guest server."""
83
    server = _get_server_or_404(server_id)
84
    party = party_service.get_party(server.party_id)
85
    setting = guest_server_service.get_setting_for_party(party.id)
86
87
    user_ids = {server.creator_id, server.owner_id}
88
    users = user_service.get_users_for_admin(user_ids)
89
    users_by_id = user_service.index_users_by_id(users)
90
91
    return {
92
        'party': party,
93
        'server': server,
94
        'setting': setting,
95
        'users_by_id': users_by_id,
96
        'sort_addresses': _sort_addresses,
97
    }
98
99
100 1
@blueprint.get('/for_party/<party_id>/servers/create')
101 1
@permission_required('guest_server.administrate')
102 1
@templated
103 1
def server_create_form(party_id, erroneous_form=None):
104
    """Show a form to create a server."""
105
    party = _get_party_or_404(party_id)
106
107
    form = erroneous_form if erroneous_form else ServerCreateForm()
108
109
    return {
110
        'party': party,
111
        'form': form,
112
    }
113
114
115 1
@blueprint.post('/for_party/<party_id>/servers')
116 1
@permission_required('guest_server.administrate')
117
def server_create(party_id):
118
    """Create a server."""
119
    party = _get_party_or_404(party_id)
120
121
    form = ServerCreateForm(request.form)
122
    if not form.validate():
123
        return server_create_form(party_id, form)
124
125
    creator = g.user
126
    owner = form.owner.data
127
    notes_admin = form.notes_admin.data.strip()
128
    approved = form.approved.data
129
    ip_address = _to_ip_address(form.ip_address.data.strip())
130
    hostname = form.hostname.data.strip() or None
131
    netmask = _to_ip_address(form.netmask.data.strip())
132
    gateway = _to_ip_address(form.gateway.data.strip())
133
134
    server, event = guest_server_service.create_server(
135
        party.id,
136
        creator.id,
137
        owner.id,
138
        notes_admin=notes_admin,
139
        approved=approved,
140
        ip_address=ip_address,
141
        hostname=hostname,
142
        netmask=netmask,
143
        gateway=gateway,
144
    )
145
146
    flash_success(gettext('The server has been registered.'))
147
148
    guest_server_signals.guest_server_registered.send(None, event=event)
149
150
    return redirect_to('.server_view', server_id=server.id)
151
152
153 1
@blueprint.get('/servers/<uuid:server_id>/update')
154 1
@permission_required('guest_server.administrate')
155 1
@templated
156 1
def server_update_form(server_id, erroneous_form=None):
157
    """Show form to update a server."""
158
    server = _get_server_or_404(server_id)
159
    party = party_service.get_party(server.party_id)
160
161
    form = erroneous_form if erroneous_form else ServerUpdateForm(obj=server)
162
163
    return {
164
        'party': party,
165
        'server': server,
166
        'form': form,
167
    }
168
169
170 1
@blueprint.post('/servers/<uuid:server_id>')
171 1
@permission_required('guest_server.administrate')
172
def server_update(server_id):
173
    """Update a server."""
174
    server = _get_server_or_404(server_id)
175
176
    form = ServerUpdateForm(request.form)
177
    if not form.validate():
178
        return server_update_form(server.id, form)
179
180
    notes_admin = form.notes_admin.data.strip() or None
181
    approved = form.approved.data
182
183
    guest_server_service.update_server(server.id, notes_admin, approved)
184
185
    flash_success(gettext('Changes have been saved.'))
186
187
    return redirect_to('.server_view', server_id=server.id)
188
189
190 1
@blueprint.delete('/guest_servers/<uuid:server_id>')
191 1
@permission_required('guest_server.administrate')
192 1
@respond_no_content_with_location
193
def server_delete(server_id):
194
    """Delete a guest server."""
195
    server = _get_server_or_404(server_id)
196
197
    party_id = server.party_id
198
199
    guest_server_service.delete_server(server_id)
200
201
    flash_success(gettext('Server has been deleted.'))
202
203
    return url_for('.server_index', party_id=party_id)
204
205
206
# -------------------------------------------------------------------- #
207
# addresses
208
209
210 1 View Code Duplication
@blueprint.get('/for_party/<party_id>/addresses')
211 1
@permission_required('guest_server.view')
212 1
@templated
213
def address_index(party_id):
214
    """Show addresses for a party."""
215
    party = _get_party_or_404(party_id)
216
217
    servers = guest_server_service.get_all_servers_for_party(party.id)
218
219
    addresses = []
220
    for server in servers:
221
        addresses.extend(server.addresses)
222
223
    user_ids = {server.owner_id for server in servers}
224
    users = user_service.get_users(user_ids, include_avatars=True)
225
    users_by_id = user_service.index_users_by_id(users)
226
    owners_by_server_id = {
227
        server.id: users_by_id[server.owner_id] for server in servers
228
    }
229
230
    setting = guest_server_service.get_setting_for_party(party.id)
231
232
    return {
233
        'party': party,
234
        'addresses': addresses,
235
        'sort_addresses': _sort_addresses,
236
        'owners_by_server_id': owners_by_server_id,
237
        'setting': setting,
238
    }
239
240
241 1
@blueprint.get('/for_party/<party_id>/addresses/export/netbox')
242 1
@permission_required('guest_server.view')
243 1
@textified
244
def address_export_netbox(party_id):
245
    """Export addresses for a party as NetBox-compatible CSV.
246
247
    Suitable for importing into NetBox
248
    (https://github.com/netbox-community/netbox).
249
    """
250
    party = _get_party_or_404(party_id)
251
252
    setting = guest_server_service.get_setting_for_party(party.id)
253
254
    all_servers = guest_server_service.get_all_servers_for_party(party.id)
255
    approved_servers = [server for server in all_servers if server.approved]
256
257
    owner_ids = {server.owner_id for server in approved_servers}
258
    owners = user_service.get_users(owner_ids, include_avatars=False)
259
    owners_by_id = user_service.index_users_by_id(owners)
260
261
    # field names as defined by NetBox
262
    field_names = ('address', 'status', 'dns_name', 'description')
263
264
    rows = []
265
266
    for server in approved_servers:
267
        for address in server.addresses:
268
            if address.ip_address and address.hostname:
269
                rows.append(
270
                    (
271
                        f'{str(address.ip_address)}/24',
272
                        'active',
273
                        _get_full_hostname(address.hostname, setting),
274
                        owners_by_id[server.owner_id].screen_name or 'unknown',
275
                    )
276
                )
277
278
    rows.sort()
279
280
    return serialize_tuples_to_csv([field_names] + rows)
281
282
283 1
def _get_full_hostname(hostname: str, setting: Setting) -> str:
284
    """Return the hostname with the domain (if configured) appended."""
285
    return hostname + '.' + setting.domain if setting.domain else hostname
286
287
288 1
@blueprint.get('/servers/<uuid:server_id>/addresses/create')
289 1
@permission_required('guest_server.administrate')
290 1
@templated
291 1
def address_create_form(server_id, erroneous_form=None):
292
    """Show a form to add an address to a server."""
293
    server = _get_server_or_404(server_id)
294
    party = party_service.get_party(server.party_id)
295
296
    form = erroneous_form if erroneous_form else AddressCreateForm()
297
298
    return {
299
        'party': party,
300
        'server': server,
301
        'form': form,
302
    }
303
304
305 1
@blueprint.post('/servers/<uuid:server_id>/addresses')
306 1
@permission_required('guest_server.administrate')
307
def address_create(server_id):
308
    """Add an address to a server."""
309
    server = _get_server_or_404(server_id)
310
311
    form = AddressCreateForm(request.form)
312
    if not form.validate():
313
        return address_create_form(server_id, form)
314
315
    ip_address = _to_ip_address(form.ip_address.data.strip())
316
    hostname = form.hostname.data.strip() or None
317
    netmask = _to_ip_address(form.netmask.data.strip())
318
    gateway = _to_ip_address(form.gateway.data.strip())
319
320
    address = guest_server_service.create_address(
321
        server.id, ip_address, hostname, netmask, gateway
322
    )
323
324
    flash_success(gettext('The address has been added.'))
325
326
    return redirect_to('.server_view', server_id=server.id)
327
328
329 1
@blueprint.get('/addresses/<uuid:address_id>/update')
330 1
@permission_required('guest_server.administrate')
331 1
@templated
332 1
def address_update_form(address_id, erroneous_form=None):
333
    """Show form to update an address."""
334
    address = _get_address_or_404(address_id)
335
    server = guest_server_service.find_server(address.server_id)
336
    party = party_service.get_party(server.party_id)
337
338
    form = erroneous_form if erroneous_form else AddressUpdateForm(obj=address)
339
340
    return {
341
        'party': party,
342
        'address': address,
343
        'form': form,
344
    }
345
346
347 1
@blueprint.post('/addresses/<uuid:address_id>')
348 1
@permission_required('guest_server.administrate')
349
def address_update(address_id):
350
    """Update an address."""
351
    address = _get_address_or_404(address_id)
352
    server = guest_server_service.find_server(address.server_id)
353
354
    form = AddressUpdateForm(request.form)
355
    if not form.validate():
356
        return address_update_form(address.id, form)
357
358
    ip_address = _to_ip_address(form.ip_address.data.strip())
359
    hostname = form.hostname.data.strip() or None
360
    netmask = _to_ip_address(form.netmask.data.strip())
361
    gateway = _to_ip_address(form.gateway.data.strip())
362
363
    guest_server_service.update_address(
364
        address.id, ip_address, hostname, netmask, gateway
365
    )
366
367
    flash_success(gettext('Changes have been saved.'))
368
369
    return redirect_to('.server_view', server_id=server.id)
370
371
372
# -------------------------------------------------------------------- #
373
# setting
374
375
376 1
@blueprint.get('/for_party/<party_id>/settings')
377 1
@permission_required('guest_server.view')
378 1
@templated
379
def setting_view(party_id):
380
    """Show settings for a party."""
381
    party = _get_party_or_404(party_id)
382
383
    setting = guest_server_service.get_setting_for_party(party.id)
384
385
    return {
386
        'party': party,
387
        'setting': setting,
388
    }
389
390
391 1
@blueprint.get('/for_party/<party_id>/settings/update')
392 1
@permission_required('guest_server.administrate')
393 1
@templated
394 1
def setting_update_form(party_id, erroneous_form=None):
395
    """Show form to update the settings for a party."""
396
    party = _get_party_or_404(party_id)
397
398
    setting = guest_server_service.get_setting_for_party(party.id)
399
400
    form = erroneous_form if erroneous_form else SettingUpdateForm(obj=setting)
401
402
    return {
403
        'party': party,
404
        'form': form,
405
    }
406
407
408 1
@blueprint.post('/for_party/<party_id>/settings')
409 1
@permission_required('guest_server.administrate')
410
def setting_update(party_id):
411
    """Update the settings for a party."""
412
    party = _get_party_or_404(party_id)
413
414
    form = SettingUpdateForm(request.form)
415
    if not form.validate():
416
        return setting_update_form(party.id, form)
417
418
    netmask = _to_ip_address(form.netmask.data.strip())
419
    gateway = _to_ip_address(form.gateway.data.strip())
420
    dns_server1 = _to_ip_address(form.dns_server1.data.strip())
421
    dns_server2 = _to_ip_address(form.dns_server2.data.strip())
422
    domain = form.domain.data.strip() or None
423
424
    guest_server_service.update_setting(
425
        party.id, netmask, gateway, dns_server1, dns_server2, domain
426
    )
427
428
    flash_success(gettext('Changes have been saved.'))
429
430
    return redirect_to('.setting_view', party_id=party.id)
431
432
433
# -------------------------------------------------------------------- #
434
# helpers
435
436
437 1
def _get_party_or_404(party_id):
438
    party = party_service.find_party(party_id)
439
440
    if party is None:
441
        abort(404)
442
443
    return party
444
445
446 1
def _get_server_or_404(server_id):
447
    server = guest_server_service.find_server(server_id)
448
449
    if server is None:
450
        abort(404)
451
452
    return server
453
454
455 1
def _get_address_or_404(address_id):
456
    address = guest_server_service.find_address(address_id)
457
458
    if address is None:
459
        abort(404)
460
461
    return address
462
463
464 1
def _to_ip_address(value: str) -> Optional[IPAddress]:
465
    return ipaddress.ip_address(value) if value else None
466
467
468 1
def _sort_addresses(addresses: Iterable[Address]) -> list[Address]:
469
    """Sort addresses.
470
471
    By IP address first, hostname second. `None` at the end.
472
    """
473
    return list(
474
        sorted(
475
            addresses,
476
            key=lambda addr: (
477
                addr.ip_address is None,
478
                addr.ip_address,
479
                addr.hostname is None,
480
                addr.hostname,
481
            ),
482
        )
483
    )
484