Skip to content

Commit

Permalink
feat: track whether sites and projects have been seen by users
Browse files Browse the repository at this point in the history
  • Loading branch information
shrouxm committed Sep 14, 2023
1 parent d57b8cf commit 4bc0e9f
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 8 deletions.
11 changes: 10 additions & 1 deletion terraso_backend/apps/graphql/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,17 @@
ProjectAddMutation,
ProjectArchiveMutation,
ProjectDeleteMutation,
ProjectMarkSeenMutation,
ProjectNode,
ProjectUpdateMutation,
)
from .sites import SiteAddMutation, SiteDeleteMutation, SiteNode, SiteUpdateMutation
from .sites import (
SiteAddMutation,
SiteDeleteMutation,
SiteMarkSeenMutation,
SiteNode,
SiteUpdateMutation,
)
from .story_maps import (
StoryMapDeleteMutation,
StoryMapMembershipApproveMutation,
Expand Down Expand Up @@ -157,10 +164,12 @@ class Mutations(graphene.ObjectType):
add_site = SiteAddMutation.Field()
update_site = SiteUpdateMutation.Field()
delete_site = SiteDeleteMutation.Field()
mark_site_seen = SiteMarkSeenMutation.Field()
add_project = ProjectAddMutation.Field()
update_project = ProjectUpdateMutation.Field()
archive_project = ProjectArchiveMutation.Field()
delete_project = ProjectDeleteMutation.Field()
mark_project_seen = ProjectMarkSeenMutation.Field()
update_soil_data = SoilDataUpdateMutation.Field()
update_depth_dependent_soil_data = DepthDependentSoilDataUpdateMutation.Field()

Expand Down
30 changes: 29 additions & 1 deletion terraso_backend/apps/graphql/schema/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@
from apps.project_management.models import Project
from apps.project_management.models.sites import Site

from .commons import BaseDeleteMutation, BaseWriteMutation, TerrasoConnection
from .commons import (
BaseAuthenticatedMutation,
BaseDeleteMutation,
BaseMutation,
BaseWriteMutation,
TerrasoConnection,
)


class ProjectFilterSet(FilterSet):
Expand All @@ -39,6 +45,7 @@ class Meta:

class ProjectNode(DjangoObjectType):
id = graphene.ID(source="pk", required=True)
seen = graphene.Boolean(required=True)

class Meta:
model = Project
Expand All @@ -49,6 +56,12 @@ class Meta:
interfaces = (relay.Node,)
connection_class = TerrasoConnection

def resolve_seen(self, info):
user = info.context.user
if user.is_anonymous:
return True
return self.seen_by.filter(id=user.id).exists()


class ProjectPrivacy(graphene.Enum):
PRIVATE = Project.PRIVATE
Expand All @@ -74,6 +87,7 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
kwargs["privacy"] = kwargs["privacy"].value
result = super().mutate_and_get_payload(root, info, **kwargs)
result.project.add_manager(user)
result.project.mark_seen_by(user)

client_time = kwargs.get("client_time", None)
if not client_time:
Expand All @@ -90,6 +104,20 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
return result


class ProjectMarkSeenMutation(BaseAuthenticatedMutation):
project = graphene.Field(ProjectNode, required=True)

class Input:
id = graphene.ID(required=True)

@classmethod
def mutate_and_get_payload(cls, root, info, id):
user = info.context.user
project = BaseMutation.get_or_throw(Project, "id", id)
project.mark_seen_by(user)
return ProjectMarkSeenMutation(project=project)


class ProjectDeleteMutation(BaseDeleteMutation):
project = graphene.Field(ProjectNode, required=True)

Expand Down
26 changes: 26 additions & 0 deletions terraso_backend/apps/graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ type ProjectNode implements Node {
orderBy: String
): SiteNodeConnection!
id: ID!
seen: Boolean!
}

"""An enumeration."""
Expand Down Expand Up @@ -695,6 +696,7 @@ type SiteNode implements Node {
archived: Boolean!
soilData: SoilDataNode
id: ID!
seen: Boolean!
}

"""An enumeration."""
Expand Down Expand Up @@ -1290,10 +1292,12 @@ type Mutations {
addSite(input: SiteAddMutationInput!): SiteAddMutationPayload!
updateSite(input: SiteUpdateMutationInput!): SiteUpdateMutationPayload!
deleteSite(input: SiteDeleteMutationInput!): SiteDeleteMutationPayload!
markSiteSeen(input: SiteMarkSeenMutationInput!): SiteMarkSeenMutationPayload!
addProject(input: ProjectAddMutationInput!): ProjectAddMutationPayload!
updateProject(input: ProjectUpdateMutationInput!): ProjectUpdateMutationPayload!
archiveProject(input: ProjectArchiveMutationInput!): ProjectArchiveMutationPayload!
deleteProject(input: ProjectDeleteMutationInput!): ProjectDeleteMutationPayload!
markProjectSeen(input: ProjectMarkSeenMutationInput!): ProjectMarkSeenMutationPayload!
updateSoilData(input: SoilDataUpdateMutationInput!): SoilDataUpdateMutationPayload!
updateDepthDependentSoilData(input: DepthDependentSoilDataUpdateMutationInput!): DepthDependentSoilDataUpdateMutationPayload!
}
Expand Down Expand Up @@ -1737,6 +1741,17 @@ input SiteDeleteMutationInput {
clientMutationId: String
}

type SiteMarkSeenMutationPayload {
errors: GenericScalar
site: SiteNode!
clientMutationId: String
}

input SiteMarkSeenMutationInput {
id: ID!
clientMutationId: String
}

type ProjectAddMutationPayload {
errors: GenericScalar
project: ProjectNode!
Expand Down Expand Up @@ -1793,6 +1808,17 @@ input ProjectDeleteMutationInput {
clientMutationId: String
}

type ProjectMarkSeenMutationPayload {
errors: GenericScalar
project: ProjectNode!
clientMutationId: String
}

input ProjectMarkSeenMutationInput {
id: ID!
clientMutationId: String
}

type SoilDataUpdateMutationPayload {
errors: GenericScalar
soilData: SoilDataNode
Expand Down
30 changes: 29 additions & 1 deletion terraso_backend/apps/graphql/schema/sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
from apps.audit_logs import api as audit_log_api
from apps.project_management.models import Project, Site, sites

from .commons import BaseDeleteMutation, BaseWriteMutation, TerrasoConnection
from .commons import (
BaseAuthenticatedMutation,
BaseDeleteMutation,
BaseMutation,
BaseWriteMutation,
TerrasoConnection,
)
from .constants import MutationTypes


Expand All @@ -47,6 +53,7 @@ class Meta:

class SiteNode(DjangoObjectType):
id = graphene.ID(source="pk", required=True)
seen = graphene.Boolean(required=True)

class Meta:
model = Site
Expand Down Expand Up @@ -78,6 +85,12 @@ def get_queryset(cls, queryset, info):
def privacy_enum(cls):
return cls._meta.fields["privacy"].type.of_type()

def resolve_seen(self, info):
user = info.context.user
if user.is_anonymous:
return True
return self.seen_by.filter(id=user.id).exists()


class SiteAddMutation(BaseWriteMutation):
site = graphene.Field(SiteNode, required=True)
Expand Down Expand Up @@ -117,6 +130,7 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
return result

site = result.site
site.mark_seen_by(user)
metadata = {
"latitude": kwargs["latitude"],
"longitude": kwargs["longitude"],
Expand All @@ -134,6 +148,20 @@ def mutate_and_get_payload(cls, root, info, **kwargs):
return result


class SiteMarkSeenMutation(BaseAuthenticatedMutation):
site = graphene.Field(SiteNode, required=True)

class Input:
id = graphene.ID(required=True)

@classmethod
def mutate_and_get_payload(cls, root, info, id):
user = info.context.user
site = BaseMutation.get_or_throw(Site, "id", id)
site.mark_seen_by(user)
return SiteMarkSeenMutation(site=site)


class SiteUpdateMutation(BaseWriteMutation):
site = graphene.Field(SiteNode)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.5 on 2023-09-14 23:47

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("project_management", "0014_site_privacy_alter_project_description"),
]

operations = [
migrations.AddField(
model_name="project",
name="seen_by",
field=models.ManyToManyField(related_name="+", to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name="site",
name="seen_by",
field=models.ManyToManyField(related_name="+", to=settings.AUTH_USER_MODEL),
),
]
5 changes: 5 additions & 0 deletions terraso_backend/apps/project_management/models/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class Meta(BaseModel.Meta):
max_length=32, choices=PRIVACY_STATUS, default=DEFAULT_PRIVACY_STATUS
)

seen_by = models.ManyToManyField(User, related_name="+")

@staticmethod
def default_settings():
settings = ProjectSettings()
Expand Down Expand Up @@ -105,5 +107,8 @@ def add_manager(self, user: User):
def add_member(self, user: User):
return self.group.add_member(user)

def mark_seen_by(self, user: User):
self.seen_by.add(user)

def __str__(self):
return self.name
5 changes: 5 additions & 0 deletions terraso_backend/apps/project_management/models/sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class Meta(BaseModel.Meta):
verbose_name="owner to which the site belongs",
)

seen_by = models.ManyToManyField(User, related_name="+")

PRIVATE = "private"
PUBLIC = "public"
DEFAULT_PRIVACY_STATUS = PRIVATE
Expand Down Expand Up @@ -97,6 +99,9 @@ def owned_by(self, obj: Union[Project, User]):
def human_readable(self) -> str:
return self.name

def mark_seen_by(self, user: User):
self.seen_by.add(user)


def filter_only_sites_user_owner_or_member(user: User, queryset):
return queryset.filter(
Expand Down
46 changes: 41 additions & 5 deletions terraso_backend/tests/graphql/mutations/test_projects.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json

import pytest
import structlog
from graphene_django.utils.testing import graphql_query
from mixer.backend.django import mixer

Expand All @@ -12,19 +13,24 @@

pytestmark = pytest.mark.django_db

logger = structlog.get_logger(__name__)

def test_create_project(client, user):
client.force_login(user)
response = graphql_query(
"""
CREATE_PROJECT_QUERY = """
mutation createProject($input: ProjectAddMutationInput!) {
addProject(input: $input) {
project {
id
seen
}
}
}
""",
"""


def test_create_project(client, user):
client.force_login(user)
response = graphql_query(
CREATE_PROJECT_QUERY,
variables={
"input": {"name": "testProject", "privacy": "PRIVATE", "description": "A test project"}
},
Expand Down Expand Up @@ -194,3 +200,33 @@ def test_update_project_user_not_manager(project, client):
error_result = response.json()["data"]["updateProject"]["errors"][0]["message"]
json_error = json.loads(error_result)
assert json_error[0]["code"] == "change_not_allowed"


def test_mark_project_seen(client, user):
client.force_login(user)
response = graphql_query(
CREATE_PROJECT_QUERY,
variables={"input": {"name": "project", "privacy": "PUBLIC"}},
client=client,
)
project = response.json()["data"]["addProject"]["project"]
assert project["seen"] is True

client.force_login(mixer.blend(User))
response = graphql_query(
"query project($id: ID!){ project(id: $id) { seen } }",
variables={"id": project["id"]},
client=client,
)
assert response.json()["data"]["project"]["seen"] is False

response = graphql_query(
"""
mutation($input: ProjectMarkSeenMutationInput!){
markProjectSeen(input: $input) { project { seen } }
}
""",
variables={"input": {"id": project["id"]}},
client=client,
)
assert response.json()["data"]["markProjectSeen"]["project"]["seen"] is True
Loading

0 comments on commit 4bc0e9f

Please sign in to comment.