dodati payment kodovi, generisanje kodova, pdf view,promena payment page
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
25
backend/core/migrations/0016_paymentcode.py
Normal file
25
backend/core/migrations/0016_paymentcode.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -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'}")
|
||||
|
||||
11
backend/core/templates/admin/generate_codes.html
Normal file
11
backend/core/templates/admin/generate_codes.html
Normal 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 %}
|
||||
14
backend/core/templates/admin/paymentcode_changelist.html
Normal file
14
backend/core/templates/admin/paymentcode_changelist.html
Normal 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 %}
|
||||
@@ -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">
|
||||
|
||||
@@ -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 %}
|
||||
39
backend/core/templates/payment_code_report.html
Normal file
39
backend/core/templates/payment_code_report.html
Normal 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>
|
||||
@@ -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'),
|
||||
|
||||
]
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user