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:
@ -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)
|
||||
|
Reference in New Issue
Block a user