From 65b58e3bb9de26edad744654c9cace2e00fb17cd Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 20 Jun 2025 00:56:57 +0200 Subject: [PATCH] dodati payment kodovi, generisanje kodova, pdf view,promena payment page --- backend/core/admin.py | 39 ++++++++++++- backend/core/forms.py | 4 ++ backend/core/migrations/0016_paymentcode.py | 25 ++++++++ backend/core/models.py | 13 ++++- .../core/templates/admin/generate_codes.html | 11 ++++ .../admin/paymentcode_changelist.html | 14 +++++ backend/core/templates/index.html | 5 -- backend/core/templates/payment.html | 36 ++++++++---- .../core/templates/payment_code_report.html | 39 +++++++++++++ backend/core/urls.py | 3 + backend/core/utils.py | 6 +- backend/core/views.py | 58 ++++++++++++++----- backend/settings.py | 4 +- 13 files changed, 221 insertions(+), 36 deletions(-) create mode 100644 backend/core/migrations/0016_paymentcode.py create mode 100644 backend/core/templates/admin/generate_codes.html create mode 100644 backend/core/templates/admin/paymentcode_changelist.html create mode 100644 backend/core/templates/payment_code_report.html diff --git a/backend/core/admin.py b/backend/core/admin.py index 2a366f5..e9c1e27 100644 --- a/backend/core/admin.py +++ b/backend/core/admin.py @@ -1,8 +1,11 @@ from django.contrib import admin -from .models import Document, DocumentSegment, Organization, Risk, Control, DocumentTemplate, DocumentRiskControl +from .models import Document, DocumentSegment, Organization, Risk, Control, DocumentTemplate, DocumentRiskControl, PaymentCode from django.urls import reverse from django.utils.html import format_html - +from .utils import generate_payment_code +from django.urls import path +from django.shortcuts import render, redirect +from .forms import GenerateCodesForm class DocumentSegmentInline(admin.StackedInline): model = DocumentSegment @@ -41,6 +44,37 @@ class ControlAdmin(admin.ModelAdmin): class DocumentRiskControlAdmin(admin.ModelAdmin): list_display = ('document', 'risk', 'control', 'weight','likelihood') +class PaymentCodeAdmin(admin.ModelAdmin): + + list_display = ('code', 'created_at', 'used', 'company', 'used_at') + change_list_template = "admin/paymentcode_changelist.html" + + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path('generate-codes/', self.admin_site.admin_view(self.generate_codes_view), name='generate-codes'), + ] + return custom_urls + urls + + def generate_codes_view(self, request): + if request.method == 'POST': + form = GenerateCodesForm(request.POST) + if form.is_valid(): + count = form.cleaned_data['count'] + created = 0 + for _ in range(count): + while True: + code = generate_payment_code() + if not PaymentCode.objects.filter(code=code).exists(): + PaymentCode.objects.create(code=code) + created += 1 + break + self.message_user(request, f"{created} codes generated.") + return redirect('..') + else: + form = GenerateCodesForm() + return render(request, 'admin/generate_codes.html', {'form': form}) + admin.site.register(Document, DocumentAdmin) admin.site.register(Organization, OrganizationAdmin) @@ -48,3 +82,4 @@ admin.site.register(Risk ,RiskAdmin) admin.site.register(Control, ControlAdmin) admin.site.register(DocumentTemplate, DocumentTemplateAdmin) admin.site.register(DocumentRiskControl, DocumentRiskControlAdmin) +admin.site.register(PaymentCode, PaymentCodeAdmin) diff --git a/backend/core/forms.py b/backend/core/forms.py index ad26651..92bf720 100644 --- a/backend/core/forms.py +++ b/backend/core/forms.py @@ -47,3 +47,7 @@ class OrganizationForm(forms.ModelForm): cleaned_data['sensitive_data_types'] = types return cleaned_data + + +class GenerateCodesForm(forms.Form): + count = forms.IntegerField(label="How many codes to generate?", min_value=1, max_value=1000) diff --git a/backend/core/migrations/0016_paymentcode.py b/backend/core/migrations/0016_paymentcode.py new file mode 100644 index 0000000..c694900 --- /dev/null +++ b/backend/core/migrations/0016_paymentcode.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.3 on 2025-06-19 18:42 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0015_control_description_control_safeguard_id'), + ] + + operations = [ + migrations.CreateModel( + name='PaymentCode', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.CharField(max_length=10, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('used', models.BooleanField(default=False)), + ('used_at', models.DateTimeField(blank=True, null=True)), + ('company', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.organization')), + ], + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index 75cc0a4..09b1288 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -171,4 +171,15 @@ class DocumentRiskControl(models.Model): likelihood = models.IntegerField(null=True, blank=True) class Meta: - unique_together = ('document', 'risk', 'control') \ No newline at end of file + unique_together = ('document', 'risk', 'control') + + +class PaymentCode(models.Model): + code = models.CharField(max_length=10, unique=True) + created_at = models.DateTimeField(auto_now_add=True) + used = models.BooleanField(default=False) + used_at = models.DateTimeField(null=True, blank=True) + company = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.SET_NULL) + + def __str__(self): + return (f"{self.code} - {'Used' if self.used else 'Available'}") diff --git a/backend/core/templates/admin/generate_codes.html b/backend/core/templates/admin/generate_codes.html new file mode 100644 index 0000000..69afabd --- /dev/null +++ b/backend/core/templates/admin/generate_codes.html @@ -0,0 +1,11 @@ +{% extends "admin/base_site.html" %} + +{% block content %} +
+

Generate Payment Codes

+
{% csrf_token %} + {{ form.as_p }} + +
+
+{% endblock %} \ No newline at end of file diff --git a/backend/core/templates/admin/paymentcode_changelist.html b/backend/core/templates/admin/paymentcode_changelist.html new file mode 100644 index 0000000..efcbb95 --- /dev/null +++ b/backend/core/templates/admin/paymentcode_changelist.html @@ -0,0 +1,14 @@ +{% extends "admin/change_list.html" %} +{% block object-tools %} +
+
+ Generate Payment Codes +
+
+ All (PDF) + Used (PDF) + Unused (PDF) +
+
+{{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/backend/core/templates/index.html b/backend/core/templates/index.html index 5705dbb..ec8f99a 100644 --- a/backend/core/templates/index.html +++ b/backend/core/templates/index.html @@ -5,11 +5,6 @@
-
- -
diff --git a/backend/core/templates/payment.html b/backend/core/templates/payment.html index 6690464..0672221 100644 --- a/backend/core/templates/payment.html +++ b/backend/core/templates/payment.html @@ -1,19 +1,33 @@ {% extends 'base.html' %} - {% block content %} -
-
-

Payment

-

- Click the button below to securely pay and access your document. -

-
+
+
+

Payment

+

Please enter your payment code to proceed to document.

+ + {% if success %} +

{{ success }}

+ {% endif %} + {% csrf_token %} - + {% if error %} +

{{ error }}

+ {% endif %}
- {% endblock %} \ No newline at end of file diff --git a/backend/core/templates/payment_code_report.html b/backend/core/templates/payment_code_report.html new file mode 100644 index 0000000..dc20bb1 --- /dev/null +++ b/backend/core/templates/payment_code_report.html @@ -0,0 +1,39 @@ +{% load static %} + + + + + + PDF payment report + + + +

Payment Codes Report

+ + + + + + + + + + + + {% for code in codes %} + + + + + {% if code.company %} + + {% else %} + + {% endif %} + + + {% endfor %} + +
CodeCreated AtUsedCompanyUsed At
{{ code.code }}{{ code.created_at }}{{ code.used|yesno:"Yes,No" }}{{ code.company.name }}-{{ code.used_at|default:"-" }}
+ + diff --git a/backend/core/urls.py b/backend/core/urls.py index b32c814..4c57b3c 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -17,4 +17,7 @@ urlpatterns = [ path('no_confidential_data/', v.no_confidential_data, name='no_confidential_data'), path('downloads/risklet_example_document.pdf', v.download_example_pdf, name='download_example_pdf'), + #admin urls + path('admin/payment-codes-pdf/', v.payment_codes_pdf_view, name='payment_codes_pdf'), + ] diff --git a/backend/core/utils.py b/backend/core/utils.py index 1cb168a..50e4fbe 100644 --- a/backend/core/utils.py +++ b/backend/core/utils.py @@ -12,7 +12,7 @@ import matplotlib.pyplot as plt from django.contrib.staticfiles.finders import find import matplotlib.image as mpimg site_domain = settings.SITE_DOMAIN - +import random @@ -342,3 +342,7 @@ def generate_residual_risk_graph(risks_with_controls): plt.close() return base64.b64encode(image_png).decode("utf-8") + +def generate_payment_code(length=6): + chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' + return ''.join(random.choices(chars, k=length)) \ No newline at end of file diff --git a/backend/core/views.py b/backend/core/views.py index 47d968d..69f528f 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -3,7 +3,7 @@ import yaml from django.shortcuts import render, redirect , get_object_or_404 from .forms import OrganizationForm -from .models import Organization,Document, DocumentTemplate +from .models import Organization,Document, DocumentTemplate, PaymentCode from backend.accounts.utils import send_confirmation_email, send_document_email from django.contrib.admin.views.decorators import staff_member_required from .utils import generate_pdf, generate_risk_graph, generate_residual_risk_graph @@ -11,11 +11,13 @@ from .tables import risk_matrix_table ,get_risk_table, get_safeguard_summary_tab from django.conf import settings site_domain = settings.SITE_DOMAIN from .processors import render_template -from django.http import JsonResponse, FileResponse, Http404 +from django.http import JsonResponse, FileResponse, Http404, HttpResponse from django.core.exceptions import ValidationError from django.core.validators import validate_email import os - +from django.utils import timezone +from weasyprint import HTML +from django.template.loader import render_to_string # @login_required @@ -112,16 +114,28 @@ def pdf_view(request, document_id): return generate_pdf(document) def payment_page(request): - email = request.GET.get("email") - organization = get_object_or_404(Organization, email=email) - document = get_object_or_404(Document, organization=organization) - - if request.method == "POST": - pdf_url = f"{site_domain}/pdf/{document.id}/" - send_document_email(email, pdf_url, document) - return redirect(pdf_url) - - return render(request, "payment.html", {"email": email}) + error = None + email = request.GET.get('email') + if request.method == 'POST': + import re + code = re.sub(r'\s+', '', request.POST.get('code', '')).upper()[:10] + try: + payment_code = PaymentCode.objects.get(code=code) + if payment_code.used: + error = "CODE INVALID" + else: + org = Organization.objects.filter(email__iexact=email).first() + payment_code.used = True + payment_code.used_at = timezone.now() + payment_code.company = org + payment_code.save() + document = Document.objects.get(organization = org) + url = f"{site_domain}/pdf/{document.id}/" + send_document_email(email, url, document) + return redirect(url) + except PaymentCode.DoesNotExist: + error = "CODE INVALID" + return render(request, 'payment.html', {'error': error}) def no_confidential_data(request): return render(request, "no_confidential_data.html") @@ -131,4 +145,20 @@ def download_example_pdf(request): pdf_path = os.path.join(settings.BASE_DIR, 'backend/core/static/pdf/risklet_example_document.pdf') if not os.path.exists(pdf_path): raise Http404("File not found.") - return FileResponse(open(pdf_path, 'rb'), as_attachment=True, filename='risklet_example_document.pdf') \ No newline at end of file + return FileResponse(open(pdf_path, 'rb'), as_attachment=True, filename='risklet_example_document.pdf') + + +@staff_member_required +def payment_codes_pdf_view(request): + filter_by = request.GET.get('filter_by', 'all') + if filter_by == 'used': + codes = PaymentCode.objects.filter(used=True) + elif filter_by == 'available': + codes = PaymentCode.objects.filter(used=False) + else: + codes = PaymentCode.objects.all() + html_string = render_to_string('payment_code_report.html', {'codes': codes}) + pdf_content = HTML(string=html_string, base_url=request.build_absolute_uri('/')).write_pdf() + response = HttpResponse(pdf_content, content_type='application/pdf') + response['Content-Disposition'] = f'inline; filename=payment_codes_{timezone.now().strftime("%Y%m%d_%H%M%S")}.pdf' + return response \ No newline at end of file diff --git a/backend/settings.py b/backend/settings.py index 412c374..19eeeb4 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -37,8 +37,8 @@ DEBUG = config('DEBUG', default=False, cast=bool) ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) -SITE_DOMAIN = "http://risklet.kompanijabroj4.com" -#SITE_DOMAIN = "http://127.0.0.1:8000" +#SITE_DOMAIN = "http://risklet.kompanijabroj4.com" +SITE_DOMAIN = "http://127.0.0.1:8000" # Application definition