Skip to content

Commit

Permalink
Add Pinned Project Mutation
Browse files Browse the repository at this point in the history
- Create
- Reorder
- Delete
  • Loading branch information
sudan45 committed Jan 16, 2024
1 parent 4be78f0 commit 7c5b20a
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 15 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')
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),
),
]
1 change: 1 addition & 0 deletions apps/project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,3 +839,4 @@ class ProjectPinned(models.Model):
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)
81 changes: 74 additions & 7 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 @@ -45,14 +46,16 @@
ProjectMembershipGqlSerializer as ProjectMembershipSerializer,
ProjectUserGroupMembershipGqlSerializer as ProjectUserGroupMembershipSerializer,
ProjectVizConfigurationSerializer,
ProjectPinnedSerializer
UserPinnedProjectSerializer,
BulkProjectPinnedSerializer
)
from .schema import (
ProjectDetailType,
ProjectJoinRequestType,
ProjectMembershipType,
ProjectUserGroupMembershipType,
ProjectVizDataType,
UserPinnedProjectType
)


Expand Down Expand Up @@ -94,7 +97,12 @@

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

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


Expand Down Expand Up @@ -322,12 +330,13 @@ class Arguments:
permissions = [PP.Permission.UPDATE_PROJECT]


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


class ProjectMutationType(
Expand Down Expand Up @@ -358,7 +367,6 @@ class Meta:
project_viz_configuration_update = UpdateProjectVizConfiguration.Field()
unified_connector = graphene.Field(UnifiedConnectorMutationType)
assisted_tagging = graphene.Field(AssistedTaggingMutationType)
project_pinned = ProjectPinnedByUser.Field()

@staticmethod
def get_custom_node(_, info, id):
Expand All @@ -381,8 +389,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 compreshive 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()
17 changes: 17 additions & 0 deletions apps/project/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
ProjectOrganization,
ProjectStats,
RecentActivityType as ActivityTypes,
ProjectPinned
)
from .enums import (
ProjectPermissionEnum,
Expand Down Expand Up @@ -386,6 +387,17 @@ def resolve_public_url(root, info, **_):
return root.get_public_url(info.context.request)


class UserPinnedProjectType(ClientIdMixin, DjangoObjectType):
class Meta:
model = ProjectPinned
only_fields = (
"project",
"user",
"order",
"client_id",
)


class ProjectDetailType(
# -- Start --Project scopped entities
LeadQuery,
Expand Down Expand Up @@ -575,6 +587,7 @@ class Query:
page_size_query_param='pageSize'
)
)
pinned_project = 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 @@ -611,3 +624,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_pinned_project(root, info, **kwargs):
return ProjectPinned.objects.filter(user=info.context.user)
65 changes: 57 additions & 8 deletions apps/project/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,20 +890,69 @@ def create(self, validated_data):
return project


class ProjectPinnedSerializer(serializers.ModelSerializer):
class UserPinnedProjectSerializer(serializers.ModelSerializer):

class Meta:
model = ProjectPinned
fields = (
'order',
'project',
)

def validate(self, user):
if len(self.instance.user) > 5:
raise serializers.ValidationError('Only 5 project can be pinned')
return user
@cached_property
def current_user(self):
return self.context['request'].user

@cached_property
def get_queryset(self):
pinned_project = ProjectPinned.objects.filter(user=self.current_user)
return pinned_project

def validate(self, data):
if (self.get_queryset.count() >= 5):
raise serializers.ValidationError("User can pinned 5 project only!!!")
return data

def create(self, validated_data):
if self.get_queryset.filter(project=validated_data['project']).exists():
raise serializers.ValidationError("Project already pinned!!")
validated_data['user'] = self.current_user
if self.get_queryset:
validated_data['order'] = self.get_queryset.latest('order').order + 1
return super().create(validated_data)
validated_data['order'] = 1
return super().create(validated_data)

def update(self, validated_data):
pass
def update(self):
raise serializers.ValidationError("not allowed for update")


class BulkProjectPinnedSerializer(TempClientIdMixin, UserResourceSerializer):
id = serializers.IntegerField(required=True)

class Meta:
model = ProjectPinned
fields = (
'order',
'client_id',
'id'
)

@cached_property
def current_user(self):
return self.context['request'].user

@cached_property
def get_queryset(self):
pinned_project = ProjectPinned.objects.filter(user=self.current_user)
return pinned_project

def validate(self, data):
if (self.get_queryset.count() >= 5):
raise serializers.ValidationError("User can pinned 5 project only!!!")
return data

def create(self, validated_data):
raise serializers.ValidationError("Create not allowed")

def update(self, instance, validated_data):
return super().update(instance, validated_data)
40 changes: 40 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,12 @@ type BulkUpdateProjectMembership {
deletedResult: [ProjectMembershipType!]
}

input BulkUpdateProjectPinned {
order: Int!
clientId: String
id: Int!
}

type BulkUpdateProjectRegion {
errors: [[GenericScalar!]]
result: [RegionDetailType!]
Expand Down Expand Up @@ -1822,6 +1828,12 @@ type CreateUserGroup {
result: UserGroupType
}

type CreateUserPinnedProject {
errors: [GenericScalar!]
ok: Boolean
result: ProjectPinnedDataType
}

type CreateWrongPredictionReview {
errors: [GenericScalar!]
ok: Boolean
Expand Down Expand Up @@ -3073,6 +3085,9 @@ type Mutation {
joinProject(data: ProjectJoinRequestInputType!): CreateProjectJoin
projectJoinRequestDelete(projectId: ID!): ProjectJoinRequestDelete
project(id: ID!): ProjectMutationType
createUserPinnedProject(data: ProjectPinnedInputType): CreateUserPinnedProject
reorderPinnedProjects(items: [BulkUpdateProjectPinned!]): ReorderPinnedProjects
deleteUserPinnedProject(id: ID!): deleteUserPinnedProject
userGroupCreate(data: UserGroupInputType!): CreateUserGroup
userGroup(id: ID!): UserGroupMutationType
login(data: LoginInputType!): Login
Expand Down Expand Up @@ -3514,6 +3529,17 @@ enum ProjectPermission {
CREATE_ANALYSIS_MODULE
}

type ProjectPinnedDataType {
project: ProjectType!
user: UserType!
order: Int!
clientId: String!
}

input ProjectPinnedInputType {
project: ID!
}

type ProjectRoleType {
id: ID!
title: String!
Expand Down Expand Up @@ -3826,6 +3852,7 @@ type Query {
projectsByRegion(projectFilter: RegionProjectFilterData, ordering: String): ProjectByRegionListType
publicProjects(createdAt: DateTime, createdAtGte: DateTime, createdAtLte: DateTime, modifiedAt: DateTime, modifiedAtGte: DateTime, modifiedAtLte: DateTime, createdBy: [ID!], modifiedBy: [ID!], ids: [ID!], excludeIds: [ID!], status: ProjectStatusEnum, organizations: [ID!], analysisFrameworks: [ID!], regions: [ID!], search: String, isCurrentUserMember: Boolean, hasPermissionAccess: ProjectPermission, ordering: [PublicProjectOrderingEnum!], isTest: Boolean, page: Int = 1, pageSize: Int): PublicProjectListType
publicProjectsByRegion(projectFilter: RegionProjectFilterData, page: Int = 1, ordering: String, pageSize: Int): PublicProjectByRegionListType
pinnedProject: [ProjectPinnedDataType!]!
assistedTagging: AssistedTaggingRootQueryType
enums: AppEnumCollection
_debug: DjangoDebug
Expand Down Expand Up @@ -3922,6 +3949,13 @@ input RegisterInputType {
captcha: String!
}

type ReorderPinnedProjects {
errors: [GenericScalar!]
ok: Boolean
result: [ProjectPinnedDataType]
deletedResult: [ProjectPinnedDataType]
}

type ResetPassword {
errors: [GenericScalar!]
ok: Boolean!
Expand Down Expand Up @@ -4459,3 +4493,9 @@ type WrongPredictionReviewType {
modifiedBy: UserType
prediction: ID!
}

type deleteUserPinnedProject {
errors: [GenericScalar!]
ok: Boolean
result: ProjectPinnedDataType
}

0 comments on commit 7c5b20a

Please sign in to comment.