import logging from typing import ClassVar from django.contrib.auth.models import Permission, User from django.db import models, transaction from django.utils.translation import gettext_lazy as _ from allianceauth.eveonline.models import EveAllianceInfo, EveCharacter, EveCorporationInfo, EveFactionInfo from allianceauth.notifications import notify from .managers import CharacterOwnershipManager, StateManager logger = logging.getLogger(__name__) class State(models.Model): name = models.CharField(max_length=32, unique=True) permissions = models.ManyToManyField(Permission, blank=True) priority = models.IntegerField(unique=True, help_text="Users get assigned the state with the highest priority available to them.") member_characters = models.ManyToManyField(EveCharacter, blank=True, help_text="Characters to which this state is available.") member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True, help_text="Corporations to whose members this state is available.") member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True, help_text="Alliances to whose members this state is available.") member_factions = models.ManyToManyField(EveFactionInfo, blank=True, help_text="Factions to whose members this state is available.") public = models.BooleanField(default=False, help_text="Make this state available to any character.") objects: ClassVar[StateManager] = StateManager() class Meta: ordering = ['-priority'] def __str__(self) -> str: return self.name def available_to_character(self, character): return self in State.objects.available_to_character(character) def available_to_user(self, user): return self in State.objects.available_to_user(user) def delete(self, **kwargs): with transaction.atomic(): for profile in self.userprofile_set.all(): profile.assign_state(state=State.objects.exclude(pk=self.pk).get_for_user(profile.user)) super().delete(**kwargs) def get_guest_state(): try: return State.objects.get(name='Guest') except State.DoesNotExist: return State.objects.create(name='Guest', priority=0, public=True) def get_guest_state_pk(): return get_guest_state().pk class UserProfile(models.Model): class Language(models.TextChoices): """ Choices for UserProfile.language """ # Sorted by Language Code alphabetical order + English at top ENGLISH = 'en', _('English') CZECH = 'cs-cz', _("Czech") # Not yet at 50% translated GERMAN = 'de', _('German') SPANISH = 'es', _('Spanish') ITALIAN = 'it-it', _('Italian') JAPANESE = 'ja', _('Japanese') KOREAN = 'ko-kr', _('Korean') FRENCH = 'fr-fr', _('French') RUSSIAN = 'ru', _('Russian') DUTCH = 'nl-nl', _("Dutch") POLISH = 'pl-pl', _("Polish") UKRAINIAN = 'uk', _('Ukrainian') CHINESE = 'zh-hans', _('Simplified Chinese') user = models.OneToOneField( User, related_name='profile', on_delete=models.CASCADE) main_character = models.OneToOneField( EveCharacter, blank=True, null=True, on_delete=models.SET_NULL) state = models.ForeignKey( State, on_delete=models.SET_DEFAULT, default=get_guest_state_pk) language = models.CharField( _("Language"), max_length=10, choices=Language.choices, blank=True, default='') night_mode = models.BooleanField( _("Night Mode"), blank=True, null=True) theme = models.CharField( _("Theme"), max_length=200, blank=True, help_text="Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps" ) minimize_sidebar = models.BooleanField( _("Minimize Sidebar Menu"), default=False, help_text=_("Keep the sidebar menu minimized") ) class Meta: default_permissions = ('change',) def __str__(self) -> str: return str(self.user) def assign_state(self, state=None, commit=True): if not state: state = State.objects.get_for_user(self.user) if self.state != state: self.state = state if commit: logger.info(f'Updating {self.user} state to {self.state}') self.save(update_fields=['state']) notify( self.user, _(f'State changed to: {state}'), _('Your user\'s state is now: %(state)s') % ({'state': state}), 'info' ) from allianceauth.authentication.signals import state_changed # We need to ensure we get up to date perms here as they will have just changed. # Clear all attribute caches and reload the model that will get passed to the signals! self.refresh_from_db() state_changed.send( sender=self.__class__, user=self.user, state=self.state ) class CharacterOwnership(models.Model): character = models.OneToOneField(EveCharacter, on_delete=models.CASCADE, related_name='character_ownership') owner_hash = models.CharField(max_length=28, unique=True) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships') objects: ClassVar[CharacterOwnershipManager] = CharacterOwnershipManager() class Meta: default_permissions = ('change', 'delete') ordering = ['user', 'character__character_name'] def __str__(self) -> str: return f"{self.user}: {self.character}" class OwnershipRecord(models.Model): character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE, related_name='ownership_records') owner_hash = models.CharField(max_length=28, db_index=True) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ownership_records') created = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created'] def __str__(self) -> str: return f"{self.user}: {self.character} on {self.created}"