154 lines
5.9 KiB
Python
154 lines
5.9 KiB
Python
"""
|
||
PDF generatsiya view — JSON yuborasiz, PDF olasiz.
|
||
Yuborilmagan maydonlar uchun bo'sh string ('') qaytariladi —
|
||
shunda templateda {{ ... }} qoldiqlari ko'rinmaydi.
|
||
"""
|
||
import json
|
||
from django.http import HttpResponse, JsonResponse
|
||
from django.template.loader import render_to_string
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.views.decorators.http import require_http_methods
|
||
from weasyprint import HTML
|
||
from rest_framework import permissions, views
|
||
|
||
# Kompaniya defaultlari
|
||
COMPANY_DEFAULTS = {
|
||
'company_name': 'SIFAT BAHOLASH',
|
||
'membership_number': '122',
|
||
'membership_date': '01.06.2023',
|
||
'phone_1': '(71) 278-85-85',
|
||
'phone_2': '(91) 585-77-77',
|
||
'phone_3': '(90) 535-99-99',
|
||
'email': 'sifat.baholash@gmail.com',
|
||
'bank_account': '2020 8 000 405 309 735 001',
|
||
'bank_mfo': '00440',
|
||
'inn': '307 930 412',
|
||
'director_name': 'Тураев Т.Р.',
|
||
'appraiser_name': 'Тураев Т.Р.',
|
||
'appraiser_certificate': '0988',
|
||
'appraiser_certificate_date': '17.11.2021',
|
||
'insurance_policy': '19-01-25/0000368-2025',
|
||
'insurance_start': '30.05.2025',
|
||
'insurance_end': '29.05.2026',
|
||
'market_name': 'Сергели автомобил бозори',
|
||
'city': 'Ташкент',
|
||
'year': '2026',
|
||
'total_pages': 21,
|
||
'valuation_purpose': 'для обеспечения залогом',
|
||
'valuation_purpose_full': 'определение рыночной стоимости для обеспечения залогом',
|
||
'adj_bargaining': '-9,00%',
|
||
'adj_tech': '2,00%',
|
||
'adj_equipment': '3,00%',
|
||
'cost_weight': '20',
|
||
'comp_weight': '80',
|
||
}
|
||
|
||
# Template ichida ishlatiladigan barcha maydonlar
|
||
ALL_FIELDS = [
|
||
'report_number', 'report_date', 'valuation_date',
|
||
'customer_name', 'customer_short_name', 'customer_passport',
|
||
'owner_name', 'owner_address',
|
||
'vehicle_brand', 'vehicle_model', 'vehicle_plate',
|
||
'vehicle_type', 'vehicle_year', 'vehicle_mileage',
|
||
'vehicle_engine_number', 'vehicle_body_number', 'vehicle_chassis_number',
|
||
'vehicle_color', 'vehicle_tech_passport',
|
||
'spec_type', 'spec_seats', 'spec_length', 'spec_width', 'spec_height',
|
||
'spec_wheelbase', 'spec_clearance',
|
||
'engine_type', 'engine_volume', 'engine_cylinders',
|
||
'engine_power', 'engine_torque', 'engine_max_speed',
|
||
'engine_acceleration', 'transmission',
|
||
'fuel_tank', 'trunk', 'curb_weight', 'gross_weight',
|
||
'brakes_front', 'brakes_rear', 'tires',
|
||
'cost_c0', 'cost_tf', 'cost_lf', 'cost_type_name',
|
||
'cost_kt', 'cost_kl', 'cost_omega',
|
||
'cost_wear_andrianov', 'cost_wear_expert', 'cost_wear_avg',
|
||
'cost_func_wear', 'cost_total_wear', 'cost_capital',
|
||
'cost_approach_value', 'cost_approach_value_words',
|
||
'analog_1_source', 'analog_1_year', 'analog_1_mileage', 'analog_1_price',
|
||
'analog_1_adj_1', 'analog_1_adj_2', 'analog_1_adj_3', 'analog_1_weight',
|
||
'analog_2_source', 'analog_2_year', 'analog_2_mileage', 'analog_2_price',
|
||
'analog_2_adj_1', 'analog_2_adj_2', 'analog_2_adj_3', 'analog_2_weight',
|
||
'analog_3_source', 'analog_3_year', 'analog_3_mileage', 'analog_3_price',
|
||
'analog_3_adj_1', 'analog_3_adj_2', 'analog_3_adj_3', 'analog_3_weight',
|
||
'comp_total_usd', 'comp_total_sum', 'comp_total_words',
|
||
'cost_specific', 'comp_specific', 'weighted_value',
|
||
'market_value_number', 'market_value_words',
|
||
'rate_rur', 'rate_usd', 'rate_eur',
|
||
'exploitation_period', 'retail_price',
|
||
]
|
||
|
||
|
||
def _img_or_placeholder(image_url, placeholder_text, alt='', height='200mm'):
|
||
"""Rasm URL bo'lsa <img>, bo'lmasa placeholder qaytaradi"""
|
||
if image_url:
|
||
return f'<img src="{image_url}" alt="{alt}" style="max-width:100%;max-height:{height};">'
|
||
return (
|
||
f'<div class="image-placeholder" style="height: {height};">'
|
||
f'{placeholder_text}'
|
||
f'</div>'
|
||
)
|
||
|
||
|
||
def build_context(data: dict) -> dict:
|
||
"""Templatedagi barcha o'zgaruvchilar uchun qiymat tayyorlaydi"""
|
||
# 1) Barcha maydonlarni bo'sh string bilan to'ldirish
|
||
context = {field: '' for field in ALL_FIELDS}
|
||
|
||
# 2) Kompaniya defaultlari
|
||
context.update(COMPANY_DEFAULTS)
|
||
|
||
# 3) Foydalanuvchi yuborgan ma'lumotlarni ustiga yozish
|
||
context.update(data)
|
||
|
||
# 4) Rasmlar uchun tayyor HTML
|
||
context['cover_photo_html'] = _img_or_placeholder(
|
||
data.get('vehicle_photo'),
|
||
'[ Фото автомобиля ]',
|
||
alt='Vehicle',
|
||
height='50mm',
|
||
)
|
||
context['customer_passport_html'] = _img_or_placeholder(
|
||
data.get('customer_passport_image'),
|
||
'[ Изображение паспорта заказчика ]',
|
||
alt='Паспорт заказчика',
|
||
)
|
||
context['tech_passport_html'] = _img_or_placeholder(
|
||
data.get('tech_passport_image'),
|
||
'[ Изображение технического паспорта ]',
|
||
alt='Технический паспорт',
|
||
)
|
||
context['price_list_html'] = _img_or_placeholder(
|
||
data.get('price_list_image'),
|
||
'[ Прайс-лист завода-изготовителя ]',
|
||
alt='Прайс-лист',
|
||
)
|
||
|
||
# 5) Loop maydonlar
|
||
if not context.get('analog_screenshots'):
|
||
context['analog_screenshots'] = [
|
||
{'page_num': 18, 'image': None},
|
||
{'page_num': 19, 'image': None},
|
||
{'page_num': 20, 'image': None},
|
||
]
|
||
if not context.get('vehicle_photos'):
|
||
context['vehicle_photos'] = []
|
||
|
||
return context
|
||
|
||
|
||
class GenerateApiView(views.APIView):
|
||
|
||
def post(self, request):
|
||
|
||
try:
|
||
html_string = render_to_string('report_template.html', context)
|
||
pdf_bytes = HTML(string=html_string).write_pdf()
|
||
except Exception as e:
|
||
return JsonResponse({'error': f'PDF yaratishda xato: {str(e)}'}, status=500)
|
||
|
||
filename = f'otchet_1.pdf'
|
||
|
||
response = HttpResponse(pdf_bytes, content_type='application/pdf')
|
||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||
return response
|