Skip to content

Commit

Permalink
Merge branch 'develop' into feature/user-permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
lukavdplas committed Dec 9, 2024
2 parents 4e68e1a + 782517e commit 4c7d689
Show file tree
Hide file tree
Showing 116 changed files with 3,545 additions and 478 deletions.
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ authors:
website: 'https://cdh.uu.nl/rsl/'
repository-code: 'https://github.com/CentreForDigitalHumanities/lettercraft'
license: BSD-3-Clause
version: 0.4.0
date-released: '2024-06-10'
version: 0.6.0
date-released: '2024-11-27'
12 changes: 12 additions & 0 deletions backend/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ def episode(db, source, agent_description, agent_description_2, letter_descripti
return event


@pytest.fixture()
def episode_2(db, source, agent_description, agent_description_2, letter_description):
event = Episode.objects.create(
name="Ernie eats a letter",
source=source,
)
event.agents.add(agent_description)
event.agents.add(agent_description_2)
event.letters.add(letter_description)
return event


@pytest.fixture()
def case_study(db):
case_study = CaseStudy.objects.create(name="Test Case Study")
Expand Down
2 changes: 2 additions & 0 deletions backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class Meta:
class SourceMention(models.TextChoices):
DIRECT = "direct", "directly mentioned"
IMPLIED = "implied", "implied"
UP_FOR_DEBATE = "up_for_debate", "up for debate"


class EntityDescription(Named, models.Model):
Expand Down Expand Up @@ -169,6 +170,7 @@ class EntityDescription(Named, models.Model):

class Meta:
abstract = True
order_with_respect_to = "source"

def __str__(self):
return f"{self.name} ({self.source})"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 4.2.7 on 2024-10-23 12:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('event', '0027_alter_episode_agents_alter_episode_gifts_and_more'),
]

operations = [
migrations.AlterField(
model_name='episode',
name='source_mention',
field=models.CharField(blank=True, choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], help_text='How is this entity presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='episodeagent',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='episodegift',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='episodeletter',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='episodespace',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.7 on 2024-10-30 13:39

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('event', '0028_alter_episode_source_mention_and_more'),
]

operations = [
migrations.AlterOrderWithRespectTo(
name='episode',
order_with_respect_to='source',
),
]
13 changes: 13 additions & 0 deletions backend/event/mutations/CreateEpisodeMutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,26 @@ def mutate(cls, root: None, info: ResolveInfo, episode_data: CreateEpisodeInput)
name=getattr(episode_data, "name"),
source=source,
)
cls.append_to_source_episode_order(episode)
cls.add_contribution(episode, episode_data, info)

user = info.context.user
episode.contributors.add(user)

return cls(episode=episode, errors=[]) # type: ignore

@staticmethod
def append_to_source_episode_order(episode: Episode) -> None:
"""
Append the episode ID to the source's episode order.
This makes sure that the newly created episode is at the bottom of the episode list.
"""
ordered_episodes = episode.source.get_episode_order()
episode_ids = list(ordered_episodes.values_list("id", flat=True))
episode_ids.append(episode.pk)
episode.source.set_episode_order(episode_ids)

@staticmethod
def add_contribution(obj: Episode, data: CreateEpisodeInput, info: ResolveInfo):
if info.context:
Expand Down
56 changes: 56 additions & 0 deletions backend/event/mutations/UpdateEpisodeOrderMutation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from graphene import Mutation, Boolean, List, NonNull, ID, ResolveInfo

from event.models import Episode
from graphql_app.types.LettercraftErrorType import LettercraftErrorType
from source.models import Source


class UpdateEpisodeOrderMutation(Mutation):
ok = Boolean(required=True)
errors = List(NonNull(LettercraftErrorType), required=True)

class Arguments:
episode_ids = List(
NonNull(ID), required=True, description="Ordered list of episode IDs"
)

@classmethod
def mutate(cls, root: None, info: ResolveInfo, episode_ids: list[str]):
if len(episode_ids) == 0:
error = LettercraftErrorType(
field="episode_ids", messages=["No episode IDs provided."]
)
return cls(ok=False, errors=[error]) # type: ignore

# Check if all episode IDs are valid.
episodes = Episode.objects.filter(id__in=episode_ids).prefetch_related("source")
if episodes.count() != len(episode_ids):
error = LettercraftErrorType(
field="episode_ids",
messages=["Not every provided episode ID is valid."],
)
return cls(ok=False, errors=[error]) # type: ignore

corresponding_sources: set[Source] = {episode.source for episode in episodes}

# Check if there is at least one source for the provided episode IDs.
# (There should always be.)
if len(corresponding_sources) == 0:
error = LettercraftErrorType(
field="episode_ids", messages=["No source found for given episode IDs."]
)
return cls(ok=False, errors=[error]) # type: ignore

# Check if all provided episode IDs belong to the same source.
if len(corresponding_sources) > 1:
error = LettercraftErrorType(
field="episode_ids",
messages=["The provided episode IDs belong to more than one source."],
)
return cls(ok=False, errors=[error]) # type: ignore

source = corresponding_sources.pop()

source.set_episode_order(episode_ids) # type: ignore

return cls(ok=True, errors=[]) # type: ignore
29 changes: 29 additions & 0 deletions backend/event/tests/test_event_mutations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.db.models.query import QuerySet


def test_episode_order_mutation(graphql_client, episode, episode_2):
result = graphql_client.execute(
f"""
mutation TestMutation {{
updateEpisodeOrder(
episodeIds: [
{episode_2.pk}, {episode.pk}
]
) {{
ok
errors {{
field
messages
}}
}}
}}
"""
)

assert result["data"]["updateEpisodeOrder"]["ok"] == True
assert result["data"]["updateEpisodeOrder"]["errors"] == []

order_queryset = episode.source.get_episode_order() # type: QuerySet
ids_list = list(order_queryset.values_list("id", flat=True))

assert ids_list == [episode_2.pk, episode.pk]
8 changes: 8 additions & 0 deletions backend/graphql_app/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from event.mutations.CreateEpisodeMutation import CreateEpisodeMutation
from event.mutations.DeleteEpisodeMutation import DeleteEpisodeMutation
from event.mutations.UpdateEpisodeOrderMutation import UpdateEpisodeOrderMutation
from event.queries import EventQueries
from letter.queries import LetterQueries
from person.queries import PersonQueries
Expand Down Expand Up @@ -29,6 +30,9 @@
from letter.mutations.CreateGiftMutation import CreateGiftMutation
from letter.mutations.DeleteGiftMutation import DeleteGiftMutation
from letter.mutations.UpdateGiftMutation import UpdateGiftMutation
from space.mutations.CreateSpaceMutation import CreateSpaceMutation
from space.mutations.DeleteSpaceMutation import DeleteSpaceMutation
from space.mutations.UpdateSpaceMutation import UpdateSpaceMutation
from person.mutations.CreatePersonReferenceMutation import CreatePersonReferenceMutation
from person.mutations.UpdatePersonReferenceMutation import UpdatePersonReferenceMutation
from person.mutations.DeletePersonReferenceMutation import DeletePersonReferenceMutation
Expand All @@ -54,6 +58,7 @@ class Mutation(ObjectType):
create_episode_entity_link = CreateEpisodeEntityLinkMutation.Field()
update_episode_entity_link = UpdateEpisodeEntityLinkMutation.Field()
delete_episode_entity_link = DeleteEpisodeEntityLinkMutation.Field()
update_episode_order = UpdateEpisodeOrderMutation.Field()
create_agent = CreateAgentMutation.Field()
update_agent = UpdateAgentMutation.Field()
delete_agent = DeleteAgentMutation.Field()
Expand All @@ -63,6 +68,9 @@ class Mutation(ObjectType):
create_gift = CreateGiftMutation.Field()
update_gift = UpdateGiftMutation.Field()
delete_gift = DeleteGiftMutation.Field()
create_space = CreateSpaceMutation.Field()
update_space = UpdateSpaceMutation.Field()
delete_space = DeleteSpaceMutation.Field()
create_person_reference = CreatePersonReferenceMutation.Field()
update_person_reference = UpdatePersonReferenceMutation.Field()
delete_person_reference = DeletePersonReferenceMutation.Field()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.7 on 2024-10-23 12:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('letter', '0020_remove_giftdescription_designators_and_more'),
]

operations = [
migrations.AlterField(
model_name='giftdescription',
name='source_mention',
field=models.CharField(blank=True, choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], help_text='How is this entity presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='giftdescriptioncategory',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='letterdescription',
name='source_mention',
field=models.CharField(blank=True, choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], help_text='How is this entity presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='letterdescriptioncategory',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.7 on 2024-10-30 13:39

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('letter', '0021_alter_giftdescription_source_mention_and_more'),
]

operations = [
migrations.AlterOrderWithRespectTo(
name='giftdescription',
order_with_respect_to='source',
),
migrations.AlterOrderWithRespectTo(
name='letterdescription',
order_with_respect_to='source',
),
]
3 changes: 3 additions & 0 deletions backend/lettercraft/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
"dj_rest_auth.registration",
"allauth",
"allauth.account",
# Required for deleting accounts, but not actually used,
# cf. https://github.com/iMerica/dj-rest-auth/pull/110.
"allauth.socialaccount",
"revproxy",
"core",
"case_study",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.7 on 2024-10-23 12:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('person', '0023_remove_agentdescription_designators'),
]

operations = [
migrations.AlterField(
model_name='agentdescription',
name='source_mention',
field=models.CharField(blank=True, choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], help_text='How is this entity presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='agentdescriptiongender',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
migrations.AlterField(
model_name='agentdescriptionlocation',
name='source_mention',
field=models.CharField(choices=[('direct', 'directly mentioned'), ('implied', 'implied'), ('up_for_debate', 'up for debate')], default='direct', help_text='How is this information presented in the text?', max_length=32),
),
]
9 changes: 8 additions & 1 deletion backend/source/types/SourceType.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from graphene import Field, Int, List, NonNull, ResolveInfo
from django.db.models import QuerySet, Value, Case, When, F
from django.db.models import QuerySet
from graphene_django import DjangoObjectType
from typing import Type

Expand All @@ -17,6 +17,7 @@
from space.models import SpaceDescription
from space.types.SpaceDescriptionType import SpaceDescriptionType


class SourceType(DjangoObjectType):
episodes = List(NonNull(EpisodeType), required=True)
num_of_episodes = Int(required=True)
Expand All @@ -38,6 +39,12 @@ class Meta:
"edition_author",
]

# It would be proper to decorate _entity_resolver() with @staticmethod,
# but we are running Python 3.9 on the server, which does not support
# calling static methods as regular functions, so the tests fail.
# This is solved in Python 3.10, cf. https://github.com/python/cpython/issues/87848

# @staticmethod
def _entity_resolver(
Model: Type[EntityDescription], OutputType: Type[DjangoObjectType]
):
Expand Down
Loading

0 comments on commit 4c7d689

Please sign in to comment.