You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

385 lines
12 KiB

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}"