Files
2026-10-06 10:03:22 +03:30

94 lines
4.3 KiB
Python

import hashlib
import re
import warnings
from pathlib import Path
import jdatetime
import magic
from PIL import Image, UnidentifiedImageError
from django.core.exceptions import ValidationError
from django.utils import timezone
from apps.attachments.models import AttachmentPolicy
from .models import MAX_AMOUNT
DIGIT_MAP = str.maketrans('۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩', '01234567890123456789')
def digits(value):
return str(value or '').translate(DIGIT_MAP).strip()
def amount(value):
value = digits(value)
if not re.fullmatch(r'\d+|\d{1,3}(?:[,٬ ]\d{3})+', value):
raise ValidationError('مبلغ را به ریال، فقط با عدد صحیح وارد کنید.')
value = int(re.sub(r'[,٬ ]', '', value))
if not 0 < value <= MAX_AMOUNT:
raise ValidationError('مبلغ باید بیشتر از صفر و در محدوده مجاز باشد.')
return value
def bank_number(value):
value = re.sub(r'[\s-]', '', digits(value))
if value and not value.isascii():
raise ValidationError('شماره را با ارقام معتبر وارد کنید.')
if value and not value.isdecimal():
raise ValidationError('شماره حساب یا کارت باید فقط عدد باشد.')
return value
def iban(value):
value = re.sub(r'\s', '', digits(value)).upper()
if not value:
return ''
if not re.fullmatch(r'IR\d{24}', value) or int(value[4:] + '1827' + value[2:4]) % 97 != 1:
raise ValidationError('شماره شبای معتبر ایرانی وارد کنید؛ IR و ۲۴ رقم.')
return value
def paid_date(day, month, year):
if not all((day, month, year)):
raise ValidationError('تاریخ را کامل کنید؛ روز، ماه و سال لازم‌اند.')
try:
value = jdatetime.date(int(digits(year)), int(digits(month)), int(digits(day))).togregorian()
except (ValueError, TypeError, OverflowError):
raise ValidationError('تاریخ شمسی معتبر انتخاب کنید.')
today = timezone.localdate()
if value > today:
raise ValidationError('تاریخ واریز نمی‌تواند بعد از امروز باشد.')
if jdatetime.date.fromgregorian(date=value).year < jdatetime.date.fromgregorian(date=today).year - 10:
raise ValidationError('تاریخ خارج از بازه ده سال گذشته است.')
return value
def image(upload):
policy = AttachmentPolicy.objects.filter(attachment_type='IMAGE', is_enabled=True).first()
if not policy:
raise ValidationError('ارسال تصویر فعلاً فعال نیست؛ با کارشناس هماهنگ کنید.')
if not upload.size or upload.size > policy.max_file_size:
raise ValidationError('تصویر خالی یا بزرگ‌تر از حجم مجاز است.')
ext = Path(upload.name).suffix.lower()
if ext not in (policy.allowed_extensions or ['.jpg', '.jpeg', '.png', '.gif', '.webp']):
raise ValidationError('فقط تصویر مجاز فیش را انتخاب کنید.')
head = upload.read(8192)
upload.seek(0)
mime = magic.from_buffer(head, mime=True)
expected = {'.jpg':'image/jpeg', '.jpeg':'image/jpeg', '.png':'image/png', '.gif':'image/gif', '.webp':'image/webp'}
if expected.get(ext) != mime or (policy.allowed_mime_types and mime not in policy.allowed_mime_types):
raise ValidationError('محتوای واقعی فایل با نوع تصویر سازگار نیست.')
try:
with warnings.catch_warnings():
warnings.simplefilter('error', Image.DecompressionBombWarning)
with Image.open(upload) as picture:
if picture.width * picture.height > 25_000_000:
raise ValidationError('ابعاد تصویر بیش از حد مجاز است.')
picture.verify()
except (UnidentifiedImageError, OSError, SyntaxError, Image.DecompressionBombError, Image.DecompressionBombWarning):
raise ValidationError('تصویر خراب است یا باز نمی‌شود؛ تصویر دیگری انتخاب کنید.')
finally:
upload.seek(0)
digest = hashlib.sha256()
for chunk in upload.chunks():
digest.update(chunk)
upload.seek(0)
return {'mime_type': mime, 'sha256': digest.hexdigest(), 'original_name': Path(upload.name).name[:255], 'file_size': upload.size}