feat: Improve list import logic

This commit is contained in:
Kumi 2025-10-25 17:29:11 +02:00
commit 7c12371655
No known key found for this signature in database
GPG key ID: ECBCC9082395383F
3 changed files with 77 additions and 44 deletions

View file

@ -1,11 +1,12 @@
import requests import requests
import re import re
from django.db import transaction
from .models import HostsOverrideRecord from .models import HostsOverrideRecord
from django.utils import timezone from django.utils import timezone
def parse_hosts_lines(lines, source_name): def parse_hosts_lines(lines, source_obj):
result = [] result = []
hosts_re = re.compile(r"^\s*([0-9a-fA-F:\.]+)\s+([^\s#]+)") hosts_re = re.compile(r"^\s*([0-9a-fA-F:\.]+)\s+([^\s#]+)")
for line in lines: for line in lines:
@ -27,7 +28,7 @@ def parse_hosts_lines(lines, source_name):
"record_type": rtype, "record_type": rtype,
"value": ip, "value": ip,
"wildcard": wildcard, "wildcard": wildcard,
"source": source_name, "import_source": source_obj,
} }
) )
return result return result
@ -37,44 +38,25 @@ def import_hosts_from_url(source):
resp = requests.get(source.url, timeout=60) resp = requests.get(source.url, timeout=60)
resp.raise_for_status() resp.raise_for_status()
lines = resp.text.splitlines() lines = resp.text.splitlines()
entries = parse_hosts_lines(lines, source.description or source.url) entries = parse_hosts_lines(lines, source)
new_records = {(e["name"], e["record_type"], e["value"]): e for e in entries}
existing_qs = HostsOverrideRecord.objects.filter( # Remove all old records from this import source
source=source.description or source.url HostsOverrideRecord.objects.filter(import_source=source).delete()
)
existing_records = {(r.name, r.record_type, r.value): r for r in existing_qs} # Create all new records
to_create, to_update, to_enable, to_disable = [], [], [], [] bulk = [
HostsOverrideRecord(
name=e["name"],
record_type=e["record_type"],
value=e["value"],
wildcard=e["wildcard"],
import_source=source,
enabled=True,
)
for e in entries
]
HostsOverrideRecord.objects.bulk_create(bulk, batch_size=500)
for key, entry in new_records.items():
if key in existing_records:
record = existing_records[key]
if not record.enabled:
to_enable.append(record.pk)
if record.wildcard != entry["wildcard"]:
record.wildcard = entry["wildcard"]
to_update.append(record)
else:
to_create.append(
HostsOverrideRecord(
name=entry["name"],
record_type=entry["record_type"],
value=entry["value"],
wildcard=entry["wildcard"],
source=entry["source"],
enabled=True,
)
)
for key, record in existing_records.items():
if key not in new_records and record.enabled:
to_disable.append(record.pk)
with transaction.atomic():
if to_create:
HostsOverrideRecord.objects.bulk_create(to_create, batch_size=500)
if to_update:
HostsOverrideRecord.objects.bulk_update(to_update, ["wildcard"])
if to_enable:
HostsOverrideRecord.objects.filter(pk__in=to_enable).update(enabled=True)
if to_disable:
HostsOverrideRecord.objects.filter(pk__in=to_disable).update(enabled=False)
source.last_import = timezone.now() source.last_import = timezone.now()
source.save() source.save()

View file

@ -0,0 +1,35 @@
# Generated by Django 5.2.7 on 2025-10-25 15:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("records", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="hostsoverriderecord",
name="import_source",
field=models.ForeignKey(
blank=True,
help_text="Set for records imported from a hosts file source.",
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="records",
to="records.hostsfileimportsource",
),
),
migrations.AlterField(
model_name="hostsoverriderecord",
name="source",
field=models.CharField(
blank=True,
help_text="Optional note for manually-added records.",
max_length=255,
),
),
]

View file

@ -48,11 +48,27 @@ class HostsOverrideRecord(models.Model):
) )
value = models.CharField(max_length=255) value = models.CharField(max_length=255)
wildcard = models.BooleanField(default=False) wildcard = models.BooleanField(default=False)
source = models.CharField(max_length=255) source = models.CharField(
max_length=255,
blank=True,
help_text="Optional note for manually-added records.",
)
import_source = models.ForeignKey(
"HostsFileImportSource",
null=True,
blank=True,
on_delete=models.CASCADE,
related_name="records",
help_text="Set for records imported from a hosts file source.",
)
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
def __str__(self): def __str__(self):
return f"{self.name} {self.record_type} {self.value} ({'wildcard' if self.wildcard else 'exact'})" if self.import_source:
src = f"(imported from: {self.import_source})"
else:
src = self.source or "manual"
return f"{self.name} {self.record_type} {self.value} ({'wildcard' if self.wildcard else 'exact'}) {src}"
class HostsFileImportSource(models.Model): class HostsFileImportSource(models.Model):
@ -84,4 +100,4 @@ class TrustAnchor(models.Model):
last_updated = models.DateTimeField(auto_now=True) last_updated = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):
return f"KSK {self.key_tag}" if self.flags & 0x0001 else f"ZSK {self.key_tag}" return f"KSK {self.key_tag}" if self.flags & 0x0001 else f"ZSK {self.key_tag}"