From 596dd59780ed306b915030c198157e0e2ed63523 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Wed, 2 Nov 2022 23:10:48 +0100 Subject: [PATCH] Add parties --- src/character/admin.py | 12 +++ src/character/migrations/0033_party.py | 68 +++++++++++++++ .../migrations/0034_alter_party_options.py | 17 ++++ ...ons_alter_harmfulstate_options_and_more.py | 83 +++++++++++++++++++ src/character/migrations/max_migration.txt | 2 +- src/character/models/__init__.py | 2 + src/character/models/capabilities.py | 6 +- src/character/models/character.py | 17 ++-- src/character/models/equipment.py | 2 +- src/character/models/party.py | 23 +++++ src/character/views.py | 38 ++++----- src/common/models.py | 1 + 12 files changed, 242 insertions(+), 29 deletions(-) create mode 100644 src/character/migrations/0033_party.py create mode 100644 src/character/migrations/0034_alter_party_options.py create mode 100644 src/character/migrations/0035_alter_capability_options_alter_harmfulstate_options_and_more.py create mode 100644 src/character/models/party.py diff --git a/src/character/admin.py b/src/character/admin.py index 2fdd4cc..9d0888e 100644 --- a/src/character/admin.py +++ b/src/character/admin.py @@ -105,6 +105,11 @@ class CharacterAdminForm(ModelForm): ].queryset = models.RacialCapability.objects.select_related("race") +class PartyInline(admin.TabularInline): + model = models.Character.parties.through + extra = 0 + + @admin.register(models.Character) class CharacterAdmin(admin.ModelAdmin): list_display = ["name", "player", "race", "profile", "level"] @@ -185,6 +190,7 @@ class CharacterAdmin(admin.ModelAdmin): "weapons", "states", ] + inlines = [PartyInline] form = CharacterAdminForm @@ -204,3 +210,9 @@ class WeaponAdmin(admin.ModelAdmin): class HarmfulStateAdmin(admin.ModelAdmin): list_display = ["name", "description"] search_fields = ["name"] + + +@admin.register(models.Party) +class PartyAdmin(admin.ModelAdmin): + list_display = ["name", "game_master"] + search_fields = ["name"] diff --git a/src/character/migrations/0033_party.py b/src/character/migrations/0033_party.py new file mode 100644 index 0000000..45ec7a2 --- /dev/null +++ b/src/character/migrations/0033_party.py @@ -0,0 +1,68 @@ +# Generated by Django 4.1.2 on 2022-11-02 22:07 + +import django.db.models.deletion +import django_extensions.db.fields +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("character", "0032_weapon_url"), + ] + + operations = [ + migrations.CreateModel( + name="Party", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=100, unique=True, verbose_name="nom"), + ), + ( + "created", + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name="created" + ), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name="modified" + ), + ), + ( + "characters", + models.ManyToManyField( + blank=True, + related_name="parties", + to="character.character", + verbose_name="personnages", + ), + ), + ( + "game_master", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="parties", + to=settings.AUTH_USER_MODEL, + verbose_name="meneur de jeu", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/src/character/migrations/0034_alter_party_options.py b/src/character/migrations/0034_alter_party_options.py new file mode 100644 index 0000000..6ad6efd --- /dev/null +++ b/src/character/migrations/0034_alter_party_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.2 on 2022-11-02 22:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("character", "0033_party"), + ] + + operations = [ + migrations.AlterModelOptions( + name="party", + options={"ordering": ["name"]}, + ), + ] diff --git a/src/character/migrations/0035_alter_capability_options_alter_harmfulstate_options_and_more.py b/src/character/migrations/0035_alter_capability_options_alter_harmfulstate_options_and_more.py new file mode 100644 index 0000000..c3593ff --- /dev/null +++ b/src/character/migrations/0035_alter_capability_options_alter_harmfulstate_options_and_more.py @@ -0,0 +1,83 @@ +# Generated by Django 4.1.2 on 2022-11-02 22:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("character", "0034_alter_party_options"), + ] + + operations = [ + migrations.AlterModelOptions( + name="capability", + options={ + "get_latest_by": "modified", + "verbose_name": "Capacité", + "verbose_name_plural": "Capacités", + }, + ), + migrations.AlterModelOptions( + name="harmfulstate", + options={ + "get_latest_by": "modified", + "ordering": ["name"], + "verbose_name": "État préjudiciable", + "verbose_name_plural": "États préjudiciables", + }, + ), + migrations.AlterModelOptions( + name="party", + options={ + "get_latest_by": "modified", + "ordering": ["name"], + "verbose_name": "Groupe", + "verbose_name_plural": "Groupes", + }, + ), + migrations.AlterModelOptions( + name="path", + options={ + "get_latest_by": "modified", + "ordering": ["name"], + "verbose_name": "Voie", + "verbose_name_plural": "Voies", + }, + ), + migrations.AlterModelOptions( + name="profile", + options={ + "get_latest_by": "modified", + "ordering": ["name"], + "verbose_name": "Profil", + "verbose_name_plural": "Profils", + }, + ), + migrations.AlterModelOptions( + name="race", + options={ + "get_latest_by": "modified", + "ordering": ["name"], + "verbose_name": "Race", + "verbose_name_plural": "Races", + }, + ), + migrations.AlterModelOptions( + name="racialcapability", + options={ + "get_latest_by": "modified", + "verbose_name": "Capacité raciale", + "verbose_name_plural": "Capacités raciales", + }, + ), + migrations.AlterModelOptions( + name="weapon", + options={ + "get_latest_by": "modified", + "ordering": ["name"], + "verbose_name": "Arme", + "verbose_name_plural": "Armes", + }, + ), + ] diff --git a/src/character/migrations/max_migration.txt b/src/character/migrations/max_migration.txt index cfadb44..f9e6e2f 100644 --- a/src/character/migrations/max_migration.txt +++ b/src/character/migrations/max_migration.txt @@ -1 +1 @@ -0032_weapon_url +0035_alter_capability_options_alter_harmfulstate_options_and_more diff --git a/src/character/models/__init__.py b/src/character/models/__init__.py index c945787..db3967e 100644 --- a/src/character/models/__init__.py +++ b/src/character/models/__init__.py @@ -1,6 +1,7 @@ from .capabilities import Capability, Path, RacialCapability from .character import Character, HarmfulState, Profile, Race from .equipment import Weapon +from .party import Party __all__ = [ "Capability", @@ -11,4 +12,5 @@ __all__ = [ "Profile", "Race", "Weapon", + "Party", ] diff --git a/src/character/models/capabilities.py b/src/character/models/capabilities.py index 3de2b79..6314a85 100644 --- a/src/character/models/capabilities.py +++ b/src/character/models/capabilities.py @@ -36,7 +36,7 @@ class Path(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model): ) notes = models.TextField(blank=True, verbose_name="notes") - class Meta: + class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta): verbose_name = "Voie" verbose_name_plural = "Voies" @@ -105,7 +105,7 @@ class Capability(DocumentedModel, TimeStampedModel, models.Model): objects = CapabilityManager() - class Meta: + class Meta(TimeStampedModel.Meta): constraints = [models.UniqueConstraint("path", "rank", name="unique_path_rank")] verbose_name = "Capacité" verbose_name_plural = "Capacités" @@ -134,7 +134,7 @@ class RacialCapability(DocumentedModel, TimeStampedModel, models.Model): objects = RacialCapabilityManager() - class Meta: + class Meta(TimeStampedModel.Meta): verbose_name = "Capacité raciale" verbose_name_plural = "Capacités raciales" constraints = [models.UniqueConstraint("name", "race", name="unique_name_race")] diff --git a/src/character/models/character.py b/src/character/models/character.py index 7bf11c9..78c790c 100644 --- a/src/character/models/character.py +++ b/src/character/models/character.py @@ -39,13 +39,13 @@ class Profile(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Mode ) notes = models.TextField(blank=True, verbose_name="notes") - class Meta: + class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta): verbose_name = "Profil" verbose_name_plural = "Profils" class Race(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model): - class Meta: + class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta): verbose_name = "Race" verbose_name_plural = "Races" @@ -54,10 +54,9 @@ class HarmfulState(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models description = models.TextField() icon_url = models.URLField() - class Meta: + class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta): verbose_name = "État préjudiciable" verbose_name_plural = "États préjudiciables" - ordering = ["name"] def modifier(value: int) -> int: @@ -74,6 +73,14 @@ class CharacterManager(models.Manager): return self.get(name=name, player_id=player_id) +class CharacterQuerySet(models.QuerySet): + def managed_by(self, user): + return self.filter(player=user) + + def owned_by(self, user): + return self.filter(player=user) + + DEFAULT_NOTES = """ #### Traits personnalisés @@ -190,7 +197,7 @@ class Character(models.Model): states = models.ManyToManyField(HarmfulState, blank=True, related_name="characters") - objects = CharacterManager() + objects = CharacterManager.from_queryset(CharacterQuerySet)() class Meta: verbose_name = "Personnage" diff --git a/src/character/models/equipment.py b/src/character/models/equipment.py index 26dde3f..c74ed30 100644 --- a/src/character/models/equipment.py +++ b/src/character/models/equipment.py @@ -16,6 +16,6 @@ class Weapon(UniquelyNamedModel, DocumentedModel, TimeStampedModel, models.Model max_length=3, choices=Category.choices, default=Category.NONE ) - class Meta: + class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta): verbose_name = "Arme" verbose_name_plural = "Armes" diff --git a/src/character/models/party.py b/src/character/models/party.py new file mode 100644 index 0000000..2ce6651 --- /dev/null +++ b/src/character/models/party.py @@ -0,0 +1,23 @@ +from django.db import models +from django_extensions.db.models import TimeStampedModel + +from common.models import UniquelyNamedModel + + +class Party(UniquelyNamedModel, TimeStampedModel, models.Model): + game_master = models.ForeignKey( + "common.User", + on_delete=models.PROTECT, + related_name="parties", + verbose_name="meneur de jeu", + ) + characters = models.ManyToManyField( + "character.Character", + blank=True, + related_name="parties", + verbose_name="personnages", + ) + + class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta): + verbose_name = "Groupe" + verbose_name_plural = "Groupes" diff --git a/src/character/views.py b/src/character/views.py index 0bb6dfe..d968e4d 100644 --- a/src/character/views.py +++ b/src/character/views.py @@ -11,7 +11,7 @@ from character.templatetags.character_extras import modifier @login_required def characters_list(request): context = { - "characters": Character.objects.filter(player=request.user).select_related( + "characters": Character.objects.owned_by(request.user).select_related( "race", "profile" ) } @@ -26,7 +26,7 @@ def character_create(request): @login_required def character_view(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user) + Character.objects.managed_by(request.user) .select_related("player", "racial_capability", "profile", "race") .prefetch_related("capabilities__path", "weapons"), pk=pk, @@ -42,7 +42,7 @@ def character_view(request, pk: int): @login_required def add_path(request, pk: int): - character = get_object_or_404(Character.objects.filter(player=request.user), pk=pk) + character = get_object_or_404(Character.objects.managed_by(request.user), pk=pk) form = AddPathForm(character, request.POST) context = {"character": character} if form.is_valid(): @@ -64,7 +64,7 @@ def add_path(request, pk: int): @login_required def character_health_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only( + Character.objects.managed_by(request.user).only( "health_max", "health_remaining" ), pk=pk, @@ -78,7 +78,7 @@ def character_health_change(request, pk: int): @login_required def character_mana_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user) + Character.objects.managed_by(request.user) .only("mana_remaining", "level", "value_intelligence", "profile") .select_related("profile"), pk=pk, @@ -92,7 +92,7 @@ def character_mana_change(request, pk: int): @login_required def character_recovery_points_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only("recovery_points_remaining"), + Character.objects.managed_by(request.user).only("recovery_points_remaining"), pk=pk, ) value = get_updated_value( @@ -106,7 +106,7 @@ def character_recovery_points_change(request, pk: int): @login_required def character_defense_misc_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only("defense_misc"), pk=pk + Character.objects.managed_by(request.user).only("defense_misc"), pk=pk ) value = get_updated_value(request, character.defense_misc, float("inf")) character.defense_misc = value @@ -118,7 +118,7 @@ def character_defense_misc_change(request, pk: int): @login_required def character_shield_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only("shield"), pk=pk + Character.objects.managed_by(request.user).only("shield"), pk=pk ) value = get_updated_value(request, character.shield, float("inf")) character.shield = value @@ -130,7 +130,7 @@ def character_shield_change(request, pk: int): @login_required def character_armor_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only("armor"), pk=pk + Character.objects.managed_by(request.user).only("armor"), pk=pk ) value = get_updated_value(request, character.armor, float("inf")) character.armor = value @@ -142,7 +142,7 @@ def character_armor_change(request, pk: int): @login_required def character_initiative_misc_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only("initiative_misc"), pk=pk + Character.objects.managed_by(request.user).only("initiative_misc"), pk=pk ) value = get_updated_value(request, character.initiative_misc, float("inf")) character.initiative_misc = value @@ -154,7 +154,7 @@ def character_initiative_misc_change(request, pk: int): @login_required def character_luck_points_change(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only( + Character.objects.managed_by(request.user).only( "luck_points_remaining", "value_charisma" ), pk=pk, @@ -184,7 +184,7 @@ def get_updated_value(request, remaining_value: int, max_value: int | float) -> @login_required def character_get_defense(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only( + Character.objects.managed_by(request.user).only( "defense_misc", "armor", "shield", "value_dexterity" ), pk=pk, @@ -195,7 +195,7 @@ def character_get_defense(request, pk: int): @login_required def character_get_initiative(request, pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user).only( + Character.objects.managed_by(request.user).only( "initiative_misc", "value_dexterity" ), pk=pk, @@ -212,7 +212,7 @@ def character_notes_change(request, pk: int): def character_equipment_change(request, pk: int): field = "equipment" character = get_object_or_404( - Character.objects.filter(player=request.user).only(field), pk=pk + Character.objects.managed_by(request.user).only(field), pk=pk ) context = {"character": character} if request.method == "GET": @@ -245,7 +245,7 @@ def character_damage_reduction_change(request, pk: int): def update_text_field(request, pk, field): character = get_object_or_404( - Character.objects.filter(player=request.user).only(field), pk=pk + Character.objects.managed_by(request.user).only(field), pk=pk ) context = {"character": character} if request.method == "GET": @@ -264,7 +264,7 @@ def update_text_field(request, pk, field): @login_required def add_next_in_path(request, character_pk: int, path_pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user), pk=character_pk + Character.objects.managed_by(request.user), pk=character_pk ) path = get_object_or_404(Path, pk=path_pk) capability = path.get_next_capability(character) @@ -283,7 +283,7 @@ def add_next_in_path(request, character_pk: int, path_pk: int): @login_required def remove_last_in_path(request, character_pk: int, path_pk: int): character = get_object_or_404( - Character.objects.filter(player=request.user), pk=character_pk + Character.objects.managed_by(request.user), pk=character_pk ) last_rank = max( character.capabilities.filter(path_id=path_pk).values_list("rank", flat=True) @@ -304,7 +304,7 @@ def remove_last_in_path(request, character_pk: int, path_pk: int): @login_required def remove_state(request, pk: int, state_pk: int): character: Character = get_object_or_404( - Character.objects.filter(player=request.user), pk=pk + Character.objects.managed_by(request.user), pk=pk ) state = get_object_or_404(HarmfulState, pk=state_pk) character.states.remove(state) @@ -318,7 +318,7 @@ def remove_state(request, pk: int, state_pk: int): @login_required def add_state(request, pk: int, state_pk: int): character: Character = get_object_or_404( - Character.objects.filter(player=request.user), pk=pk + Character.objects.managed_by(request.user), pk=pk ) state = get_object_or_404(HarmfulState, pk=state_pk) character.states.add(state) diff --git a/src/common/models.py b/src/common/models.py index da17860..70871d1 100644 --- a/src/common/models.py +++ b/src/common/models.py @@ -19,6 +19,7 @@ class UniquelyNamedModel(models.Model): class Meta: abstract = True + ordering = ["name"] def __str__(self): return self.name