Merge branch 'faction' into 'v2.9.x'

Allow assigning factions to states.

See merge request allianceauth/allianceauth!1338
This commit is contained in:
Ariel Rin 2021-10-26 09:16:29 +00:00
commit 527c016c72
19 changed files with 629 additions and 67 deletions

View File

@ -17,7 +17,7 @@ from allianceauth.authentication.models import State, get_guest_state,\
CharacterOwnership, UserProfile, OwnershipRecord CharacterOwnership, UserProfile, OwnershipRecord
from allianceauth.hooks import get_hooks from allianceauth.hooks import get_hooks
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\ from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
EveAllianceInfo EveAllianceInfo, EveFactionInfo
from allianceauth.eveonline.tasks import update_character from allianceauth.eveonline.tasks import update_character
from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \ from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \
AUTHENTICATION_ADMIN_USERS_MAX_CHARS AUTHENTICATION_ADMIN_USERS_MAX_CHARS
@ -159,18 +159,14 @@ def user_main_organization(obj):
""" """
user_obj = obj.user if hasattr(obj, 'user') else obj user_obj = obj.user if hasattr(obj, 'user') else obj
if not user_obj.profile.main_character: if not user_obj.profile.main_character:
result = None result = ''
else: else:
corporation = user_obj.profile.main_character.corporation_name result = user_obj.profile.main_character.corporation_name
if user_obj.profile.main_character.alliance_id: if user_obj.profile.main_character.alliance_id:
result = format_html( result += f'<br>{user_obj.profile.main_character.alliance_name}'
'{}<br>{}', elif user_obj.profile.main_character.faction_name:
corporation, result += f'<br>{user_obj.profile.main_character.faction_name}'
user_obj.profile.main_character.alliance_name return format_html(result)
)
else:
result = corporation
return result
user_main_organization.short_description = 'Corporation / Alliance (Main)' user_main_organization.short_description = 'Corporation / Alliance (Main)'
@ -243,6 +239,38 @@ class MainAllianceFilter(admin.SimpleListFilter):
) )
class MainFactionFilter(admin.SimpleListFilter):
"""Custom filter to filter on factions from mains only
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
title = 'faction'
parameter_name = 'main_faction_id__exact'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(faction_id=None)\
.exclude(userprofile=None)\
.values('faction_id', 'faction_name')\
.distinct()\
.order_by(Lower('faction_name'))
return tuple(
(x['faction_id'], x['faction_name']) for x in qs
)
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs.filter(profile__main_character__faction_id=self.value())
else:
return qs.filter(
user__profile__main_character__faction_id=self.value()
)
def update_main_character_model(modeladmin, request, queryset): def update_main_character_model(modeladmin, request, queryset):
tasks_count = 0 tasks_count = 0
for obj in queryset: for obj in queryset:
@ -342,6 +370,7 @@ class UserAdmin(BaseUserAdmin):
'groups', 'groups',
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
MainFactionFilter,
'is_active', 'is_active',
'date_joined', 'date_joined',
'is_staff', 'is_staff',
@ -426,7 +455,8 @@ class StateAdmin(admin.ModelAdmin):
'public', 'public',
'member_characters', 'member_characters',
'member_corporations', 'member_corporations',
'member_alliances' 'member_alliances',
'member_factions'
), ),
}) })
) )
@ -434,6 +464,7 @@ class StateAdmin(admin.ModelAdmin):
'member_characters', 'member_characters',
'member_corporations', 'member_corporations',
'member_alliances', 'member_alliances',
'member_factions',
'permissions' 'permissions'
] ]
@ -448,6 +479,9 @@ class StateAdmin(admin.ModelAdmin):
elif db_field.name == "member_alliances": elif db_field.name == "member_alliances":
kwargs["queryset"] = EveAllianceInfo.objects.all()\ kwargs["queryset"] = EveAllianceInfo.objects.all()\
.order_by(Lower('alliance_name')) .order_by(Lower('alliance_name'))
elif db_field.name == "member_factions":
kwargs["queryset"] = EveFactionInfo.objects.all()\
.order_by(Lower('faction_name'))
elif db_field.name == "permissions": elif db_field.name == "permissions":
kwargs["queryset"] = Permission.objects.select_related("content_type").all() kwargs["queryset"] = Permission.objects.select_related("content_type").all()
return super().formfield_for_manytomany(db_field, request, **kwargs) return super().formfield_for_manytomany(db_field, request, **kwargs)
@ -485,7 +519,8 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
'user__username', 'user__username',
'character__character_name', 'character__character_name',
'character__corporation_name', 'character__corporation_name',
'character__alliance_name' 'character__alliance_name',
'character__faction_name'
) )
list_filter = ( list_filter = (
MainCorporationsFilter, MainCorporationsFilter,

View File

@ -16,6 +16,8 @@ def available_states_query(character):
query |= Q(member_corporations__corporation_id=character.corporation_id) query |= Q(member_corporations__corporation_id=character.corporation_id)
if character.alliance_id: if character.alliance_id:
query |= Q(member_alliances__alliance_id=character.alliance_id) query |= Q(member_alliances__alliance_id=character.alliance_id)
if character.faction_id:
query |= Q(member_factions__faction_id=character.faction_id)
return query return query

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.13 on 2021-10-12 20:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0015_factions'),
('authentication', '0017_remove_fleetup_permission'),
]
operations = [
migrations.AddField(
model_name='state',
name='member_factions',
field=models.ManyToManyField(blank=True, help_text='Factions to whose members this state is available.', to='eveonline.EveFactionInfo'),
),
]

View File

@ -3,7 +3,7 @@ import logging
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.db import models, transaction from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo, EveFactionInfo
from allianceauth.notifications import notify from allianceauth.notifications import notify
from .managers import CharacterOwnershipManager, StateManager from .managers import CharacterOwnershipManager, StateManager
@ -16,9 +16,14 @@ class State(models.Model):
permissions = models.ManyToManyField(Permission, blank=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.") 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_characters = models.ManyToManyField(EveCharacter, blank=True,
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True, help_text="Corporations to whose members this state is available.") help_text="Characters to which this state is available.")
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True, help_text="Alliances to whose members 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.") public = models.BooleanField(default=False, help_text="Make this state available to any character.")
objects = StateManager() objects = StateManager()

View File

@ -46,6 +46,11 @@ def state_member_alliances_changed(sender, instance, action, *args, **kwargs):
logger.debug(f'State {instance} member alliances changed. Re-evaluating membership.') logger.debug(f'State {instance} member alliances changed. Re-evaluating membership.')
trigger_state_check(instance) trigger_state_check(instance)
@receiver(m2m_changed, sender=State.member_factions.through)
def state_member_factions_changed(sender, instance, action, *args, **kwargs):
if action.startswith('post_'):
logger.debug(f'State {instance} member factions changed. Re-evaluating membership.')
trigger_state_check(instance)
@receiver(post_save, sender=State) @receiver(post_save, sender=State)
def state_saved(sender, instance, *args, **kwargs): def state_saved(sender, instance, *args, **kwargs):

View File

@ -60,6 +60,17 @@
<td class="text-center">{{ main.alliance_name }}</td> <td class="text-center">{{ main.alliance_name }}</td>
<tr> <tr>
</table> </table>
{% elif main.faction_id %}
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.faction_logo_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.faction_name }}</td>
<tr>
</table>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -67,12 +78,22 @@
<p> <p>
<img class="ra-avatar" src="{{ main.portrait_url_64 }}"> <img class="ra-avatar" src="{{ main.portrait_url_64 }}">
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}"> <img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}">
{% if main.alliance_id %}
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}"> <img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}">
{% endif %}
{% if main.faction_id %}
<img class="ra-avatar" src="{{ main.faction_logo_url_64 }}">
{% endif %}
</p> </p>
<p> <p>
<strong>{{ main.character_name }}</strong><br> <strong>{{ main.character_name }}</strong><br>
{{ main.corporation_name }}<br> {{ main.corporation_name }}<br>
{{ main.alliance_name }} {% if main.alliance_id %}
{{ main.alliance_name }}<br>
{% endif %}
{% if main.faction_id %}
{{ main.faction_name }}
{% endif %}
</p> </p>
</div> </div>
{% endwith %} {% endwith %}

View File

@ -9,7 +9,7 @@ from allianceauth.authentication.models import (
CharacterOwnership, State, OwnershipRecord CharacterOwnership, State, OwnershipRecord
) )
from allianceauth.eveonline.models import ( from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo EveCharacter, EveCorporationInfo, EveAllianceInfo, EveFactionInfo
) )
from allianceauth.services.hooks import ServicesHook from allianceauth.services.hooks import ServicesHook
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
@ -20,6 +20,7 @@ from ..admin import (
StateAdmin, StateAdmin,
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
MainFactionFilter,
OwnershipRecordAdmin, OwnershipRecordAdmin,
User, User,
UserAdmin, UserAdmin,
@ -180,6 +181,30 @@ class TestCaseWithTestData(TestCase):
cls.user_3.is_superuser = True cls.user_3.is_superuser = True
cls.user_3.save() cls.user_3.save()
# user 4 - corp and faction, no alliance
cls.character_4 = EveCharacter.objects.create(
character_id=4321,
character_name='Professor X',
corporation_id=5432,
corporation_name="Xavier's School for Gifted Youngsters",
corporation_ticker='MUTNT',
alliance_id = None,
faction_id=999,
faction_name='The X-Men',
)
cls.user_4 = User.objects.create_user(
cls.character_4.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=cls.character_4,
owner_hash='x1' + cls.character_4.character_name,
user=cls.user_4
)
cls.user_4.profile.main_character = cls.character_4
cls.user_4.profile.save()
EveFactionInfo.objects.create(faction_id=999, faction_name='The X-Men')
def make_generic_search_request(ModelClass: type, search_term: str): def make_generic_search_request(ModelClass: type, search_term: str):
User.objects.create_superuser( User.objects.create_superuser(
@ -315,9 +340,13 @@ class TestUserAdmin(TestCaseWithTestData):
self.assertEqual(user_main_organization(self.user_2), expected) self.assertEqual(user_main_organization(self.user_2), expected)
def test_user_main_organization_u3(self): def test_user_main_organization_u3(self):
expected = None expected = ''
self.assertEqual(user_main_organization(self.user_3), expected) self.assertEqual(user_main_organization(self.user_3), expected)
def test_user_main_organization_u4(self):
expected="Xavier's School for Gifted Youngsters<br>The X-Men"
self.assertEqual(user_main_organization(self.user_4), expected)
def test_characters_u1(self): def test_characters_u1(self):
expected = 'Batman, Bruce Wayne' expected = 'Batman, Bruce Wayne'
result = self.modeladmin._characters(self.user_1) result = self.modeladmin._characters(self.user_1)
@ -420,6 +449,7 @@ class TestUserAdmin(TestCaseWithTestData):
expected = [ expected = [
(2002, 'Daily Planet'), (2002, 'Daily Planet'),
(2001, 'Wayne Technologies'), (2001, 'Wayne Technologies'),
(5432, "Xavier's School for Gifted Youngsters"),
] ]
self.assertEqual(filterspec.lookup_choices, expected) self.assertEqual(filterspec.lookup_choices, expected)
@ -463,6 +493,34 @@ class TestUserAdmin(TestCaseWithTestData):
expected = [self.user_1] expected = [self.user_1]
self.assertSetEqual(set(queryset), set(expected)) self.assertSetEqual(set(queryset), set(expected))
def test_filter_main_factions(self):
class UserAdminTest(BaseUserAdmin):
list_filter = (MainFactionFilter,)
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_4
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(999, 'The X-Men'),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get(
'/',
{'main_faction_id__exact': self.character_4.faction_id}
)
request.user = self.user_4
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = [self.user_4]
self.assertSetEqual(set(queryset), set(expected))
def test_change_view_loads_normally(self): def test_change_view_loads_normally(self):
User.objects.create_superuser( User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com' username='superuser', password='secret', email='admin@example.com'

View File

@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\ from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
EveAllianceInfo EveAllianceInfo, EveFactionInfo
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
from esi.errors import IncompleteResponseError from esi.errors import IncompleteResponseError
from esi.models import Token from esi.models import Token
@ -80,13 +80,15 @@ class StateTestCase(TestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True) cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1', AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
corp_name='Test Corp', alliance_name='Test Alliance') corp_name='Test Corp', alliance_name='Test Alliance', faction_id=1337,
faction_name='Permabanned')
cls.guest_state = get_guest_state() cls.guest_state = get_guest_state()
cls.test_character = EveCharacter.objects.get(character_id='1') cls.test_character = EveCharacter.objects.get(character_id='1')
cls.test_corporation = EveCorporationInfo.objects.create(corporation_id='1', corporation_name='Test Corp', cls.test_corporation = EveCorporationInfo.objects.create(corporation_id='1', corporation_name='Test Corp',
corporation_ticker='TEST', member_count=1) corporation_ticker='TEST', member_count=1)
cls.test_alliance = EveAllianceInfo.objects.create(alliance_id='1', alliance_name='Test Alliance', cls.test_alliance = EveAllianceInfo.objects.create(alliance_id='1', alliance_name='Test Alliance',
alliance_ticker='TEST', executor_corp_id='1') alliance_ticker='TEST', executor_corp_id='1')
cls.test_faction = EveFactionInfo.objects.create(faction_id=1337, faction_name='Permabanned')
cls.member_state = State.objects.create( cls.member_state = State.objects.create(
name='Test Member', name='Test Member',
priority=150, priority=150,
@ -122,6 +124,15 @@ class StateTestCase(TestCase):
self._refresh_user() self._refresh_user()
self.assertEqual(self.user.profile.state, self.guest_state) self.assertEqual(self.user.profile.state, self.guest_state)
def test_state_assignment_on_faction_change(self):
self.member_state.member_factions.add(self.test_faction)
self._refresh_user()
self.assertEqual(self.user.profile.state, self.member_state)
self.member_state.member_factions.remove(self.test_faction)
self._refresh_user()
self.assertEqual(self.user.profile.state, self.guest_state)
def test_state_assignment_on_higher_priority_state_creation(self): def test_state_assignment_on_higher_priority_state_creation(self):
self.member_state.member_characters.add(self.test_character) self.member_state.member_characters.add(self.test_character)
higher_state = State.objects.create( higher_state = State.objects.create(

View File

@ -6,6 +6,7 @@ from .providers import ObjectNotFound
from .models import EveAllianceInfo from .models import EveAllianceInfo
from .models import EveCharacter from .models import EveCharacter
from .models import EveCorporationInfo from .models import EveCorporationInfo
from .models import EveFactionInfo
class EveEntityExistsError(forms.ValidationError): class EveEntityExistsError(forms.ValidationError):
@ -34,6 +35,38 @@ class EveEntityForm(forms.ModelForm):
pass pass
def get_faction_choices():
# use a method to avoid making an ESI call when the app loads
# restrict to only those factions a player can join for faction warfare
player_factions = [x for x in EveFactionInfo.provider.get_all_factions() if x['militia_corporation_id']]
return [(x['faction_id'], x['name']) for x in player_factions]
class EveFactionForm(EveEntityForm):
id = forms.ChoiceField(
choices=get_faction_choices,
label="Name"
)
def clean_id(self):
try:
assert self.Meta.model.provider.get_faction(self.cleaned_data['id'])
except (AssertionError, ObjectNotFound):
raise EveEntityNotFoundError('faction', self.cleaned_data['id'])
if self.Meta.model.objects.filter(faction_id=self.cleaned_data['id']).exists():
raise EveEntityExistsError('faction', self.cleaned_data['id'])
return self.cleaned_data['id']
def save(self, commit=True):
faction = self.Meta.model.provider.get_faction(self.cleaned_data['id'])
return self.Meta.model.objects.create(faction_id=faction.id, faction_name=faction.name)
class Meta:
fields = ['id']
model = EveFactionInfo
class EveCharacterForm(EveEntityForm): class EveCharacterForm(EveEntityForm):
entity_type_name = 'character' entity_type_name = 'character'
@ -94,6 +127,21 @@ class EveAllianceForm(EveEntityForm):
model = EveAllianceInfo model = EveAllianceInfo
@admin.register(EveFactionInfo)
class EveFactionInfoAdmin(admin.ModelAdmin):
search_fields = ['faction_name']
list_display = ('faction_name',)
ordering = ('faction_name',)
def has_change_permission(self, request, obj=None):
return False
def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk:
return EveFactionForm
return super().get_form(request, obj=obj, **kwargs)
@admin.register(EveCorporationInfo) @admin.register(EveCorporationInfo)
class EveCorporationInfoAdmin(admin.ModelAdmin): class EveCorporationInfoAdmin(admin.ModelAdmin):
search_fields = ['corporation_name'] search_fields = ['corporation_name']
@ -135,7 +183,7 @@ class EveCharacterAdmin(admin.ModelAdmin):
'character_ownership__user__username' 'character_ownership__user__username'
] ]
list_display = ( list_display = (
'character_name', 'corporation_name', 'alliance_name', 'user', 'main_character' 'character_name', 'corporation_name', 'alliance_name', 'faction_name', 'user', 'main_character'
) )
list_select_related = ( list_select_related = (
'character_ownership', 'character_ownership__user__profile__main_character' 'character_ownership', 'character_ownership__user__profile__main_character'
@ -143,6 +191,7 @@ class EveCharacterAdmin(admin.ModelAdmin):
list_filter = ( list_filter = (
'corporation_name', 'corporation_name',
'alliance_name', 'alliance_name',
'faction_name',
( (
'character_ownership__user__profile__main_character', 'character_ownership__user__profile__main_character',
admin.RelatedOnlyFieldListFilter admin.RelatedOnlyFieldListFilter

View File

@ -27,6 +27,8 @@ class EveCharacterManager(models.Manager):
alliance_id=character.alliance.id, alliance_id=character.alliance.id,
alliance_name=character.alliance.name, alliance_name=character.alliance.name,
alliance_ticker=getattr(character.alliance, 'ticker', None), alliance_ticker=getattr(character.alliance, 'ticker', None),
faction_id=character.faction.id,
faction_name=character.faction.name
) )
def update_character(self, character_id): def update_character(self, character_id):

View File

@ -0,0 +1,35 @@
# Generated by Django 3.1.13 on 2021-10-12 01:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0014_auto_20210105_1413'),
]
operations = [
migrations.CreateModel(
name='EveFactionInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('faction_id', models.PositiveIntegerField(db_index=True, unique=True)),
('faction_name', models.CharField(max_length=254, unique=True)),
],
),
migrations.AddField(
model_name='evecharacter',
name='faction_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='evecharacter',
name='faction_name',
field=models.CharField(blank=True, default='', max_length=254, null=True),
),
migrations.AddIndex(
model_name='evecharacter',
index=models.Index(fields=['faction_id'], name='eveonline_e_faction_d5274e_idx'),
),
]

View File

@ -10,6 +10,47 @@ from .evelinks import eveimageserver
_DEFAULT_IMAGE_SIZE = 32 _DEFAULT_IMAGE_SIZE = 32
class EveFactionInfo(models.Model):
faction_id = models.PositiveIntegerField(unique=True, db_index=True)
faction_name = models.CharField(max_length=254, unique=True)
provider = providers.provider
def __str__(self):
return self.faction_name
@staticmethod
def generic_logo_url(
faction_id: int, size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""image URL for the given faction ID"""
return eveimageserver.corporation_logo_url(faction_id, size)
def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
"""image URL of this faction"""
return self.generic_logo_url(self.faction_id, size)
@property
def logo_url_32(self) -> str:
"""image URL for this faction"""
return self.logo_url(32)
@property
def logo_url_64(self) -> str:
"""image URL for this faction"""
return self.logo_url(64)
@property
def logo_url_128(self) -> str:
"""image URL for this faction"""
return self.logo_url(128)
@property
def logo_url_256(self) -> str:
"""image URL for this faction"""
return self.logo_url(256)
class EveAllianceInfo(models.Model): class EveAllianceInfo(models.Model):
alliance_id = models.PositiveIntegerField(unique=True) alliance_id = models.PositiveIntegerField(unique=True)
alliance_name = models.CharField(max_length=254, unique=True) alliance_name = models.CharField(max_length=254, unique=True)
@ -149,6 +190,8 @@ class EveCharacter(models.Model):
alliance_id = models.PositiveIntegerField(blank=True, null=True, default=None) alliance_id = models.PositiveIntegerField(blank=True, null=True, default=None)
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='') alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='') alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='')
faction_id = models.PositiveIntegerField(blank=True, null=True, default=None)
faction_name = models.CharField(max_length=254, blank=True, null=True, default='')
objects = EveCharacterManager() objects = EveCharacterManager()
provider = EveCharacterProviderManager() provider = EveCharacterProviderManager()
@ -159,6 +202,7 @@ class EveCharacter(models.Model):
models.Index(fields=['alliance_id',]), models.Index(fields=['alliance_id',]),
models.Index(fields=['corporation_name',]), models.Index(fields=['corporation_name',]),
models.Index(fields=['alliance_name',]), models.Index(fields=['alliance_name',]),
models.Index(fields=['faction_id',]),
] ]
@property @property
@ -181,6 +225,17 @@ class EveCharacter(models.Model):
""" """
return EveCorporationInfo.objects.get(corporation_id=self.corporation_id) return EveCorporationInfo.objects.get(corporation_id=self.corporation_id)
@property
def faction(self) -> Union[EveFactionInfo, None]:
"""
Pseudo foreign key from faction_id to EveFactionInfo
:raises: EveFactionInfo.DoesNotExist
:return: EveFactionInfo
"""
if self.faction_id is None:
return None
return EveFactionInfo.objects.get(faction_id=self.faction_id)
def update_character(self, character: providers.Character = None): def update_character(self, character: providers.Character = None):
if character is None: if character is None:
character = self.provider.get_character(self.character_id) character = self.provider.get_character(self.character_id)
@ -191,6 +246,8 @@ class EveCharacter(models.Model):
self.alliance_id = character.alliance.id self.alliance_id = character.alliance.id
self.alliance_name = character.alliance.name self.alliance_name = character.alliance.name
self.alliance_ticker = getattr(character.alliance, 'ticker', None) self.alliance_ticker = getattr(character.alliance, 'ticker', None)
self.faction_id = character.faction.id
self.faction_name = character.faction.name
self.save() self.save()
return self return self
@ -278,3 +335,31 @@ class EveCharacter(models.Model):
def alliance_logo_url_256(self) -> str: def alliance_logo_url_256(self) -> str:
"""image URL for alliance of this character or empty string""" """image URL for alliance of this character or empty string"""
return self.alliance_logo_url(256) return self.alliance_logo_url(256)
def faction_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
"""image URL for alliance of this character or empty string"""
if self.faction_id:
return EveFactionInfo.generic_logo_url(self.faction_id, size)
else:
return ''
@property
def faction_logo_url_32(self) -> str:
"""image URL for alliance of this character or empty string"""
return self.faction_logo_url(32)
@property
def faction_logo_url_64(self) -> str:
"""image URL for alliance of this character or empty string"""
return self.faction_logo_url(64)
@property
def faction_logo_url_128(self) -> str:
"""image URL for alliance of this character or empty string"""
return self.faction_logo_url(128)
@property
def faction_logo_url_256(self) -> str:
"""image URL for alliance of this character or empty string"""
return self.faction_logo_url(256)

View File

@ -22,6 +22,7 @@ get_corporations_corporation_id
get_characters_character_id get_characters_character_id
get_universe_types_type_id get_universe_types_type_id
post_character_affiliation post_character_affiliation
get_universe_factions
""" """
@ -38,7 +39,8 @@ class ObjectNotFound(Exception):
class Entity: class Entity:
def __init__(self, id=None, name=None): def __init__(self, id=None, name=None, **kwargs):
super().__init__(**kwargs)
self.id = id self.id = id
self.name = name self.name = name
@ -55,15 +57,11 @@ class Entity:
return self.id == other.id return self.id == other.id
class Corporation(Entity): class AllianceMixin:
def __init__(self, ticker=None, ceo_id=None, members=None, alliance_id=None, **kwargs): def __init__(self, alliance_id=None, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.ticker = ticker
self.ceo_id = ceo_id
self.members = members
self.alliance_id = alliance_id self.alliance_id = alliance_id
self._alliance = None self._alliance = None
self._ceo = None
@property @property
def alliance(self): def alliance(self):
@ -73,6 +71,30 @@ class Corporation(Entity):
return self._alliance return self._alliance
return Entity(None, None) return Entity(None, None)
class FactionMixin:
def __init__(self, faction_id=None, **kwargs):
super().__init__(**kwargs)
self.faction_id = faction_id
self._faction = None
@property
def faction(self):
if self.faction_id:
if not self._faction:
self._faction = provider.get_faction(self.faction_id)
return self._faction
return Entity(None, None)
class Corporation(Entity, AllianceMixin, FactionMixin):
def __init__(self, ticker=None, ceo_id=None, members=None, **kwargs):
super().__init__(**kwargs)
self.ticker = ticker
self.ceo_id = ceo_id
self.members = members
self._ceo = None
@property @property
def ceo(self): def ceo(self):
if not self._ceo: if not self._ceo:
@ -80,7 +102,7 @@ class Corporation(Entity):
return self._ceo return self._ceo
class Alliance(Entity): class Alliance(Entity, FactionMixin):
def __init__(self, ticker=None, corp_ids=None, executor_corp_id=None, **kwargs): def __init__(self, ticker=None, corp_ids=None, executor_corp_id=None, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.ticker = ticker self.ticker = ticker
@ -106,13 +128,11 @@ class Alliance(Entity):
return Entity(None, None) return Entity(None, None)
class Character(Entity): class Character(Entity, AllianceMixin, FactionMixin):
def __init__(self, corp_id=None, alliance_id=None, **kwargs): def __init__(self, corp_id=None, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.corp_id = corp_id self.corp_id = corp_id
self.alliance_id = alliance_id
self._corp = None self._corp = None
self._alliance = None
@property @property
def corp(self): def corp(self):
@ -120,12 +140,6 @@ class Character(Entity):
self._corp = provider.get_corp(self.corp_id) self._corp = provider.get_corp(self.corp_id)
return self._corp return self._corp
@property
def alliance(self):
if self.alliance_id:
return self.corp.alliance
return Entity(None, None)
class ItemType(Entity): class ItemType(Entity):
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -179,6 +193,7 @@ class EveSwaggerProvider(EveProvider):
self._token = token self._token = token
self.adapter = adapter or self self.adapter = adapter or self
self._faction_list = None # what are the odds this will change? could cache forever!
@property @property
def client(self): def client(self):
@ -201,6 +216,7 @@ class EveSwaggerProvider(EveProvider):
ticker=data['ticker'], ticker=data['ticker'],
corp_ids=corps, corp_ids=corps,
executor_corp_id=data['executor_corporation_id'] if 'executor_corporation_id' in data else None, executor_corp_id=data['executor_corporation_id'] if 'executor_corporation_id' in data else None,
faction_id=data['faction_id'] if 'faction_id' in data else None,
) )
return model return model
except HTTPNotFound: except HTTPNotFound:
@ -216,6 +232,7 @@ class EveSwaggerProvider(EveProvider):
ceo_id=data['ceo_id'], ceo_id=data['ceo_id'],
members=data['member_count'], members=data['member_count'],
alliance_id=data['alliance_id'] if 'alliance_id' in data else None, alliance_id=data['alliance_id'] if 'alliance_id' in data else None,
faction_id=data['faction_id'] if 'faction_id' in data else None,
) )
return model return model
except HTTPNotFound: except HTTPNotFound:
@ -231,11 +248,30 @@ class EveSwaggerProvider(EveProvider):
name=data['name'], name=data['name'],
corp_id=affiliation['corporation_id'], corp_id=affiliation['corporation_id'],
alliance_id=affiliation['alliance_id'] if 'alliance_id' in affiliation else None, alliance_id=affiliation['alliance_id'] if 'alliance_id' in affiliation else None,
faction_id=affiliation['faction_id'] if 'faction_id' in affiliation else None,
) )
return model return model
except (HTTPNotFound, HTTPUnprocessableEntity): except (HTTPNotFound, HTTPUnprocessableEntity):
raise ObjectNotFound(character_id, 'character') raise ObjectNotFound(character_id, 'character')
def get_all_factions(self):
if not self._faction_list:
self._faction_list = self.client.Universe.get_universe_factions().result()
return self._faction_list
def get_faction(self, faction_id):
faction_id=int(faction_id)
try:
if not self._faction_list:
_ = self.get_all_factions()
for f in self._faction_list:
if f['faction_id'] == faction_id:
return Entity(id=f['faction_id'], name=f['name'])
else:
raise KeyError()
except (HTTPNotFound, HTTPUnprocessableEntity, KeyError):
raise ObjectNotFound(faction_id, 'faction')
def get_itemtype(self, type_id): def get_itemtype(self, type_id):
try: try:
data = self.client.Universe.get_universe_types_type_id(type_id=type_id).result() data = self.client.Universe.get_universe_types_type_id(type_id=type_id).result()

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,7 @@ from unittest.mock import Mock, patch
from django.test import TestCase from django.test import TestCase
from ..models import ( from ..models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo EveCharacter, EveCorporationInfo, EveAllianceInfo, EveFactionInfo
) )
from ..providers import Alliance, Corporation, Character from ..providers import Alliance, Corporation, Character
from ..evelinks import eveimageserver from ..evelinks import eveimageserver
@ -126,6 +126,66 @@ class EveCharacterTestCase(TestCase):
self.assertIsNone(character.alliance) self.assertIsNone(character.alliance)
def test_faction_prop(self):
"""
Test that the correct faction is returned by the alliance property
"""
character = EveCharacter.objects.create(
character_id=1234,
character_name='character.name',
corporation_id=2345,
corporation_name='character.corp.name',
corporation_ticker='cc1',
alliance_id=3456,
alliance_name='character.alliance.name',
faction_id=1337,
faction_name='character.faction.name'
)
expected = EveFactionInfo.objects.create(faction_id=1337, faction_name='faction.name')
incorrect = EveFactionInfo.objects.create(faction_id=8008, faction_name='faction.badname')
self.assertEqual(character.faction, expected)
self.assertNotEqual(character.faction, incorrect)
def test_faction_prop_exception(self):
"""
Check that an exception is raised when the expected
object is not in the database
"""
character = EveCharacter.objects.create(
character_id=1234,
character_name='character.name',
corporation_id=2345,
corporation_name='character.corp.name',
corporation_ticker='cc1',
alliance_id=3456,
alliance_name='character.alliance.name',
faction_id=1337,
faction_name='character.faction.name'
)
with self.assertRaises(EveFactionInfo.DoesNotExist):
character.faction
def test_faction_prop_none(self):
"""
Check that None is returned when the character has no alliance
"""
character = EveCharacter.objects.create(
character_id=1234,
character_name='character.name',
corporation_id=2345,
corporation_name='character.corp.name',
corporation_ticker='cc1',
alliance_id=None,
alliance_name=None,
faction_id=None,
faction_name=None,
)
self.assertIsNone(character.faction)
@patch('allianceauth.eveonline.providers.provider') @patch('allianceauth.eveonline.providers.provider')
def test_update_character(self, mock_provider): def test_update_character(self, mock_provider):
mock_provider.get_corp.return_value = Corporation( mock_provider.get_corp.return_value = Corporation(
@ -144,13 +204,17 @@ class EveCharacterTestCase(TestCase):
corporation_ticker='DC1', corporation_ticker='DC1',
alliance_id=3001, alliance_id=3001,
alliance_name='Dummy Alliance 1', alliance_name='Dummy Alliance 1',
faction_id=1337,
faction_name='Dummy Faction 1',
) )
my_updated_character = Character( my_updated_character = Character(
name='Bruce X. Wayne', name='Bruce X. Wayne',
corp_id=2002 corp_id=2002,
faction_id=None,
) )
my_character.update_character(my_updated_character) my_character.update_character(my_updated_character)
self.assertEqual(my_character.character_name, 'Bruce X. Wayne') self.assertEqual(my_character.character_name, 'Bruce X. Wayne')
self.assertFalse(my_character.faction_id)
# todo: add test cases not yet covered, e.g. with alliance # todo: add test cases not yet covered, e.g. with alliance

View File

@ -10,6 +10,8 @@ from . import set_logger
from ..providers import ( from ..providers import (
ObjectNotFound, ObjectNotFound,
Entity, Entity,
AllianceMixin,
FactionMixin,
Character, Character,
Corporation, Corporation,
Alliance, Alliance,
@ -18,7 +20,6 @@ from ..providers import (
EveSwaggerProvider EveSwaggerProvider
) )
MODULE_PATH = 'allianceauth.eveonline.providers' MODULE_PATH = 'allianceauth.eveonline.providers'
SWAGGER_OLD_SPEC_PATH = os.path.join(os.path.dirname( SWAGGER_OLD_SPEC_PATH = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'swagger_old.json' os.path.abspath(__file__)), 'swagger_old.json'
@ -80,6 +81,72 @@ class TestEntity(TestCase):
# bug: missing _neq_ in Equity to compliment _eq_ # bug: missing _neq_ in Equity to compliment _eq_
class TestAllianceMixin(TestCase):
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
def test_alliance_defined(self, mock_provider_get_alliance):
my_alliance = Alliance(
id=3001,
name='Dummy Alliance',
ticker='Dummy',
corp_ids=[2001, 2002, 2003],
executor_corp_id=2001
)
mock_provider_get_alliance.return_value = my_alliance
x = AllianceMixin(alliance_id=3001)
self.assertEqual(
x.alliance,
my_alliance
)
self.assertEqual(
x.alliance,
my_alliance
)
# should fetch alliance once only
self.assertEqual(mock_provider_get_alliance.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
def test_alliance_not_defined(self, mock_provider_get_alliance):
mock_provider_get_alliance.return_value = None
x = AllianceMixin()
self.assertEqual(
x.alliance,
Entity(None, None)
)
self.assertEqual(mock_provider_get_alliance.call_count, 0)
class TestFactionMixin(TestCase):
@patch(MODULE_PATH + '.EveSwaggerProvider.get_faction')
def test_faction_defined(self, mock_provider_get_faction):
my_faction = Entity(id=1337, name='Permabanned')
mock_provider_get_faction.return_value = my_faction
x = FactionMixin(faction_id=3001)
self.assertEqual(
x.faction,
my_faction
)
self.assertEqual(
x.faction,
my_faction
)
# should fetch alliance once only
self.assertEqual(mock_provider_get_faction.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
def test_faction_not_defined(self, mock_provider_get_faction):
mock_provider_get_faction.return_value = None
x = FactionMixin()
self.assertEqual(
x.faction,
Entity(None, None)
)
self.assertEqual(mock_provider_get_faction.call_count, 0)
class TestCorporation(TestCase): class TestCorporation(TestCase):
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance') @patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
@ -114,6 +181,7 @@ class TestCorporation(TestCase):
x.alliance, x.alliance,
Entity(None, None) Entity(None, None)
) )
self.assertEqual(mock_provider_get_alliance.call_count, 0)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_character') @patch(MODULE_PATH + '.EveSwaggerProvider.get_character')
def test_ceo(self, mock_provider_get_character): def test_ceo(self, mock_provider_get_character):
@ -142,6 +210,26 @@ class TestCorporation(TestCase):
# bug in ceo(): will try to fetch character even if ceo_id is None # bug in ceo(): will try to fetch character even if ceo_id is None
@patch(MODULE_PATH + '.EveSwaggerProvider.get_faction')
def test_faction_defined(self, mock_provider_get_faction):
my_faction = Entity(id=1337, name='Permabanned')
mock_provider_get_faction.return_value = my_faction
# fetch from provider if not defined
x = Corporation(faction_id=1337)
self.assertEqual(x.faction, my_faction)
# return existing if defined
mock_provider_get_faction.return_value = None
self.assertEqual(x.faction, my_faction)
self.assertEqual(mock_provider_get_faction.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_faction')
def test_faction_undefined(self, mock_provider_get_faction):
x = Corporation()
self.assertEqual(x.faction, Entity())
self.assertEqual(mock_provider_get_faction.call_count, 0)
class TestAlliance(TestCase): class TestAlliance(TestCase):
@ -151,7 +239,8 @@ class TestAlliance(TestCase):
name='Dummy Alliance', name='Dummy Alliance',
ticker='Dummy', ticker='Dummy',
corp_ids=[2001, 2002, 2003], corp_ids=[2001, 2002, 2003],
executor_corp_id=2001 executor_corp_id=2001,
faction_id=1337
) )
@staticmethod @staticmethod
@ -232,6 +321,25 @@ class TestAlliance(TestCase):
Entity(None, None), Entity(None, None),
) )
@patch(MODULE_PATH + '.EveSwaggerProvider.get_faction')
def test_faction_defined(self, mock_provider_get_faction):
my_faction = Entity(id=1337, name='Permabanned')
mock_provider_get_faction.return_value = my_faction
# fetch from provider if not defined
self.assertEqual(self.my_alliance.faction, my_faction)
# return existing if defined
mock_provider_get_faction.return_value = None
self.assertEqual(self.my_alliance.faction, my_faction)
self.assertEqual(mock_provider_get_faction.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_faction')
def test_faction_undefined(self, mock_provider_get_faction):
self.my_alliance.faction_id = None
self.assertEqual(self.my_alliance.faction, Entity())
self.assertEqual(mock_provider_get_faction.call_count, 0)
class TestCharacter(TestCase): class TestCharacter(TestCase):
@ -240,7 +348,8 @@ class TestCharacter(TestCase):
id=1001, id=1001,
name='Bruce Wayne', name='Bruce Wayne',
corp_id=2001, corp_id=2001,
alliance_id=3001 alliance_id=3001,
faction_id=1337,
) )
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp') @patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
@ -282,12 +391,32 @@ class TestCharacter(TestCase):
self.assertEqual(self.my_character.alliance, my_alliance) self.assertEqual(self.my_character.alliance, my_alliance)
# should call the provider one time only # should call the provider one time only
self.assertEqual(mock_provider_get_corp.call_count, 1)
self.assertEqual(mock_provider_get_alliance.call_count, 1) self.assertEqual(mock_provider_get_alliance.call_count, 1)
def test_alliance_has_none(self): @patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
def test_alliance_has_none(self, mock_provider_get_alliance):
self.my_character.alliance_id = None self.my_character.alliance_id = None
self.assertEqual(self.my_character.alliance, Entity(None, None)) self.assertEqual(self.my_character.alliance, Entity(None, None))
self.assertEqual(mock_provider_get_alliance.call_count, 0)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_faction')
def test_faction_defined(self, mock_provider_get_faction):
my_faction = Entity(id=1337, name='Permabanned')
mock_provider_get_faction.return_value = my_faction
# fetch from provider if not defined
self.assertEqual(self.my_character.faction, my_faction)
# return existing if defined
mock_provider_get_faction.return_value = None
self.assertEqual(self.my_character.faction, my_faction)
self.assertEqual(mock_provider_get_faction.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_faction')
def test_faction_undefined(self, mock_provider_get_faction):
self.my_character.faction_id = None
self.assertEqual(self.my_character.faction, Entity())
self.assertEqual(mock_provider_get_faction.call_count, 0)
class TestItemType(TestCase): class TestItemType(TestCase):
@ -597,7 +726,7 @@ class TestEveSwaggerProvider(TestCase):
def test_user_agent_header(self): def test_user_agent_header(self):
my_provider = EveSwaggerProvider() my_provider = EveSwaggerProvider()
my_client = my_provider.client my_client = my_provider.client
operation = my_client.Status.get_status() operation = my_client.Universe.get_universe_factions()
self.assertEqual( self.assertEqual(
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0' operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0'
) )

View File

@ -207,7 +207,7 @@ class TestColumnRendering(TestDataMixin, TestCase):
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_user_main_organization_u3(self): def test_user_main_organization_u3(self):
expected = None expected = ''
result = user_main_organization(self.user_3.discord) result = user_main_organization(self.user_3.discord)
self.assertEqual(result, expected) self.assertEqual(result, expected)

View File

@ -142,7 +142,7 @@ class AuthUtils:
@classmethod @classmethod
def add_main_character(cls, user, name, character_id, corp_id=2345, corp_name='', corp_ticker='', alliance_id=None, def add_main_character(cls, user, name, character_id, corp_id=2345, corp_name='', corp_ticker='', alliance_id=None,
alliance_name=''): alliance_name='', faction_id=None, faction_name=''):
if alliance_id: if alliance_id:
try: try:
alliance_id = int(alliance_id) alliance_id = int(alliance_id)
@ -157,6 +157,8 @@ class AuthUtils:
corporation_ticker=corp_ticker, corporation_ticker=corp_ticker,
alliance_id=alliance_id, alliance_id=alliance_id,
alliance_name=alliance_name, alliance_name=alliance_name,
faction_id=faction_id,
faction_name=faction_name
) )
UserProfile.objects.update_or_create(user=user, defaults={'main_character': char}) UserProfile.objects.update_or_create(user=user, defaults={'main_character': char})

View File

@ -42,6 +42,10 @@ This lets you select which Corporations the state is available to. Corporations
This lets you select which Alliances the state is available to. Alliances can be added by selecting the green plus icon. This lets you select which Alliances the state is available to. Alliances can be added by selecting the green plus icon.
### Member Factions
This lets you select which factions the state is available to. Factions can be added by selecting the green plus icon, and are limited to those which can be enlisted in for faction warfare.
## Determining a User's State ## Determining a User's State
States are mutually exclusive, meaning a user can only be in one at a time. States are mutually exclusive, meaning a user can only be in one at a time.