dodati payment kodovi, generisanje kodova, pdf view,promena payment page

This commit is contained in:
2025-06-20 00:56:57 +02:00
parent 44bb4578b1
commit 65b58e3bb9
13 changed files with 221 additions and 36 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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')),
],
),
]

View File

@@ -171,4 +171,15 @@ class DocumentRiskControl(models.Model):
likelihood = models.IntegerField(null=True, blank=True)
class Meta:
unique_together = ('document', 'risk', 'control')
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'}")

View File

@@ -0,0 +1,11 @@
{% extends "admin/base_site.html" %}
{% block content %}
<div id="content-main">
<h2>Generate Payment Codes</h2>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="apply" value="Generate" class="default">
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "admin/change_list.html" %}
{% block object-tools %}
<div style="padding: 12px 0; display: flex; gap: 8px;">
<div style="padding: 12px 0;">
<a href="{% url 'admin:generate-codes' %}" class="button">Generate Payment Codes</a>
</div>
<div style="padding: 12px 0;">
<a href="{% url 'core:payment_codes_pdf' %}?filter_by=all" class="button">All (PDF)</a>
<a href="{% url 'core:payment_codes_pdf' %}?filter_by=used" class="button">Used (PDF)</a>
<a href="{% url 'core:payment_codes_pdf' %}?filter_by=available" class="button">Unused (PDF)</a>
</div>
</div>
{{ block.super }}
{% endblock %}

View File

@@ -5,11 +5,6 @@
<main class="flex-grow">
<section id="cyber-measures" class="bg-secondary">
<div class="text-center">
<a class="text-accent font-semibold tracking-wider uppercase text-sm" href="{% url 'core:no_confidential_data' %}">No confidential data</a>
</div>
</section>
<!-- WhitepaperSection -->
<section class="bg-gradient-to-br from-primary to-teal-700 text-light-text py-20 md:py-32 relative">

View File

@@ -1,19 +1,33 @@
{% extends 'base.html' %}
{% block content %}
<section class="py-16 bg-secondary sm:py-24 p-body-full">
<div class="max-w-lg w-full mx-auto text-center shadow-lg border border-success rounded-xl p-8 bg-white">
<h2 class="text-3xl font-extrabold mb-4 text-success">Payment</h2>
<p class="mb-8 text-gray-700 text-lg">
Click the button below to securely pay and access your document.
</p>
<form method="post">
<section class="py-24 bg-gradient-to-br from-teal-100 to-primary flex items-center justify-center">
<div class="max-w-md w-full mx-auto text-center shadow-2xl border border-accent rounded-2xl p-4 bg-white/90 backdrop-blur">
<h2 class="text-3xl font-extrabold mb-6 text-accent">Payment</h2>
<p class="text-lg text-gray-700 mb-6">Please enter your payment code to proceed to document.</p>
{% if success %}
<p class="text-green-600 font-semibold mb-4">{{ success }}</p>
{% endif %}
<form method="post" class="space-y-6">
{% csrf_token %}
<button type="submit" class="nav-link-desktop bg-transparent border-2 border-accent text-accent hover:bg-accent hover:text-primary font-semibold py-3 px-8 rounded-lg text-lg transition-all duration-300 ease-in-out">
Pay & Check Your document
<input
type="text"
name="code"
maxlength="10"
class="w-full px-4 py-3 border-2 border-accent rounded-lg focus:outline-none focus:ring-2 focus:ring-accent text-lg tracking-widest text-center font-mono mb-2"
placeholder="Enter your code"
required
>
<button
type="submit"
class="w-full bg-accent text-primary hover:bg-yellow-400 font-bold py-3 px-8 rounded-lg shadow-lg text-lg transition-all duration-200 ease-in-out transform hover:scale-105"
>
Enter Code
</button>
</form>
{% if error %}
<p class="text-red-600 mt-6 font-semibold text-lg">{{ error }}</p>
{% endif %}
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,39 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF payment report</title>
<link rel="stylesheet" href="{% static 'css/document.css' %}">
</head>
<body>
<h1>Payment Codes Report</h1>
<table>
<thead>
<tr>
<th>Code</th>
<th>Created At</th>
<th>Used</th>
<th>Company</th>
<th>Used At</th>
</tr>
</thead>
<tbody>
{% for code in codes %}
<tr>
<td>{{ code.code }}</td>
<td>{{ code.created_at }}</td>
<td>{{ code.used|yesno:"Yes,No" }}</td>
{% if code.company %}
<td>{{ code.company.name }}</td>
{% else %}
<td>-</td>
{% endif %}
<td>{{ code.used_at|default:"-" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

View File

@@ -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'),
]

View File

@@ -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))

View File

@@ -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')
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

View File

@@ -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