import random from django.conf import settings from django.db import models from django.utils import timezone from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from apps.profiles.models import Institution class CampaignCategory(models.TextChoices): MOSQUE_CONSTRUCTION = 'mosque_construction', _('Mosque & Center Construction / Renovation') HUMANITARIAN_RELIEF = 'humanitarian_relief', _('Humanitarian & Disaster Relief') PILGRIM_SUPPORT = 'pilgrim_support', _('Imam Reza (AS) Pilgrim Services') CULTURAL_PUBLICATIONS = 'cultural_publications', _('Quran & Islamic Publications Translation') EDUCATIONAL_SCHOLARSHIPS = 'educational_scholarships', _('Seminary & Academic Scholarships') GENERAL = 'general', _('General Waqf & Endowment') class CampaignPriority(models.TextChoices): URGENT = 'urgent', _('Urgent Appeal') HIGH = 'high', _('High Priority') MEDIUM = 'medium', _('Medium Priority') class PaymentStatus(models.TextChoices): COMPLETED = 'completed', _('Completed') PENDING = 'pending', _('Pending') FAILED = 'failed', _('Failed') class DonationCampaign(models.Model): title = models.CharField(max_length=255, verbose_name=_('Campaign Title')) slug = models.SlugField( max_length=280, unique=True, blank=True, null=True, allow_unicode=True, verbose_name=_('Slug / URL Identifier') ) description = models.TextField(verbose_name=_('Campaign Description & Objectives')) target_amount = models.DecimalField( max_digits=14, decimal_places=2, verbose_name=_('Target Amount') ) raised_amount = models.DecimalField( max_digits=14, decimal_places=2, default=0.00, verbose_name=_('Accumulated Raised Amount') ) currency = models.CharField( max_length=10, default='USD', verbose_name=_('Currency (e.g. USD, EUR, IRR, AED)') ) category = models.CharField( max_length=50, choices=CampaignCategory.choices, default=CampaignCategory.HUMANITARIAN_RELIEF, verbose_name=_('Category') ) priority = models.CharField( max_length=20, choices=CampaignPriority.choices, default=CampaignPriority.MEDIUM, verbose_name=_('Priority') ) cover_image = models.ImageField( upload_to='donations/covers/%Y/%m/', blank=True, null=True, verbose_name=_('Campaign Image') ) institution = models.ForeignKey( Institution, on_delete=models.SET_NULL, null=True, blank=True, related_name='donation_campaigns', verbose_name=_('Beneficiary Institution / Mosque') ) organization_name = models.CharField( max_length=255, blank=True, null=True, verbose_name=_('Managing Foundation Name') ) deadline = models.DateField( null=True, blank=True, verbose_name=_('Campaign Deadline') ) is_active = models.BooleanField(default=True, verbose_name=_('Is Active')) is_featured = models.BooleanField(default=False, verbose_name=_('Is Featured')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At')) class Meta: ordering = ('-is_featured', '-priority', '-created_at') verbose_name = _('Donation Campaign') verbose_name_plural = _('Donation Campaigns') def __str__(self): return f"{self.title} ({self.raised_amount}/{self.target_amount} {self.currency})" def save(self, *args, **kwargs): if not self.slug: base_slug = slugify(self.title, allow_unicode=True) or f"campaign-{random.randint(1000, 9999)}" slug = base_slug counter = 1 while DonationCampaign.objects.filter(slug=slug).exclude(pk=self.pk).exists(): slug = f"{base_slug}-{counter}" counter += 1 self.slug = slug super().save(*args, **kwargs) @property def progress_percentage(self) -> int: if self.target_amount and self.target_amount > 0: pct = int((self.raised_amount / self.target_amount) * 100) return min(100, pct) return 0 @property def donors_count(self) -> int: return self.transactions.filter(payment_status=PaymentStatus.COMPLETED).count() class DonationTransaction(models.Model): transaction_reference = models.CharField( max_length=100, unique=True, blank=True, verbose_name=_('Transaction Reference'), help_text=_('e.g. DON-RAZAVI-2026-XYZ123') ) campaign = models.ForeignKey( DonationCampaign, on_delete=models.CASCADE, related_name='transactions', verbose_name=_('Campaign') ) donor_user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='donations', verbose_name=_('Donor Account') ) donor_name = models.CharField( max_length=255, default='Anonymous Donor', verbose_name=_('Donor Display Name') ) donor_email = models.EmailField( blank=True, null=True, verbose_name=_('Donor Email') ) amount = models.DecimalField( max_digits=12, decimal_places=2, verbose_name=_('Donation Amount') ) currency = models.CharField( max_length=10, default='USD', verbose_name=_('Currency') ) payment_status = models.CharField( max_length=20, choices=PaymentStatus.choices, default=PaymentStatus.COMPLETED, verbose_name=_('Payment Status') ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Donated At')) class Meta: ordering = ('-created_at',) verbose_name = _('Donation Transaction') verbose_name_plural = _('Donation Transactions') def __str__(self): return f"{self.transaction_reference} - {self.amount} {self.currency} by {self.donor_name}" def save(self, *args, **kwargs): if not self.transaction_reference: year = timezone.now().year rand_code = ''.join(random.choices('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', k=6)) self.transaction_reference = f"DON-RAZAVI-{year}-{rand_code}" super().save(*args, **kwargs)