changes
This commit is contained in:
266
app/views.py
266
app/views.py
@@ -27,6 +27,8 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
import json
|
||||
import io
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from num2words import num2words
|
||||
from django.db import transaction
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
@@ -1347,4 +1349,266 @@ def generate_document(request):
|
||||
time.sleep(5)
|
||||
count += 1
|
||||
|
||||
return Response({"error": "Timeout chờ generate."}, status=status.HTTP_408_REQUEST_TIMEOUT)
|
||||
return Response({"error": "Timeout chờ generate."}, status=status.HTTP_408_REQUEST_TIMEOUT)
|
||||
|
||||
#=============================================================================
|
||||
# EMAIL TEMPLATE PREVIEW
|
||||
#=============================================================================
|
||||
class EmailTemplatePreview:
|
||||
"""
|
||||
Class để preview nội dung email template với dữ liệu đã được map
|
||||
Không gửi email, chỉ trả về nội dung đã render
|
||||
"""
|
||||
|
||||
def __init__(self, template, context_pks: dict):
|
||||
self.template = template
|
||||
self.context_pks = context_pks
|
||||
self.config = self.template.content
|
||||
self.data_context = {}
|
||||
self.replacements = {}
|
||||
|
||||
def _get_model(self, model_string):
|
||||
"""Lấy model class từ string 'app.Model'"""
|
||||
app_label, model_name = model_string.split(".")
|
||||
return apps.get_model(app_label, model_name)
|
||||
|
||||
def _get_value_from_object(self, obj, field_path):
|
||||
"""Lấy giá trị từ object theo field path (hỗ trợ nested: 'user.profile.name')"""
|
||||
if obj is None:
|
||||
return None
|
||||
value = obj
|
||||
for part in field_path.replace("__", ".").split("."):
|
||||
if value is None:
|
||||
return None
|
||||
value = getattr(value, part, None)
|
||||
return value
|
||||
|
||||
def _resolve_lookup_value(self, lookup_from):
|
||||
"""Resolve giá trị lookup từ context_pks hoặc data_context"""
|
||||
if lookup_from in self.context_pks:
|
||||
return self.context_pks[lookup_from]
|
||||
|
||||
try:
|
||||
alias, field_path = lookup_from.split(".", 1)
|
||||
if alias not in self.data_context:
|
||||
raise ValueError(f"Alias '{alias}' not found in data context.")
|
||||
|
||||
source_object = self.data_context.get(alias)
|
||||
return self._get_value_from_object(source_object, field_path)
|
||||
except ValueError:
|
||||
raise ValueError(f"Could not resolve '{lookup_from}'.")
|
||||
|
||||
def fetch_data(self):
|
||||
"""Fetch data từ database theo mappings config"""
|
||||
mappings = self.config.get("mappings", [])
|
||||
if not isinstance(mappings, list):
|
||||
raise TypeError("Email template 'mappings' must be a list.")
|
||||
|
||||
# Process trigger object first
|
||||
trigger_model_mapping = next((m for m in mappings if m.get("is_trigger_object", False)), None)
|
||||
if trigger_model_mapping:
|
||||
model_cls = self._get_model(trigger_model_mapping["model"])
|
||||
lookup_field = trigger_model_mapping["lookup_field"]
|
||||
lookup_value = self._resolve_lookup_value(trigger_model_mapping["lookup_value_from"])
|
||||
alias = trigger_model_mapping["alias"]
|
||||
if lookup_value is not None:
|
||||
self.data_context[alias] = model_cls.objects.filter(**{lookup_field: lookup_value}).first()
|
||||
else:
|
||||
self.data_context[alias] = None
|
||||
|
||||
# Process other mappings
|
||||
for mapping in mappings:
|
||||
if mapping.get("is_trigger_object", False):
|
||||
continue
|
||||
|
||||
model_cls = self._get_model(mapping["model"])
|
||||
lookup_field = mapping["lookup_field"]
|
||||
lookup_value = self._resolve_lookup_value(mapping["lookup_value_from"])
|
||||
alias = mapping["alias"]
|
||||
|
||||
if lookup_value is None:
|
||||
self.data_context[alias] = None if mapping.get("type") == "object" else []
|
||||
continue
|
||||
|
||||
queryset = model_cls.objects.filter(**{lookup_field: lookup_value})
|
||||
|
||||
if mapping.get("type") == "object":
|
||||
self.data_context[alias] = queryset.first()
|
||||
elif mapping.get("type") == "list":
|
||||
self.data_context[alias] = list(queryset)
|
||||
|
||||
def _format_value(self, value, format_config):
|
||||
"""Format value theo config (currency, date, number_to_words, conditional)"""
|
||||
if value is None:
|
||||
return ""
|
||||
|
||||
format_type = format_config.get("type")
|
||||
if not format_type:
|
||||
return str(value)
|
||||
|
||||
try:
|
||||
if format_type == "currency":
|
||||
return "{:,}".format(np.int64(value)).replace(",", ".")
|
||||
if format_type == "date":
|
||||
date_format = format_config.get("format", "dd/mm/YYYY").replace("dd", "%d").replace("mm", "%m").replace("YYYY", "%Y")
|
||||
return value.strftime(date_format)
|
||||
if format_type == "number_to_words":
|
||||
return num2words(value, lang=format_config.get("lang", "vi"))
|
||||
if format_type == "conditional":
|
||||
return format_config["true_value"] if value else format_config["false_value"]
|
||||
except Exception as e:
|
||||
print(f"Error formatting value '{value}' with config '{format_config}': {e}")
|
||||
return ""
|
||||
|
||||
return str(value)
|
||||
|
||||
def prepare_replacements(self):
|
||||
"""Chuẩn bị dict replacements cho placeholders"""
|
||||
# Add date placeholders
|
||||
today = datetime.now()
|
||||
self.replacements['[day]'] = str(today.day)
|
||||
self.replacements['[month]'] = str(today.month)
|
||||
self.replacements['[year]'] = str(today.year)
|
||||
self.replacements['[date]'] = today.strftime("%d/%m/%Y")
|
||||
|
||||
# Process field mappings
|
||||
mappings = self.config.get("mappings", [])
|
||||
for mapping in mappings:
|
||||
alias = mapping["alias"]
|
||||
data = self.data_context.get(alias)
|
||||
fields = mapping.get("fields", {})
|
||||
|
||||
if mapping.get("type") == "object":
|
||||
if data is None:
|
||||
for placeholder in fields:
|
||||
self.replacements[placeholder] = ""
|
||||
continue
|
||||
|
||||
for placeholder, config in fields.items():
|
||||
if isinstance(config, dict):
|
||||
value = self._get_value_from_object(data, config["source"])
|
||||
self.replacements[placeholder] = self._format_value(value, config.get("format", {}))
|
||||
else:
|
||||
value = self._get_value_from_object(data, config)
|
||||
self.replacements[placeholder] = str(value) if value is not None else ""
|
||||
|
||||
def get_preview(self):
|
||||
"""
|
||||
Main method để lấy preview email content - trả về nội dung y nguyên đã thay thế placeholders
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
'subject': subject đã thay thế placeholders,
|
||||
'content': body content đã thay thế placeholders (giữ nguyên format, căn lề),
|
||||
'recipient_email': email người nhận,
|
||||
'replacements': dict của tất cả replacements được áp dụng
|
||||
}
|
||||
"""
|
||||
try:
|
||||
print(f"Generating preview for template: {self.template.name}")
|
||||
|
||||
# Fetch data and prepare replacements
|
||||
self.fetch_data()
|
||||
self.prepare_replacements()
|
||||
|
||||
# Get templates from config
|
||||
subject_template = self.config.get("subject", "")
|
||||
body_template = self.config.get("content", "")
|
||||
recipient_placeholder = self.config.get("recipient_placeholder", "[customer.email]")
|
||||
|
||||
# Apply replacements - giữ nguyên format, căn lề của template gốc
|
||||
final_subject = subject_template
|
||||
final_content = body_template
|
||||
|
||||
for key, value in self.replacements.items():
|
||||
final_subject = final_subject.replace(key, str(value))
|
||||
final_content = final_content.replace(key, str(value))
|
||||
|
||||
recipient_email = self.replacements.get(recipient_placeholder, "")
|
||||
|
||||
result = {
|
||||
'subject': final_subject,
|
||||
'content': final_content, # Nội dung y nguyên đã thay thế
|
||||
'recipient_email': recipient_email,
|
||||
'replacements': self.replacements.copy()
|
||||
}
|
||||
|
||||
print(f"Preview generated successfully for '{self.template.name}'")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error generating preview for template '{self.template.name}': {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
def preview_email_template(request):
|
||||
"""
|
||||
API để preview email template - trả về nội dung đã thay thế placeholders
|
||||
|
||||
POST /api/email/preview/
|
||||
Body: {
|
||||
"template_id": 1,
|
||||
"context_pks": {
|
||||
"contract_id": 456,
|
||||
"customer_id": 789
|
||||
}
|
||||
}
|
||||
|
||||
Response: {
|
||||
"subject": "Thông báo hợp đồng #HD-001",
|
||||
"content": "<div>Nội dung y nguyên đã thay thế...</div>",
|
||||
"recipient_email": "customer@example.com",
|
||||
"replacements": {
|
||||
"[contract.code]": "HD-001",
|
||||
"[customer.name]": "Nguyễn Văn A",
|
||||
...
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# Validate input
|
||||
template_id = request.data.get('template_id')
|
||||
if not template_id:
|
||||
return Response(
|
||||
{'error': 'template_id is required'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
context_pks = request.data.get('context_pks', {})
|
||||
if not isinstance(context_pks, dict):
|
||||
return Response(
|
||||
{'error': 'context_pks must be a dictionary'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Get template
|
||||
try:
|
||||
template = Email_Template.objects.get(pk=template_id)
|
||||
except Email_Template.DoesNotExist:
|
||||
return Response(
|
||||
{'error': f'Email_Template with id={template_id} does not exist'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
# Generate preview
|
||||
previewer = EmailTemplatePreview(template, context_pks)
|
||||
preview = previewer.get_preview()
|
||||
|
||||
if preview:
|
||||
return Response(preview, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
{'error': 'Failed to generate preview'},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return Response(
|
||||
{'error': f'Unexpected error: {str(e)}'},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
Reference in New Issue
Block a user