2021-10-15 03:25:38 -05:00
"""
django - admin interface definitions
This is the interface used by peeringdb admin - com that is currently
exposed at the path ` / cp ` .
New admin views wrapping HandleRef models need to extend the
` SoftDeleteAdmin ` class .
Admin views wrapping verification - queue enabled models need to also
add the ` ModelAdminWithVQCtrl ` Mixin .
Version history is implemented through django - handleref .
"""
2018-11-08 19:45:21 +00:00
import datetime
import ipaddress
2021-07-10 10:12:35 -05:00
import json
2020-07-26 23:36:27 -05:00
import re
2018-11-08 19:45:21 +00:00
2020-01-08 13:29:58 -06:00
import django . urls
2021-07-10 10:12:35 -05:00
import reversion
from django import forms as baseForms
from django . conf import settings
2020-02-05 21:25:25 -06:00
from django . contrib import admin , messages
2018-11-08 19:45:21 +00:00
from django . contrib . admin import helpers
from django . contrib . admin . actions import delete_selected
2021-01-13 20:35:07 +00:00
from django . contrib . auth . admin import UserAdmin
2022-03-08 09:27:45 -04:00
from django . contrib . auth . forms import UserChangeForm
from django . contrib . auth . models import Group
2021-07-10 10:12:35 -05:00
from django . contrib . contenttypes . models import ContentType
from django . core . exceptions import ValidationError
2021-11-12 11:16:25 -06:00
from django . db import transaction
2021-07-10 10:12:35 -05:00
from django . db . models import Q
2020-07-26 23:36:27 -05:00
from django . db . utils import OperationalError
2021-03-09 13:30:30 -06:00
from django . forms import DecimalField
2023-06-20 03:26:06 +03:00
from django . http import HttpResponseForbidden , JsonResponse
2021-08-18 08:21:22 -05:00
from django . shortcuts import redirect
2018-11-08 19:45:21 +00:00
from django . template import loader
from django . template . response import TemplateResponse
2023-06-20 03:26:06 +03:00
from django . urls import re_path
2021-07-10 10:12:35 -05:00
from django . utils import html
2020-01-08 13:29:58 -06:00
from django . utils . safestring import mark_safe
2023-06-20 03:26:06 +03:00
from django . utils . translation import gettext_lazy as _
2021-08-18 08:21:22 -05:00
from django_grainy . admin import UserPermissionInlineAdmin
2019-09-06 21:32:19 -05:00
from django_handleref . admin import VersionAdmin as HandleRefVersionAdmin
2023-05-16 21:04:05 +03:00
from django_otp . plugins . otp_totp . models import TOTPDevice
from django_security_keys . models import SecurityKey
2022-10-11 15:45:07 +03:00
from import_export . admin import ExportMixin
2021-07-10 10:12:35 -05:00
from rest_framework_api_key . admin import APIKeyModelAdmin
from rest_framework_api_key . models import APIKey
from reversion . admin import VersionAdmin
2019-09-06 21:32:19 -05:00
2018-11-08 19:45:21 +00:00
import peeringdb_server . admin_commandline_tools as acltools
2021-07-10 10:12:35 -05:00
from peeringdb_server . inet import RdapException , RdapLookup , rdap_pretty_error_message
2022-05-10 16:56:30 +03:00
from peeringdb_server . mail import (
mail_sponsorship_admin_merge ,
mail_sponsorship_admin_merge_conflict ,
mail_users_entity_merge ,
)
2018-11-08 19:45:21 +00:00
from peeringdb_server . models import (
2019-12-05 16:57:52 +00:00
COMMANDLINE_TOOLS ,
2021-07-10 10:12:35 -05:00
QUEUE_ENABLED ,
REFTAG_MAP ,
UTC ,
2023-02-15 09:55:01 +02:00
Campus ,
2023-01-18 18:32:46 +02:00
Carrier ,
CarrierFacility ,
2021-07-10 10:12:35 -05:00
CommandLineTool ,
2022-05-10 16:56:30 +03:00
DataChangeEmail ,
DataChangeNotificationQueue ,
DataChangeWatchedObject ,
2021-07-10 10:12:35 -05:00
DeskProTicket ,
DeskProTicketCC ,
2022-02-08 13:14:27 -06:00
EnvironmentSetting ,
2019-12-05 16:57:52 +00:00
Facility ,
2021-07-10 10:12:35 -05:00
GeoCoordinateCache ,
2019-12-05 16:57:52 +00:00
InternetExchange ,
InternetExchangeFacility ,
2021-07-10 10:12:35 -05:00
IXFImportEmail ,
IXFMemberData ,
2019-12-05 16:57:52 +00:00
IXLan ,
IXLanIXFMemberImportLog ,
IXLanIXFMemberImportLogEntry ,
IXLanPrefix ,
2021-07-10 10:12:35 -05:00
Network ,
2019-12-05 16:57:52 +00:00
NetworkContact ,
NetworkFacility ,
NetworkIXLan ,
2021-07-10 10:12:35 -05:00
Organization ,
2021-03-09 13:30:30 -06:00
OrganizationAPIKey ,
2021-07-10 10:12:35 -05:00
OrganizationMerge ,
OrganizationMergeEntity ,
Partnership ,
ProtectedAction ,
Sponsorship ,
SponsorshipOrganization ,
User ,
2021-03-09 13:30:30 -06:00
UserAPIKey ,
2021-07-10 10:12:35 -05:00
UserOrgAffiliationRequest ,
VerificationQueueItem ,
2019-12-05 16:57:52 +00:00
)
2021-07-10 10:12:35 -05:00
from peeringdb_server . util import coerce_ipaddr , round_decimal
2021-03-09 13:30:30 -06:00
2021-07-10 10:12:35 -05:00
from . import forms
2018-11-08 19:45:21 +00:00
2020-01-08 13:29:58 -06:00
delete_selected . short_description = " HARD DELETE - Proceed with caution "
2018-11-08 19:45:21 +00:00
2020-06-24 12:55:01 -05:00
# these app labels control permissions for the views
# currently exposed in admin
2018-11-08 19:45:21 +00:00
2020-06-24 12:55:01 -05:00
PERMISSION_APP_LABELS = [
" peeringdb_server " ,
" socialaccount " ,
" sites " ,
" auth " ,
" account " ,
2020-07-15 02:07:01 -05:00
" oauth2_provider " ,
2020-06-24 12:55:01 -05:00
]
2018-11-08 19:45:21 +00:00
2020-07-15 02:07:01 -05:00
2018-11-08 19:45:21 +00:00
class StatusFilter ( admin . SimpleListFilter ) :
"""
2021-10-15 03:25:38 -05:00
A listing filter that , by default , will only show entities
with status = " ok " .
2018-11-08 19:45:21 +00:00
"""
title = _ ( " Status " )
parameter_name = " status "
2019-03-14 08:27:46 +00:00
dflt = " all "
2018-11-08 19:45:21 +00:00
def lookups ( self , request , model_admin ) :
2019-12-05 16:57:52 +00:00
return [
( " ok " , " ok " ) ,
( " pending " , " pending " ) ,
( " deleted " , " deleted " ) ,
( " all " , " all " ) ,
]
2018-11-08 19:45:21 +00:00
def choices ( self , cl ) :
2019-03-14 08:27:46 +00:00
val = self . value ( )
if val is None :
val = " all "
2018-11-08 19:45:21 +00:00
for lookup , title in self . lookup_choices :
yield {
2019-12-05 16:57:52 +00:00
" selected " : val == lookup ,
" query_string " : cl . get_query_string ( { self . parameter_name : lookup } , [ ] ) ,
" display " : title ,
2018-11-08 19:45:21 +00:00
}
def queryset ( self , request , queryset ) :
2019-03-14 08:27:46 +00:00
if self . value ( ) is None or self . value ( ) == " all " :
2018-11-08 19:45:21 +00:00
return queryset . all ( )
return queryset . filter ( * * { self . parameter_name : self . value ( ) } )
def fk_handleref_filter ( form , field , tag = None ) :
"""
This filters foreign key dropdowns that hold handleref objects
2021-10-15 03:25:38 -05:00
so they only contain undeleted objects and the object the instance is currently
set to .
2018-11-08 19:45:21 +00:00
"""
if tag is None :
tag = field
if tag in REFTAG_MAP and form . instance :
model = REFTAG_MAP . get ( tag )
qset = model . handleref . filter (
2019-12-05 16:57:52 +00:00
Q ( status = " ok " ) | Q ( id = getattr ( form . instance , " %s _id " % field ) )
)
2018-11-08 19:45:21 +00:00
try :
qset = qset . order_by ( " name " )
except Exception :
pass
2020-06-24 12:55:01 -05:00
if field in form . fields :
form . fields [ field ] . queryset = qset
2018-11-08 19:45:21 +00:00
###############################################################################
2022-05-10 16:56:30 +03:00
class SponsorshipConflict ( ValueError ) :
def __init__ ( self , orgs ) :
self . orgs = orgs
self . org_names = " , " . join ( [ org . name for org in orgs ] )
return super ( ) . __init__ ( self . org_names )
def merge_organizations_handle_sponsors ( source_orgs , target_org ) :
target_sponsor = target_org . active_or_pending_sponsorship
source_sponsors = { }
for source_org in source_orgs :
source_sponsor = source_org . active_or_pending_sponsorship
if source_sponsor :
source_sponsors . setdefault ( source_sponsor , [ ] )
source_sponsors [ source_sponsor ] . append ( source_org )
conflicting_orgs = [ ]
# find if any of the source orgs have a sponsorship that conflicts
for source_sponsor , _orgs in source_sponsors . items ( ) :
# source sponsorship is same as target sponsorship do nothing
if target_sponsor and source_sponsor != target_sponsor :
conflicting_orgs . extend ( _orgs )
conflicting_orgs . append ( target_org )
# more than one sponsorship found in the source orgs
if len ( source_sponsors ) > 1 :
for source_sponsor , _orgs in source_sponsors . items ( ) :
conflicting_orgs . extend ( _orgs )
# there was at least one conflict
if conflicting_orgs :
raise SponsorshipConflict ( list ( set ( [ target_org ] + conflicting_orgs ) ) )
for source_sponsor , _orgs in source_sponsors . items ( ) :
if target_sponsor == source_sponsor :
continue
return merge_organizations_transfer_sponsor ( source_sponsor , _orgs , target_org )
def merge_organizations_transfer_sponsor ( sponsor , source_orgs , target_org ) :
if not sponsor :
return
for org in source_orgs :
sponsor . orgs . remove ( org )
sponsor . orgs . add ( target_org )
return ( source_orgs , sponsor )
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
@reversion.create_revision ( )
def merge_organizations ( targets , target , request ) :
"""
Merge organizations specified in targets into organization specified
2021-10-15 03:25:38 -05:00
in target .
2018-11-08 19:45:21 +00:00
Arguments :
targets < QuerySet | list > iterable of Organization instances
target < Organization > merge organizations with this organization
"""
if request . user :
reversion . set_user ( request . user )
# preare stats
ix_moved = 0
fac_moved = 0
net_moved = 0
user_moved = 0
org_merged = 0
for org in targets :
if org == target :
2019-12-05 16:57:52 +00:00
raise ValueError ( _ ( " Target org cannot be in selected organizations list " ) )
2018-11-08 19:45:21 +00:00
2022-05-10 16:56:30 +03:00
try :
sponsorship_moved = merge_organizations_handle_sponsors ( targets , target )
except SponsorshipConflict as exc :
mail_sponsorship_admin_merge_conflict ( exc . orgs , target )
return {
" error " : _ (
" There exist some sponsor ship conflicts that will need to be manually resolved before this merge can happen. {} has been notified of this conflict. Conflicting organizations: {} "
) . format ( settings . SPONSORSHIPS_EMAIL , exc . org_names )
}
2018-11-08 19:45:21 +00:00
for org in targets :
merge = OrganizationMerge . objects . create ( from_org = org , to_org = target )
source_admins = [ ]
# move entities
for ix in org . ix_set . all ( ) :
ix . org = target
ix . save ( )
merge . log_entity ( ix )
ix_moved + = 1
for net in org . net_set . all ( ) :
net . org = target
net . save ( )
merge . log_entity ( net )
net_moved + = 1
for fac in org . fac_set . all ( ) :
fac . org = target
fac . save ( )
merge . log_entity ( fac )
fac_moved + = 1
# move users
for user in org . usergroup . user_set . all ( ) :
2022-06-15 15:23:26 +03:00
# Skip user migration if user is already in the admin group
if user in target . admin_usergroup . user_set . all ( ) :
continue
2018-11-08 19:45:21 +00:00
target . usergroup . user_set . add ( user )
org . usergroup . user_set . remove ( user )
merge . log_entity ( user , note = " usergroup " )
user_moved + = 1
for user in org . admin_usergroup . user_set . all ( ) :
2022-06-15 15:23:26 +03:00
if user in target . admin_usergroup . user_set . all ( ) :
continue
2018-11-08 19:45:21 +00:00
target . usergroup . user_set . add ( user )
org . admin_usergroup . user_set . remove ( user )
merge . log_entity ( user , note = " admin_usergroup " )
user_moved + = 1
source_admins . append ( user )
# mark deleted
org . delete ( )
org_merged + = 1
2022-05-10 16:56:30 +03:00
if sponsorship_moved and org in sponsorship_moved [ 0 ] :
merge . log_entity ( sponsorship_moved [ 1 ] )
2019-12-05 16:57:52 +00:00
mail_users_entity_merge (
source_admins , target . admin_usergroup . user_set . all ( ) , org , target
)
2018-11-08 19:45:21 +00:00
2022-05-10 16:56:30 +03:00
if sponsorship_moved :
mail_sponsorship_admin_merge ( sponsorship_moved [ 0 ] , target )
2018-11-08 19:45:21 +00:00
return {
" ix " : ix_moved ,
" fac " : fac_moved ,
" net " : net_moved ,
" user " : user_moved ,
2019-12-05 16:57:52 +00:00
" org " : org_merged ,
2022-05-10 16:56:30 +03:00
" sponsorship_moved " : f " { sponsorship_moved } " ,
2018-11-08 19:45:21 +00:00
}
###############################################################################
class StatusForm ( baseForms . ModelForm ) :
2019-12-05 16:57:52 +00:00
status = baseForms . ChoiceField (
choices = [ ( " ok " , " ok " ) , ( " pending " , " pending " ) , ( " deleted " , " deleted " ) ]
)
2018-11-08 19:45:21 +00:00
def __init__ ( self , * args , * * kwargs ) :
2020-07-15 02:07:01 -05:00
super ( ) . __init__ ( * args , * * kwargs )
2018-11-08 19:45:21 +00:00
if " instance " in kwargs and kwargs . get ( " instance " ) :
inst = kwargs . get ( " instance " )
if inst . status == " ok " :
self . fields [ " status " ] . choices = [ ( " ok " , " ok " ) ]
elif inst . status == " pending " :
2019-12-05 16:57:52 +00:00
self . fields [ " status " ] . choices = [ ( " ok " , " ok " ) , ( " pending " , " pending " ) ]
2018-11-08 19:45:21 +00:00
elif inst . status == " deleted " :
2019-12-05 16:57:52 +00:00
self . fields [ " status " ] . choices = [ ( " ok " , " ok " ) , ( " deleted " , " deleted " ) ]
2018-11-08 19:45:21 +00:00
2020-08-18 09:30:40 -05:00
def clean ( self ) :
"""
Catches and raises validation errors where an object
is to be soft - deleted but cannot be because it is currently
protected .
"""
if self . cleaned_data . get ( " DELETE " ) :
if self . instance and hasattr ( self . instance , " deletable " ) :
if not self . instance . deletable :
self . cleaned_data [ " DELETE " ] = False
raise ValidationError ( self . instance . not_deletable_reason )
2018-11-08 19:45:21 +00:00
class ModelAdminWithUrlActions ( admin . ModelAdmin ) :
def make_redirect ( self , obj , action ) :
opts = obj . model . _meta
2020-07-15 02:07:01 -05:00
return redirect ( f " admin: { opts . app_label } _ { opts . model_name } _changelist " )
2018-11-08 19:45:21 +00:00
def actions_view ( self , request , object_id , action , * * kwargs ) :
"""
2021-10-15 03:25:38 -05:00
Allows one to call any actions defined in this model admin
to be called via an admin view placed at < model_name > / < id > / < action > / < action_name > .
2018-11-08 19:45:21 +00:00
"""
if not request . user . is_superuser :
return HttpResponseForbidden ( request )
obj = self . get_queryset ( request ) . filter ( pk = object_id )
if obj . exists ( ) :
redir = self . make_redirect ( obj , action )
action = self . get_action ( action )
if action :
action [ 0 ] ( self , request , obj )
return redir
# return redirect("admin:%s_%s_changelist" % (opts.app_label, opts.model_name))
return redirect (
2019-12-05 16:57:52 +00:00
" admin: %s _ %s _changelist "
% ( obj . model . _meta . app_label , obj . model . _meta . model_name )
)
2018-11-08 19:45:21 +00:00
def get_urls ( self ) :
"""
2021-10-15 03:25:38 -05:00
Adds the actions view as a subview of this model ' s admin views.
2018-11-08 19:45:21 +00:00
"""
info = self . model . _meta . app_label , self . model . _meta . model_name
urls = [
2023-06-20 03:26:06 +03:00
re_path (
2019-12-05 16:57:52 +00:00
r " ^( \ d+)/action/([ \ w]+)/$ " ,
2018-11-08 19:45:21 +00:00
self . admin_site . admin_view ( self . actions_view ) ,
2019-12-05 16:57:52 +00:00
name = " %s _ %s _actions " % info ,
) ,
2020-07-15 02:07:01 -05:00
] + super ( ) . get_urls ( )
2018-11-08 19:45:21 +00:00
return urls
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
@reversion.create_revision ( )
def rollback ( modeladmin , request , queryset ) :
if request . user :
reversion . set_user ( request . user )
for row in queryset :
row . rollback ( )
rollback . short_description = _ ( " ROLLBACK " )
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
@reversion.create_revision ( )
def soft_delete ( modeladmin , request , queryset ) :
2022-01-11 08:56:47 -06:00
if request . POST . get ( " delete " ) :
if request . user :
reversion . set_user ( request . user )
if queryset . model . handleref . tag == " ixlan " :
messages . error (
request ,
_ (
" Ixlans can no longer be directly deleted as they are now synced to the parent exchange "
) ,
)
return
for row in queryset :
try :
row . delete ( )
except ProtectedAction as err :
messages . error ( request , _ ( " Protected object ' {} ' : {} " ) . format ( row , err ) )
continue
else :
context = dict (
admin . site . each_context ( request ) ,
deletable_objects = queryset ,
action_checkbox_name = helpers . ACTION_CHECKBOX_NAME ,
title = _ ( " Delete selected objects " ) ,
2020-02-05 21:25:25 -06:00
)
2022-01-11 08:56:47 -06:00
messages . warning ( request , _ ( " Please confirm deletion of selected objects. " ) )
return TemplateResponse ( request , " admin/soft_delete.html " , context )
2018-11-08 19:45:21 +00:00
soft_delete . short_description = _ ( " SOFT DELETE " )
2021-07-07 17:57:04 -05:00
class CustomResultLengthFilter ( admin . SimpleListFilter ) :
"""
Filter object that enables custom result length
2021-10-15 03:25:38 -05:00
in django - admin change lists .
2021-07-07 17:57:04 -05:00
This should only be used in a model admin that extends
2021-10-15 03:25:38 -05:00
CustomResultLengthAdmin .
2021-07-07 17:57:04 -05:00
"""
2022-09-09 18:39:50 +02:00
title = _ ( " Result length " )
2021-07-07 17:57:04 -05:00
parameter_name = " sz "
def lookups ( self , request , model_admin ) :
return (
( " 10 " , _ ( " Show {} rows " ) . format ( 10 ) ) ,
( " 25 " , _ ( " Show {} rows " ) . format ( 25 ) ) ,
( " 50 " , _ ( " Show {} rows " ) . format ( 50 ) ) ,
( " 100 " , _ ( " Show {} rows " ) . format ( 100 ) ) ,
( " 250 " , _ ( " Show {} rows " ) . format ( 250 ) ) ,
( " all " , _ ( " Show {} rows " ) . format ( " all " ) ) ,
)
def queryset ( self , request , queryset ) :
# we simply give back the queryset, since result
# length is controlled by the changelist instance
# and not the queryset
return queryset
def choices ( self , changelist ) :
value = self . value ( )
if value is None :
value = f " { changelist . list_per_page } "
for lookup , title in self . lookup_choices :
yield {
" selected " : value == str ( lookup ) ,
" query_string " : changelist . get_query_string (
{ self . parameter_name : lookup }
) ,
" display " : title ,
}
class CustomResultLengthAdmin :
def get_list_filter ( self , request ) :
list_filter = super ( ) . get_list_filter ( request )
return list_filter + ( CustomResultLengthFilter , )
def get_changelist ( self , request , * * kwargs ) :
# handle the customizable result length filter
# in the django-admin change list listings (#587)
#
# this is accomplished through the `sz` url parameter
if " sz " in request . GET :
try :
sz = request . GET . get ( " sz " )
# all currently translates to a max of 100k entries
# this is a conservative limit that should be fine for
# the time being (possible performance concerns going
# bigger than that)
if sz == " all " :
sz = request . list_max_show_all = 100000
else :
sz = int ( sz )
except TypeError :
# value could not be converted to integer
# fall back to default
sz = self . list_per_page
else :
sz = self . list_per_page
request . list_per_page = sz
return super ( ) . get_changelist ( request , * * kwargs )
def get_changelist_instance ( self , request ) :
"""
2021-10-15 03:25:38 -05:00
Returns a ` ChangeList ` instance based on ` request ` . May raise
2021-07-07 17:57:04 -05:00
` IncorrectLookupParameters ` .
This is copied from the original function in the dango source
for 2.2
2021-10-15 03:25:38 -05:00
This is overriden it here so one can set the list_per_page and list_max_show_all
2021-07-07 17:57:04 -05:00
values on the ChangeList accordingly .
"""
list_display = self . get_list_display ( request )
list_display_links = self . get_list_display_links ( request , list_display )
# Add the action checkboxes if any actions are available.
if self . get_actions ( request ) :
list_display = [ " action_checkbox " , * list_display ]
sortable_by = self . get_sortable_by ( request )
ChangeList = self . get_changelist ( request )
list_per_page = getattr ( request , " list_per_page " , self . list_per_page )
list_max_show_all = getattr (
request , " list_max_show_all " , self . list_max_show_all
)
cl = ChangeList (
request ,
self . model ,
list_display ,
list_display_links ,
self . get_list_filter ( request ) ,
self . date_hierarchy ,
self . get_search_fields ( request ) ,
self . get_list_select_related ( request ) ,
list_per_page ,
list_max_show_all ,
self . list_editable ,
self ,
2023-06-20 03:26:06 +03:00
None ,
2021-07-07 17:57:04 -05:00
sortable_by ,
)
cl . allow_custom_result_length = True
return cl
class SanitizedAdmin ( CustomResultLengthAdmin ) :
2018-11-08 19:45:21 +00:00
def get_readonly_fields ( self , request , obj = None ) :
2020-07-26 23:36:27 -05:00
return ( " version " , ) + tuple ( super ( ) . get_readonly_fields ( request , obj = obj ) )
2018-11-08 19:45:21 +00:00
2019-12-05 16:57:52 +00:00
class SoftDeleteAdmin (
2022-10-11 15:45:07 +03:00
ExportMixin , SanitizedAdmin , HandleRefVersionAdmin , VersionAdmin , admin . ModelAdmin
2019-12-05 16:57:52 +00:00
) :
2018-11-08 19:45:21 +00:00
"""
2021-10-15 03:25:38 -05:00
Soft delete admin .
2018-11-08 19:45:21 +00:00
"""
2019-12-05 16:57:52 +00:00
2018-11-08 19:45:21 +00:00
actions = [ soft_delete ]
2022-01-11 08:56:47 -06:00
2019-09-06 21:32:19 -05:00
object_history_template = " handleref/grappelli/object_history.html "
version_details_template = " handleref/grappelli/version_details.html "
version_revert_template = " handleref/grappelli/version_revert.html "
version_rollback_template = " handleref/grappelli/version_rollback.html "
2018-11-08 19:45:21 +00:00
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
@reversion.create_revision ( )
def save_formset ( self , request , form , formset , change ) :
if request . user :
reversion . set_user ( request . user )
2020-07-15 02:07:01 -05:00
super ( ) . save_formset ( request , form , formset , change )
2018-11-08 19:45:21 +00:00
2021-01-13 20:35:07 +00:00
def grainy_namespace ( self , obj ) :
return obj . grainy_namespace
2022-01-11 08:56:47 -06:00
def get_actions ( self , request ) :
actions = super ( ) . get_actions ( request )
if " delete_selected " in actions :
del actions [ " delete_selected " ]
return actions
class ProtectedDeleteAdmin ( admin . ModelAdmin ) :
"""
Allow deletion of objects if the user is superuser
"""
def has_delete_permission ( self , request , obj = None ) :
return request . user . is_superuser
2018-11-08 19:45:21 +00:00
2020-07-15 02:07:01 -05:00
class ModelAdminWithVQCtrl :
2018-11-08 19:45:21 +00:00
"""
Extend from this model admin if you want to add verification queue
2021-10-15 03:25:38 -05:00
approve | deny controls to the top of its form .
2018-11-08 19:45:21 +00:00
"""
def get_fieldsets ( self , request , obj = None ) :
"""
2021-10-15 03:25:38 -05:00
Overrides get_fieldsets so one can attach the vq controls
to the top of the existing fieldset - whether it ' s manually or automatically
defined .
2018-11-08 19:45:21 +00:00
"""
2020-07-26 23:36:27 -05:00
fieldsets = tuple ( super ( ) . get_fieldsets ( request , obj = obj ) )
2018-11-08 19:45:21 +00:00
# on automatically defined fieldsets it will insert the controls
2020-09-29 20:01:47 +00:00
# somewhere towards the bottom, we don't want that - so we look for it and
2018-11-08 19:45:21 +00:00
# remove it
for k , s in fieldsets :
2019-12-05 16:57:52 +00:00
if " verification_queue " in s [ " fields " ] :
2018-11-08 19:45:21 +00:00
s [ " fields " ] . remove ( " verification_queue " )
# attach controls to top of fieldset
2019-12-05 16:57:52 +00:00
fieldsets = (
( None , { " classes " : ( " wide, " ) , " fields " : ( " verification_queue " , ) } ) ,
) + fieldsets
2018-11-08 19:45:21 +00:00
return fieldsets
def get_readonly_fields ( self , request , obj = None ) :
"""
2021-10-15 03:25:38 -05:00
Makes the modeladmin aware that " verification_queue " is a valid
readonly field .
2018-11-08 19:45:21 +00:00
"""
2019-12-05 16:57:52 +00:00
return ( " verification_queue " , ) + tuple (
2020-07-15 02:07:01 -05:00
super ( ) . get_readonly_fields ( request , obj = obj )
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
def verification_queue ( self , obj ) :
"""
2021-10-15 03:25:38 -05:00
Renders the controls or a status message .
2018-11-08 19:45:21 +00:00
"""
2019-01-04 10:02:28 +00:00
if getattr ( settings , " DISABLE_VERIFICATION_QUEUE " , False ) :
2018-11-08 19:45:21 +00:00
return _ ( " Verification Queue is currently disabled " )
if self . model not in QUEUE_ENABLED :
2019-12-05 16:57:52 +00:00
return _ ( " Verification Queue is currently disabled for this object type " )
2018-11-08 19:45:21 +00:00
vq = VerificationQueueItem . objects . filter (
2019-12-05 16:57:52 +00:00
content_type = ContentType . objects . get_for_model ( type ( obj ) ) , object_id = obj . id
) . first ( )
2018-11-08 19:45:21 +00:00
if vq :
2020-01-08 13:29:58 -06:00
return mark_safe (
' <a class= " grp-button " href= " {} " > {} </a> <a class= " grp-button grp-delete-link " href= " {} " > {} </a> ' . format (
vq . approve_admin_url , _ ( " APPROVE " ) , vq . deny_admin_url , _ ( " DENY " )
)
)
2018-11-08 19:45:21 +00:00
return _ ( " APPROVED " )
2020-07-15 02:07:01 -05:00
class IXLanPrefixForm ( StatusForm ) :
def clean_prefix ( self ) :
value = ipaddress . ip_network ( self . cleaned_data [ " prefix " ] )
self . prefix_changed = self . instance . prefix != value
return value
def clean ( self ) :
2020-08-18 09:30:40 -05:00
super ( ) . clean ( )
2020-07-15 02:07:01 -05:00
if self . prefix_changed and not self . instance . deletable :
raise ValidationError ( self . instance . not_deletable_reason )
2018-11-08 19:45:21 +00:00
class IXLanPrefixInline ( SanitizedAdmin , admin . TabularInline ) :
model = IXLanPrefix
extra = 0
2020-07-15 02:07:01 -05:00
form = IXLanPrefixForm
2020-08-18 09:28:18 -05:00
fields = [ " status " , " protocol " , " prefix " ]
2018-11-08 19:45:21 +00:00
class IXLanInline ( SanitizedAdmin , admin . StackedInline ) :
model = IXLan
extra = 0
form = StatusForm
2021-08-18 08:21:22 -05:00
exclude = [ " arp_sponge " , " dot1q_support " ]
2020-06-24 12:55:01 -05:00
readonly_fields = [ " ixf_import_attempt_info " , " prefixes " ]
2020-02-05 21:25:25 -06:00
2021-08-18 08:21:22 -05:00
def has_add_permission ( self , request , obj = None ) :
2020-02-05 21:25:25 -06:00
return False
def has_delete_permission ( self , request , obj ) :
return False
2018-11-08 19:45:21 +00:00
def ixf_import_attempt_info ( self , obj ) :
if obj . ixf_import_attempt :
2020-07-15 02:07:01 -05:00
return mark_safe ( f " <pre> { obj . ixf_import_attempt . info } </pre> " )
2018-11-08 19:45:21 +00:00
return " "
def prefixes ( self , obj ) :
return " , " . join (
2019-12-05 16:57:52 +00:00
[ str ( ixpfx . prefix ) for ixpfx in obj . ixpfx_set_active_or_pending ]
)
2018-11-08 19:45:21 +00:00
class InternetExchangeFacilityInline ( SanitizedAdmin , admin . TabularInline ) :
model = InternetExchangeFacility
extra = 0
form = StatusForm
2021-01-13 20:35:07 +00:00
raw_id_fields = ( " ix " , " facility " )
2018-11-08 19:45:21 +00:00
2021-01-13 20:35:07 +00:00
def __init__ ( self , parent_model , admin_site ) :
super ( ) . __init__ ( parent_model , admin_site )
if parent_model == Facility :
self . autocomplete_lookup_fields = { " fk " : [ " ix " ] }
elif parent_model == InternetExchange :
self . autocomplete_lookup_fields = { " fk " : [ " facility " ] }
2020-02-05 21:26:21 -06:00
2018-11-08 19:45:21 +00:00
class NetworkContactInline ( SanitizedAdmin , admin . TabularInline ) :
model = NetworkContact
extra = 0
form = StatusForm
class NetworkFacilityInline ( SanitizedAdmin , admin . TabularInline ) :
model = NetworkFacility
extra = 0
form = StatusForm
2021-01-13 20:35:07 +00:00
raw_id_fields = ( " network " , " facility " )
exclude = ( " local_asn " , )
def __init__ ( self , parent_model , admin_site ) :
super ( ) . __init__ ( parent_model , admin_site )
if parent_model == Facility :
self . autocomplete_lookup_fields = { " fk " : [ " network " ] }
elif parent_model == Network :
self . autocomplete_lookup_fields = { " fk " : [ " facility " ] }
2020-02-05 21:26:21 -06:00
2020-07-15 02:07:01 -05:00
class NetworkIXLanForm ( StatusForm ) :
def clean_ipaddr4 ( self ) :
value = self . cleaned_data [ " ipaddr4 " ]
if not value :
return None
return value
def clean_ipaddr6 ( self ) :
value = self . cleaned_data [ " ipaddr6 " ]
if not value :
return None
return value
2018-11-08 19:45:21 +00:00
class NetworkInternetExchangeInline ( SanitizedAdmin , admin . TabularInline ) :
model = NetworkIXLan
extra = 0
raw_id_fields = ( " ixlan " , " network " )
2020-07-15 02:07:01 -05:00
form = NetworkIXLanForm
2018-11-08 19:45:21 +00:00
class UserOrgAffiliationRequestInlineForm ( baseForms . ModelForm ) :
def clean ( self ) :
2020-07-15 02:07:01 -05:00
super ( ) . clean ( )
2018-11-08 19:45:21 +00:00
try :
2020-03-09 18:16:59 +00:00
asn = self . cleaned_data . get ( " asn " )
if asn :
2021-08-18 08:21:22 -05:00
RdapLookup ( ) . get_asn ( asn ) . emails
2018-11-08 19:45:21 +00:00
except RdapException as exc :
2021-01-13 20:35:07 +00:00
raise ValidationError ( { " asn " : rdap_pretty_error_message ( exc ) } )
2018-11-08 19:45:21 +00:00
class UserOrgAffiliationRequestInline ( admin . TabularInline ) :
model = UserOrgAffiliationRequest
extra = 0
form = UserOrgAffiliationRequestInlineForm
2019-12-05 16:57:52 +00:00
verbose_name_plural = _ ( " User is looking to be affiliated to these Organizations " )
2020-04-10 15:00:27 +00:00
raw_id_fields = ( " org " , )
autocomplete_lookup_fields = {
" fk " : [ " org " ] ,
}
2018-11-08 19:45:21 +00:00
2023-05-16 21:04:05 +03:00
class UserDeviceInline ( admin . TabularInline ) :
model = TOTPDevice
extra = 0
verbose_name_plural = _ ( " User has these TOTP devices " )
raw_id_fields = ( " user " , )
autocomplete_lookup_fields = {
" fk " : [ " user " ] ,
}
class UserWebauthnSecurityKeyInline ( admin . TabularInline ) :
model = SecurityKey
extra = 0
verbose_name_plural = _ ( " User has these Webauthn Security Keys " )
raw_id_fields = ( " user " , )
autocomplete_lookup_fields = {
" fk " : [ " user " ] ,
}
2018-11-08 19:45:21 +00:00
class InternetExchangeAdminForm ( StatusForm ) :
def __init__ ( self , * args , * * kwargs ) :
2020-07-15 02:07:01 -05:00
super ( ) . __init__ ( * args , * * kwargs )
2018-11-08 19:45:21 +00:00
fk_handleref_filter ( self , " org " )
class InternetExchangeAdmin ( ModelAdminWithVQCtrl , SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = (
" name " ,
2021-03-09 13:30:30 -06:00
" aka " ,
2019-12-05 16:57:52 +00:00
" name_long " ,
" city " ,
" country " ,
" status " ,
" created " ,
" updated " ,
)
ordering = ( " -created " , )
list_filter = ( StatusFilter , )
search_fields = ( " name " , )
2020-07-15 02:07:01 -05:00
readonly_fields = (
" id " ,
2021-01-13 20:35:07 +00:00
" grainy_namespace " ,
2020-07-15 02:07:01 -05:00
" ixf_import_history " ,
" ixf_last_import " ,
" ixf_net_count " ,
2021-05-19 09:11:30 -04:00
" proto_unicast_readonly " ,
" proto_ipv6_readonly " ,
" proto_multicast_readonly " ,
2023-08-15 21:40:18 +03:00
" org_website " ,
2020-07-15 02:07:01 -05:00
)
2018-11-08 19:45:21 +00:00
inlines = ( InternetExchangeFacilityInline , IXLanInline )
form = InternetExchangeAdminForm
2021-05-19 09:11:30 -04:00
exclude = (
" proto_unicast " ,
" proto_ipv6 " ,
" proto_multicast " ,
)
2022-01-11 08:56:47 -06:00
raw_id_fields = ( " org " , " ixf_import_request_user " )
2020-02-05 21:26:21 -06:00
autocomplete_lookup_fields = {
2022-01-11 08:56:47 -06:00
" fk " : [ " org " , " ixf_import_request_user " ] ,
2020-02-05 21:26:21 -06:00
}
2018-11-08 19:45:21 +00:00
def ixf_import_history ( self , obj ) :
2020-01-08 13:29:58 -06:00
return mark_safe (
' <a href= " {} ?q= {} " > {} </a> ' . format (
django . urls . reverse (
" admin:peeringdb_server_ixlanixfmemberimportlog_changelist "
) ,
obj . id ,
2021-01-13 20:35:07 +00:00
_ ( " IX-F Import History " ) ,
2020-01-08 13:29:58 -06:00
)
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
2021-05-19 09:11:30 -04:00
def proto_unicast_readonly ( self , obj ) :
return obj . derived_proto_unicast
def proto_ipv6_readonly ( self , obj ) :
return obj . derived_proto_ipv6
def proto_multicast_readonly ( self , obj ) :
return obj . proto_multicast
proto_unicast_readonly . short_description = _ ( " Unicast IPv4 " )
proto_ipv6_readonly . short_description = _ ( " Unicast IPv6 " )
proto_multicast_readonly . short_description = _ ( " Multicast " )
2023-08-15 21:40:18 +03:00
def org_website ( self , obj ) :
if obj . org and obj . org . website :
url = html . escape ( obj . org . website )
return mark_safe ( f ' <a href= " { url } " > { url } </a> ' )
return None
2018-11-08 19:45:21 +00:00
class IXLanAdminForm ( StatusForm ) :
def __init__ ( self , * args , * * kwargs ) :
2020-07-15 02:07:01 -05:00
super ( ) . __init__ ( * args , * * kwargs )
2018-11-08 19:45:21 +00:00
fk_handleref_filter ( self , " ix " )
class IXLanAdmin ( SoftDeleteAdmin ) :
2020-02-05 21:25:25 -06:00
actions = [ ]
2019-12-05 16:57:52 +00:00
list_display = ( " ix " , " name " , " descr " , " status " )
2020-01-10 14:18:11 -06:00
search_fields = ( " name " , " ix__name " )
2021-08-18 08:21:22 -05:00
exclude = ( " dot1q_support " , )
2019-12-05 16:57:52 +00:00
list_filter = ( StatusFilter , )
readonly_fields = ( " id " , )
2018-11-08 19:45:21 +00:00
inlines = ( IXLanPrefixInline , NetworkInternetExchangeInline )
form = IXLanAdminForm
2020-02-05 21:26:21 -06:00
raw_id_fields = ( " ix " , )
autocomplete_lookup_fields = {
2020-09-30 01:13:38 +00:00
" fk " : [
" ix " ,
] ,
2020-02-05 21:26:21 -06:00
}
2018-11-08 19:45:21 +00:00
class IXLanIXFMemberImportLogEntryInline ( admin . TabularInline ) :
model = IXLanIXFMemberImportLogEntry
2019-12-05 16:57:52 +00:00
fields = (
" netixlan " ,
2020-07-15 02:07:01 -05:00
" versions " ,
2019-12-05 16:57:52 +00:00
" ipv4 " ,
" ipv6 " ,
" asn " ,
" changes " ,
" rollback_status " ,
" action " ,
" reason " ,
)
readonly_fields = (
" netixlan " ,
" ipv4 " ,
" ipv6 " ,
" asn " ,
" changes " ,
" rollback_status " ,
" action " ,
" reason " ,
2020-07-15 02:07:01 -05:00
" versions " ,
2019-12-05 16:57:52 +00:00
)
raw_id_fields = ( " netixlan " , )
2018-11-08 19:45:21 +00:00
extra = 0
def has_delete_permission ( self , request , obj = None ) :
return False
def has_add_permission ( self , request , obj = None ) :
return False
2020-07-15 02:07:01 -05:00
def versions ( self , obj ) :
before = self . before_id ( obj )
after = self . after_id ( obj )
return f " { before } -> { after } "
def before_id ( self , obj ) :
if obj . version_before :
return obj . version_before . id
return " - "
def after_id ( self , obj ) :
if obj . version_after :
return obj . version_after . id
return " - "
2018-11-08 19:45:21 +00:00
def ipv4 ( self , obj ) :
2020-07-15 02:07:01 -05:00
v = obj . version_after
if v :
return v . field_dict . get ( " ipaddr4 " )
2018-11-08 19:45:21 +00:00
return obj . netixlan . ipaddr4 or " "
def ipv6 ( self , obj ) :
2020-07-15 02:07:01 -05:00
v = obj . version_after
if v :
return v . field_dict . get ( " ipaddr6 " )
2018-11-08 19:45:21 +00:00
return obj . netixlan . ipaddr6 or " "
def asn ( self , obj ) :
return obj . netixlan . asn
def changes ( self , obj ) :
vb = obj . version_before
va = obj . version_after
if not vb :
return _ ( " Initial creation of netixlan " )
rv = { }
2020-01-08 13:29:58 -06:00
for k , v in list ( va . field_dict . items ( ) ) :
2018-11-08 19:45:21 +00:00
if k in [ " created " , " updated " , " version " ] :
continue
v2 = vb . field_dict . get ( k )
if v != v2 :
if isinstance ( v , ipaddress . IPv4Address ) or isinstance (
2019-12-05 16:57:52 +00:00
v , ipaddress . IPv6Address
) :
2018-11-08 19:45:21 +00:00
rv [ k ] = str ( v )
else :
rv [ k ] = v
return json . dumps ( rv )
def rollback_status ( self , obj ) :
rs = obj . rollback_status ( )
text = " "
color = " "
if rs == 0 :
text = _ ( " CAN BE ROLLED BACK " )
color = " #e5f3d6 "
elif rs == 1 :
2020-01-08 13:29:58 -06:00
text = ( " {} <br><small> {} </small> " ) . format (
2019-12-05 16:57:52 +00:00
_ ( " CANNOT BE ROLLED BACK " ) , _ ( " Has been changed since " )
)
2018-11-08 19:45:21 +00:00
color = " #f3ded6 "
elif rs == 2 :
2020-01-08 13:29:58 -06:00
text = ( " {} <br><small> {} </small> " ) . format (
2018-11-08 19:45:21 +00:00
_ ( " CANNOT BE ROLLED BACK " ) ,
2019-12-05 16:57:52 +00:00
_ ( " Netixlan with conflicting ipaddress now exists elsewhere " ) ,
)
2018-11-08 19:45:21 +00:00
color = " #f3ded6 "
elif rs == - 1 :
text = _ ( " HAS BEEN ROLLED BACK " )
color = " #d6f0f3 "
2020-07-26 23:36:27 -05:00
return mark_safe ( f ' <div style= " background-color: { color } " > { text } </div> ' )
2018-11-08 19:45:21 +00:00
2022-10-11 15:45:07 +03:00
class IXLanIXFMemberImportLogAdmin (
ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin
) :
2019-12-05 16:57:52 +00:00
search_fields = ( " ixlan__ix__id " , )
2018-11-08 19:45:21 +00:00
list_display = ( " id " , " ix " , " ixlan_name " , " source " , " created " , " changes " )
readonly_fields = ( " ix " , " ixlan_name " , " source " , " changes " )
2019-12-05 16:57:52 +00:00
inlines = ( IXLanIXFMemberImportLogEntryInline , )
2018-11-08 19:45:21 +00:00
actions = [ rollback ]
def has_delete_permission ( self , request , obj = None ) :
return False
def changes ( self , obj ) :
return obj . entries . count ( )
def ix ( self , obj ) :
2020-01-08 13:29:58 -06:00
return mark_safe (
' <a href= " {} " > {} (ID: {} )</a> ' . format (
django . urls . reverse (
" admin:peeringdb_server_internetexchange_change " ,
args = ( obj . ixlan . ix . id , ) ,
) ,
obj . ixlan . ix . name ,
obj . ixlan . ix . id ,
)
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
def ixlan_name ( self , obj ) :
2020-01-08 13:29:58 -06:00
return mark_safe (
' <a href= " {} " > {} (ID: {} )</a> ' . format (
django . urls . reverse (
" admin:peeringdb_server_ixlan_change " , args = ( obj . ixlan . id , )
) ,
obj . ixlan . name or " " ,
obj . ixlan . id ,
)
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
def source ( self , obj ) :
return obj . ixlan . ixf_ixp_member_list_url
2019-12-05 16:57:52 +00:00
2019-11-16 21:32:27 -06:00
class SponsorshipOrganizationInline ( admin . TabularInline ) :
model = SponsorshipOrganization
2019-11-20 10:29:57 -06:00
extra = 1
2019-12-05 16:57:52 +00:00
raw_id_fields = ( " org " , )
2019-11-16 21:32:27 -06:00
autocomplete_lookup_fields = {
2019-12-05 16:57:52 +00:00
" fk " : [ " org " ] ,
2019-11-16 21:32:27 -06:00
}
2018-11-08 19:45:21 +00:00
2019-12-05 16:57:52 +00:00
2022-10-11 15:45:07 +03:00
class SponsorshipAdmin ( ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = ( " organizations " , " start_date " , " end_date " , " level " , " status " )
readonly_fields = ( " organizations " , " status " , " notify_date " )
2019-11-16 21:32:27 -06:00
inlines = ( SponsorshipOrganizationInline , )
2018-11-08 19:45:21 +00:00
2019-12-05 16:57:52 +00:00
raw_id_fields = ( " orgs " , )
2023-08-15 21:40:18 +03:00
search_fields = ( " orgs__name " , " level " , " start_date " , " end_date " )
2018-11-08 19:45:21 +00:00
2019-11-16 21:32:27 -06:00
autocomplete_lookup_fields = {
2019-12-05 16:57:52 +00:00
" m2m " : [ " orgs " ] ,
2019-11-16 21:32:27 -06:00
}
2018-11-08 19:45:21 +00:00
def status ( self , obj ) :
now = datetime . datetime . now ( ) . replace ( tzinfo = UTC ( ) )
if not obj . start_date or not obj . end_date :
return _ ( " Not Set " )
if obj . start_date < = now and obj . end_date > = now :
2019-11-16 21:32:27 -06:00
for row in obj . sponsorshiporg_set . all ( ) :
2019-11-20 10:29:57 -06:00
if row . logo :
return _ ( " Active " )
return _ ( " Logo Missing " )
2018-11-08 19:45:21 +00:00
elif now > obj . end_date :
return _ ( " Over " )
else :
return _ ( " Waiting " )
2019-11-16 21:32:27 -06:00
def organizations ( self , obj ) :
qset = obj . orgs . all ( ) . order_by ( " name " )
2019-11-20 10:29:57 -06:00
if not qset . count ( ) :
return _ ( " No organization(s) set " )
2020-01-08 13:29:58 -06:00
return mark_safe ( " <br> \n " . join ( [ html . escape ( org . name ) for org in qset ] ) )
2019-11-16 21:32:27 -06:00
2019-12-05 16:57:52 +00:00
2018-11-08 19:45:21 +00:00
class PartnershipAdminForm ( baseForms . ModelForm ) :
def __init__ ( self , * args , * * kwargs ) :
2020-07-15 02:07:01 -05:00
super ( ) . __init__ ( * args , * * kwargs )
2018-11-08 19:45:21 +00:00
fk_handleref_filter ( self , " org " )
2022-10-11 15:45:07 +03:00
class PartnershipAdmin ( ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = ( " org_name " , " level " , " status " )
readonly_fields = ( " status " , " org_name " )
2018-11-08 19:45:21 +00:00
form = PartnershipAdminForm
2020-04-10 15:00:27 +00:00
raw_id_fields = ( " org " , )
autocomplete_lookup_fields = {
" fk " : [ " org " ] ,
}
2023-08-15 21:40:18 +03:00
search_fields = ( " org__name " , )
2018-11-08 19:45:21 +00:00
def org_name ( self , obj ) :
if not obj . org :
return " "
return obj . org . name
org_name . admin_order_field = " org__name "
org_name . short_description = " Organization "
def status ( self , obj ) :
if not obj . logo :
return _ ( " Logo Missing " )
return _ ( " Active " )
2021-03-09 13:30:30 -06:00
class RoundingDecimalFormField ( DecimalField ) :
def to_python ( self , value ) :
2021-07-10 10:12:35 -05:00
value = super ( ) . to_python ( value )
2021-03-09 13:30:30 -06:00
return round_decimal ( value , self . decimal_places )
class OrganizationAdminForm ( StatusForm ) :
2021-04-13 08:59:23 -05:00
latitude = RoundingDecimalFormField ( max_digits = 9 , decimal_places = 6 , required = False )
longitude = RoundingDecimalFormField ( max_digits = 9 , decimal_places = 6 , required = False )
2021-03-09 13:30:30 -06:00
2018-11-08 19:45:21 +00:00
class OrganizationAdmin ( ModelAdminWithVQCtrl , SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = ( " handle " , " name " , " status " , " created " , " updated " )
ordering = ( " -created " , )
search_fields = ( " name " , )
2021-11-09 09:47:38 -06:00
list_filter = ( StatusFilter , " flagged " )
2021-01-13 20:35:07 +00:00
readonly_fields = ( " id " , " grainy_namespace " )
2021-03-09 13:30:30 -06:00
form = OrganizationAdminForm
2018-11-08 19:45:21 +00:00
2020-11-04 00:26:15 +00:00
fields = [
" status " ,
" name " ,
2021-03-09 13:30:30 -06:00
" aka " ,
" name_long " ,
2020-11-04 00:26:15 +00:00
" address1 " ,
" address2 " ,
" city " ,
" state " ,
" zipcode " ,
" country " ,
2021-01-13 20:35:07 +00:00
" floor " ,
" suite " ,
2020-11-04 00:26:15 +00:00
" latitude " ,
" longitude " ,
2021-01-13 20:35:07 +00:00
" geocode_status " ,
" geocode_date " ,
2020-11-04 00:26:15 +00:00
" website " ,
" notes " ,
" logo " ,
2022-09-12 16:29:28 +03:00
" restrict_user_emails " ,
" email_domains " ,
2020-11-04 00:26:15 +00:00
" verification_queue " ,
" version " ,
" id " ,
2021-11-09 09:47:38 -06:00
" flagged " ,
" flagged_date " ,
2021-01-13 20:35:07 +00:00
" grainy_namespace " ,
2020-11-04 00:26:15 +00:00
]
2018-11-08 19:45:21 +00:00
def get_urls ( self ) :
2020-07-15 02:07:01 -05:00
urls = super ( ) . get_urls ( )
2018-11-08 19:45:21 +00:00
my_urls = [
2023-06-20 03:26:06 +03:00
re_path ( r " ^org-merge-tool/merge$ " , self . org_merge_tool_merge_action ) ,
re_path ( r " ^org-merge-tool/$ " , self . org_merge_tool_view ) ,
2018-11-08 19:45:21 +00:00
]
return my_urls + urls
def org_merge_tool_merge_action ( self , request ) :
if not request . user . is_superuser :
2022-05-10 16:56:30 +03:00
return HttpResponseForbidden ( )
2018-11-08 19:45:21 +00:00
try :
2019-12-05 16:57:52 +00:00
orgs = Organization . objects . filter ( id__in = request . GET . get ( " ids " ) . split ( " , " ) )
2018-11-08 19:45:21 +00:00
except ValueError :
2019-12-05 16:57:52 +00:00
return JsonResponse ( { " error " : _ ( " Malformed organization ids " ) } , status = 400 )
2018-11-08 19:45:21 +00:00
try :
org = Organization . objects . get ( id = request . GET . get ( " id " ) )
except Organization . DoesNotExist :
2019-12-05 16:57:52 +00:00
return JsonResponse (
{ " error " : _ ( " Merge target organization does not exist " ) } , status = 400
)
2018-11-08 19:45:21 +00:00
rv = merge_organizations ( orgs , org , request )
return JsonResponse ( rv )
def org_merge_tool_view ( self , request ) :
if not request . user . is_superuser :
2020-06-24 12:55:01 -05:00
return HttpResponseForbidden ( )
2018-11-08 19:45:21 +00:00
context = dict (
self . admin_site . each_context ( request ) ,
2020-01-08 13:29:58 -06:00
undo_url = django . urls . reverse (
2019-12-05 16:57:52 +00:00
" admin:peeringdb_server_organizationmerge_changelist "
) ,
title = _ ( " Organization Merging Tool " ) ,
)
2018-11-08 19:45:21 +00:00
return TemplateResponse ( request , " admin/org_merge_tool.html " , context )
# inlines = (InternetExchangeFacilityInline,NetworkFacilityInline,)
admin . site . register ( Organization , OrganizationAdmin )
class OrganizationMergeEntities ( admin . TabularInline ) :
model = OrganizationMergeEntity
extra = 0
readonly_fields = ( " content_type " , " object_id " , " note " )
def has_delete_permission ( self , request , obj = None ) :
return False
class OrganizationMergeLog ( ModelAdminWithUrlActions ) :
2019-12-05 16:57:52 +00:00
list_display = ( " id " , " from_org " , " to_org " , " created " )
search_fields = ( " from_org__name " , " to_org__name " )
readonly_fields = ( " from_org " , " to_org " , " undo_merge " )
inlines = ( OrganizationMergeEntities , )
2018-11-08 19:45:21 +00:00
def undo_merge ( self , obj ) :
2020-01-08 13:29:58 -06:00
return mark_safe (
2020-06-24 12:55:01 -05:00
' <a class= " grp-button grp-delete-link " href= " {} " > {} </a> ' . format (
django . urls . reverse (
" admin:peeringdb_server_organizationmerge_actions " ,
2020-07-15 02:07:01 -05:00
args = ( obj . id , " undo " ) ,
2020-06-24 12:55:01 -05:00
) ,
2020-07-15 02:07:01 -05:00
_ ( " Undo merge " ) ,
2020-01-08 13:29:58 -06:00
)
)
2018-11-08 19:45:21 +00:00
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
@reversion.create_revision ( )
def undo ( modeladmin , request , queryset ) :
if request . user :
reversion . set_user ( request . user )
for each in queryset :
each . undo ( )
undo . short_description = _ ( " Undo merge " )
2020-07-15 02:07:01 -05:00
undo . allowed_permissions = ( " change " , )
2018-11-08 19:45:21 +00:00
actions = [ undo ]
def has_delete_permission ( self , request , obj = None ) :
return False
2023-02-15 09:55:01 +02:00
class CampusAdminForm ( StatusForm ) :
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
fk_handleref_filter ( self , " org " )
class CampusAdmin ( SoftDeleteAdmin ) :
list_display = ( " name " , " org " , " status " , " created " , " updated " )
ordering = ( " -created " , )
list_filter = ( StatusFilter , )
search_fields = ( " name " , )
readonly_fields = ( " id " , " grainy_namespace " )
form = CampusAdminForm
raw_id_fields = ( " org " , )
autocomplete_lookup_fields = {
" fk " : [ " org " ] ,
}
fields = [
" status " ,
" name " ,
" aka " ,
" name_long " ,
" website " ,
" org " ,
" version " ,
" id " ,
" grainy_namespace " ,
]
2023-01-18 18:32:46 +02:00
class CarrierAdminForm ( StatusForm ) :
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
fk_handleref_filter ( self , " org " )
class CarrierFacilityAdmin ( SoftDeleteAdmin ) :
list_display = ( " carrier " , " facility " , " status " , " created " , " updated " )
search_fields = ( " carrier__name " , " facility__name " )
readonly_fields = ( " id " , " grainy_namespace " )
raw_id_fields = ( " carrier " , " facility " )
autocomplete_lookup_fields = { " fk " : [ " carrier " , " facility " ] }
form = StatusForm
fields = [
" status " ,
" carrier " ,
" facility " ,
" version " ,
" id " ,
" grainy_namespace " ,
]
class CarrierAdmin ( ModelAdminWithVQCtrl , SoftDeleteAdmin ) :
list_display = ( " name " , " org " , " status " , " created " , " updated " )
ordering = ( " -created " , )
list_filter = ( StatusFilter , )
search_fields = ( " name " , )
readonly_fields = ( " id " , " grainy_namespace " )
form = CarrierAdminForm
raw_id_fields = ( " org " , )
autocomplete_lookup_fields = {
" fk " : [ " org " ] ,
}
fields = [
" status " ,
" name " ,
" aka " ,
" name_long " ,
" website " ,
" notes " ,
" org " ,
" verification_queue " ,
" version " ,
" id " ,
" grainy_namespace " ,
]
2018-11-08 19:45:21 +00:00
class FacilityAdminForm ( StatusForm ) :
2021-04-13 08:59:23 -05:00
latitude = RoundingDecimalFormField ( max_digits = 9 , decimal_places = 6 , required = False )
longitude = RoundingDecimalFormField ( max_digits = 9 , decimal_places = 6 , required = False )
2021-03-09 13:30:30 -06:00
2018-11-08 19:45:21 +00:00
def __init__ ( self , * args , * * kwargs ) :
2020-07-15 02:07:01 -05:00
super ( ) . __init__ ( * args , * * kwargs )
2018-11-08 19:45:21 +00:00
fk_handleref_filter ( self , " org " )
class FacilityAdmin ( ModelAdminWithVQCtrl , SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = ( " name " , " org " , " city " , " country " , " status " , " created " , " updated " )
ordering = ( " -created " , )
list_filter = ( StatusFilter , )
search_fields = ( " name " , )
2023-08-15 21:40:18 +03:00
readonly_fields = ( " id " , " grainy_namespace " , " org_website " )
2020-02-05 21:26:21 -06:00
raw_id_fields = ( " org " , )
autocomplete_lookup_fields = {
" fk " : [ " org " ] ,
}
2018-11-08 19:45:21 +00:00
form = FacilityAdminForm
inlines = (
InternetExchangeFacilityInline ,
NetworkFacilityInline ,
)
2020-11-04 00:26:15 +00:00
fields = [
" status " ,
" name " ,
2021-03-09 13:30:30 -06:00
" aka " ,
" name_long " ,
2020-11-04 00:26:15 +00:00
" address1 " ,
" address2 " ,
" city " ,
" state " ,
" zipcode " ,
" country " ,
2021-10-12 11:05:25 -05:00
" region_continent " ,
2021-01-13 20:35:07 +00:00
" floor " ,
" suite " ,
2020-11-04 00:26:15 +00:00
" latitude " ,
" longitude " ,
" website " ,
2023-08-15 21:40:18 +03:00
" org_website " ,
2020-11-04 00:26:15 +00:00
" clli " ,
" rencode " ,
" npanxx " ,
" tech_email " ,
" tech_phone " ,
" sales_email " ,
" sales_phone " ,
2021-09-14 08:59:09 -05:00
" property " ,
" diverse_serving_substations " ,
# django-admin doesnt seem to support multichoicefield automatically
# admins can edit this through the user-facing UX for now
# TODO: revisit enabling this field in django admin if AC communicates the need
# "available_voltage_services",
2020-11-04 00:26:15 +00:00
" notes " ,
" geocode_status " ,
" geocode_date " ,
" org " ,
" verification_queue " ,
" version " ,
" id " ,
2021-01-13 20:35:07 +00:00
" grainy_namespace " ,
2022-03-08 09:27:45 -04:00
" status_dashboard " ,
2020-11-04 00:26:15 +00:00
]
2023-08-15 21:40:18 +03:00
def org_website ( self , obj ) :
if obj . org and obj . org . website :
url = html . escape ( obj . org . website )
return mark_safe ( f ' <a href= " { url } " > { url } </a> ' )
return None
2018-11-08 19:45:21 +00:00
class NetworkAdminForm ( StatusForm ) :
2019-12-05 16:57:52 +00:00
# set initial values on info_prefixes4 and 6 to 0
# this streamlines the process of adding a network through
# the django admin controlpanel (#289)
2019-08-16 20:00:55 -05:00
info_prefixes4 = baseForms . IntegerField ( required = False , initial = 0 )
info_prefixes6 = baseForms . IntegerField ( required = False , initial = 0 )
2018-11-08 19:45:21 +00:00
def __init__ ( self , * args , * * kwargs ) :
2020-07-15 02:07:01 -05:00
super ( ) . __init__ ( * args , * * kwargs )
2018-11-08 19:45:21 +00:00
fk_handleref_filter ( self , " org " )
2022-04-12 16:39:19 -04:00
def clean_asn ( self ) :
asn = self . cleaned_data [ " asn " ]
if Network . objects . filter ( asn = asn ) . exclude ( id = self . instance . id ) . exists ( ) :
# Clear ASN field from form
self . cleaned_data [ " asn " ] = None
raise ValidationError ( _ ( " ASN is already in use by another network " ) )
return asn
def clean_name ( self ) :
name = self . cleaned_data [ " name " ]
if Network . objects . filter ( name = name ) . exclude ( id = self . instance . id ) . exists ( ) :
# Clear name field from form
self . cleaned_data [ " name " ] = None
raise ValidationError ( _ ( " Name is already in use by another network " ) )
return name
2018-11-08 19:45:21 +00:00
class NetworkAdmin ( ModelAdminWithVQCtrl , SoftDeleteAdmin ) :
2021-03-09 13:30:30 -06:00
list_display = ( " name " , " asn " , " aka " , " name_long " , " status " , " created " , " updated " )
2019-12-05 16:57:52 +00:00
ordering = ( " -created " , )
list_filter = ( StatusFilter , )
search_fields = ( " name " , " asn " )
2023-08-15 21:40:18 +03:00
readonly_fields = (
" id " ,
" grainy_namespace " ,
" rir_status " ,
" rir_status_updated " ,
" org_website " ,
)
2018-11-08 19:45:21 +00:00
form = NetworkAdminForm
inlines = (
NetworkContactInline ,
NetworkFacilityInline ,
NetworkInternetExchangeInline ,
)
2020-02-05 21:26:21 -06:00
raw_id_fields = ( " org " , )
autocomplete_lookup_fields = {
2020-09-30 01:13:38 +00:00
" fk " : [
" org " ,
] ,
2020-02-05 21:26:21 -06:00
}
2023-08-15 21:40:18 +03:00
def org_website ( self , obj ) :
if obj . org and obj . org . website :
url = html . escape ( obj . org . website )
return mark_safe ( f ' <a href= " { url } " > { url } </a> ' )
return None
def get_search_results ( self , request , queryset , search_term ) :
# Check if the search_term starts with 'AS' or 'ASN'
asn = re . match ( r " (asn|as)( \ d+) " , search_term . lower ( ) )
if asn :
# Filter the queryset to find the Network with the specified ASN
matching_networks = queryset . filter ( asn = asn . group ( 2 ) )
if matching_networks . count ( ) == 1 :
# Redirect to the detail view of the matching Network
return matching_networks , False
# If the search_term doesn't start with 'AS' or 'ASN', perform a regular search
return super ( ) . get_search_results ( request , queryset , search_term )
2020-02-05 21:26:21 -06:00
2019-09-06 21:32:19 -05:00
class InternetExchangeFacilityAdmin ( SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = ( " id " , " ix " , " facility " , " status " , " created " , " updated " )
search_fields = ( " ix__name " , " facility__name " )
readonly_fields = ( " id " , )
2019-09-06 21:32:19 -05:00
list_filter = ( StatusFilter , )
form = StatusForm
2020-02-05 21:26:21 -06:00
raw_id_fields = ( " ix " , " facility " )
autocomplete_lookup_fields = {
" fk " : [ " ix " , " facility " ] ,
}
2019-09-06 21:32:19 -05:00
class IXLanPrefixAdmin ( SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = ( " id " , " prefix " , " ixlan " , " ix " , " status " , " created " , " updated " )
2020-08-18 09:28:18 -05:00
readonly_fields = ( " ix " , " id " , " in_dfz " )
2019-12-05 16:57:52 +00:00
search_fields = ( " ixlan__name " , " ixlan__ix__name " , " prefix " )
2019-09-06 21:32:19 -05:00
list_filter = ( StatusFilter , )
2020-07-15 02:07:01 -05:00
form = IXLanPrefixForm
2019-09-06 21:32:19 -05:00
2020-04-10 15:00:27 +00:00
raw_id_fields = ( " ixlan " , )
autocomplete_lookup_fields = {
" fk " : [ " ixlan " ] ,
}
2019-09-06 21:32:19 -05:00
def ix ( self , obj ) :
return obj . ixlan . ix
class NetworkIXLanAdmin ( SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = (
" id " ,
" asn " ,
" net " ,
" ixlan " ,
" ix " ,
" ipaddr4 " ,
" ipaddr6 " ,
" status " ,
" created " ,
" updated " ,
)
search_fields = (
" asn " ,
" network__asn " ,
" network__name " ,
" ixlan__name " ,
" ixlan__ix__name " ,
" ipaddr4 " ,
" ipaddr6 " ,
)
readonly_fields = ( " id " , " ix " , " net " )
2019-09-06 21:32:19 -05:00
list_filter = ( StatusFilter , )
form = StatusForm
2020-07-15 02:07:01 -05:00
raw_id_fields = ( " network " , " ixlan " )
2020-02-05 21:26:21 -06:00
autocomplete_lookup_fields = {
2020-07-15 02:07:01 -05:00
" fk " : [ " network " , " ixlan " ] ,
2020-02-05 21:26:21 -06:00
}
2019-09-06 21:32:19 -05:00
def ix ( self , obj ) :
return obj . ixlan . ix
def net ( self , obj ) :
2020-07-15 02:07:01 -05:00
return f " { obj . network . name } (AS { obj . network . asn } ) "
2019-09-06 21:32:19 -05:00
2021-04-13 08:59:23 -05:00
def get_search_results ( self , request , queryset , search_term ) :
# Issue 913
# If the search_term is for an ipaddress6, this will compress it
search_term = coerce_ipaddr ( search_term )
queryset , use_distinct = super ( ) . get_search_results (
request , queryset , search_term
)
return queryset , use_distinct
2019-09-06 21:32:19 -05:00
class NetworkContactAdmin ( SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = (
" id " ,
" net " ,
" role " ,
" name " ,
" phone " ,
" email " ,
" status " ,
" created " ,
" updated " ,
)
search_fields = ( " network__asn " , " network__name " )
readonly_fields = ( " id " , " net " )
2019-09-06 21:32:19 -05:00
list_filter = ( StatusFilter , )
form = StatusForm
2020-02-05 21:26:21 -06:00
raw_id_fields = ( " network " , )
autocomplete_lookup_fields = {
2020-09-30 01:13:38 +00:00
" fk " : [
" network " ,
] ,
2020-02-05 21:26:21 -06:00
}
2019-09-06 21:32:19 -05:00
def net ( self , obj ) :
2020-07-15 02:07:01 -05:00
return f " { obj . network . name } (AS { obj . network . asn } ) "
2019-09-06 21:32:19 -05:00
class NetworkFacilityAdmin ( SoftDeleteAdmin ) :
2019-12-05 16:57:52 +00:00
list_display = ( " id " , " net " , " facility " , " status " , " created " , " updated " )
search_fields = ( " network__asn " , " network__name " , " facility__name " )
readonly_fields = ( " id " , " net " )
2019-09-06 21:32:19 -05:00
list_filter = ( StatusFilter , )
form = StatusForm
2020-02-05 21:26:21 -06:00
raw_id_fields = ( " network " , " facility " )
autocomplete_lookup_fields = {
" fk " : [ " network " , " facility " ] ,
}
2019-09-06 21:32:19 -05:00
def net ( self , obj ) :
2020-07-15 02:07:01 -05:00
return f " { obj . network . name } (AS { obj . network . asn } ) "
2019-09-06 21:32:19 -05:00
2018-11-08 19:45:21 +00:00
class VerificationQueueAdmin ( ModelAdminWithUrlActions ) :
2019-12-05 16:57:52 +00:00
list_display = ( " content_type " , " item " , " created " , " view " , " extra " )
filter_fields = ( " content_type " , )
readonly_fields = ( " created " , " view " , " extra " )
search_fields = ( " item " , )
2018-11-08 19:45:21 +00:00
2020-04-10 15:00:27 +00:00
raw_id_fields = ( " user " , )
autocomplete_lookup_fields = {
" fk " : [ " user " ] ,
}
2018-11-08 19:45:21 +00:00
def get_search_results ( self , request , queryset , search_term ) :
2019-12-05 16:57:52 +00:00
# queryset, use_distinct = super(VerificationQueueAdmin, self).get_search_results(request, queryset, search_term)
if not search_term or search_term == " " :
2018-11-08 19:45:21 +00:00
return queryset , False
use_distinct = True
myset = VerificationQueueItem . objects . none ( )
for model in QUEUE_ENABLED :
if model == User :
qrs = model . objects . filter ( username__icontains = search_term )
else :
qrs = model . objects . filter ( name__icontains = search_term )
content_type = ContentType . objects . get_for_model ( model )
for instance in list ( qrs ) :
vq = VerificationQueueItem . objects . filter (
2019-12-05 16:57:52 +00:00
content_type = content_type , object_id = instance . id
)
myset | = queryset & vq
2018-11-08 19:45:21 +00:00
return myset , use_distinct
def make_redirect ( self , obj , action ) :
if action == " vq_approve " :
opts = type ( obj . first ( ) . item ) . _meta
return redirect (
2020-01-08 13:29:58 -06:00
django . urls . reverse (
2020-07-15 02:07:01 -05:00
f " admin: { opts . app_label } _ { opts . model_name } _change " ,
2019-12-05 16:57:52 +00:00
args = ( obj . first ( ) . item . id , ) ,
)
)
2018-11-08 19:45:21 +00:00
opts = obj . model . _meta
2020-07-15 02:07:01 -05:00
return redirect ( f " admin: { opts . app_label } _ { opts . model_name } _changelist " )
2018-11-08 19:45:21 +00:00
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
def vq_approve ( self , request , queryset ) :
2019-11-16 21:33:01 -06:00
with reversion . create_revision ( ) :
reversion . set_user ( request . user )
for each in queryset :
each . approve ( )
2018-11-08 19:45:21 +00:00
vq_approve . short_description = _ ( " APPROVE selected items " )
2020-07-15 02:07:01 -05:00
vq_approve . allowed_permissions = ( " change " , )
2018-11-08 19:45:21 +00:00
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
def vq_deny ( modeladmin , request , queryset ) :
for each in queryset :
each . deny ( )
vq_deny . short_description = _ ( " DENY and delete selected items " )
2020-07-15 02:07:01 -05:00
vq_deny . allowed_permissions = ( " change " , )
2018-11-08 19:45:21 +00:00
actions = [ vq_approve , vq_deny ]
def view ( self , obj ) :
2020-01-08 13:29:58 -06:00
return mark_safe ( ' <a href= " {} " > {} </a> ' . format ( obj . item_admin_url , _ ( " View " ) ) )
2018-11-08 19:45:21 +00:00
def extra ( self , obj ) :
2019-12-05 16:57:52 +00:00
if hasattr ( obj . item , " org " ) and obj . item . org . id == settings . SUGGEST_ENTITY_ORG :
2018-11-08 19:45:21 +00:00
return " Suggestion "
return " "
2022-01-11 08:56:47 -06:00
class UserOrgAffiliationRequestAdmin ( ModelAdminWithUrlActions , ProtectedDeleteAdmin ) :
2020-11-04 00:26:15 +00:00
list_display = (
" user " ,
" asn " ,
" org " ,
" created " ,
" status " ,
)
search_fields = (
" user__username " ,
" asn " ,
)
2019-12-05 16:57:52 +00:00
readonly_fields = ( " created " , )
2018-11-08 19:45:21 +00:00
2020-04-01 11:08:12 +00:00
raw_id_fields = ( " user " , " org " )
autocomplete_lookup_fields = {
" fk " : [ " user " , " org " ] ,
}
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
def approve_and_notify ( self , request , queryset ) :
for each in queryset :
2020-04-01 11:08:12 +00:00
if each . status == " canceled " :
2020-07-15 02:07:01 -05:00
messages . error (
request , _ ( " Cannot approve a canceled affiliation request " )
)
2020-04-01 11:08:12 +00:00
continue
2023-07-11 16:20:46 +03:00
if each . org . require_2fa and not each . user . has_2fa :
messages . error (
request ,
_ (
" Cannot approve while User has 2FA disabled - organization requires 2FA "
) ,
)
continue
2020-04-01 11:08:12 +00:00
2018-11-08 19:45:21 +00:00
each . approve ( )
each . notify_ownership_approved ( )
2020-04-01 11:08:12 +00:00
self . message_user (
2020-07-15 02:07:01 -05:00
request ,
_ ( " Affiliation request was approved and the user was notified. " ) ,
2020-04-01 11:08:12 +00:00
)
2018-11-08 19:45:21 +00:00
approve_and_notify . short_description = _ ( " Approve and notify User " )
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
def approve ( self , request , queryset ) :
for each in queryset :
2020-04-01 11:08:12 +00:00
if each . status == " canceled " :
2020-07-15 02:07:01 -05:00
messages . error (
request , _ ( " Cannot approve a canceled affiliation request " )
)
2020-04-01 11:08:12 +00:00
continue
2018-11-08 19:45:21 +00:00
each . approve ( )
approve . short_description = _ ( " Approve " )
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
def deny ( self , request , queryset ) :
for each in queryset :
2020-04-01 11:08:12 +00:00
if each . status == " canceled " :
messages . error ( request , _ ( " Cannot deny a canceled affiliation request " ) )
continue
2018-11-08 19:45:21 +00:00
each . deny ( )
deny . short_description = _ ( " Deny " )
actions = [ approve_and_notify , approve , deny ]
# need to do this for add via django admin to use the right model
class UserCreationForm ( forms . UserCreationForm ) :
2021-08-18 08:21:22 -05:00
# user creation through django-admin doesnt need
# captcha checking
require_captcha = False
2018-11-08 19:45:21 +00:00
def clean_username ( self ) :
username = self . cleaned_data [ " username " ]
2022-04-12 16:39:19 -04:00
if username . startswith ( " apikey " ) :
raise forms . ValidationError ( _ ( ' Usernames cannot start with " apikey " ' ) )
2018-11-08 19:45:21 +00:00
try :
User . _default_manager . get ( username = username )
except User . DoesNotExist :
return username
raise ValidationError ( self . error_messages [ " duplicate_username " ] )
class Meta ( forms . UserCreationForm . Meta ) :
model = User
2019-12-05 16:57:52 +00:00
fields = ( " username " , " password " , " email " )
2018-11-08 19:45:21 +00:00
2022-03-08 09:27:45 -04:00
class UserGroupForm ( UserChangeForm ) :
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
# "groups" is oddly missing from the test-environment
# probably missing some installed dep
if " groups " in self . fields :
self . fields [ " groups " ] . queryset = Group . objects . all ( ) . order_by ( " id " )
2022-10-11 15:45:07 +03:00
class UserAdmin ( ExportMixin , ModelAdminWithVQCtrl , UserAdmin ) :
2023-05-16 21:04:05 +03:00
inlines = (
UserOrgAffiliationRequestInline ,
UserDeviceInline ,
UserWebauthnSecurityKeyInline ,
)
2019-12-05 16:57:52 +00:00
readonly_fields = (
" email_status " ,
" organizations " ,
" view_permissions " ,
" change_password " ,
)
list_display = (
" username " ,
" email " ,
" first_name " ,
" last_name " ,
" email_status " ,
" status " ,
2022-06-15 15:23:26 +03:00
" last_login " ,
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
add_form = UserCreationForm
2019-12-05 16:57:52 +00:00
add_fieldsets = (
(
None ,
{
" classes " : ( " wide " , ) ,
" fields " : ( " username " , " password1 " , " password2 " , " email " ) ,
} ,
) ,
)
fieldsets = (
( ( None , { " classes " : ( " wide " , ) , " fields " : ( " email_status " , " change_password " ) } ) , )
+ UserAdmin . fieldsets
+ ( ( None , { " classes " : ( " wide " , ) , " fields " : ( " organizations " , ) } ) , )
2022-09-12 16:29:28 +03:00
+ (
(
None ,
{
" classes " : ( " wide " , ) ,
" fields " : (
" never_flag_for_deletion " ,
" flagged_for_deletion " ,
" notified_for_deletion " ,
) ,
} ,
) ,
)
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
# we want to get rid of user permissions and group editor as that
# will be displayed on a separate page, for performance reasons
for name , grp in fieldsets :
2019-12-05 16:57:52 +00:00
grp [ " fields " ] = tuple (
2021-07-10 10:12:35 -05:00
fld
for fld in grp [ " fields " ]
if fld
not in [
" groups " ,
" user_permissions " ,
" is_staff " ,
" is_active " ,
" is_superuser " ,
2018-11-08 19:45:21 +00:00
]
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
if name == " Permissions " :
2019-12-05 16:57:52 +00:00
grp [ " fields " ] + = ( " view_permissions " , )
2018-11-08 19:45:21 +00:00
def version ( self , obj ) :
"""
Users are not versioned , but ModelAdminWithVQCtrl defines
2021-10-15 03:25:38 -05:00
a readonly field called " version. " For the sake of completion ,
return a 0 version here .
2018-11-08 19:45:21 +00:00
"""
return 0
def change_password ( self , obj ) :
2020-01-08 13:29:58 -06:00
return mark_safe (
' <a href= " {} " > {} </a> ' . format (
django . urls . reverse ( " admin:auth_user_password_change " , args = ( obj . id , ) ) ,
_ ( " Change Password " ) ,
)
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
def view_permissions ( self , obj ) :
2020-01-08 13:29:58 -06:00
url = django . urls . reverse (
2019-12-05 16:57:52 +00:00
" admin: %s _ %s _change "
% ( UserPermission . _meta . app_label , UserPermission . _meta . model_name ) ,
args = ( obj . id , ) ,
)
2018-11-08 19:45:21 +00:00
2020-01-08 13:29:58 -06:00
return mark_safe ( ' <a href= " {} " > {} </a> ' . format ( url , _ ( " Edit Permissions " ) ) )
2018-11-08 19:45:21 +00:00
def email_status ( self , obj ) :
if obj . email_confirmed :
2020-01-08 13:29:58 -06:00
return mark_safe (
' <span style= " color:darkgreen " > {} </span> ' . format ( _ ( " VERIFIED " ) )
)
2018-11-08 19:45:21 +00:00
else :
2020-01-08 13:29:58 -06:00
return mark_safe (
' <span style= " color:darkred " > {} </span> ' . format ( _ ( " UNVERIFIED " ) )
)
2018-11-08 19:45:21 +00:00
def organizations ( self , obj ) :
2020-01-08 13:29:58 -06:00
return mark_safe (
2019-12-05 16:57:52 +00:00
loader . get_template ( " admin/user-organizations.html " )
. render ( { " organizations " : obj . organizations , " user " : obj } )
. replace ( " \n " , " " )
)
2018-11-08 19:45:21 +00:00
class UserPermission ( User ) :
class Meta :
proxy = True
verbose_name = _ ( " User Permission " )
verbose_name_plural = _ ( " User Permissions " )
class UserPermissionAdmin ( UserAdmin ) :
2019-12-05 16:57:52 +00:00
search_fields = ( " username " , )
2018-11-08 19:45:21 +00:00
2019-12-05 16:57:52 +00:00
inlines = (
UserOrgAffiliationRequestInline ,
2021-01-13 20:35:07 +00:00
UserPermissionInlineAdmin ,
2019-12-05 16:57:52 +00:00
)
2018-11-08 19:45:21 +00:00
2019-12-05 16:57:52 +00:00
fieldsets = (
(
None ,
{
" fields " : (
" user " ,
" is_active " ,
" is_staff " ,
" is_superuser " ,
" groups " ,
) ,
" classes " : ( " wide " , ) ,
} ,
) ,
)
2018-11-08 19:45:21 +00:00
2019-12-05 16:57:52 +00:00
readonly_fields = ( " user " , )
2018-11-08 19:45:21 +00:00
2022-03-08 09:27:45 -04:00
form = UserGroupForm
# def get_form(self, request, obj=None, **kwargs):
# # we want to remove the password field from the form
# # since we don't send it and don't want to run clean for it
# form = super().get_form(request, obj, **kwargs)
# del form.base_fields["password"]
# return form
2018-11-08 19:45:21 +00:00
def user ( self , obj ) :
2020-01-08 13:29:58 -06:00
url = django . urls . reverse (
2020-07-15 02:07:01 -05:00
f " admin: { User . _meta . app_label } _ { User . _meta . model_name } _change " ,
2019-12-05 16:57:52 +00:00
args = ( obj . id , ) ,
)
2018-11-08 19:45:21 +00:00
2020-07-15 02:07:01 -05:00
return mark_safe ( f ' <a href= " { url } " > { obj . username } </a> ' )
2018-11-08 19:45:21 +00:00
2022-07-15 21:47:59 +03:00
def username ( self , obj ) :
return obj . user . username
2018-11-08 19:45:21 +00:00
def clean_password ( self ) :
pass
2022-07-15 21:47:59 +03:00
def save_formset ( self , request , form , formset , change ) :
# get user
user = None
for inline_form in formset . forms :
user = inline_form . cleaned_data . get ( " user " )
if user :
break
# save the form
result = super ( ) . save_formset ( request , form , formset , change )
# remove unmanageable permission namespaces for all the organizations
# the user is an administrator of (#1157)
if user :
for org in user . admin_organizations :
user . grainy_permissions . filter (
namespace__startswith = f " peeringdb.organization. { org . id } . "
) . delete ( )
return result
2018-11-08 19:45:21 +00:00
## COMMANDLINE TOOL ADMIN
class CommandLineToolPrepareForm ( baseForms . Form ) :
"""
Form that allows user to select which commandline tool
2021-10-15 03:25:38 -05:00
to run .
2018-11-08 19:45:21 +00:00
"""
2019-12-05 16:57:52 +00:00
2018-11-08 19:45:21 +00:00
tool = baseForms . ChoiceField ( choices = COMMANDLINE_TOOLS )
2022-10-11 15:45:07 +03:00
class CommandLineToolAdmin ( ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin ) :
2018-11-08 19:45:21 +00:00
"""
2021-10-15 03:25:38 -05:00
View that lets staff users run peeringdb command line tools .
2018-11-08 19:45:21 +00:00
"""
2019-12-05 16:57:52 +00:00
2019-01-04 10:02:28 +00:00
list_display = ( " tool " , " description " , " user " , " created " , " status " )
2019-12-05 16:57:52 +00:00
readonly_fields = (
" tool " ,
" description " ,
" arguments " ,
2022-02-08 13:14:27 -06:00
" download " ,
2019-12-05 16:57:52 +00:00
" result " ,
" user " ,
" created " ,
" status " ,
)
2023-01-18 18:32:46 +02:00
change_list_template = " admin/peeringdb_server/commandlinetool/change_list.html "
2023-08-15 21:40:18 +03:00
search_fields = ( " tool " , " description " )
2018-11-08 19:45:21 +00:00
def has_delete_permission ( self , request , obj = None ) :
return False
2022-02-08 13:14:27 -06:00
def download ( self , obj ) :
tool = acltools . get_tool_from_data ( { " tool " : obj . tool } )
if obj . status != " done " or not tool . download_link ( ) :
return " - "
args = json . loads ( obj . arguments )
tool . args = args [ " args " ]
tool . kwargs = args [ " kwargs " ]
url , label = tool . download_link ( )
url = html . escape ( url )
label = html . escape ( label )
return mark_safe ( f ' <a href= " { url } " > { label } </a> ' )
2018-11-08 19:45:21 +00:00
def get_urls ( self ) :
2020-07-15 02:07:01 -05:00
urls = super ( ) . get_urls ( )
2018-11-08 19:45:21 +00:00
my_urls = [
2023-06-20 03:26:06 +03:00
re_path (
2019-12-05 16:57:52 +00:00
r " ^prepare/$ " ,
self . prepare_command_view ,
name = " peeringdb_server_commandlinetool_prepare " ,
) ,
2023-06-20 03:26:06 +03:00
re_path (
2019-12-05 16:57:52 +00:00
r " ^preview/$ " ,
self . preview_command_view ,
name = " peeringdb_server_commandlinetool_preview " ,
) ,
2023-06-20 03:26:06 +03:00
re_path (
2019-12-05 16:57:52 +00:00
r " ^run/$ " ,
self . run_command_view ,
name = " peeringdb_server_commandlinetool_run " ,
) ,
2018-11-08 19:45:21 +00:00
]
return my_urls + urls
def prepare_command_view ( self , request ) :
"""
This view has the user select which command they want to run and
with which arguments .
"""
2020-06-24 12:55:01 -05:00
if not self . has_add_permission ( request ) :
return HttpResponseForbidden ( )
2018-11-08 19:45:21 +00:00
context = dict ( self . admin_site . each_context ( request ) )
title = " Commandline Tools "
action = " prepare "
if request . method == " POST " :
form = CommandLineToolPrepareForm ( request . POST , request . FILES )
if form . is_valid ( ) :
action = " preview "
tool = acltools . get_tool ( request . POST . get ( " tool " ) , form )
context . update ( tool = tool )
title = tool . name
form = tool . form
else :
form = CommandLineToolPrepareForm ( )
2019-12-05 16:57:52 +00:00
context . update (
{
" adminform " : helpers . AdminForm (
form ,
list ( [ ( None , { " fields " : form . base_fields } ) ] ) ,
self . get_prepopulated_fields ( request ) ,
) ,
" action " : action ,
" app_label " : self . model . _meta . app_label ,
" opts " : self . model . _meta ,
" title " : title ,
}
)
2018-11-08 19:45:21 +00:00
return TemplateResponse (
request ,
" admin/peeringdb_server/commandlinetool/prepare_command.html " ,
2019-12-05 16:57:52 +00:00
context ,
)
2018-11-08 19:45:21 +00:00
def preview_command_view ( self , request ) :
"""
2021-10-15 03:25:38 -05:00
This view has the user preview the result of running the command .
2018-11-08 19:45:21 +00:00
"""
2020-06-24 12:55:01 -05:00
if not self . has_add_permission ( request ) :
return HttpResponseForbidden ( )
2018-11-08 19:45:21 +00:00
context = dict ( self . admin_site . each_context ( request ) )
if request . method == " POST " :
tool = acltools . get_tool_from_data ( request . POST )
context . update ( tool = tool )
if tool . form_instance . is_valid ( ) :
action = " run "
tool . run ( request . user , commit = False )
2019-09-07 01:08:37 -05:00
else :
2020-06-24 12:55:01 -05:00
action = " run "
2018-11-08 19:45:21 +00:00
form = tool . form_instance
else :
raise Exception ( _ ( " Only POST requests allowed. " ) )
2019-12-05 16:57:52 +00:00
context . update (
{
" adminform " : helpers . AdminForm (
form ,
list ( [ ( None , { " fields " : form . base_fields } ) ] ) ,
self . get_prepopulated_fields ( request ) ,
) ,
" action " : action ,
" app_label " : self . model . _meta . app_label ,
" opts " : self . model . _meta ,
" title " : _ ( " {} (Preview) " ) . format ( tool . name ) ,
}
)
2018-11-08 19:45:21 +00:00
return TemplateResponse (
request ,
" admin/peeringdb_server/commandlinetool/preview_command.html " ,
2019-12-05 16:57:52 +00:00
context ,
)
2018-11-08 19:45:21 +00:00
2021-11-12 11:16:25 -06:00
@transaction.atomic
2018-11-08 19:45:21 +00:00
def run_command_view ( self , request ) :
"""
This view has the user running the command and commiting changes
to the database .
"""
2020-06-24 12:55:01 -05:00
if not self . has_add_permission ( request ) :
return HttpResponseForbidden ( )
2018-11-08 19:45:21 +00:00
context = dict ( self . admin_site . each_context ( request ) )
if request . method == " POST " :
tool = acltools . get_tool_from_data ( request . POST )
context . update ( tool = tool )
if tool . form_instance . is_valid ( ) :
tool . run ( request . user , commit = True )
form = tool . form_instance
else :
raise Exception ( _ ( " Only POST requests allowed. " ) )
2019-12-05 16:57:52 +00:00
context . update (
{
" adminform " : helpers . AdminForm (
form ,
list ( [ ( None , { " fields " : form . base_fields } ) ] ) ,
self . get_prepopulated_fields ( request ) ,
) ,
" action " : " run " ,
" app_label " : self . model . _meta . app_label ,
" opts " : self . model . _meta ,
" title " : tool . name ,
}
)
2018-11-08 19:45:21 +00:00
return TemplateResponse (
2019-12-05 16:57:52 +00:00
request , " admin/peeringdb_server/commandlinetool/run_command.html " , context
)
2018-11-08 19:45:21 +00:00
2022-10-11 15:45:07 +03:00
class IXFImportEmailAdmin ( ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin ) :
2020-09-29 18:07:56 +00:00
list_display = (
" subject " ,
" recipients " ,
" created " ,
" sent " ,
" net " ,
" ix " ,
" stale_info " ,
)
2020-07-26 23:36:27 -05:00
readonly_fields = (
" net " ,
" ix " ,
)
search_fields = ( " subject " , " ix__name " , " net__name " )
change_list_template = " admin/change_list_with_regex_search.html "
2020-09-29 18:07:56 +00:00
def stale_info ( self , obj ) :
not_sent = obj . sent is None
2023-08-15 21:40:18 +03:00
if isinstance ( obj . sent , datetime . datetime ) :
2020-09-29 18:07:56 +00:00
re_sent = ( obj . sent - obj . created ) > datetime . timedelta ( minutes = 5 )
else :
re_sent = False
prod_mail_mode = not settings . MAIL_DEBUG
return prod_mail_mode and ( not_sent or re_sent )
2020-07-26 23:36:27 -05:00
def get_search_results ( self , request , queryset , search_term ) :
queryset , use_distinct = super ( ) . get_search_results (
request , queryset , search_term
)
# Require ^ and $ for regex
if search_term . startswith ( " ^ " ) and search_term . endswith ( " $ " ) :
# Convert search to raw string
try :
search_term = search_term . encode ( " unicode-escape " ) . decode ( )
except AttributeError :
return queryset , use_distinct
# Validate regex expression
try :
re . compile ( search_term )
except re . error :
return queryset , use_distinct
# Add (case insensitive) regex search results to standard search results
try :
2021-01-13 20:35:07 +00:00
queryset = self . model . objects . filter (
subject__iregex = search_term
) . order_by ( " -created " )
2020-07-26 23:36:27 -05:00
except OperationalError :
return queryset , use_distinct
return queryset , use_distinct
2021-01-13 20:35:07 +00:00
class DeskProTicketCCInline ( admin . TabularInline ) :
model = DeskProTicketCC
2022-10-11 15:45:07 +03:00
class DeskProTicketAdmin ( ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin ) :
2020-07-26 23:36:27 -05:00
list_display = (
" id " ,
" subject " ,
" user " ,
" created " ,
" published " ,
" deskpro_ref " ,
" deskpro_id " ,
)
search_fields = ( " subject " , )
change_list_template = " admin/change_list_with_regex_search.html "
2021-01-13 20:35:07 +00:00
inlines = ( DeskProTicketCCInline , )
raw_id_fields = ( " user " , )
autocomplete_lookup_fields = {
" fk " : [
" user " ,
] ,
}
def get_readonly_fields ( self , request , obj = None ) :
if not obj :
return self . readonly_fields
return self . readonly_fields + ( " user " , )
2020-07-26 23:36:27 -05:00
def get_search_results ( self , request , queryset , search_term ) :
queryset , use_distinct = super ( ) . get_search_results (
request , queryset , search_term
)
# Require ^ and $ for regex
if search_term . startswith ( " ^ " ) and search_term . endswith ( " $ " ) :
# Convert search to raw string
try :
search_term = search_term . encode ( " unicode-escape " ) . decode ( )
except AttributeError :
return queryset , use_distinct
# Validate regex expression
try :
re . compile ( search_term )
except re . error :
return queryset , use_distinct
# Add (case insensitive) regex search results to standard search results
try :
2021-01-13 20:35:07 +00:00
queryset = self . model . objects . filter (
subject__iregex = search_term
) . order_by ( " -created " )
2020-07-26 23:36:27 -05:00
except OperationalError :
return queryset , use_distinct
return queryset , use_distinct
2018-11-08 19:45:21 +00:00
2020-07-26 23:36:27 -05:00
def save_model ( self , request , obj , form , change ) :
if not obj . id and not obj . user_id :
obj . user = request . user
return super ( ) . save_model ( request , obj , form , change )
2018-11-08 19:45:21 +00:00
2020-07-26 23:36:27 -05:00
@reversion.create_revision ( )
2020-07-15 02:07:01 -05:00
def apply_ixf_member_data ( modeladmin , request , queryset ) :
for ixf_member_data in queryset :
try :
2022-05-10 16:56:30 +03:00
ixf_member_data . apply (
user = request . user , comment = " Applied IX-F suggestion " , manual = True
)
2020-07-15 02:07:01 -05:00
except ValidationError as exc :
messages . error ( request , f " { ixf_member_data . ixf_id_pretty_str } : { exc } " )
apply_ixf_member_data . short_description = _ ( " Apply " )
2022-10-11 15:45:07 +03:00
class IXFMemberDataAdmin ( ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin ) :
2020-07-15 02:07:01 -05:00
change_form_template = " admin/ixf_member_data_change_form.html "
list_display = (
" id " ,
" ix " ,
" asn " ,
" ipaddr4 " ,
" ipaddr6 " ,
" action " ,
" netixlan " ,
" speed " ,
" operational " ,
" is_rs_peer " ,
" created " ,
" updated " ,
" fetched " ,
" changes " ,
2020-07-26 23:36:27 -05:00
" actionable_error " ,
" reason " ,
" requirements " ,
2020-07-15 02:07:01 -05:00
)
readonly_fields = (
" marked_for_removal " ,
" fetched " ,
" ix " ,
" action " ,
" changes " ,
" reason " ,
" netixlan " ,
" log " ,
" error " ,
2020-07-26 23:36:27 -05:00
" actionable_error " ,
2020-07-15 02:07:01 -05:00
" created " ,
" updated " ,
2020-07-26 23:36:27 -05:00
" status " ,
2020-07-15 02:07:01 -05:00
" remote_data " ,
2020-07-26 23:36:27 -05:00
" requirements " ,
" requirement_of " ,
" requirement_detail " ,
2023-03-20 14:34:56 +02:00
" extra_notifications_net_num " ,
" extra_notifications_net_date " ,
2020-07-15 02:07:01 -05:00
)
search_fields = ( " asn " , " ixlan__id " , " ixlan__ix__name " , " ipaddr4 " , " ipaddr6 " )
fields = (
" ix " ,
" asn " ,
" ipaddr4 " ,
" ipaddr6 " ,
" action " ,
" netixlan " ,
" speed " ,
" operational " ,
" is_rs_peer " ,
" created " ,
" updated " ,
" fetched " ,
" changes " ,
" reason " ,
" error " ,
" log " ,
" remote_data " ,
2020-07-26 23:36:27 -05:00
" requirement_of " ,
" requirement_detail " ,
" deskpro_id " ,
" deskpro_ref " ,
2023-03-20 14:34:56 +02:00
" extra_notifications_net_num " ,
" extra_notifications_net_date " ,
2020-07-15 02:07:01 -05:00
)
actions = [ apply_ixf_member_data ]
raw_id_fields = ( " ixlan " , )
autocomplete_lookup_fields = {
2020-09-30 01:13:38 +00:00
" fk " : [
" ixlan " ,
] ,
2020-07-15 02:07:01 -05:00
}
2020-07-26 23:36:27 -05:00
def get_queryset ( self , request ) :
qset = super ( ) . get_queryset ( request )
if request . resolver_match . kwargs . get ( " object_id " ) :
return qset
return qset . filter ( requirement_of__isnull = True )
2020-07-15 02:07:01 -05:00
def ix ( self , obj ) :
return obj . ixlan . ix
2020-07-26 23:36:27 -05:00
def requirements ( self , obj ) :
return len ( obj . requirements )
def requirement_detail ( self , obj ) :
lines = [ ]
for requirement in obj . requirements :
url = django . urls . reverse (
" admin:peeringdb_server_ixfmemberdata_change " , args = ( requirement . id , )
)
lines . append ( f ' <a href= " { url } " > { requirement } { requirement . action } </a> ' )
if not lines :
return _ ( " No requirements " )
return mark_safe ( " <br> " . join ( lines ) )
2020-07-15 02:07:01 -05:00
def netixlan ( self , obj ) :
if not obj . netixlan . id :
return " - "
url = django . urls . reverse (
" admin:peeringdb_server_networkixlan_change " , args = ( obj . netixlan . id , )
)
return mark_safe ( f ' <a href= " { url } " > { obj . netixlan . id } </a> ' )
def get_readonly_fields ( self , request , obj = None ) :
if obj and obj . action != " add " :
# make identifying fields read-only
# for modify / delete actions
return self . readonly_fields + ( " asn " , " ipaddr4 " , " ipaddr6 " )
return self . readonly_fields
2021-08-18 08:21:22 -05:00
def has_add_permission ( self , request , obj = None ) :
2020-07-15 02:07:01 -05:00
return False
def has_delete_permission ( self , request , obj = None ) :
return False
def remote_data ( self , obj ) :
return obj . json
2021-11-12 11:16:25 -06:00
@transaction.atomic
2020-07-26 23:36:27 -05:00
@reversion.create_revision ( )
2020-07-15 02:07:01 -05:00
def response_change ( self , request , obj ) :
if " _save-and-apply " in request . POST :
obj . save ( )
obj . apply ( user = request . user , comment = " Applied IX-F suggestion " )
return super ( ) . response_change ( request , obj )
2020-07-26 23:36:27 -05:00
class EnvironmentSettingForm ( baseForms . ModelForm ) :
value = baseForms . CharField ( required = True , label = _ ( " Value " ) )
class Meta :
fields = [ " setting " , " value " ]
2022-04-12 16:39:19 -04:00
def __init__ ( self , * args , * * kwargs ) :
envsetting = kwargs . get ( " instance " )
if envsetting :
kwargs [ " initial " ] = { " value " : envsetting . value }
return super ( ) . __init__ ( * args , * * kwargs )
2022-02-08 13:14:27 -06:00
def clean ( self ) :
cleaned_data = super ( ) . clean ( )
setting = cleaned_data . get ( " setting " )
value = cleaned_data . get ( " value " )
2022-04-12 16:39:19 -04:00
cleaned_data [ " value " ] = EnvironmentSetting . validate_value ( setting , value )
2022-02-08 13:14:27 -06:00
return cleaned_data
2020-07-26 23:36:27 -05:00
2022-10-11 15:45:07 +03:00
class EnvironmentSettingAdmin ( ExportMixin , CustomResultLengthAdmin , admin . ModelAdmin ) :
2020-07-26 23:36:27 -05:00
list_display = [ " setting " , " value " , " created " , " updated " , " user " ]
fields = [ " setting " , " value " ]
readonly_fields = [ " created " , " updated " ]
search_fields = [ " setting " ]
form = EnvironmentSettingForm
2021-11-12 11:16:25 -06:00
@transaction.atomic
2020-07-26 23:36:27 -05:00
def save_model ( self , request , obj , form , save ) :
obj . user = request . user
return obj . set_value ( form . cleaned_data [ " value " ] )
2021-03-09 13:30:30 -06:00
class OrganizationAPIKeyAdmin ( APIKeyModelAdmin ) :
2023-01-18 18:32:46 +02:00
list_display = [ " org " , " prefix " , " name " , " status " , " created " , " revoked " ]
2021-03-09 13:30:30 -06:00
search_fields = ( " prefix " , " org__name " )
2023-01-18 18:32:46 +02:00
raw_id_fields = ( " org " , )
autocomplete_lookup_fields = {
" fk " : [
" org " ,
] ,
}
2021-03-09 13:30:30 -06:00
class UserAPIKeyAdmin ( APIKeyModelAdmin ) :
2023-01-18 18:32:46 +02:00
list_display = [
" user " ,
" prefix " ,
" name " ,
" readonly " ,
" status " ,
" created " ,
" revoked " ,
]
2021-03-09 13:30:30 -06:00
search_fields = ( " prefix " , " user__username " , " user__email " )
2023-01-18 18:32:46 +02:00
raw_id_fields = ( " user " , )
autocomplete_lookup_fields = {
" fk " : [
" user " ,
] ,
}
2021-03-09 13:30:30 -06:00
2021-07-07 17:57:04 -05:00
class GeoCoordinateAdmin ( admin . ModelAdmin ) :
list_display = [
" id " ,
" country " ,
" city " ,
" state " ,
" zipcode " ,
" address1 " ,
" longitude " ,
" latitude " ,
" fetched " ,
]
2023-08-15 21:40:18 +03:00
search_fields = (
" country " ,
" city " ,
" state " ,
" zipcode " ,
" address1 " ,
" longitude " ,
" latitude " ,
)
2021-07-07 17:57:04 -05:00
2022-05-10 16:56:30 +03:00
@admin.register ( DataChangeWatchedObject )
class DataChangeWatchedObjectAdmin ( admin . ModelAdmin ) :
list_display = ( " id " , " user " , " ref_tag " , " object_id " , " last_notified " , " created " )
raw_id_fields = ( " user " , )
2023-08-15 21:40:18 +03:00
search_fields = ( " object_id " , " user__username " )
2022-05-10 16:56:30 +03:00
autocomplete_lookup_fields = {
" fk " : [
" user " ,
] ,
}
@admin.register ( DataChangeNotificationQueue )
class DataChangeNotificationQueueAdmin ( admin . ModelAdmin ) :
list_display = (
" id " ,
" watched_ref_tag " ,
" watched_object_id " ,
" watched_object " ,
" ref_tag " ,
" object_id " ,
" target_object " ,
" title " ,
" source " ,
" action " ,
" details " ,
" created " ,
)
readonly_fields = ( " watched_object " , " target_object " , " title " , " details " )
2023-08-15 21:40:18 +03:00
search_fields = ( " action " , " watched_object_id " , " reason " )
2022-05-10 16:56:30 +03:00
def has_change_permission ( self , request , obj = None ) :
return
def has_add_permission ( self , request , obj = None ) :
return
@admin.register ( DataChangeEmail )
class DataChangeEmail ( admin . ModelAdmin ) :
list_display = (
" id " ,
" user " ,
" email " ,
" subject " ,
" watched_object " ,
" created " ,
" sent " ,
)
raw_id_fields = ( " user " , )
2023-08-15 21:40:18 +03:00
search_fields = ( " email " , " subject " , " user__username " )
2022-05-10 16:56:30 +03:00
autocomplete_lookup_fields = {
" fk " : [
" user " ,
] ,
}
2022-02-08 13:14:27 -06:00
admin . site . register ( EnvironmentSetting , EnvironmentSettingAdmin )
2020-07-15 02:07:01 -05:00
admin . site . register ( IXFMemberData , IXFMemberDataAdmin )
2018-11-08 19:45:21 +00:00
admin . site . register ( Facility , FacilityAdmin )
2023-02-15 09:55:01 +02:00
admin . site . register ( Campus , CampusAdmin )
2023-01-18 18:32:46 +02:00
admin . site . register ( Carrier , CarrierAdmin )
admin . site . register ( CarrierFacility , CarrierFacilityAdmin )
2018-11-08 19:45:21 +00:00
admin . site . register ( InternetExchange , InternetExchangeAdmin )
2019-09-06 21:32:19 -05:00
admin . site . register ( InternetExchangeFacility , InternetExchangeFacilityAdmin )
2018-11-08 19:45:21 +00:00
admin . site . register ( IXLan , IXLanAdmin )
2019-09-06 21:32:19 -05:00
admin . site . register ( IXLanPrefix , IXLanPrefixAdmin )
admin . site . register ( NetworkIXLan , NetworkIXLanAdmin )
admin . site . register ( NetworkContact , NetworkContactAdmin )
admin . site . register ( NetworkFacility , NetworkFacilityAdmin )
2018-11-08 19:45:21 +00:00
admin . site . register ( Network , NetworkAdmin )
2021-01-13 20:35:07 +00:00
admin . site . unregister ( User )
2018-11-08 19:45:21 +00:00
admin . site . register ( User , UserAdmin )
admin . site . register ( VerificationQueueItem , VerificationQueueAdmin )
admin . site . register ( Sponsorship , SponsorshipAdmin )
admin . site . register ( Partnership , PartnershipAdmin )
admin . site . register ( OrganizationMerge , OrganizationMergeLog )
admin . site . register ( UserPermission , UserPermissionAdmin )
admin . site . register ( IXLanIXFMemberImportLog , IXLanIXFMemberImportLogAdmin )
admin . site . register ( CommandLineTool , CommandLineToolAdmin )
admin . site . register ( UserOrgAffiliationRequest , UserOrgAffiliationRequestAdmin )
admin . site . register ( DeskProTicket , DeskProTicketAdmin )
2020-07-26 23:36:27 -05:00
admin . site . register ( IXFImportEmail , IXFImportEmailAdmin )
2021-03-09 13:30:30 -06:00
admin . site . unregister ( APIKey )
admin . site . register ( OrganizationAPIKey , OrganizationAPIKeyAdmin )
admin . site . register ( UserAPIKey , UserAPIKeyAdmin )
2021-07-07 17:57:04 -05:00
admin . site . register ( GeoCoordinateCache , GeoCoordinateAdmin )