Skip to content

Commit

Permalink
Change the way registration numbers are stored
Browse files Browse the repository at this point in the history
  • Loading branch information
tudoramariei committed Sep 27, 2024
1 parent 3423982 commit eefe8cb
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 83 deletions.
12 changes: 11 additions & 1 deletion backend/donations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class NgoAdmin(ModelAdmin):
HasOwnerFilter,
"county",
"active_region",
"registration_number_valid",
)
list_per_page = 30

Expand All @@ -92,7 +93,7 @@ class NgoAdmin(ModelAdmin):

readonly_fields = ("date_created", "date_updated", "get_donations_link")

actions = ("generate_donations_archive",)
actions = ("generate_donations_archive", "clean_registration_numbers")

fieldsets = (
(
Expand Down Expand Up @@ -158,6 +159,15 @@ def generate_donations_archive(self, request, queryset: QuerySet[Ngo]):

self.message_user(request, message)

@admin.action(description=_("Clean up registration numbers"))
def clean_registration_numbers(self, request, queryset: QuerySet[Ngo]):
result = call_command("registration_numbers_cleanup")

if result:
self.message_user(request, result, level="ERROR")
else:
self.message_user(request, _("Registration numbers are clean."), level="SUCCESS")


@admin.register(Donor)
class DonorAdmin(ModelAdmin):
Expand Down
132 changes: 132 additions & 0 deletions backend/donations/management/commands/registration_numbers_cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import re
from typing import Dict, List, Optional

from django.core.exceptions import ValidationError
from django.core.management import BaseCommand

from donations.models.main import Ngo, ngo_id_number_validator


class Command(BaseCommand):
help = "Clean up registration numbers for all NGOs."

registration_number_pattern = r"^(RO|)\d{1,10}$"
registration_number_with_vat_id_pattern = r"^RO\d{1,10}$"

def handle(self, *args, **options):
errors: List[str] = []
# for ngo_id in Ngo.objects.filter(registration_number_valid=None).values_list("pk", flat=True):
for ngo_id in Ngo.objects.filter(registration_number_valid=False).values_list("pk", flat=True):
result = self.clean_ngo(ngo_id)

if result["state"] != "success":
errors.append(f"[{result['registration_number']}{result['state']}]")

if errors:
self.stdout.write(
self.style.ERROR(f"Errors occurred while cleaning registration numbers for NGOs: ({','.join(errors)})")
)

return f"Errors occurred while cleaning registration numbers for NGOs: ({','.join(errors)})"

def clean_ngo(self, ngo_id: int) -> Dict[str, str]:
ngo = Ngo.objects.get(pk=ngo_id)

try:
return self.clean_ngo_registration_number(ngo)
except Exception as e:
self.stdout.write(self.style.ERROR(f"Error while cleaning NGO {ngo.pk}: {e}"))

return {
"state": "error",
"registration_number": ngo.registration_number,
"message": f"Error while cleaning NGO {ngo.pk}: {e}",
}

def clean_ngo_registration_number(self, ngo: Ngo) -> Dict[str, str]:
initial_registration_number = ngo.registration_number
cleaned_registration_number = self._clean_registration_number(initial_registration_number)

if not re.match(self.registration_number_pattern, cleaned_registration_number):
self.stdout.write(
self.style.ERROR(f"NGO {ngo.pk} has an invalid registration number: {ngo.registration_number}")
)

ngo.registration_number_valid = False
ngo.save()

return {
"state": "invalid",
"registration_number": ngo.registration_number,
"message": f"NGO {ngo.pk} has an invalid registration number: {ngo.registration_number}",
}

if cleaned_registration_number:
matching_ngos = Ngo.objects.filter(registration_number=cleaned_registration_number).exclude(pk=ngo.pk)
if matching_ngos.exists():
self.stdout.write(
self.style.ERROR(
f"NGO {ngo.pk} has a duplicate registration number(s): "
f"{matching_ngos.values_list('pk', flat=True)}"
)
)

return {
"state": "duplicate",
"registration_number": ngo.registration_number,
"message": (
f"NGO {ngo.pk} has a duplicate registration number(s): "
f"{matching_ngos.values_list('pk', flat=True)}"
),
}

vat_information = self._extract_vat_id(cleaned_registration_number)

ngo.vat_id = vat_information["vat_id"]
ngo.registration_number = vat_information["registration_number"]
ngo.registration_number_valid = self._validate_registration_number(ngo.registration_number)

ngo.save()

return {
"state": "success",
"registration_number": ngo.vat_id + ngo.registration_number,
"message": (
f"NGO {ngo.pk} registration number cleaned: "
f"{vat_information['vat_id']} {vat_information['registration_number']}"
),
}

def _clean_registration_number(self, reg_num: str) -> Optional[str]:
if re.match(self.registration_number_pattern, reg_num):
return reg_num

# uppercase the string and strip of any whitespace
reg_num = reg_num.upper().strip()

# remove all the whitespace
reg_num = re.sub(r"\s+", "", reg_num)

return reg_num

def _extract_vat_id(self, reg_num: str) -> Dict[str, str]:
result = {
"vat_id": "",
"registration_number": reg_num,
}

# if the registration number matches the RO########## pattern separate the VAT ID from the CUI
if re.match(self.registration_number_with_vat_id_pattern, reg_num):
result["vat_id"] = reg_num[:2]
result["registration_number"] = reg_num[2:]

return result

@staticmethod
def _validate_registration_number(reg_num: str) -> bool:
try:
ngo_id_number_validator(reg_num)
except ValidationError:
return False

return True
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 5.1.1 on 2024-09-27 09:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("donations", "0011_alter_donor_first_name_alter_donor_last_name"),
]

operations = [
migrations.RemoveField(
model_name="ngo",
name="form_url",
),
migrations.RemoveField(
model_name="ngo",
name="image",
),
migrations.RemoveField(
model_name="ngo",
name="image_url",
),
migrations.RemoveField(
model_name="ngo",
name="logo_url",
),
migrations.RemoveField(
model_name="ngo",
name="other_emails",
),
migrations.AddField(
model_name="ngo",
name="registration_number_valid",
field=models.BooleanField(db_index=True, null=True, verbose_name="registration validation failed"),
),
migrations.AddField(
model_name="ngo",
name="vat_id",
field=models.CharField(blank=True, db_index=True, default="", max_length=2, verbose_name="VAT ID"),
),
]
41 changes: 17 additions & 24 deletions backend/donations/models/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ def ngo_slug_validator(value):
def ngo_id_number_validator(value):
cif = "".join([char for char in value.upper() if char.isalnum()])

if cif == len(cif) * "0":
raise ValidationError(_("The ID number cannot be all zeros"))

if value.startswith("RO"):
cif = value[2:]

Expand All @@ -95,7 +98,7 @@ def ngo_id_number_validator(value):

cif_control_digit: int = reversed_cif.pop(0)

cif_key_pairs: Tuple[Tuple[int, int], ...] = tuple(
cif_key_pairs: Tuple[int, ...] = tuple(
cif_digit * key_digit for cif_digit, key_digit in zip(reversed_cif, reversed_key)
)
control_result: int = sum(cif_key_pairs) * 10 % 11
Expand Down Expand Up @@ -128,7 +131,6 @@ class Ngo(models.Model):
description = models.TextField(verbose_name=_("description"))

# originally: logo
logo_url = models.URLField(verbose_name=_("logo url"), blank=True, null=False, default="")
logo = models.ImageField(
verbose_name=_("logo"),
blank=True,
Expand All @@ -137,15 +139,6 @@ class Ngo(models.Model):
upload_to=partial(ngo_directory_path, "logos"),
)

image_url = models.URLField(verbose_name=_("image url"), blank=True, null=False, default="")
image = models.ImageField(
verbose_name=_("image"),
blank=True,
null=False,
storage=select_public_storage,
upload_to=partial(ngo_directory_path, "images"),
)

# originally: account
bank_account = models.CharField(verbose_name=_("bank account"), max_length=100)

Expand All @@ -159,6 +152,19 @@ class Ngo(models.Model):
unique=True,
validators=[ngo_id_number_validator],
)
vat_id = models.CharField(
verbose_name=_("VAT ID"),
max_length=2,
blank=True,
null=False,
default="",
db_index=True,
)
registration_number_valid = models.BooleanField(
verbose_name=_("registration validation failed"),
db_index=True,
null=True,
)

address = models.TextField(verbose_name=_("address"), blank=True, null=False, default="")
county = models.CharField(
Expand All @@ -183,8 +189,6 @@ class Ngo(models.Model):

email = models.EmailField(verbose_name=_("email"), blank=True, null=False, default="", db_index=True)
website = models.URLField(verbose_name=_("website"), blank=True, null=False, default="")
# TODO: this seems unused
other_emails = models.TextField(verbose_name=_("other emails"), blank=True, null=False, default="")

# originally: verified
is_verified = models.BooleanField(verbose_name=_("is verified"), db_index=True, default=False)
Expand All @@ -201,13 +205,6 @@ class Ngo(models.Model):
is_active = models.BooleanField(verbose_name=_("is active"), db_index=True, default=True)

# url to the form that contains only the ngo's details
form_url = models.URLField(
verbose_name=_("form url"),
default="",
blank=True,
null=False,
max_length=255,
)
prefilled_form = models.FileField(
verbose_name=_("form with prefilled ngo data"),
blank=True,
Expand Down Expand Up @@ -258,10 +255,6 @@ def delete_prefilled_form(ngo_id):

changed = False

if ngo.form_url:
ngo.form_url = ""
changed = True

if ngo.prefilled_form:
ngo.prefilled_form.delete(save=False)
changed = True
Expand Down
3 changes: 0 additions & 3 deletions backend/donations/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,6 @@ def get(self, request, ngo_url, *args, **kwargs):
# close the file after it has been uploaded
pdf.close()

ngo.form_url = ngo.prefilled_form.url
ngo.save()

return redirect(ngo.prefilled_form.url)


Expand Down
1 change: 0 additions & 1 deletion backend/donations/views/cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ def get(self, request):
# loop through them and remove the form_url
# this will force an update on it when downloaded again
for ngo in ngos:
ngo.form_url = ""
ngo.prefilled_form.delete()
total_removed += 1

Expand Down
42 changes: 0 additions & 42 deletions backend/importer/tasks/logos.py

This file was deleted.

2 changes: 0 additions & 2 deletions backend/templates/v1/all-ngos.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ <h1>Organizații pentru care poți redirecționa 3.5%</h1>
<a href="{{ reverse('twopercent', kwargs={'ngo_url': ngo.slug}) }}">
{% if ngo and ngo.logo %}
{% set logo = ngo.logo.url %}
{% elif ngo and ngo.logo_url %}
{% set logo = ngo.logo_url %}
{% else %}
{% set logo = default_ngo_logo %}
{% endif %}
Expand Down
2 changes: 0 additions & 2 deletions backend/templates/v1/components/ngo-details-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
<div id="display-logo" class="text-center {{ 'hidden' if not ngo.logo }}">
{% if ngo.logo %}
<img id="ngo-logo" src="{{ ngo.logo.url }}" class="img-responsive center-block" />
{% elif ngo.logo_url %}
<img id="ngo-logo" src="{{ ngo.logo_url }}" class="img-responsive center-block" />
{% endif %}
<span id="delete-ngo-logo" class="text-danger">Șterge</span>
<input type="hidden" id="ong-logo-url" name="ong-logo-url" value="{{ ngo.logo.url if ngo and ngo.logo }}" />
Expand Down
2 changes: 0 additions & 2 deletions backend/templates/v1/components/ngo-header.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
<div class="media-left">
{% if ngo.logo %}
<img class="media-object" itemprop="logo" src="{{ ngo.logo.url }}" />
{% elif ngo.logo_url %}
<img class="media-object" itemprop="logo" src="{{ ngo.logo_url }}" />
{% endif %}
</div>
<div class="media-body">
Expand Down
Loading

0 comments on commit eefe8cb

Please sign in to comment.