import random from django.conf import settings from django.db import models from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ class Institution(models.Model): class InstitutionType(models.TextChoices): MOSQUE = 'mosque', _('Mosque') HUSSAINIYA = 'hussainiya', _('Hussainiya / Islamic Center') CULTURAL_CENTER = 'cultural_center', _('Cultural Center') LIBRARY = 'library', _('Islamic Library') INSTITUTE = 'institute', _('Seminary & Institute') CHARITY = 'charity', _('Charity & Foundation') OTHER = 'other', _('Other Organization') class VerificationStatus(models.TextChoices): PENDING = 'pending', _('Pending Review') APPROVED = 'approved', _('Approved & Verified') REJECTED = 'rejected', _('Rejected') name = models.CharField( max_length=255, verbose_name=_('Institution Name'), help_text=_('Official name of the mosque, center, or institute.') ) slug = models.SlugField( max_length=255, unique=True, blank=True, null=True, allow_unicode=True, verbose_name=_('Slug / URL Identifier') ) type = models.CharField( max_length=50, choices=InstitutionType.choices, default=InstitutionType.CULTURAL_CENTER, verbose_name=_('Institution Type') ) region = models.ForeignKey( 'profiles.Region', on_delete=models.PROTECT, related_name='institutions', verbose_name=_('Region'), help_text=_('Geographical or administrative region of the institution.') ) country = models.CharField( max_length=255, verbose_name=_('Country') ) city = models.CharField( max_length=255, verbose_name=_('City') ) address = models.TextField( blank=True, null=True, verbose_name=_('Full Address') ) latitude = models.FloatField( blank=True, null=True, verbose_name=_('Latitude') ) longitude = models.FloatField( blank=True, null=True, verbose_name=_('Longitude') ) description = models.TextField( blank=True, null=True, verbose_name=_('Description & Background') ) website = models.URLField( max_length=500, blank=True, null=True, verbose_name=_('Website') ) email = models.EmailField( blank=True, null=True, verbose_name=_('Official Email') ) phone = models.CharField( max_length=50, blank=True, null=True, verbose_name=_('Official Phone') ) established_year = models.IntegerField( blank=True, null=True, verbose_name=_('Established Year (CE)') ) cover_image = models.ImageField( upload_to='institutions/covers/%Y/%m/', blank=True, null=True, verbose_name=_('Cover Image') ) avatar = models.ImageField( upload_to='institutions/avatars/%Y/%m/', blank=True, null=True, verbose_name=_('Logo / Avatar') ) social_media = models.JSONField( default=dict, blank=True, verbose_name=_('Social Media Links'), help_text=_('Dictionary with keys: facebook, twitter, instagram, linkedin, youtube, telegram') ) working_hours = models.JSONField( default=dict, blank=True, verbose_name=_('Working Hours / Prayer Times Schedule') ) tags = models.JSONField( default=list, blank=True, verbose_name=_('Tags & Specializations') ) verification_status = models.CharField( max_length=20, choices=VerificationStatus.choices, default=VerificationStatus.PENDING, verbose_name=_('Verification Status') ) follower_count = models.PositiveIntegerField( default=0, verbose_name=_('Followers Count') ) is_featured = models.BooleanField( default=False, verbose_name=_('Is Featured') ) is_active = models.BooleanField( default=True, verbose_name=_('Is Active') ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_institutions', verbose_name=_('Created By') ) 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', '-created_at') verbose_name = _('Institution') verbose_name_plural = _('Institutions') def __str__(self): return f"{self.name} ({self.city}, {self.country})" def save(self, *args, **kwargs): if not getattr(self, 'region_id', None): from apps.profiles.models.region import Region default_region = Region.get_default_region() self.region = default_region if not self.slug: base_slug = slugify(self.name, allow_unicode=True) or f"institution-{random.randint(1000, 9999)}" slug = base_slug counter = 1 while Institution.objects.filter(slug=slug).exclude(pk=self.pk).exists(): slug = f"{base_slug}-{counter}" counter += 1 self.slug = slug super().save(*args, **kwargs) def is_admin(self, user): if not user or not user.is_authenticated: return False if getattr(user, 'is_super_admin', False) or user.is_superuser: return True if getattr(user, 'is_regional_admin', False) or (hasattr(user, 'has_perm_flag') and user.has_perm_flag('can_manage_institutions')): if hasattr(user, 'can_manage_region'): return user.can_manage_region(self.region) return True return self.members.filter(user=user, role=InstitutionMember.MemberRole.ADMIN).exists() def is_editor(self, user): if not user or not user.is_authenticated: return False if self.is_admin(user): return True if getattr(user, 'is_regional_admin', False) and hasattr(user, 'can_manage_region'): if user.can_manage_region(self.region): return True return self.members.filter(user=user, role__in=[InstitutionMember.MemberRole.ADMIN, InstitutionMember.MemberRole.EDITOR]).exists() def get_user_role(self, user): if not user or not user.is_authenticated: return None member = self.members.filter(user=user).first() if member: return member.role if getattr(user, 'is_super_admin', False) or user.is_superuser: return 'super_admin' if getattr(user, 'is_regional_admin', False): if hasattr(user, 'can_manage_region'): if user.can_manage_region(self.region): return 'regional_admin' return None return 'regional_admin' return None def is_followed_by(self, user): if not user or not user.is_authenticated: return False return self.followers.filter(user=user).exists() class InstitutionMember(models.Model): class MemberRole(models.TextChoices): ADMIN = 'admin', _('Administrator') EDITOR = 'editor', _('Content & Project Editor') VIEWER = 'viewer', _('Viewer / Member') institution = models.ForeignKey( Institution, on_delete=models.CASCADE, related_name='members', verbose_name=_('Institution') ) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='institution_memberships', verbose_name=_('User') ) role = models.CharField( max_length=20, choices=MemberRole.choices, default=MemberRole.VIEWER, verbose_name=_('Role') ) title = models.CharField( max_length=150, blank=True, null=True, verbose_name=_('Organizational Title'), help_text=_('e.g., Director, Media Manager, Cultural Affairs Officer') ) joined_at = models.DateTimeField( auto_now_add=True, verbose_name=_('Joined At') ) class Meta: unique_together = ('institution', 'user') ordering = ('-joined_at',) verbose_name = _('Institution Member') verbose_name_plural = _('Institution Members') def __str__(self): return f"{self.user} - {self.institution.name} ({self.get_role_display()})" class InstitutionTimeline(models.Model): institution = models.ForeignKey( Institution, on_delete=models.CASCADE, related_name='timeline_entries', verbose_name=_('Institution') ) year = models.CharField( max_length=50, verbose_name=_('Year / Period'), help_text=_('e.g. 1995, 1416 AH, or 2020-Present') ) title = models.CharField( max_length=255, verbose_name=_('Milestone Title') ) description = models.TextField( blank=True, null=True, verbose_name=_('Milestone Description') ) order = models.IntegerField( default=0, verbose_name=_('Display Order') ) created_at = models.DateTimeField( auto_now_add=True, verbose_name=_('Created At') ) class Meta: ordering = ('order', 'id') verbose_name = _('Timeline Milestone') verbose_name_plural = _('Timeline Milestones') def __str__(self): return f"{self.institution.name} - {self.year}: {self.title}" class InstitutionMedia(models.Model): class MediaType(models.TextChoices): IMAGE = 'image', _('Image') VIDEO = 'video', _('Video') DOCUMENT = 'document', _('Document / Publication') institution = models.ForeignKey( Institution, on_delete=models.CASCADE, related_name='media_items', verbose_name=_('Institution') ) file = models.FileField( upload_to='institutions/gallery/%Y/%m/', verbose_name=_('Media File') ) media_type = models.CharField( max_length=20, choices=MediaType.choices, default=MediaType.IMAGE, verbose_name=_('Media Type') ) title = models.CharField( max_length=255, blank=True, null=True, verbose_name=_('Media Title') ) caption = models.TextField( blank=True, null=True, verbose_name=_('Caption / Notes') ) uploaded_at = models.DateTimeField( auto_now_add=True, verbose_name=_('Uploaded At') ) class Meta: ordering = ('-uploaded_at',) verbose_name = _('Institution Media') verbose_name_plural = _('Institution Media Gallery') def __str__(self): return f"{self.institution.name} - {self.title or self.media_type}" class InstitutionFollower(models.Model): institution = models.ForeignKey( Institution, on_delete=models.CASCADE, related_name='followers', verbose_name=_('Institution') ) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='followed_institutions', verbose_name=_('User') ) created_at = models.DateTimeField( auto_now_add=True, verbose_name=_('Followed At') ) class Meta: unique_together = ('institution', 'user') ordering = ('-created_at',) verbose_name = _('Institution Follower') verbose_name_plural = _('Institution Followers') def __str__(self): return f"{self.user} follows {self.institution.name}"