diff --git a/backend/donations/migrations/0006_alter_donor_pdf_file_alter_ngo_prefilled_form.py b/backend/donations/migrations/0006_alter_donor_pdf_file_alter_ngo_prefilled_form.py new file mode 100644 index 00000000..6e654fa9 --- /dev/null +++ b/backend/donations/migrations/0006_alter_donor_pdf_file_alter_ngo_prefilled_form.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.9 on 2024-01-31 12:37 + +from django.db import migrations, models +import donations.models.main +import functools + + +class Migration(migrations.Migration): + + dependencies = [ + ("donations", "0005_alter_ngo_prefilled_form"), + ] + + operations = [ + migrations.AlterField( + model_name="donor", + name="pdf_file", + field=models.FileField( + blank=True, + upload_to=functools.partial( + donations.models.main.year_ngo_donor_directory_path, *("donation-forms",), **{} + ), + verbose_name="PDF file", + ), + ), + migrations.AlterField( + model_name="ngo", + name="prefilled_form", + field=models.FileField( + blank=True, + storage=donations.models.main.select_public_storage, + upload_to=functools.partial(donations.models.main.year_ngo_directory_path, *("ngo-forms",), **{}), + verbose_name="form with prefilled ngo data", + ), + ), + ] diff --git a/backend/donations/models/main.py b/backend/donations/models/main.py index 447c5d3d..27684071 100644 --- a/backend/donations/models/main.py +++ b/backend/donations/models/main.py @@ -15,24 +15,39 @@ def select_public_storage(): return storages["public"] -def _id_code(prefix: str, id: int) -> str: +def hash_id_secret(prefix: str, id: int) -> str: return hashlib.sha1(f"{prefix}-{id}-{settings.SECRET_KEY}".encode()).hexdigest()[:10] -def ngo_directory_path(subdir, instance, filename) -> str: - # file will be uploaded to MEDIA_ROOT/ngo--// - return "{0}/ngo-{1}-{2}/{3}".format(subdir, instance.pk, _id_code("ngo", instance.pk), filename) +def ngo_directory_path(subdir: str, instance: "Ngo", filename: str) -> str: + """ + The file will be uploaded to MEDIA_ROOT//ngo--/ + """ + return "{0}/ngo-{1}-{2}/{3}".format(subdir, instance.pk, hash_id_secret("ngo", instance.pk), filename) -def year_ngo_donor_directory_path(subdir, instance, filename) -> str: +def year_ngo_directory_path(subdir: str, instance: "Ngo", filename: str) -> str: + """ + The file will be uploaded to MEDIA_ROOT///ngo--/ + """ + timestamp = timezone.now() + return "{0}/{1}/ngo-{2}-{3}/{4}".format( + subdir, timestamp.date().year, instance.pk, hash_id_secret("ngo", instance.pk), filename + ) + + +def year_ngo_donor_directory_path(subdir: str, instance: "Donor", filename: str) -> str: + """ + The file will be uploaded to MEDIA_ROOT///ngo--/__ + """ timestamp = timezone.now() return "{0}/{1}/ngo-{2}-{3}/{4}_{5}_{6}".format( subdir, timestamp.date().year, instance.ngo.pk if instance.ngo else 0, - _id_code("ngo", instance.ngo.pk if instance.ngo else 0), + hash_id_secret("ngo", instance.ngo.pk if instance.ngo else 0), instance.pk, - _id_code("donor", instance.pk), + hash_id_secret("donor", instance.pk), filename, ) @@ -156,7 +171,7 @@ class Ngo(models.Model): blank=True, null=False, storage=select_public_storage, - upload_to=partial(ngo_directory_path, "prefilled_forms"), + upload_to=partial(year_ngo_directory_path, "ngo-forms"), ) date_created = models.DateTimeField(verbose_name=_("date created"), db_index=True, auto_now_add=timezone.now) @@ -174,8 +189,10 @@ def __str__(self): return f"{self.name}" def get_full_form_url(self): - if self.form_url: - return "https://{}/{}".format(settings.APEX_DOMAIN, self.form_url) + if self.prefilled_form: + return self.prefilled_form.url + elif self.form_url: + return self.form_url else: return "" @@ -249,7 +266,7 @@ class Donor(models.Model): verbose_name=_("PDF file"), blank=True, null=False, - upload_to=partial(year_ngo_donor_directory_path, "forms"), + upload_to=partial(year_ngo_donor_directory_path, "donation-forms"), ) date_created = models.DateTimeField(verbose_name=_("date created"), db_index=True, auto_now_add=timezone.now) diff --git a/backend/donations/views/api.py b/backend/donations/views/api.py index f90cb344..5727e6d0 100644 --- a/backend/donations/views/api.py +++ b/backend/donations/views/api.py @@ -1,18 +1,22 @@ import json import logging +from urllib.request import Request, urlopen +from datetime import datetime, date + from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied, BadRequest from django.core.files import File from django.http import HttpResponse, JsonResponse, Http404 from django.shortcuts import redirect -from django.urls import reverse -from django.urls import reverse_lazy +from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator +from django.utils import timezone +from django.views.decorators.csrf import csrf_exempt -from ..models.main import Ngo -from ..models.jobs import Job +from ..models.main import Ngo, Donor, ngo_directory_path, hash_id_secret +from ..models.jobs import Job, JobStatusChoices from ..pdf import create_pdf from .base import BaseHandler, AccountHandler @@ -93,30 +97,91 @@ def get(self, request, ngo_url, *args, **kwargs): class GetNgoForms(AccountHandler): - def get(self, request, *args, **kwargs): - raise NotImplementedError("GetNgoForms not implemented yet") + def post(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return redirect(reverse("login")) + + ngo = request.user.ngo + if not ngo: + return redirect(reverse("contul-meu")) + + DONATION_LIMIT = date(timezone.now().year, 5, 25) + + now = timezone.now() + start_of_year = datetime(now.year, 1, 1, 0, 0) + + if now.date() > DONATION_LIMIT: + return redirect(reverse("contul-meu")) + + # get all the forms that have been completed since the start of the year + # and they are also signed + donations = Donor.objects.filter(ngo=ngo, date_created__gte=start_of_year, has_signed=True).all() + + # extract only the urls from the array of models + urls = [u.pdf_file.url if u.pdf_file else u.pdf_url for u in donations if u.has_signed] + + # if no forms + if len(urls) == 0: + logging.warn("Could not find any signed forms for this ngo: {}".format(ngo.id)) + return redirect(reverse("contul-meu")) + + # create job + job = Job( + ngo=ngo, + owner=request.user, + ) + job.save() + + export_destination = ngo_directory_path( + "exports", ngo, "export_{}_{}.zip".format(job.id, hash_id_secret(timezone.now(), job.id)) + ) + + # make request + params = { + "passphrase": settings.ZIP_SECRET, + "urls": urls, + "path": export_destination, + "webhook": { + "url": "https://{}{}".format(settings.APEX_DOMAIN, reverse("webhook")), + "data": {"jobId": job.id}, + }, + } + + request = Request(url=settings.ZIP_ENDPOINT, data=params, headers={"Content-type": "application/json"}) + + try: + httpresp = urlopen(request) + response = json.decode(httpresp.read()) + logging.info(response) + httpresp.close() + except Exception as e: + logging.exception(e) + # if job failed to start remotely + job.status = JobStatusChoices.ERROR + job.save() + finally: + return redirect(reverse("contul-meu")) @method_decorator(login_required(login_url=reverse_lazy("login")), name="dispatch") +@method_decorator(csrf_exempt, name="dispatch") class GetUploadUrl(AccountHandler): def post(self, request, *args, **kwargs): - files = request.FILES - if len(files) != 1: + logo_file = request.FILES.get("files") + if not logo_file: raise BadRequest() ngo = request.user.ngo if not ngo: - raise BadRequest() - # # TODO: should we create the NGO here? - # ngo = Ngo.objects.create() - # ngo.save() - # request.user.ngo = ngo - # request.user.save() + ngo = Ngo.objects.create() + ngo.save() + request.user.ngo = ngo + request.user.save() - ngo.logo = files[0] + ngo.logo = logo_file ngo.save() - self.return_json({"file_urls": [ngo.logo.url]}) + return JsonResponse({"file_urls": [ngo.logo.url]}) class Webhook(BaseHandler): diff --git a/backend/donations/views/ngo.py b/backend/donations/views/ngo.py index 2247bacd..2efdce42 100644 --- a/backend/donations/views/ngo.py +++ b/backend/donations/views/ngo.py @@ -114,7 +114,7 @@ def post(self, request, ngo_url): self.donor.pdf_file.delete() self.donor.pdf_file = None - self.donor.pdf_file.save("declaratia_completata.pdf", File(new_pdf)) + self.donor.pdf_file.save("declaratie_semnata.pdf", File(new_pdf)) new_pdf.close() # # TODO: Send email @@ -306,9 +306,6 @@ def get_post_value(arg, add_to_error_list=True): # self.return_error(errors) # return - filename = "filled_form.pdf" - pdf = create_pdf(donor_dict, ngo_data) - # create the donor and save it donor = Donor( first_name=donor_dict["first_name"], @@ -324,11 +321,12 @@ def get_post_value(arg, add_to_error_list=True): # make a request to get geo ip data for this user # geoip = self.get_geoip_data(), ngo=ngo, - filename=filename, + # TODO: 'filename' is unused ) donor.save() - donor.pdf_file.save(filename, File(pdf)) + pdf = create_pdf(donor_dict, ngo_data) + donor.pdf_file.save("declaratie_nesemnata.pdf", File(pdf)) # close the file after it has been uploaded pdf.close() diff --git a/backend/redirectioneaza/settings.py b/backend/redirectioneaza/settings.py index d97233d9..f161eb3b 100644 --- a/backend/redirectioneaza/settings.py +++ b/backend/redirectioneaza/settings.py @@ -54,7 +54,8 @@ CORS_ALLOWED_ORIGINS=(list, []), CORS_ALLOW_ALL_ORIGINS=(bool, False), # zipping settings - ZIPPY_URL=(str, "zippy:8000"), + ZIP_ENDPOINT=(str, "zippy:8000"), + ZIP_SECRET=(str, ""), # email settings EMAIL_SEND_METHOD=(str, "async"), EMAIL_BACKEND=(str, "django.core.mail.backends.console.EmailBackend"), @@ -446,5 +447,5 @@ CAPTCHA_ENABLED = True if CAPTCHA_PUBLIC_KEY else False - -USER_FORMS = "documents" +ZIP_ENDPOINT = env.str("ZIP_ENDPOINT") +ZIP_SECRET = env.str("ZIP_SECRET") diff --git a/backend/static_extras/js/ngo/ngo-setup.js b/backend/static_extras/js/ngo/ngo-setup.js index d5dda099..b7e382d6 100644 --- a/backend/static_extras/js/ngo/ngo-setup.js +++ b/backend/static_extras/js/ngo/ngo-setup.js @@ -2,7 +2,7 @@ $(function () { var uploadLogo = $("#upload-logo"); var displayLogo = $("#display-logo"); - var aws_api_url = '/api/ngo/upload-url'; + var aws_api_url = '/api/ngo/upload-url/'; var photoLogoClass = "fa-picture-o"; var loadingLogoClass = "fa-spinner fa-pulse"; diff --git a/backend/templates/v1/all-ngos.html b/backend/templates/v1/all-ngos.html index 4aed3439..a23fd109 100644 --- a/backend/templates/v1/all-ngos.html +++ b/backend/templates/v1/all-ngos.html @@ -24,7 +24,7 @@

Asociații pentru care poți redirecționa 3.5%