Skip to content

Commit

Permalink
Merge pull request #1394 from the-deep/feature/pinned-project
Browse files Browse the repository at this point in the history
Feature/pinned project
  • Loading branch information
AdityaKhatri authored Apr 4, 2024
2 parents 2da279a + 04ccec4 commit 30abb9d
Show file tree
Hide file tree
Showing 11 changed files with 414 additions and 2 deletions.
6 changes: 6 additions & 0 deletions apps/project/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
ProjectJoinRequest,
ProjectOrganization,
ProjectChangeLog,
ProjectPinned
)

TRIGGER_LIMIT = 5
Expand Down Expand Up @@ -213,3 +214,8 @@ def has_add_permission(self, request, obj=None):
@admin.display(description='Diff pretty JSON')
def diff_pretty(self, obj):
return mark_safe(f'<pre>{json.dumps(obj.diff, indent=2)}</pre>')


@admin.register(ProjectPinned)
class ProjectPinnedAdmin(admin.ModelAdmin):
list_display = ('id', 'project', 'user', 'order')
6 changes: 6 additions & 0 deletions apps/project/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Project,
ProjectJoinRequest,
ProjectOrganization,
ProjectPinned,
)


Expand All @@ -31,3 +32,8 @@ class Meta:
class ProjectOrganizationFactory(DjangoModelFactory):
class Meta:
model = ProjectOrganization


class ProjectPinnedFactory(DjangoModelFactory):
class Meta:
model = ProjectPinned
26 changes: 26 additions & 0 deletions apps/project/migrations/0005_projectpinned.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 3.2.17 on 2023-12-11 08:29

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


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('project', '0004_project_enable_publicly_viewable_analysis_report_snapshot'),
]

operations = [
migrations.CreateModel(
name='ProjectPinned',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='project.project')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
18 changes: 18 additions & 0 deletions apps/project/migrations/0006_projectpinned_modified_at.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2024-01-09 06:11

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('project', '0005_projectpinned'),
]

operations = [
migrations.AddField(
model_name='projectpinned',
name='modified_at',
field=models.DateTimeField(auto_now=True),
),
]
14 changes: 14 additions & 0 deletions apps/project/migrations/0007_merge_20240218_0618.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 3.2.17 on 2024-02-18 06:18

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('project', '0005_merge_20231227_0610'),
('project', '0006_projectpinned_modified_at'),
]

operations = [
]
8 changes: 8 additions & 0 deletions apps/project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,3 +835,11 @@ class Action(models.IntegerChoices):
user = models.ForeignKey(User, on_delete=models.PROTECT)
action = models.SmallIntegerField(choices=Action.choices)
diff = models.JSONField(null=True, blank=True)


class ProjectPinned(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
order = models.PositiveIntegerField()
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
83 changes: 83 additions & 0 deletions apps/project/mutation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.db import transaction
from django.utils.translation import gettext

import graphene
Expand Down Expand Up @@ -37,6 +38,7 @@
ProjectMembership,
ProjectUserGroupMembership,
ProjectRole,
ProjectPinned
)
from .serializers import (
ProjectGqSerializer,
Expand All @@ -45,13 +47,16 @@
ProjectMembershipGqlSerializer as ProjectMembershipSerializer,
ProjectUserGroupMembershipGqlSerializer as ProjectUserGroupMembershipSerializer,
ProjectVizConfigurationSerializer,
UserPinnedProjectSerializer,
BulkProjectPinnedSerializer
)
from .schema import (
ProjectDetailType,
ProjectJoinRequestType,
ProjectMembershipType,
ProjectUserGroupMembershipType,
ProjectVizDataType,
UserPinnedProjectType
)


Expand Down Expand Up @@ -91,6 +96,16 @@
serializer_class=ProjectVizConfigurationSerializer,
)

ProjectPinnedInputType = generate_input_type_for_serializer(
'ProjectPinnedInputType',
serializer_class=UserPinnedProjectSerializer
)

UserPinnedProjectReOrderInputType = generate_input_type_for_serializer(
'UserPinnedProjectReOrderInputType',
serializer_class=BulkProjectPinnedSerializer,
)


class CreateProject(GrapheneMutation):
class Arguments:
Expand Down Expand Up @@ -316,6 +331,15 @@ class Arguments:
permissions = [PP.Permission.UPDATE_PROJECT]


class CreateUserPinnedProject(PsGrapheneMutation):
class Arguments:
data = ProjectPinnedInputType(required=True)
model = ProjectPinned
result = graphene.Field(UserPinnedProjectType)
serializer_class = UserPinnedProjectSerializer
permissions = []


class ProjectMutationType(
# --Begin Project Scoped Mutation
LeadMutation,
Expand Down Expand Up @@ -367,8 +391,67 @@ def resolve_assisted_tagging(root, info, **kwargs):
return {}


class ReorderPinnedProjects(PsGrapheneMutation):
class Arguments:
items = graphene.List(graphene.NonNull(UserPinnedProjectReOrderInputType))
model = ProjectPinned
result = graphene.List(UserPinnedProjectType)
serializer_class = BulkProjectPinnedSerializer
permissions = []

@classmethod
@transaction.atomic()
def perform_mutate(cls, root, info, **kwargs):
errors_data = []
serializers_data = []
results = []
for data in kwargs['items']:
instance, errors = cls.get_object(info, id=data['id'])
if errors:
errors_data.append(errors)
serializer = cls.serializer_class(data=data, instance=instance, context={'request': info.context.request})
errors_data.append(mutation_is_not_valid(serializer)) # errors_data also add empty list
serializers_data.append(serializer)
errors_data = [items for items in errors_data if items] # list comprehension removing empty list
if errors_data:
return cls(errors=errors_data, ok=False)
for serializer in serializers_data:
results.append(serializer.save())
return cls(result=results, ok=True)


class DeleteUserPinnedProject(DeleteMutation):
class Arguments:
id = graphene.ID(required=True)
model = ProjectPinned
result = graphene.Field(UserPinnedProjectType)
permissions = []

@staticmethod
def mutate(root, info, id):

project_pinned_qs = ProjectPinned.objects.filter(
id=id,
user=info.context.user
)
if not project_pinned_qs.exists():
return DeleteUserPinnedProject(errors=[
dict(
field='nonFieldErrors',
messages=gettext(
'Not authorize the unpinned project '
),
)
], ok=False)
project_pinned_qs.delete()
return DeleteUserPinnedProject(result=root, errors=None, ok=True)


class Mutation(object):
project_create = CreateProject.Field()
join_project = CreateProjectJoin.Field()
project_join_request_delete = ProjectJoinRequestDelete.Field()
project = DjangoObjectField(ProjectMutationType)
create_user_pinned_project = CreateUserPinnedProject.Field()
reorder_pinned_projects = ReorderPinnedProjects.Field()
delete_user_pinned_project = DeleteUserPinnedProject.Field()
38 changes: 38 additions & 0 deletions apps/project/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
ProjectOrganization,
ProjectStats,
RecentActivityType as ActivityTypes,
ProjectPinned
)
from .enums import (
ProjectPermissionEnum,
Expand Down Expand Up @@ -250,6 +251,8 @@ class Meta:
status_display = EnumDescription(source='get_status_display', required=True)
organizations = graphene.List(graphene.NonNull(ProjectOrganizationType))
has_analysis_framework = graphene.Boolean(required=True)
has_assessment_template = graphene.Boolean(required=True)
is_project_pinned = graphene.Boolean(required=True)

# NOTE: This is a custom feature
# see: https://github.com/eamigo86/graphene-django-extras/compare/graphene-v2...the-deep:graphene-v2
Expand Down Expand Up @@ -297,6 +300,12 @@ def resolve_regions(root, info, **kwargs):
return info.context.dl.project.geo_region.load(root.pk)
return info.context.dl.project.public_geo_region.load(root.pk)

def resolve_is_project_pinned(root, info, **kwargs):
return ProjectPinned.objects.filter(
project=root,
user=info.context.request.user
).exists()


class RecentActivityType(graphene.ObjectType):
id = graphene.ID(required=True)
Expand Down Expand Up @@ -446,6 +455,10 @@ class Meta:
# Other scoped queries
unified_connector = graphene.Field(UnifiedConnectorQueryType)
assisted_tagging = graphene.Field(AssistedTaggingQueryType)
is_project_pinned = graphene.Boolean(
required=True,
description='Check if user have pinned the project'
)

@staticmethod
def resolve_user_members(root, info, **kwargs):
Expand Down Expand Up @@ -495,6 +508,26 @@ def resolve_assisted_tagging(root, info, **kwargs):
if root.get_current_user_role(info.context.request.user) is not None:
return {}

@staticmethod
def resolve_is_project_pinned(root, info, **kwargs):
return ProjectPinned.objects.filter(
project=root,
user=info.context.request.user
).exists()


class UserPinnedProjectType(ClientIdMixin, DjangoObjectType):
class Meta:
model = ProjectPinned
only_fields = (
'id',
"project",
"user",
"order",
"client_id",
)
project = graphene.Field(graphene.NonNull(ProjectDetailType))


class ProjectByRegion(graphene.ObjectType):
id = graphene.ID(required=True, description='Region\'s ID')
Expand Down Expand Up @@ -576,6 +609,7 @@ class Query:
page_size_query_param='pageSize'
)
)
user_pinned_projects = DjangoListField(UserPinnedProjectType, required=True)

# NOTE: This is a custom feature, see https://github.com/the-deep/graphene-django-extras
# see: https://github.com/eamigo86/graphene-django-extras/compare/graphene-v2...the-deep:graphene-v2
Expand Down Expand Up @@ -612,3 +646,7 @@ def resolve_public_projects(root, info, **kwargs) -> QuerySet:
@staticmethod
def resolve_public_projects_by_region(*args, **kwargs):
return Query.resolve_projects_by_region(*args, **kwargs)

@staticmethod
def resolve_user_pinned_project(root, info, **kwargs):
return ProjectPinned.objects.filter(user=info.context.user)
Loading

0 comments on commit 30abb9d

Please sign in to comment.