1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00

July updates (#762)

* Change label from primary ASN to ASN

* Raise validation error when trying to update ASN

* first steps for dotf importer procotol (#697)

* migrations (#697)

* Add translation to error meessage

* Make ASN readonly in table

* Add test now that ASN should not be able to update

* Set fac.rencode to '' for all entries and make it readonly in serializer

* Add unique constraints to network ixlan ip addresses

* Add migration to null out duplicate ipaddresses for deleted netixlans

* Add unique constraints to network ixlan ip addresses

* Add migration to null out duplicate ipaddresses for deleted netixlans

* remove old migrations (#697)

* fix netixlan ipaddr dedupe migration (#268)
add netixlan ipaddr unique constraint migration (#268)

* ixf_member_data migrations (#697)

* fix table name (#697)

* importer protocol (#697)

* fix netixlan ipaddr dedupe migration (#268)
add netixlan ipaddr unique constraint migration (#268)

* ixf proposed changes notifications (#697)

* Delete repeated query

* Add a test to show rencode is readonly

* Blank out rencode when mocking data

* Remove validator now that constraint exists

* Add back unique field validator w Check Deleted true

* conflict resolving (#697)

* UniqueFieldValidator raise error with code "unique" (#268)

* conflict resolution (#697)

* Add fixme comment to tests

* conflict resolution (#697)

* Remove now invalid undelete tests

* UniqueFieldValidator raise error with code "unique" (#268)

* delete admin tools for duplicate ip addresses

* Make migration to delete duplicateipnetworkixlan

* Add ixlan-ixpfx status matching validation, add corresponding test

* delete redundant checking in test

* resolve conflict ui (#697)

* fix migrations hierarchy

* squash migrations for ixf member data

* clean up preview and post-mortem tools

* remove non-sensical permission check when undeleting soft-deleted objects through unique integrity error handling

* only include the ix-f data url in notifications to admincom (#697)

* resolve on --skip-import (#697)

* ac conflict resolution (#697)

* Define more accurately the incompatible statuses for ixlan and ixpfx

* Add another status test

* Preventing disrupting changes (#697)

* fix tests (#697)

* Stop allow_ixp_update from being write only and add a global stat for automated networks

* Add tests for global stats that appear in footer

* Change how timezone is called with datetime, to get test_stats.py/test_generate_for_current_date to pass

* test for protected entities (#697)

* admincom conflict resolution refine readonly fields (#697)
network notifications only if the problem is actually actionable by the network (#697)

* ixp / ac notifcation when ix-f source cannot be parsed (#697)
fix issue with ixlan prefix protection (#697)

* migrations (#697)

* code documentation (#697)

* ux tweaks (#697)

* UX tweaks (#697)

* Fix typo

* fix netixlan returned in IXFMemberData.apply when adding a new one (#697)

* fix import log incosistencies (#697)

* Add IXFMemberData to test

* Update test data

* Add protocol tests

* Add tests for views

* always persist changes to remote data on set_conflict (#697)

* More tests

* always persist changes to remote data on set_conflict (#697)

* suggest-add test

* net_present_at_ix should check status (#697)

* Add more protocol tests

* Edit language of some tests

* django-peeringdb to 2.1.1
relock pipfile, pin django-ratelimit to <3 as it breaks stuff

* Add net_count_ixf field to ix object (#683)

* Add the IX-F Member Export URL to the ixlan API endpoint (#249)

* Lock some objects from being deleted by the owner (#696)

* regenerate api docs (#249)

* always persist changes to remote data on set_add and set_update (#697)

* IXFMemberData: always persist remote data changes during set_add and set_update, also allow for saving without touching the updated field

* always persist changes to remote data on set_add and set_update (#697)

* Fix suggest-add tests

* IXFMemberData: always persist remote data changes during set_add and set_update, also allow for saving without touching the updated field

* IXFMemberData: always persist remote data changes during set_add and set_update, also allow for saving without touching the updated field

* fix issue with deletion when ixfmemberdata for entry existed previously (#697)

* fix test_suggest_delete_local_ixf_no_flag (#697 tests)

* fix issue with deletion when ixfmemberdata for entry existed previously (#697)

* invalid ips get logged and notified to the ix via notify_error (#697)

* Fix more tests

* issue with previous_data when running without save (#697)
properly track speed errors (#697)

* reset errors on ixfmemberdata that go into pending_save (#697)

* add remote_data to admin view (#697)

* fix error reset inconsistency (#697)

* Refine invalid data tests

* remove debug output

* for notifications to ac include contact points for net and ix in the message (#697)

* settings to toggle ix-f tickets / emails (#697)

* allow turning off ix-f notifications for net and ix separately (#697)

* add jsonschema test

* Add idempotent tests to updater

* remove old ixf member tests

* Invalid data tests when ixp_updates are enabled

* fix speed error validation (#697)

* fix issue with rollback (#697)

* fix migration hierarchy

* fix ixfmemberdata _email

* django-peeringdb to 2.2 and relock

* add ixf rollback tests

* ixf email notifications off by default

* black formatted

* pyupgrade

Co-authored-by: egfrank <egfrank@20c.com>
Co-authored-by: Stefan Pratter <stefan@20c.com>
This commit is contained in:
Matt Griswold
2020-07-15 02:07:01 -05:00
committed by GitHub
parent 462c92a288
commit ea55c4dc38
108 changed files with 8908 additions and 2541 deletions

View File

@ -58,6 +58,7 @@ from peeringdb_server.models import (
IXLanIXFMemberImportLog,
IXLanIXFMemberImportLogEntry,
IXLanPrefix,
IXFMemberData,
NetworkContact,
NetworkFacility,
NetworkIXLan,
@ -82,9 +83,10 @@ PERMISSION_APP_LABELS = [
"sites",
"auth",
"account",
"oauth2_provider"
"oauth2_provider",
]
class StatusFilter(admin.SimpleListFilter):
"""
A listing filter that by default will only show entities
@ -237,7 +239,7 @@ class StatusForm(baseForms.ModelForm):
)
def __init__(self, *args, **kwargs):
super(StatusForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if "instance" in kwargs and kwargs.get("instance"):
inst = kwargs.get("instance")
if inst.status == "ok":
@ -251,7 +253,7 @@ class StatusForm(baseForms.ModelForm):
class ModelAdminWithUrlActions(admin.ModelAdmin):
def make_redirect(self, obj, action):
opts = obj.model._meta
return redirect("admin:%s_%s_changelist" % (opts.app_label, opts.model_name))
return redirect(f"admin:{opts.app_label}_{opts.model_name}_changelist")
def actions_view(self, request, object_id, action, **kwargs):
"""
@ -286,7 +288,7 @@ class ModelAdminWithUrlActions(admin.ModelAdmin):
self.admin_site.admin_view(self.actions_view),
name="%s_%s_actions" % info,
),
] + super(ModelAdminWithUrlActions, self).get_urls()
] + super().get_urls()
return urls
@ -322,10 +324,10 @@ def soft_delete(modeladmin, request, queryset):
soft_delete.short_description = _("SOFT DELETE")
class SanitizedAdmin(object):
class SanitizedAdmin:
def get_readonly_fields(self, request, obj=None):
return ("version",) + tuple(
super(SanitizedAdmin, self).get_readonly_fields(request, obj=obj)
super().get_readonly_fields(request, obj=obj)
)
@ -349,10 +351,10 @@ class SoftDeleteAdmin(
def save_formset(self, request, form, formset, change):
if request.user:
reversion.set_user(request.user)
super(SoftDeleteAdmin, self).save_formset(request, form, formset, change)
super().save_formset(request, form, formset, change)
class ModelAdminWithVQCtrl(object):
class ModelAdminWithVQCtrl:
"""
Extend from this model admin if you want to add verification queue
approve | deny controls to the top of its form
@ -366,7 +368,7 @@ class ModelAdminWithVQCtrl(object):
"""
fieldsets = tuple(
super(ModelAdminWithVQCtrl, self).get_fieldsets(request, obj=obj)
super().get_fieldsets(request, obj=obj)
)
# on automatically defined fieldsets it will insert the controls
@ -388,7 +390,7 @@ class ModelAdminWithVQCtrl(object):
readonly field
"""
return ("verification_queue",) + tuple(
super(ModelAdminWithVQCtrl, self).get_readonly_fields(request, obj=obj)
super().get_readonly_fields(request, obj=obj)
)
def verification_queue(self, obj):
@ -414,10 +416,21 @@ class ModelAdminWithVQCtrl(object):
return _("APPROVED")
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):
if self.prefix_changed and not self.instance.deletable:
raise ValidationError(self.instance.not_deletable_reason)
class IXLanPrefixInline(SanitizedAdmin, admin.TabularInline):
model = IXLanPrefix
extra = 0
form = StatusForm
form = IXLanPrefixForm
class IXLanInline(SanitizedAdmin, admin.StackedInline):
@ -435,7 +448,7 @@ class IXLanInline(SanitizedAdmin, admin.StackedInline):
def ixf_import_attempt_info(self, obj):
if obj.ixf_import_attempt:
return mark_safe("<pre>{}</pre>".format(obj.ixf_import_attempt.info))
return mark_safe(f"<pre>{obj.ixf_import_attempt.info}</pre>")
return ""
def prefixes(self, obj):
@ -475,17 +488,30 @@ class NetworkFacilityInline(SanitizedAdmin, admin.TabularInline):
}
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
class NetworkInternetExchangeInline(SanitizedAdmin, admin.TabularInline):
model = NetworkIXLan
extra = 0
raw_id_fields = ("ixlan", "network")
form = StatusForm
form = NetworkIXLanForm
class UserOrgAffiliationRequestInlineForm(baseForms.ModelForm):
def clean(self):
super(UserOrgAffiliationRequestInlineForm, self).clean()
super().clean()
try:
asn = self.cleaned_data.get("asn")
if asn:
@ -507,7 +533,7 @@ class UserOrgAffiliationRequestInline(admin.TabularInline):
class InternetExchangeAdminForm(StatusForm):
def __init__(self, *args, **kwargs):
super(InternetExchangeAdminForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
fk_handleref_filter(self, "org")
@ -524,7 +550,13 @@ class InternetExchangeAdmin(ModelAdminWithVQCtrl, SoftDeleteAdmin):
ordering = ("-created",)
list_filter = (StatusFilter,)
search_fields = ("name",)
readonly_fields = ("id", "nsp_namespace", "ixf_import_history")
readonly_fields = (
"id",
"nsp_namespace",
"ixf_import_history",
"ixf_last_import",
"ixf_net_count",
)
inlines = (InternetExchangeFacilityInline, IXLanInline)
form = InternetExchangeAdminForm
@ -547,7 +579,7 @@ class InternetExchangeAdmin(ModelAdminWithVQCtrl, SoftDeleteAdmin):
class IXLanAdminForm(StatusForm):
def __init__(self, *args, **kwargs):
super(IXLanAdminForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
fk_handleref_filter(self, "ix")
@ -570,6 +602,7 @@ class IXLanIXFMemberImportLogEntryInline(admin.TabularInline):
model = IXLanIXFMemberImportLogEntry
fields = (
"netixlan",
"versions",
"ipv4",
"ipv6",
"asn",
@ -587,6 +620,7 @@ class IXLanIXFMemberImportLogEntryInline(admin.TabularInline):
"rollback_status",
"action",
"reason",
"versions",
)
raw_id_fields = ("netixlan",)
@ -598,10 +632,31 @@ class IXLanIXFMemberImportLogEntryInline(admin.TabularInline):
def has_add_permission(self, request, obj=None):
return False
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 "-"
def ipv4(self, obj):
v = obj.version_after
if v:
return v.field_dict.get("ipaddr4")
return obj.netixlan.ipaddr4 or ""
def ipv6(self, obj):
v = obj.version_after
if v:
return v.field_dict.get("ipaddr6")
return obj.netixlan.ipaddr6 or ""
def asn(self, obj):
@ -649,7 +704,7 @@ class IXLanIXFMemberImportLogEntryInline(admin.TabularInline):
text = _("HAS BEEN ROLLED BACK")
color = "#d6f0f3"
return mark_safe(
'<div style="background-color:{}">{}</div>'.format(color, text)
f'<div style="background-color:{color}">{text}</div>'
)
@ -737,7 +792,7 @@ class SponsorshipAdmin(admin.ModelAdmin):
class PartnershipAdminForm(baseForms.ModelForm):
def __init__(self, *args, **kwargs):
super(PartnershipAdminForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
fk_handleref_filter(self, "org")
@ -773,7 +828,7 @@ class OrganizationAdmin(ModelAdminWithVQCtrl, SoftDeleteAdmin):
form = StatusForm
def get_urls(self):
urls = super(OrganizationAdmin, self).get_urls()
urls = super().get_urls()
my_urls = [
url(r"^org-merge-tool/merge$", self.org_merge_tool_merge_action),
url(r"^org-merge-tool/$", self.org_merge_tool_view),
@ -839,9 +894,9 @@ class OrganizationMergeLog(ModelAdminWithUrlActions):
'<a class="grp-button grp-delete-link" href="{}">{}</a>'.format(
django.urls.reverse(
"admin:peeringdb_server_organizationmerge_actions",
args=(obj.id,"undo")
args=(obj.id, "undo"),
),
_("Undo merge")
_("Undo merge"),
)
)
@ -853,7 +908,7 @@ class OrganizationMergeLog(ModelAdminWithUrlActions):
each.undo()
undo.short_description = _("Undo merge")
undo.allowed_permissions = ('change',)
undo.allowed_permissions = ("change",)
actions = [undo]
@ -863,7 +918,7 @@ class OrganizationMergeLog(ModelAdminWithUrlActions):
class FacilityAdminForm(StatusForm):
def __init__(self, *args, **kwargs):
super(FacilityAdminForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
fk_handleref_filter(self, "org")
@ -895,7 +950,7 @@ class NetworkAdminForm(StatusForm):
info_prefixes6 = baseForms.IntegerField(required=False, initial=0)
def __init__(self, *args, **kwargs):
super(NetworkAdminForm, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
fk_handleref_filter(self, "org")
@ -937,14 +992,13 @@ class IXLanPrefixAdmin(SoftDeleteAdmin):
readonly_fields = ("ix", "id")
search_fields = ("ixlan__name", "ixlan__ix__name", "prefix")
list_filter = (StatusFilter,)
form = StatusForm
form = IXLanPrefixForm
raw_id_fields = ("ixlan",)
autocomplete_lookup_fields = {
"fk": ["ixlan"],
}
def ix(self, obj):
return obj.ixlan.ix
@ -975,16 +1029,16 @@ class NetworkIXLanAdmin(SoftDeleteAdmin):
list_filter = (StatusFilter,)
form = StatusForm
raw_id_fields = ("network","ixlan")
raw_id_fields = ("network", "ixlan")
autocomplete_lookup_fields = {
"fk": ["network","ixlan"],
"fk": ["network", "ixlan"],
}
def ix(self, obj):
return obj.ixlan.ix
def net(self, obj):
return "{} (AS{})".format(obj.network.name, obj.network.asn)
return f"{obj.network.name} (AS{obj.network.asn})"
class NetworkContactAdmin(SoftDeleteAdmin):
@ -1010,7 +1064,7 @@ class NetworkContactAdmin(SoftDeleteAdmin):
}
def net(self, obj):
return "{} (AS{})".format(obj.network.name, obj.network.asn)
return f"{obj.network.name} (AS{obj.network.asn})"
class NetworkFacilityAdmin(SoftDeleteAdmin):
@ -1026,7 +1080,7 @@ class NetworkFacilityAdmin(SoftDeleteAdmin):
}
def net(self, obj):
return "{} (AS{})".format(obj.network.name, obj.network.asn)
return f"{obj.network.name} (AS{obj.network.asn})"
class VerificationQueueAdmin(ModelAdminWithUrlActions):
@ -1065,12 +1119,12 @@ class VerificationQueueAdmin(ModelAdminWithUrlActions):
opts = type(obj.first().item)._meta
return redirect(
django.urls.reverse(
"admin:%s_%s_change" % (opts.app_label, opts.model_name),
f"admin:{opts.app_label}_{opts.model_name}_change",
args=(obj.first().item.id,),
)
)
opts = obj.model._meta
return redirect("admin:%s_%s_changelist" % (opts.app_label, opts.model_name))
return redirect(f"admin:{opts.app_label}_{opts.model_name}_changelist")
def vq_approve(self, request, queryset):
with reversion.create_revision():
@ -1079,14 +1133,14 @@ class VerificationQueueAdmin(ModelAdminWithUrlActions):
each.approve()
vq_approve.short_description = _("APPROVE selected items")
vq_approve.allowed_permissions = ('change',)
vq_approve.allowed_permissions = ("change",)
def vq_deny(modeladmin, request, queryset):
for each in queryset:
each.deny()
vq_deny.short_description = _("DENY and delete selected items")
vq_deny.allowed_permissions = ('change',)
vq_deny.allowed_permissions = ("change",)
actions = [vq_approve, vq_deny]
@ -1109,17 +1163,19 @@ class UserOrgAffiliationRequestAdmin(ModelAdminWithUrlActions):
"fk": ["user", "org"],
}
def approve_and_notify(self, request, queryset):
for each in queryset:
if each.status == "canceled":
messages.error(request, _("Cannot approve a canceled affiliation request"))
messages.error(
request, _("Cannot approve a canceled affiliation request")
)
continue
each.approve()
each.notify_ownership_approved()
self.message_user(
request, _("Affiliation request was approved and the user was notified.")
request,
_("Affiliation request was approved and the user was notified."),
)
approve_and_notify.short_description = _("Approve and notify User")
@ -1127,7 +1183,9 @@ class UserOrgAffiliationRequestAdmin(ModelAdminWithUrlActions):
def approve(self, request, queryset):
for each in queryset:
if each.status == "canceled":
messages.error(request, _("Cannot approve a canceled affiliation request"))
messages.error(
request, _("Cannot approve a canceled affiliation request")
)
continue
each.approve()
@ -1300,201 +1358,22 @@ class UserPermissionAdmin(UserAdmin):
def get_form(self, request, obj=None, **kwargs):
# we want to remove the password field from the form
# since we dont send it and dont want to run clean for it
form = super(UserPermissionAdmin, self).get_form(request, obj, **kwargs)
form = super().get_form(request, obj, **kwargs)
del form.base_fields["password"]
return form
def user(self, obj):
url = django.urls.reverse(
"admin:%s_%s_change" % (User._meta.app_label, User._meta.model_name),
f"admin:{User._meta.app_label}_{User._meta.model_name}_change",
args=(obj.id,),
)
return mark_safe('<a href="%s">%s</a>' % (url, obj.username))
return mark_safe(f'<a href="{url}">{obj.username}</a>')
def clean_password(self):
pass
class DuplicateIPNetworkIXLan(NetworkIXLan):
"""
Issue #96, #92
Interface to streamline manual cleanup of duplicate netixlan ips
Can be removed after #92 is fully completed
"""
class Meta(object):
proxy = True
verbose_name = _("Duplicate IP")
verbose_name_plural = _("Duplicate IPs")
class DuplicateIPResultList(list):
def set_qs(self, qs):
self.qs = qs
def filter(self, **kwargs):
return self.qs.filter(**kwargs)
def _clone(self):
return self
class DuplicateIPChangeList(ChangeList):
"""
Issue #96, #92
Interface to streamline manual clean up of duplicate netixlan ips
Can be removed after #92 is fully completed
"""
def get_queryset(self, request):
ip4s = {}
ip6s = {}
qs = super(DuplicateIPChangeList, self).get_queryset(request)
sort_keys = list(qs.query.order_by)
if "pk" in sort_keys:
sort_keys.remove("pk")
if "-pk" in sort_keys:
sort_keys.remove("-pk")
def sorters(item):
v = []
for key in sort_keys:
if key[0] == "-":
key = key[1:]
v.insert(0, -getattr(item, key))
else:
v.insert(0, getattr(item, key))
return v
qs = NetworkIXLan.objects.filter(status="ok")
t = time.time()
for netixlan in qs:
if netixlan.ipaddr4:
ip4 = str(netixlan.ipaddr4)
if ip4 not in ip4s:
ip4s[ip4] = [netixlan]
else:
ip4s[ip4].append(netixlan)
if netixlan.ipaddr6:
ip6 = str(netixlan.ipaddr6)
if ip6 not in ip6s:
ip6s[ip6] = [netixlan]
else:
ip6s[ip6].append(netixlan)
collisions = []
for ip, netixlans in list(ip4s.items()):
if len(netixlans) > 1:
for netixlan in netixlans:
netixlan.ip_dupe = 4
netixlan.exchange = int(netixlan.ixlan.ix_id)
netixlan.ip_sorter = int(netixlan.ipaddr4)
netixlan.dt_sorter = int(netixlan.updated.strftime("%s"))
collisions.append(netixlan)
for ip, netixlans in list(ip6s.items()):
if len(netixlans) > 1:
for netixlan in netixlans:
netixlan.ip_dupe = 6
netixlan.exchange = int(netixlan.ixlan.ix_id)
netixlan.ip_sorter = int(netixlan.ipaddr6)
netixlan.dt_sorter = int(netixlan.updated.strftime("%s"))
collisions.append(netixlan)
if sort_keys != ["pk"] and sort_keys != ["-pk"]:
collisions = sorted(collisions, key=sorters)
collisions = DuplicateIPResultList(collisions)
collisions.set_qs(qs)
return collisions
class DuplicateIPAdmin(SoftDeleteAdmin):
"""
Issue #96, #92
Interface to streamline manual clean up of duplicate netixlan ips
Can be removed after #92 is fully completed
"""
list_display = ("id_ro", "ip", "asn", "ix", "net", "updated_ro", "status_ro")
readonly_fields = ("id_ro", "ip", "net", "ix", "asn", "updated_ro", "status_ro")
form = StatusForm
list_per_page = 1000
fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": (
"status",
"asn",
"ipaddr4",
"ipaddr6",
"ix",
"net",
"updated_ro",
),
},
),
)
def get_changelist(self, request):
return DuplicateIPChangeList
def id_ro(self, obj):
return obj.id
id_ro.admin_order_field = "id"
id_ro.short_description = "Id"
def status_ro(self, obj):
return obj.status
status_ro.admin_order_field = None
status_ro.short_description = _("Status")
def updated_ro(self, obj):
return obj.updated
updated_ro.admin_order_field = "dt_sorter"
updated_ro.short_description = _("Updated")
def ip(self, obj):
if obj.ip_dupe == 4:
return obj.ipaddr4
elif obj.ip_dupe == 6:
return obj.ipaddr6
ip.admin_order_field = "ip_sorter"
def net(self, obj):
return mark_safe('<a href="/net/%d">%d</a>' % (obj.network_id, obj.network_id))
net.admin_order_field = "network_id"
def ix(self, obj):
return mark_safe('<a href="/ix/%d">%d</a>' % (obj.ixlan.ix.id, obj.ixlan.ix.id))
ix.admin_order_field = "exchange"
def changelist_view(self, request, extra_context=None):
extra_context = {"title": "Duplicate IPs"}
return super(DuplicateIPAdmin, self).changelist_view(
request, extra_context=extra_context
)
def has_add_permission(self, request):
return False
## COMMANDLINE TOOL ADMIN
@ -1527,7 +1406,7 @@ class CommandLineToolAdmin(admin.ModelAdmin):
return False
def get_urls(self):
urls = super(CommandLineToolAdmin, self).get_urls()
urls = super().get_urls()
my_urls = [
url(
r"^prepare/$",
@ -1669,6 +1548,117 @@ class DeskProTicketAdmin(admin.ModelAdmin):
readonly_fields = ("user",)
def apply_ixf_member_data(modeladmin, request, queryset):
for ixf_member_data in queryset:
try:
ixf_member_data.apply(user=request.user, comment="Applied IX-F suggestion")
except ValidationError as exc:
messages.error(request, f"{ixf_member_data.ixf_id_pretty_str}: {exc}")
apply_ixf_member_data.short_description = _("Apply")
class IXFMemberDataAdmin(admin.ModelAdmin):
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",
"error",
)
readonly_fields = (
"marked_for_removal",
"fetched",
"ix",
"action",
"changes",
"reason",
"netixlan",
"log",
"error",
"created",
"updated",
"remote_data",
)
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",
)
actions = [apply_ixf_member_data]
raw_id_fields = ("ixlan",)
autocomplete_lookup_fields = {
"fk": ["ixlan",],
}
def ix(self, obj):
return obj.ixlan.ix
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
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False
def remote_data(self, obj):
return obj.json
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)
admin.site.register(IXFMemberData, IXFMemberDataAdmin)
admin.site.register(Facility, FacilityAdmin)
admin.site.register(InternetExchange, InternetExchangeAdmin)
admin.site.register(InternetExchangeFacility, InternetExchangeFacilityAdmin)
@ -1684,7 +1674,6 @@ admin.site.register(Sponsorship, SponsorshipAdmin)
admin.site.register(Partnership, PartnershipAdmin)
admin.site.register(OrganizationMerge, OrganizationMergeLog)
admin.site.register(UserPermission, UserPermissionAdmin)
admin.site.register(DuplicateIPNetworkIXLan, DuplicateIPAdmin)
admin.site.register(IXLanIXFMemberImportLog, IXLanIXFMemberImportLogAdmin)
admin.site.register(CommandLineTool, CommandLineToolAdmin)
admin.site.register(UserOrgAffiliationRequest, UserOrgAffiliationRequestAdmin)