Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deleting items by id #50

Open
ccsv opened this issue Jul 26, 2022 · 3 comments
Open

Deleting items by id #50

ccsv opened this issue Jul 26, 2022 · 3 comments

Comments

@ccsv
Copy link
Contributor

ccsv commented Jul 26, 2022

I find this to be a good way of setting up a delete by id:


type_defs = gql("""
   type Document {
        id:ID
        name: String!
   }

 type DeleteResults{
       success:[String]
       errors:[String]
   }

type Mutation{
DeleteDocument(input:[DocumentUpdate]): DeleteResults
}
"""

@mutation.field("DeleteDocument")
def DeleteDocument(*_, input):
    deleted =[]
    errors =[]
    for inputs in input:
        try:
           id = inputs.get("id")
           doc = Document.objects.get(pk=id)
           deleted.append("id "+ id+": "+ doc.name +" document deleted")
           doc.delete()
        except Document.DoesNotExist:
            errors.append("id: "+id+" not found")
    return {'success': deleted, 'errors': errors}

For the function just replace the model Document with whatever model. The results are the same for any model where a string is returned of the items you deleted and the errors. Is there a way some way to write the code so we can just plug in the django models to a function / class in this setup so less code is written? (I am not good at meta programming but find myself repeating this pattern)

@maxmorlocke
Copy link
Contributor

I built something like this internally, but never really built up the test coverage, etc. to make me comfortable with adding it to this package. Here's the resolver that can be subclassed:

from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist


class DeletionResolver:
    model_lookup_field = "id"

    def __init__(self, info=None, lookup_value=None):
        required_arguments = ["model_lookup_field"]
        required_fields_missing = []
        for required_argument in required_arguments:
            if getattr(self, required_argument, None) is None:
                required_fields_missing.append(required_argument)

        if required_fields_missing:
            raise ImproperlyConfigured(f"Need to define {required_fields_missing}")

        self.graphql_info = info
        self.model_lookup_value = lookup_value

    def get_queryset(self):
        raise NotImplementedError("Override me...")

    def get_object(self):
        qs = self.get_queryset()
        try:
            lookup_dict = self.get_lookup_dict()
            instance = qs.get(**lookup_dict)
        except ObjectDoesNotExist:
            instance = None
        return instance

    def destroy(self, *args, **kwargs):
        instance = self.get_object()
        original_instance = instance
        self.perform_destroy(instance)
        setattr(original_instance, self.model_lookup_field, self.model_lookup_value)
        return original_instance

    def perform_destroy(self, instance):
        if instance:
            instance.delete()

    def get_lookup_dict(self):
        return {self.model_lookup_field: self.model_lookup_value}

Here's an example of it in use:

from ariadne import gql

from core.graphql.resolvers.deletion_resolver import DeletionResolver
from resetbutton.graphql.decorators.assess_state_required import assess_state_required
from resetbutton.graphql.decorators.login_required import login_required
from resetbutton.graphql.mutation_type import mutation


type_defs = gql(
    """
    extend type Mutation {
        deleteFamilyMemberRecord(familyMemberId: UUID!): FamilyMemberRecordDeleteResult!
    }
    """
)


class FamilyMemberRecordDeletionResolver(DeletionResolver):
    object_lookup_field = "id"

    def get_queryset(self):
        return self.graphql_info.context["request"].user.filingperson.family_profile.familymemberrecord_set

    @mutation.field("deleteFamilyMemberRecord")
    @login_required()
    @assess_state_required()
    def __call__(self, info, familyMemberId, *args, **kwargs):
        obj = FamilyMemberRecordDeletionResolver(info=info, lookup_value=familyMemberId).destroy()
        return obj

There's still some boring repetitive code, but it's a smidge less and much more favorable to quick copypasta.

@ccsv
Copy link
Contributor Author

ccsv commented Jul 26, 2022

@maxmorlocke I have not written test for mine either. I think this is useful to but it is easy to do the delete mutation and queries. When you get to the update and create it gets kind of difficult especially if you have nested foreign keys and trying to process them in bulk.

@maxmorlocke
Copy link
Contributor

We did create and update pretty well while also handling nesting. This required a pretty customized set of helpers to our DRF serializer though, as it doesn't handle child objects very well (e.g. If I have a child object and a child serializer, I want parent.validate to call child.validate and produce the 'correct' error messages). That being said, bulk updates and creates would definitely be a pain point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants