Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CSV importer for hosting provider #296

Merged
merged 21 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f29d50a
Improved and documented pytests
roald-teunissen Aug 24, 2022
4349596
WIP: Reading and validating a csv file
roald-teunissen Aug 24, 2022
3a88e28
Fixed function call and provided some todo's
roald-teunissen Aug 25, 2022
043929b
WIP: Included bulk import files, creating a csv upload page
roald-teunissen Aug 25, 2022
59885db
Expanded the base importer to allow ip ranges
roald-teunissen Aug 29, 2022
02ad311
WIP: Implemented an option to import csv files
roald-teunissen Aug 30, 2022
a2ca335
Update tests so we have our failing examples
mrchrisadams Aug 30, 2022
633fb3f
Add missing pipffile.lock
mrchrisadams Aug 30, 2022
c51aeaf
Get CSV importer parsing info as intended
mrchrisadams Sep 1, 2022
5316573
Pass a type, not a value in the method signature
mrchrisadams Sep 1, 2022
57eba2b
Add tests and code for import preview in admin
mrchrisadams Sep 1, 2022
347ff9e
Add indication of ip range length before import
mrchrisadams Sep 1, 2022
8d4695c
Update method signature for updating tests
mrchrisadams Sep 1, 2022
da5ec23
Update form to use new CSV importer
mrchrisadams Sep 1, 2022
a5cfeaf
Add __init__ file for CSV Importer
mrchrisadams Sep 1, 2022
0c7534d
Update the importer to work with hosting provider objects not ids
mrchrisadams Sep 1, 2022
9da0b04
Add tests to check that the importer saves to db
mrchrisadams Sep 2, 2022
fbc0068
Update flow for import show imported networks
mrchrisadams Sep 2, 2022
360185c
Comment out unimplemented "replace" feature
mrchrisadams Sep 2, 2022
94dd852
update importer to use sightly more idiomatic python
mrchrisadams Sep 2, 2022
5d068e3
Move upload CSV option to admin section
mrchrisadams Sep 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ python-dateutil = "*"
geoip2 = "*"
iso3166 = "*"
django-logentry-admin = "*"
pandas = "*"
rich = "*"

[pipenv]
# Needed for `black`. See https://github.com/microsoft/vscode-python/pull/5967.
Expand Down
585 changes: 311 additions & 274 deletions Pipfile.lock

Large diffs are not rendered by default.

111 changes: 108 additions & 3 deletions apps/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.contrib import admin
from django.urls import reverse
from django.contrib.auth.admin import UserAdmin, GroupAdmin, Group
import django.forms as dj_forms
from django.contrib.auth.forms import UserCreationForm
from django.utils.safestring import mark_safe
from django.shortcuts import redirect, render
Expand All @@ -24,7 +25,7 @@
ActionListFilter,
UserListFilter,
)

import rich

from taggit.models import Tag
import logging
Expand All @@ -41,6 +42,8 @@
from apps.greencheck.models import GreencheckASNapprove
from apps.greencheck.choices import StatusApproval

from apps.greencheck.forms import ImporterCSVForm


from .utils import get_admin_name, reverse_admin_name
from .admin_site import greenweb_admin
Expand Down Expand Up @@ -290,7 +293,7 @@ class HostingAdmin(admin.ModelAdmin):
]
# these are not really fields, but buttons
# see the corresponding methods
readonly_fields = ["preview_email_button"]
readonly_fields = ["preview_email_button", "start_csv_import_button"]
ordering = ("name",)

# Factories
Expand Down Expand Up @@ -357,6 +360,79 @@ def preview_email(self, request, *args, **kwargs):

return render(request, "preview_email.html", context)

def start_import_from_csv(self, request, *args, **kwargs):
"""
Show the form, and preview required formate for the importer
for the given hosting provider.
"""

# get our provider
provider = Hostingprovider.objects.get(pk=kwargs["provider"])

# get our document
data = {"provider": provider.id}
form = ImporterCSVForm(data)
form.fields["provider"].widget = dj_forms.widgets.HiddenInput()

return render(
request,
"import_csv_start.html",
{"form": form, "ip_ranges": [], "provider": provider},
)

def save_import_from_csv(self, request, *args, **kwargs):
"""
Process the contents of the uploaded file, and either
show a preview of the IP ranges that would be created, or
create them, based on submitted form value
"""
provider = Hostingprovider.objects.get(pk=kwargs["provider"])

if request.method == "POST":
# get our provider
data = {"provider": provider.id}

# try to get our document
form = ImporterCSVForm(request.POST, request.FILES)
form.fields["provider"].widget = dj_forms.widgets.HiddenInput()

valid = form.is_valid()
skip_preview = form.cleaned_data["skip_preview"]

if valid and skip_preview:
# not doing preview. Run the import
completed_importer = form.save()

context = {
"ip_ranges": completed_importer,
"provider": provider,
}
return render(request, "import_csv_results.html", context,)

if valid:
# the save default we don't save the contents
# just showing what would happen
ip_ranges = form.get_ip_ranges()
context = {
"form": form,
"ip_ranges": ip_ranges,
"provider": provider,
}
return render(request, "import_csv_preview.html", context,)

# otherwise fallback to showing the form with errors,
# ready for another attempted submission

context = {
"form": form,
"ip_ranges": None,
"provider": provider,
}

return render(request, "import_csv_preview.html", context,)

return redirect("greenweb_admin:accounts_hostingprovider_change", provider.id)

def send_email(self, request, *args, **kwargs):
"""
Send the given email, log the outbound request in the admin, and
Expand Down Expand Up @@ -537,6 +613,16 @@ def get_urls(self):
self.send_email,
name=get_admin_name(self.model, "send_email"),
),
path(
"<provider>/start_import_from_csv",
self.start_import_from_csv,
name=get_admin_name(self.model, "start_import_from_csv"),
),
path(
"<provider>/save_import_from_csv",
self.save_import_from_csv,
name=get_admin_name(self.model, "save_import_from_csv"),
),
path(
"<provider>/preview_email",
self.preview_email,
Expand Down Expand Up @@ -565,7 +651,11 @@ def get_fieldsets(self, request, obj=None):
fieldset = [
(
"Hostingprovider info",
{"fields": (("name", "website",), "country", "services")},
{
"fields": (
("name", "website",), "country", "services",
)
},
)
]

Expand All @@ -577,6 +667,7 @@ def get_fieldsets(self, request, obj=None):
("partner", "model"),
("staff_labels",),
("email_template", "preview_email_button"),
("start_csv_import_button"),
)
},
)
Expand Down Expand Up @@ -658,6 +749,20 @@ def preview_email_button(self, obj):

preview_email_button.short_description = "Support Messages"

@mark_safe
def start_csv_import_button(self, obj):
"""
Create clickable link to begin process of bulk import
of IP ranges.
"""
url = reverse_admin_name(
Hostingprovider, name="start_import_from_csv", kwargs={"provider": obj.pk},
)
link = f'<a href="{url}" class="start_csv_import">Import IP Ranges from CSV</a>'
return link

send_button.short_description = "Import IP Ranges from a CSV file"

@mark_safe
def html_website(self, obj):
html = f'<a href="{obj.website}" target="_blank">{obj.website}</a>'
Expand Down
1 change: 1 addition & 0 deletions apps/accounts/admin_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def get_urls(self):
patterns = [
path("try_out/", CheckUrlView.as_view(), name="check_url"),
path("green-urls", GreenUrlsView.as_view(), name="green_urls"),
path("import-ip-ranges", GreenUrlsView.as_view(), name="import_ip_ranges"),
]
return patterns + urls

Expand Down
85 changes: 85 additions & 0 deletions apps/accounts/templates/import_csv_preview.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}

{% block pretitle %}
<h1>Import IP Ranges from a CSV file for {{ provider }}</h1>


{% endblock %}

{% block content %}

{% if ip_ranges %}

<hr / style="margin-top:3rem; margin-bottom:3rem;">

<h2>IP Range Import Preview</h2>

<p>The following IP ranges would be imported for <strong>{{ provider }}</strong>:</p>

<table>
<th>IP Range start</th><th>Ip Range End</th><th>Created / Updated</th><th>Length</th>

{% for ip in ip_ranges.green_ips %}

<tr>
<td>{{ ip.ip_start }}</td>
<td>{{ ip.ip_end }}</td>
<td>
{% if ip.id %}
Updated
{% else %}
Created
{% endif %}
</td>
<td>
{{ ip.ip_range_length }}
</td>
</tr>

{% endfor %}
</table>

<h2>AS Import Preview</h2>

<p>The following AS Numbers ranges would be imported for <strong>{{ provider }}</strong>:</p>

<table>
<th>AS number</th><th>Created / Updated</th>

{% for as in ip_ranges.green_asns %}

<tr>
<td>{{ as.asn }}</td>
<td>
{% if as.id %}
Updated
{% else %}
Created
{% endif %}
</td>
</tr>

{% endfor %}
</table>

{% endif %}

<hr / style="margin-top:3rem; margin-bottom:3rem;">

<form enctype="multipart/form-data" action="{% url 'greenweb_admin:accounts_hostingprovider_save_import_from_csv' provider.id %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit"
style="margin-top: 0px; padding: 6px 15px">



<a style="margin-left:1rem;margin-right:1rem;" href="{% url 'greenweb_admin:accounts_hostingprovider_start_import_from_csv' provider.id %}">Back to import start</a>

<a style="margin-left:1rem;margin-right:1rem;" class="" href="{% url 'greenweb_admin:accounts_hostingprovider_change' provider.id %}">Back to hosting provider</a>
</form>



{% endblock content %}
86 changes: 86 additions & 0 deletions apps/accounts/templates/import_csv_results.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}

{% block pretitle %}
<h1>CSV Import Results for {{ provider }}</h1>
{% endblock %}

{% block content %}


{% if ip_ranges %}

<hr / style="margin-top:3rem; margin-bottom:3rem;">

<h2>IP Range Import </h2>

<p>The following IP ranges were imported for <strong>{{ provider }}</strong>:</p>

<table>
<th>IP Range start</th><th>Ip Range End</th><th>Created / Updated</th><th>Length</th>

{% for ip in ip_ranges.green_ips %}

<tr>
<td>{{ ip.ip_start }}</td>
<td>{{ ip.ip_end }}</td>
<td>
{% if ip.id %}
Updated
{% else %}
Created
{% endif %}
</td>
<td>
{{ ip.ip_range_length }}
</td>
</tr>

{% endfor %}
</table>

<h2>AS Import</h2>

<p>The following AS Numbers ranges were imported for <strong>{{ provider }}</strong>:</p>

<table>
<th>AS number</th><th>Created / Updated</th>

{% for as in ip_ranges.green_asns %}

<tr>
<td>{{ as.asn }}</td>
<td>
{% if as.id %}
Updated
{% else %}
Created
{% endif %}
</td>
</tr>

{% endfor %}
</table>

{% endif %}

<hr / style="margin-top:3rem; margin-bottom:3rem;">

<style>
.submit-row a.button{
margin-right: 1rem;
}
.submit-row a.button:visited{
color: #fff;
}
</style>

<div class="submit-row">
<a class="button" href="{% url 'greenweb_admin:accounts_hostingprovider_change' provider.id %}">Back to hosting provider</a>

|

<a style="margin-left:1rem;" class="button" href="{% url 'greenweb_admin:accounts_hostingprovider_start_import_from_csv' provider.id %}">Make another CSV Import</a>
</form>

{% endblock content %}
Loading