diff --git a/apps/gcd/models/character.py b/apps/gcd/models/character.py index 5c3dc0c2..964bb16c 100644 --- a/apps/gcd/models/character.py +++ b/apps/gcd/models/character.py @@ -269,6 +269,16 @@ def active_universe_appearances(self): flat=True).distinct()) return Universe.objects.filter(id__in=universes) + def active_universe_appearances_with_origin(self, origin_universe): + from .story import Story + appearances = Story.objects.filter( + appearing_characters__character__character=self, + appearing_characters__universe_id=origin_universe, + deleted=False) + universes = appearances.values_list('universe', + flat=True).distinct() + return Universe.objects.filter(id__in=universes) + def translated_from(self): try: relation = self.from_related_character.get(relation_type__id=1) @@ -810,13 +820,21 @@ class Meta: def __init__(self, *args, **kwargs): self.character = kwargs.pop('character') + self.universe_id = kwargs.pop('universe_id', None) super(UniverseCharacterTable, self).__init__(*args, **kwargs) def render_issue_count(self, record): - url = urlresolvers.reverse( - 'character_issues_character', - kwargs={'character_id': self.character.id, - 'character_with_id': record.id}) + if self.universe_id is not None: + url = urlresolvers.reverse( + 'character_origin_universe_issues_per_character', + kwargs={'character_id': self.character.id, + 'character_with_id': record.id, + 'universe_id': self.universe_id}) + else: + url = urlresolvers.reverse( + 'character_issues_character', + kwargs={'character_id': self.character.id, + 'character_with_id': record.id}) return mark_safe('%s' % (url, record.issue_count)) diff --git a/apps/gcd/models/creator.py b/apps/gcd/models/creator.py index d5d26ac9..e9827057 100644 --- a/apps/gcd/models/creator.py +++ b/apps/gcd/models/creator.py @@ -1142,12 +1142,28 @@ def render_place_of_birth(self, record): class CreatorTable(CreatorBaseTable): role = tables.Column(accessor='script', orderable=False) + def __init__(self, *args, **kwargs): + self.resolve_name = kwargs.pop('resolve_name', None) + obj = kwargs.pop('object', None) + if self.resolve_name and obj: + setattr(self, self.resolve_name, obj) + self.universe_id = kwargs.pop('universe_id', None) + super().__init__(*args, **kwargs) + def render_issue_count(self, record): - url = urlresolvers.reverse( - '%s_creator_issues' % self.resolve_name, + # Use character_origin_universe URL if universe_id is present + if self.resolve_name == 'character' and self.universe_id is not None: + url = urlresolvers.reverse( + 'character_origin_universe_creator_issues', kwargs={'creator_id': record.id, - '%s_id' % self.resolve_name: - getattr(self, self.resolve_name).id}) + 'character_id': getattr(self, 'character').id, + 'universe_id': self.universe_id}) + else: + url = urlresolvers.reverse( + '%s_creator_issues' % self.resolve_name, + kwargs={'creator_id': record.id, + '%s_id' % self.resolve_name: + getattr(self, self.resolve_name).id}) return mark_safe('%s' % (url, record.issue_count)) def render_role(self, record): @@ -1166,11 +1182,6 @@ def render_role(self, record): class CreatorPortraitTable(CreatorTable): - def __init__(self, *args, **kwargs): - self.resolve_name = kwargs.pop('resolve_name') - setattr(self, self.resolve_name, kwargs.pop('object')) - super(CreatorPortraitTable, self).__init__(*args, **kwargs) - class Meta: model = Creator fields = ('creator', 'first_credit', 'issue_count', 'role') @@ -1199,8 +1210,9 @@ class CreatorNameTable(CreatorTable): detail_name = tables.Column(accessor='name', verbose_name='Used Name') def __init__(self, *args, **kwargs): - super(CreatorTable, self).__init__(*args, **kwargs) - self.resolve_name = 'creator_name' + super().__init__(*args, **kwargs) + if not self.resolve_name: + self.resolve_name = 'creator_name' def order_creator(self, query_set, is_descending): direction = '-' if is_descending else '' @@ -1213,11 +1225,19 @@ def order_detail_name(self, query_set, is_descending): return (query_set, True) def render_issue_count(self, record): - url = urlresolvers.reverse( - '%s_creator_name_issues' % self.resolve_name, + # Use character_origin_universe URL if universe_id is present + if self.resolve_name == 'character' and self.universe_id is not None: + url = urlresolvers.reverse( + 'character_origin_universe_creator_name_issues', kwargs={'creator_name_id': record.id, - '%s_id' % self.resolve_name: - getattr(self, self.resolve_name).id}) + 'character_id': getattr(self, 'character').id, + 'universe_id': self.universe_id}) + else: + url = urlresolvers.reverse( + '%s_creator_name_issues' % self.resolve_name, + kwargs={'creator_name_id': record.id, + '%s_id' % self.resolve_name: + getattr(self, self.resolve_name).id}) return mark_safe('%s' % (url, record.issue_count)) def render_creator(self, record): @@ -1229,22 +1249,12 @@ def value_creator(self, record): class GenericCreatorTable(CreatorTable): - def __init__(self, *args, **kwargs): - self.resolve_name = kwargs.pop('resolve_name') - setattr(self, self.resolve_name, kwargs.pop('object')) - super(CreatorTable, self).__init__(*args, **kwargs) - class Meta: model = Creator fields = ('creator', 'first_credit', 'issue_count', 'role') class GenericCreatorNameTable(CreatorNameTable): - def __init__(self, *args, **kwargs): - self.resolve_name = kwargs.pop('resolve_name') - setattr(self, self.resolve_name, kwargs.pop('object')) - super(CreatorNameTable, self).__init__(*args, **kwargs) - class Meta: model = CreatorNameDetail fields = ('creator', 'detail_name', 'first_credit', 'issue_count', diff --git a/apps/gcd/models/feature.py b/apps/gcd/models/feature.py index 68d8edfe..fd56414a 100644 --- a/apps/gcd/models/feature.py +++ b/apps/gcd/models/feature.py @@ -358,6 +358,7 @@ class Meta: def __init__(self, *args, **kwargs): self.character = kwargs.pop('character') + self.universe_id = kwargs.pop('universe_id', None) self.resolve_name = 'character' super(CharacterFeatureTable, self).__init__(*args, **kwargs) @@ -375,11 +376,18 @@ def render_first_appearance(self, value): return value def render_issue_count(self, record): - url = urlresolvers.reverse( - '%s_issues_per_feature' % self.resolve_name, - kwargs={'feature_id': record.id, - '%s_id' % self.resolve_name: - getattr(self, self.resolve_name).id}) + if self.resolve_name == 'character' and self.universe_id is not None: + url = urlresolvers.reverse( + 'character_origin_universe_issues_per_feature', + kwargs={'feature_id': record.id, + 'character_id': self.character.id, + 'universe_id': self.universe_id}) + else: + url = urlresolvers.reverse( + '%s_issues_per_feature' % self.resolve_name, + kwargs={'feature_id': record.id, + '%s_id' % self.resolve_name: + getattr(self, self.resolve_name).id}) return mark_safe('%s' % (url, record.issue_count)) diff --git a/apps/gcd/models/series.py b/apps/gcd/models/series.py index 2e8d6725..d0051482 100644 --- a/apps/gcd/models/series.py +++ b/apps/gcd/models/series.py @@ -580,6 +580,7 @@ class CharacterSeriesTable(SeriesPublisherTable): def __init__(self, *args, **kwargs): self.character = kwargs.pop('character') + self.universe_id = kwargs.pop('universe_id', None) self.resolve_name = 'character' super(SeriesTable, self).__init__(*args, **kwargs) @@ -600,11 +601,18 @@ def render_first_appearance(self, value): return value[:4] def render_appearances_count(self, record): - url = urlresolvers.reverse( - '%s_issues_per_series' % self.resolve_name, - kwargs={'series_id': record.id, - '%s_id' % self.resolve_name: - getattr(self, self.resolve_name).id}) + if self.resolve_name == 'character' and self.universe_id is not None: + url = urlresolvers.reverse( + 'character_origin_universe_issues_per_series', + kwargs={'series_id': record.id, + 'character_id': self.character.id, + 'universe_id': self.universe_id}) + else: + url = urlresolvers.reverse( + '%s_issues_per_series' % self.resolve_name, + kwargs={'series_id': record.id, + '%s_id' % self.resolve_name: + getattr(self, self.resolve_name).id}) return mark_safe('%s' % (url, record.appearances_count)) diff --git a/apps/gcd/urls.py b/apps/gcd/urls.py index 9180ca82..adf2ab41 100644 --- a/apps/gcd/urls.py +++ b/apps/gcd/urls.py @@ -464,8 +464,6 @@ gcd_views.details.character_issues, name='character_issues'), path('character//features/', gcd_views.details.character_features, name='character_features'), - re_path(r'^character/(?P\d+)/issues/character_universe/(?P-?\d+)/$', - gcd_views.details.character_issues, name='character_issues_per_universe'), re_path(r'^character/(?P\d+)/issues/character_universe/(?P-?\d+)/story_universe/(?P-?\d+)/$', gcd_views.details.character_issues, name='character_story_issues_per_universe'), re_path(r'^character/(?P\d+)/issues/story_universe/(?P-?\d+)/$', @@ -502,6 +500,40 @@ gcd_views.search.character_by_name, name='character_by_name'), path('character_relation//', gcd_views.details.character_relation, name='show_character_relation'), + # Character Universe Origin Page routes + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/$', + gcd_views.details.character_origin_universe, name='character_origin_universe'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/issues/$', + gcd_views.details.character_issues, name='character_origin_universe_issues'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/creators/$', + gcd_views.details.character_creators, name='character_origin_universe_creators'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/creator_names/$', + gcd_views.details.character_creators, {'creator_names': True}, + name='character_origin_universe_creator_names'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/creator/(?P\d+)/issues/$', + gcd_views.details.creator_issues, name='character_origin_universe_creator_issues'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/creator_name/(?P\d+)/issues/$', + gcd_views.details.creator_name_issues, name='character_origin_universe_creator_name_issues'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/covers/$', + gcd_views.details.character_covers, name='character_origin_universe_covers'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/series/$', + gcd_views.details.character_series, name='character_origin_universe_series'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/series/(?P\d+)/issues/$', + gcd_views.details.character_issues_series, name='character_origin_universe_issues_per_series'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/features/$', + gcd_views.details.character_features, name='character_origin_universe_features'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/feature/(?P\d+)/issues/$', + gcd_views.details.character_issues_feature, name='character_origin_universe_issues_per_feature'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/characters/$', + gcd_views.details.character_characters, name='character_origin_universe_characters'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/with_character/(?P\d+)/$', + gcd_views.details.character_issues_character, name='character_origin_universe_issues_per_character'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/sequences/$', + gcd_views.details.character_sequences, name='character_origin_universe_sequences'), + re_path(r'^character_name/(?P\d+)/character_origin_universe/(?P-?\d+)/issues/$', + gcd_views.details.character_name_issues, name='character_name_origin_universe_issues'), + re_path(r'^character/(?P\d+)/character_origin_universe/(?P-?\d+)/story_universe/(?P-?\d+)/$', + gcd_views.details.character_issues, name='character_origin_universe_story_issues_per_universe'), path('group/search/', gcd_views.search.group_search_hx, name='group_search_hx'), diff --git a/apps/gcd/views/details.py b/apps/gcd/views/details.py index 147a501b..f37e36cc 100644 --- a/apps/gcd/views/details.py +++ b/apps/gcd/views/details.py @@ -138,6 +138,8 @@ 'migrated to links. Therefore not all appearances ' \ ' in our database are shown here.' +WITHOUT_UNIVERSE_NAME = 'without a universe' + SORT_TABLE_TEMPLATE = 'gcd/bits/sortable_table.html' TW_SORT_TABLE_TEMPLATE = 'gcd/bits/tw_sortable_table.html' TW_SORT_GRID_TEMPLATE = 'gcd/bits/tw_sortable_grid.html' @@ -627,11 +629,12 @@ def creator_overview(request, creator_id): def creator_issues(request, creator_id, series_id=None, feature_id=None, character_id=None, group_id=None, publisher_id=None, - country=None, language=None): + country=None, language=None, universe_id=None): return checklist_by_id(request, creator_id, series_id=series_id, feature_id=feature_id, character_id=character_id, group_id=group_id, publisher_id=publisher_id, - country=country, language=language) + country=country, language=language, + universe_id=universe_id) def creator_edited_issues(request, creator_id, series_id=None, @@ -725,7 +728,7 @@ def creator_series(request, creator_id, country=None, language=None): def checklist_by_id(request, creator_id, series_id=None, character_id=None, feature_id=None, co_creator_id=None, group_id=None, publisher_id=None, edits=False, country=None, - language=None): + language=None, universe_id=None): """ Provides checklists for a Creator. These include results for all CreatorNames and for the overall House Name all uses of that House Name. @@ -765,25 +768,25 @@ def checklist_by_id(request, creator_id, series_id=None, character_id=None, publisher) elif character_id: character = get_gcd_object(Character, character_id) - if character.universe: - universe_id = character.universe.id - if character.active_generalisations(): - character = character.active_generalisations().get()\ - .from_character - issues = issues.filter( - story__credits__creator__creator=creator, - story__appearing_characters__character__character=character, - story__appearing_characters__universe_id=universe_id, - story__type__id__in=story_types, - story__appearing_characters__deleted=False).distinct() - else: - issues = issues.filter( - story__credits__creator__creator=creator, - story__appearing_characters__character__character=character, - story__type__id__in=story_types, - story__appearing_characters__deleted=False).distinct() - heading = 'for creator %s for character %s' % (creator, - character) + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) + + query = { + 'story__credits__creator__creator': creator, + 'story__appearing_characters__character__character': + filter_character, + 'story__appearing_characters__deleted': False, + 'story__type__id__in': story_types + } + + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'story__appearing_characters__', + 'for creator %s for character %s with origin %s', + 'for creator %s for character %s', + (creator, character)) + + issues = issues.filter(**query).distinct() elif group_id: group = get_gcd_object(Group, group_id) issues = issues.filter( @@ -869,23 +872,86 @@ def _table_issues_list_or_grid(request, issues, context, publisher=True): def _table_creators_list_or_grid(request, creators, context, resolve_name, - object): + object, universe_id=None): context['list_grid'] = True if 'display' not in request.GET or request.GET['display'] == 'list': - table = GenericCreatorTable(creators, - resolve_name=resolve_name, - object=object, - template_name=TW_SORT_TABLE_TEMPLATE, - order_by=('creator')) + table = GenericCreatorTable( + creators, + resolve_name=resolve_name, + object=object, + template_name=TW_SORT_TABLE_TEMPLATE, + order_by=('creator'), + universe_id=universe_id) else: - table = CreatorPortraitTable(creators, - resolve_name=resolve_name, - object=object, - template_name=TW_SORT_GRID_TEMPLATE, - order_by=('creator')) + table = CreatorPortraitTable( + creators, + resolve_name=resolve_name, + object=object, + template_name=TW_SORT_GRID_TEMPLATE, + order_by=('creator'), + universe_id=universe_id) return table +def _resolve_character_universe(character, universe_id): + """ + Helper to resolve filter_character and universe_id with character + generalisations. + + Returns tuple: (filter_character, universe_id, link_universe_id) + where link_universe_id is the original universe_id passed in. + """ + filter_character = character + link_universe_id = universe_id + + if universe_id is None and character.universe and \ + character.active_generalisations(): + universe_id = character.universe.id + filter_character = character.active_generalisations().get()\ + .from_character + + return filter_character, universe_id, link_universe_id + + +def _build_universe_filter_and_heading(universe_id, link_universe_id, query, + universe_key_prefix, + heading_with_fmt, heading_without_fmt, + fmt_args): + """ + Helper to add universe filtering to query dict and build heading string. + + Args: + universe_id: The resolved universe ID (may be '-1' for without) + link_universe_id: The original universe ID from URL + query: Dict to mutate in-place with universe filter + universe_key_prefix: Query prefix (e.g., 'story__app_characters__') + heading_with_fmt: Format string for heading when universe is specified + heading_without_fmt: Format string for heading when no origin universe + fmt_args: Tuple of arguments for format string (character, etc.) + + Returns: + heading: The formatted heading string + """ + universe_name = None + + if universe_id: + if universe_id == '-1': + universe_name = WITHOUT_UNIVERSE_NAME + query[universe_key_prefix + 'universe_id__isnull'] = True + else: + universe_obj = get_gcd_object(Universe, universe_id) + universe_name = universe_obj.universe_name() + query[universe_key_prefix + 'universe_id'] = universe_id + + if link_universe_id: + # Add universe_name to the format args tuple + heading = heading_with_fmt % (fmt_args + (universe_name,)) + else: + heading = heading_without_fmt % fmt_args + + return heading + + def cover_checklist_by_id(request, creator_id, series_id=None, country=None, language=None): creator = get_gcd_object(Creator, creator_id) @@ -988,7 +1054,7 @@ def checklist_by_name(request, creator, country=None, language=None, def creator_name_issues(request, creator_name_id, character_id=None, group_id=None, feature_id=None, series_id=None, - country=None, language=None): + country=None, language=None, universe_id=None): return creator_name_checklist(request=request, creator_name_id=creator_name_id, character_id=character_id, @@ -996,13 +1062,14 @@ def creator_name_issues(request, creator_name_id, character_id=None, feature_id=feature_id, series_id=series_id, country=country, - language=language + language=language, + universe_id=universe_id ) def creator_name_checklist(request, creator_name_id, character_id=None, group_id=None, feature_id=None, series_id=None, - country=None, language=None): + country=None, language=None, universe_id=None): """ Provides checklists for a CreatorNameDetail. """ @@ -1015,24 +1082,25 @@ def creator_name_checklist(request, creator_name_id, character_id=None, .select_related('series__publisher') if character_id: character = get_gcd_object(Character, character_id) - if character.universe: - universe_id = character.universe.id - if character.active_generalisations(): - character = character.active_generalisations().get()\ - .from_character - issues = issues.filter( - story__credits__creator=creator, - story__appearing_characters__character__character=character, - story__appearing_characters__universe_id=universe_id, - story__type__id__in=CORE_TYPES, - story__appearing_characters__deleted=False).distinct() - else: - issues = issues.filter( - story__credits__creator=creator, - story__appearing_characters__character__character=character, - story__type__id__in=CORE_TYPES, - story__appearing_characters__deleted=False).distinct() - heading_addon = 'with character %s' % (character) + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) + + query = { + 'story__credits__creator': creator, + 'story__appearing_characters__character__character': + filter_character, + 'story__appearing_characters__deleted': False, + 'story__type__id__in': CORE_TYPES + } + + heading_addon = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'story__appearing_characters__', + 'with character %s with origin %s', + 'with character %s', + (character,)) + + issues = issues.filter(**query).distinct() if group_id: group = get_gcd_object(Group, group_id) issues = issues.filter( @@ -1676,9 +1744,10 @@ def show_indicia_publisher(request, indicia_publisher, preview=False): context['list_grid'] = True if 'display' not in request.GET or request.GET['display'] == 'list': - table = IndiciaPublisherIssueTable(indicia_publisher_issues, - template_name=TW_SORT_TABLE_TEMPLATE, - order_by=('issue')) + table = IndiciaPublisherIssueTable( + indicia_publisher_issues, + template_name=TW_SORT_TABLE_TEMPLATE, + order_by=('issue')) else: table = IndiciaPublisherIssueCoverTable( indicia_publisher_issues, @@ -2391,7 +2460,7 @@ def publisher_creators(request, publisher_id, creator_names=False): else: creators = Creator.objects.all() creators = creators.filter( - creator_names__storycredit__story__issue__series__publisher=publisher, + creator_names__storycredit__story__issue__series__publisher=publisher, # noqa: E501 creator_names__storycredit__story__type__id__in=CORE_TYPES, creator_names__storycredit__deleted=False).distinct() creators = _annotate_creator_list(creators) @@ -4007,18 +4076,10 @@ def show_character(request, character, preview=False): return render(request, 'gcd/details/tw_character.html', context) -def character_characters(request, character_id): +def character_characters(request, character_id, universe_id=None): character = get_gcd_object(Character, character_id) - universe_id = None - if character.universe: - if character.active_generalisations(): - filter_character = character.active_generalisations().get()\ - .from_character - universe_id = character.universe.id - else: - filter_character = character - else: - filter_character = character + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) query = {'character_names__storycharacter__story__' 'appearing_characters__character__character': @@ -4026,9 +4087,13 @@ def character_characters(request, character_id): 'character_names__storycharacter__deleted': False, 'character_names__storycharacter__story__type__id__in': CORE_TYPES} - if universe_id: - query['character_names__storycharacter__story__' - 'appearing_characters__universe_id'] = universe_id + + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'character_names__storycharacter__story__appearing_characters__', + 'appearing together with %s with origin %s', + 'appearing together with %s', + (character,)) characters = Character.objects.filter(Q(**query))\ .exclude(id=filter_character.id).distinct() @@ -4044,23 +4109,38 @@ def character_characters(request, character_id): 'result_disclaimer': CHAR_MIGRATE_DISCLAIMER, 'item_name': 'character', 'plural_suffix': 's', - 'heading': 'appearing together with %s' % (character) + 'heading': heading } template = 'gcd/search/tw_list_sortable.html' table = CharacterCharacterTable(characters, character=character, + universe_id=link_universe_id, template_name=TW_SORT_TABLE_TEMPLATE, order_by=('name')) return generic_sortable_list(request, characters, table, template, context) -def character_features(request, character_id): +def character_features(request, character_id, universe_id=None): character = get_gcd_object(Character, character_id) - features = Feature.objects.filter( - story__appearing_characters__character__character=character, - story__type__id__in=CORE_TYPES, - story__deleted=False, - deleted=False).distinct() + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) + + query = { + 'story__appearing_characters__character__character': + filter_character, + 'story__type__id__in': CORE_TYPES, + 'story__deleted': False, + 'deleted': False + } + + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'story__appearing_characters__', + 'with an appearance of %s with origin %s', + 'with an appearance of %s', + (character,)) + + features = Feature.objects.filter(**query).distinct() features = features.annotate(issue_count=Count( 'story__issue', distinct=True)) @@ -4074,11 +4154,12 @@ def character_features(request, character_id): 'result_disclaimer': CHAR_MIGRATE_DISCLAIMER, 'item_name': 'feature', 'plural_suffix': 's', - 'heading': 'with an appearance of %s' % (character) + 'heading': heading } template = 'gcd/search/tw_list_sortable.html' table = CharacterFeatureTable(features, character=character, + universe_id=link_universe_id, template_name=TW_SORT_TABLE_TEMPLATE, order_by=('name')) return generic_sortable_list(request, features, table, template, context) @@ -4087,16 +4168,8 @@ def character_features(request, character_id): def character_issues(request, character_id, layer=None, universe_id=None, story_universe_id=None): character = get_gcd_object(Character, character_id) - if character.universe: - universe_id = character.universe.id - if character.active_generalisations(): - filter_character = character.active_generalisations().get()\ - .from_character - else: - filter_character = character - universe_id = None - else: - filter_character = character + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) story_types = process_story_type_filter_from_request(request) @@ -4108,6 +4181,8 @@ def character_issues(request, character_id, layer=None, universe_id=None, issues = Issue.objects.filter(Q(**query)).distinct()\ .select_related('series__publisher') + story_universe_heading = '' + universe_name = None if layer == -1 and character.active_specifications().exists(): characters = character.active_specifications()\ .values_list('to_character_id', flat=True) @@ -4131,15 +4206,25 @@ def character_issues(request, character_id, layer=None, universe_id=None, else: if universe_id: if universe_id == '-1': + universe_name = WITHOUT_UNIVERSE_NAME query['story__appearing_characters__universe_id__isnull'] = \ - True + True else: + universe_obj = get_gcd_object(Universe, universe_id) + universe_name = universe_obj.universe_name() query['story__appearing_characters__universe_id'] = universe_id if story_universe_id: if story_universe_id == '-1': query['story__universe__isnull'] = True + story_universe_heading = ( + ' appearing %s' % WITHOUT_UNIVERSE_NAME) else: + story_universe_obj = get_gcd_object(Universe, + story_universe_id) + story_universe_name = story_universe_obj.universe_name() query['story__universe__in'] = [story_universe_id,] + story_universe_heading = ( + ' appearing in %s' % story_universe_name) issues = Issue.objects.filter(Q(**query)).distinct()\ .select_related('series__publisher') @@ -4148,11 +4233,17 @@ def character_issues(request, character_id, layer=None, universe_id=None, filter.filters.pop('language') issues = filter.qs + if link_universe_id: + heading = 'for character %s with origin %s%s' % ( + character, universe_name, story_universe_heading) + else: + heading = 'for character %s%s' % (character, story_universe_heading) + context = { 'result_disclaimer': result_disclaimer, 'item_name': 'issue', 'plural_suffix': 's', - 'heading': 'for character %s' % (character), + 'heading': heading, 'filter_form': filter.form } template = 'gcd/search/tw_list_sortable.html' @@ -4160,17 +4251,66 @@ def character_issues(request, character_id, layer=None, universe_id=None, return generic_sortable_list(request, issues, table, template, context) -def character_issues_character(request, character_id, character_with_id): +def character_origin_universe(request, character_id, universe_id): + """ + Display the Character Universe Origin Page showing links to various + filtered views for a character when originating from a specific universe. + """ character = get_gcd_object(Character, character_id) - character_with = get_gcd_object(Character, character_with_id) - filter_character = character - universe_id = None - if character.universe: - if character.active_generalisations(): - universe_id = character.universe.id - filter_character = character.active_generalisations()\ - .get().from_character + cover_query = { + 'story__appearing_characters__character__character': character, + 'story__appearing_characters__deleted': False, + 'story__type__id': 6, + 'story__deleted': False, + 'cover__isnull': False, + 'cover__deleted': False} + + if universe_id == '-1': + cover_query['story__appearing_characters__universe_id__isnull'] = True + universe = None + universe_name = WITHOUT_UNIVERSE_NAME.title() + else: + universe = get_gcd_object(Universe, universe_id) + universe_name = universe.universe_name() + cover_query['story__appearing_characters__universe_id'] = universe_id + + issues = Issue.objects.filter(Q(**cover_query)).distinct()\ + .select_related('series__publisher') + + if issues: + selected_issue = issues[randint(0, issues.count()-1)] + image_tag = get_image_tag( + cover=selected_issue.cover_set.first(), + zoom_level=ZOOM_MEDIUM, + alt_text='Random Cover from Character with origin Universe') + else: + image_tag = '' + selected_issue = None + + context = { + 'character': character, + 'additional_names': character.active_names() + .filter(is_official_name=False), + 'universe': universe, + 'universe_id': universe_id, + 'universe_name': universe_name, + 'image_tag': image_tag, + 'image_issue': selected_issue, + 'active_universe_appearances': + character.active_universe_appearances_with_origin(universe), + 'error_subject': '%s - %s' % (character, universe_name) + } + return render(request, 'gcd/details/tw_character_origin_universe.html', + context) + + +def character_issues_character(request, character_id, character_with_id, + universe_id=None): + character = get_gcd_object(Character, character_id) + character_with = get_gcd_object(Character, character_with_id) + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) story_types = process_story_type_filter_from_request(request) @@ -4179,17 +4319,22 @@ def character_issues_character(request, character_id, character_with_id): 'appearing_characters__deleted': False, 'type__id__in': story_types, 'deleted': False} - if universe_id: - query['appearing_characters__universe_id'] = universe_id + + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'appearing_characters__', + 'for %s with origin %s with %s', + 'for %s with %s', + (character, character_with)) stories = Story.objects.filter(Q(**query)) filter_character_with = character_with if character_with.universe: if character_with.active_generalisations(): - universe_id = character_with.universe.id filter_character_with = character_with.active_generalisations()\ .get().from_character + query_with = {'story__appearing_characters__character__character': filter_character_with, 'story__appearing_characters__deleted': False, @@ -4209,7 +4354,7 @@ def character_issues_character(request, character_id, character_with_id): 'result_disclaimer': result_disclaimer, 'item_name': 'issue', 'plural_suffix': 's', - 'heading': 'for %s with %s' % (character, character_with), + 'heading': heading, 'filter_form': filter.form } template = 'gcd/search/tw_list_sortable.html' @@ -4217,29 +4362,30 @@ def character_issues_character(request, character_id, character_with_id): return generic_sortable_list(request, issues, table, template, context) -def character_issues_feature(request, character_id, feature_id): +def character_issues_feature(request, character_id, feature_id, + universe_id=None): character = get_gcd_object(Character, character_id) feature = get_gcd_object(Feature, feature_id) - - filter_character = character - universe_id = None - if character.universe: - if character.active_generalisations(): - universe_id = character.universe.id - filter_character = character.active_generalisations()\ - .get().from_character + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) story_types = process_story_type_filter_from_request(request) - query = {'story__appearing_characters__character__character': - filter_character, - 'story__appearing_characters__deleted': False, - 'story__type__id__in': story_types, - 'story__feature_object__id': feature_id, - 'story__deleted': False} + query = { + 'story__appearing_characters__character__character': + filter_character, + 'story__appearing_characters__deleted': False, + 'story__type__id__in': story_types, + 'story__feature_object__id': feature_id, + 'story__deleted': False + } - if universe_id: - query['story__appearing_characters__universe_id'] = universe_id + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'story__appearing_characters__', + 'for %s with origin %s in %s', + 'for %s in %s', + (character, feature)) issues = Issue.objects.filter(Q(**query)).distinct()\ .select_related('series__publisher') @@ -4252,7 +4398,7 @@ def character_issues_feature(request, character_id, feature_id): 'result_disclaimer': result_disclaimer, 'item_name': 'issue', 'plural_suffix': 's', - 'heading': 'for %s in %s' % (character, feature), + 'heading': heading, 'filter_form': filter.form } template = 'gcd/search/tw_list_sortable.html' @@ -4260,17 +4406,12 @@ def character_issues_feature(request, character_id, feature_id): return generic_sortable_list(request, issues, table, template, context) -def character_issues_series(request, character_id, series_id): +def character_issues_series(request, character_id, series_id, + universe_id=None): character = get_gcd_object(Character, character_id) series = get_gcd_object(Series, series_id) - - filter_character = character - universe_id = None - if character.universe: - if character.active_generalisations(): - universe_id = character.universe.id - filter_character = character.active_generalisations()\ - .get().from_character + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) query = {'story__appearing_characters__character__character': filter_character, @@ -4279,8 +4420,12 @@ def character_issues_series(request, character_id, series_id): 'series__id': series_id, 'story__deleted': False} - if universe_id: - query['story__appearing_characters__universe_id'] = universe_id + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'story__appearing_characters__', + 'for character %s with origin %s in %s', + 'for character %s in %s', + (character, series)) issues = Issue.objects.filter(Q(**query)).distinct()\ .select_related('series__publisher') @@ -4291,7 +4436,7 @@ def character_issues_series(request, character_id, series_id): 'result_disclaimer': result_disclaimer, 'item_name': 'issue', 'plural_suffix': 's', - 'heading': 'for character %s in %s' % (character, series), + 'heading': heading, } template = 'gcd/search/tw_list_sortable.html' table = IssueTable(issues, @@ -4312,33 +4457,28 @@ def character_issues_series(request, character_id, series_id): return generic_sortable_list(request, issues, table, template, context) -def character_series(request, character_id): +def character_series(request, character_id, universe_id=None): character = get_gcd_object(Character, character_id) - universe_id = None - heading = 'with character %s' % (character) - filter_character = character + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) + + query = { + 'issue__story__appearing_characters__character__character': + filter_character, + 'issue__story__appearing_characters__deleted': False, + 'issue__story__type__id__in': CORE_TYPES, + 'deleted': False + } - if character.universe: - if character.active_generalisations(): - universe_id = character.universe.id - filter_character = character.active_generalisations()\ - .get().from_character + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'issue__story__appearing_characters__', + 'with character %s with origin %s', + 'with character %s', + (character,)) - if universe_id: - series = Series.objects.filter( - issue__story__appearing_characters__character__character= - filter_character, - issue__story__appearing_characters__universe_id=universe_id, - issue__story__appearing_characters__deleted=False, - issue__story__type__id__in=CORE_TYPES, - deleted=False).distinct().select_related('publisher') - else: - series = Series.objects.filter( - issue__story__appearing_characters__character__character= - filter_character, - issue__story__appearing_characters__deleted=False, - issue__story__type__id__in=CORE_TYPES, - deleted=False).distinct().select_related('publisher') + series = Series.objects.filter(**query).distinct()\ + .select_related('publisher') filter = filter_series(request, series) filter.filters.pop('language') series = filter.qs @@ -4356,32 +4496,32 @@ def character_series(request, character_id): template = 'gcd/search/tw_list_sortable.html' table = CharacterSeriesTable(series, character=character, + universe_id=link_universe_id, template_name=TW_SORT_TABLE_TEMPLATE, order_by=('year')) return generic_sortable_list(request, series, table, template, context) -def character_creators(request, character_id, creator_names=False): +def character_creators(request, character_id, creator_names=False, + universe_id=None): character = get_gcd_object(Character, character_id) - filter_character = character - universe_id = None + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) - if character.universe: - if character.active_generalisations(): - filter_character = character.active_generalisations().get()\ - .from_character - universe_id = character.universe.id + query = { + 'appearing_characters__character__character': filter_character, + 'appearing_characters__deleted': False, + 'type__id__in': CORE_TYPES + } - if universe_id: - stories = Story.objects.filter( - appearing_characters__character__character=filter_character, - appearing_characters__universe_id=universe_id, - appearing_characters__deleted=False).distinct() - else: - stories = Story.objects.filter( - appearing_characters__character__character=filter_character, - appearing_characters__deleted=False).distinct() - stories = stories.filter(type__id__in=CORE_TYPES) + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'appearing_characters__', + 'working on character %s with origin %s', + 'working on character %s', + (character,)) + + stories = Story.objects.filter(**query).distinct() filter = filter_sequences(request, stories) filter.filters.pop('language') stories = filter.qs @@ -4402,50 +4542,52 @@ def character_creators(request, character_id, creator_names=False): creators = _annotate_creator_list(creators) result_disclaimer = ISSUE_CHECKLIST_DISCLAIMER + MIGRATE_DISCLAIMER + \ - CHAR_MIGRATE_DISCLAIMER + CHAR_MIGRATE_DISCLAIMER + context = { 'result_disclaimer': result_disclaimer, 'item_name': 'creator', 'plural_suffix': 's', - 'heading': 'working on character %s' % (character.name), + 'heading': heading, 'filter_form': filter.form } template = 'gcd/search/tw_list_sortable.html' if creator_names: - table = GenericCreatorNameTable(creators, - object=character, - resolve_name='character', - template_name=TW_SORT_TABLE_TEMPLATE, - order_by=('name')) + table = GenericCreatorNameTable( + creators, + object=character, + resolve_name='character', + template_name=TW_SORT_TABLE_TEMPLATE, + order_by=('name'), + universe_id=link_universe_id) else: - table = _table_creators_list_or_grid(request, creators, context, - resolve_name='character', - object=character) + table = _table_creators_list_or_grid( + request, creators, context, + resolve_name='character', + object=character, + universe_id=link_universe_id) # TODO: pass filter through to links to combined lists return generic_sortable_list(request, creators, table, template, context) -def character_sequences(request, character_id): +def character_sequences(request, character_id, universe_id=None): character = get_gcd_object(Character, character_id) - universe_id = None - heading = 'for character %s' % (character) + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) - if character.universe: - if character.active_generalisations(): - universe_id = character.universe.id - character = character.active_generalisations().get().from_character + query = {'appearing_characters__character__character': filter_character, + 'appearing_characters__deleted': False, + 'deleted': False} - if universe_id: - stories = Story.objects.filter( - appearing_characters__character__character=character, - appearing_characters__universe_id=universe_id, - appearing_characters__deleted=False, - deleted=False).distinct().select_related('issue__series__publisher') - else: - stories = Story.objects.filter( - appearing_characters__character__character=character, - appearing_characters__deleted=False, - deleted=False).distinct().select_related('issue__series__publisher') + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'appearing_characters__', + 'with character %s with origin %s', + 'for character %s', + (character,)) + + stories = Story.objects.filter(**query).distinct()\ + .select_related('issue__series__publisher') filter = filter_sequences(request, stories) filter.filters.pop('language') @@ -4466,37 +4608,36 @@ def character_sequences(request, character_id): return generic_sortable_list(request, stories, table, template, context) -def character_covers(request, character_id): +def character_covers(request, character_id, universe_id=None): character = get_gcd_object(Character, character_id) - universe_id = None - heading = 'with character %s' % character + filter_character, universe_id, link_universe_id = \ + _resolve_character_universe(character, universe_id) + + query = { + 'story__appearing_characters__character__character': + filter_character, + 'story__appearing_characters__deleted': False, + 'story__type__id': 6, + 'story__deleted': False + } - if character.universe: - if character.active_generalisations(): - universe_id = character.universe.id - character = character.active_generalisations().get().from_character + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'story__appearing_characters__', + 'with character %s with origin %s', + 'with character %s', + (character,)) - if universe_id: - issues = Issue.objects.filter( - story__appearing_characters__character__character=character, - story__appearing_characters__universe_id=universe_id, - story__appearing_characters__deleted=False, - story__type__id=6, - story__deleted=False).distinct().select_related('series__publisher') - else: - issues = Issue.objects.filter( - story__appearing_characters__character__character=character, - story__appearing_characters__deleted=False, - story__type__id=6, - story__deleted=False).distinct().select_related('series__publisher') + issues = Issue.objects.filter(**query).distinct()\ + .select_related('series__publisher') filter = filter_issues(request, issues) filter.filters.pop('language') issues = filter.qs context = { - 'result_disclaimer': COVER_CHECKLIST_DISCLAIMER + - CHAR_MIGRATE_DISCLAIMER, + 'result_disclaimer': (COVER_CHECKLIST_DISCLAIMER + + CHAR_MIGRATE_DISCLAIMER), 'item_name': 'cover', 'plural_suffix': 's', 'heading': heading, @@ -4512,6 +4653,7 @@ def character_covers(request, character_id): def character_name_issues(request, character_name_id, universe_id=None): character_name = get_gcd_object(CharacterNameDetail, character_name_id) character = character_name.character + link_universe_id = universe_id # look for name at generalization if not universe_id and character.universe: @@ -4548,28 +4690,22 @@ def character_name_issues(request, character_name_id, universe_id=None): story_types = process_story_type_filter_from_request(request) - issues = Issue.objects.filter( - story__appearing_characters__character=filter_character_name, - story__appearing_characters__deleted=False, - story__type__id__in=story_types, - story__deleted=False).distinct().select_related('series__publisher') - if universe_id: - if universe_id == '-1': - issues = Issue.objects.filter( - story__appearing_characters__character=filter_character_name, - story__appearing_characters__universe_id__isnull=True, - story__appearing_characters__deleted=False, - story__type__id__in=story_types, - story__deleted=False).distinct()\ - .select_related('series__publisher') - else: - issues = Issue.objects.filter( - story__appearing_characters__character=filter_character_name, - story__appearing_characters__universe_id=universe_id, - story__appearing_characters__deleted=False, - story__type__id__in=story_types, - story__deleted=False).distinct()\ - .select_related('series__publisher') + query = { + 'story__appearing_characters__character': filter_character_name, + 'story__appearing_characters__deleted': False, + 'story__type__id__in': story_types, + 'story__deleted': False + } + + heading = _build_universe_filter_and_heading( + universe_id, link_universe_id, query, + 'story__appearing_characters__', + 'for name %s of character %s with origin %s', + 'for name %s of character %s', + (character_name, character)) + + issues = Issue.objects.filter(**query).distinct()\ + .select_related('series__publisher') result_disclaimer = ISSUE_CHECKLIST_DISCLAIMER + MIGRATE_DISCLAIMER filter = filter_issues(request, issues, story_type_filter=True) @@ -4580,8 +4716,7 @@ def character_name_issues(request, character_name_id, universe_id=None): 'result_disclaimer': result_disclaimer, 'item_name': 'issue', 'plural_suffix': 's', - 'heading': 'for name %s of character %s' % (character_name, - character), + 'heading': heading, 'filter_form': filter.form } template = 'gcd/search/tw_list_sortable.html' @@ -4921,7 +5056,7 @@ def group_creators(request, group_id, creator_names=False): else: creators = Creator.objects.all() creators = creators.filter( - creator_names__storycredit__story__appearing_groups__group_name__group=group, + creator_names__storycredit__story__appearing_groups__group_name__group=group, # noqa: E501 creator_names__storycredit__story__appearing_groups__deleted=False, creator_names__storycredit__story__type__id__in=CORE_TYPES, creator_names__storycredit__deleted=False).distinct() diff --git a/requirements.txt b/requirements.txt index 51961753..4573bdb6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ djangorestframework djangorestframework-yaml # These packages are used in development and testing. -flake8 +flake8==6.1.0 mock pytest pytest-django diff --git a/templates/gcd/details/tw_character.html b/templates/gcd/details/tw_character.html index 74b449bc..c8c953ec 100644 --- a/templates/gcd/details/tw_character.html +++ b/templates/gcd/details/tw_character.html @@ -64,7 +64,7 @@ {% if character.active_generalisations and not character.universe %}
  • Issue List, including generalisations
  • {% endif %} -
  • Cover List
  • +
  • Cover List
  • Series List
  • Feature List
  • Characters Appeared With
  • @@ -117,19 +117,19 @@

    Notes:

    {% block content %} {% if character.active_universe_origins %}
    -

    Appearances With a Character Origin Universe:

    +

    Overviews of Character Origin Universe:

    {% for universe in character.active_universe_origins %} diff --git a/templates/gcd/details/tw_character_origin_universe.html b/templates/gcd/details/tw_character_origin_universe.html new file mode 100644 index 00000000..bd09c2ac --- /dev/null +++ b/templates/gcd/details/tw_character_origin_universe.html @@ -0,0 +1,98 @@ +{% extends "gcd/tw_object_base.html" %} +{% load i18n %} +{% load display %} + +{% block title %} +GCD :: Character Universe :: {{ character.name }} - {{ universe_name }} +{% endblock %} + +{% block header %} +

    + {{ character.name }} with origin {{ universe_name }} +

    +{% endblock %} + +{% block content_left %} +
    - without a universe + without a universe
    - {{ universe }} + {{ universe }} {{ character|character_for_universe:universe.id }}
    + + + + +
    + + +
    +
    + +{% if character.disambiguation %} + + + + +{% endif %} +
    Disambiguation:{{ character.disambiguation }}
    + +

    Name:

    +
      + {{ character.official_name.name }} +
    + {% if additional_names %} +

    Additional Names:

    +
      + {% for name in additional_names %} +
    1. + {{ name.name }} +
    2. + {% endfor %} +
    + {% endif %} +
    +
    +
    +{% if active_universe_appearances %} +
    +

    Appearances With a Story Universe:

    + + + + + + {% for universe in active_universe_appearances %} + + + + + {% endfor %} +
    + without a universe +
    + {{ universe }} +
    +{% endif %} +{% endblock content_left %} + +{% block www_my_comics_cross_link %} + {% url "character_origin_universe" character_id=character.id universe_id=universe_id as object_url %} + {% with model_name='Character' object_url=object_url %} + {{ block.super }} + {% endwith %} +{% endblock %} + +{% block change_history %} +{% endblock change_history %}