strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
https://strawberry.rocks/docs/django
MIT License
404 stars 117 forks source link

Update existing file #179

Closed webcircles-franco closed 1 year ago

webcircles-franco commented 2 years ago

Describe the Bug

Hello, I'm experiencing a problem updating a FileField on an existing instance, filename is correctly updated but file is not uploaded. If I upload the file when I create the instance, everything works fine. This is my code models.py

class WorkDimension(models.Model):
    person = models.OneToOneField(Person, on_delete=models.CASCADE, related_name='work_dimension', verbose_name=_('Person'))
    curriculum = models.FileField(verbose_name=_("Curriculum"), null=True, upload_to='curricula')

types.py

from strawberry.file_uploads import Upload

@strawberry.django.input(models.WorkDimension)
class WorkDimensionInput:
    person: int
    curriculum: Upload

@strawberry.django.input(models.WorkDimension, filters=WorkDimensionFilter, partial=True)
class WorkDimensionPartialInput:
    curriculum: Upload

schema.py

@strawberry.type
class DimensionsMutation:
    createWorkDimension: WorkDimension = mutations.create(WorkDimensionInput)
    updateWorkDimension: List[WorkDimension] = mutations.update(WorkDimensionPartialInput)

System Information

Additional Context

In my opinion the problem could be caused by updated mutation, queryset.update(**input_data) doesn't call model.save() so file isn't uploaded.

class DjangoUpdateMutation(
    DjangoMutationBase,
    StrawberryDjangoFieldFilters,
    StrawberryDjangoFieldBase,
    StrawberryField,
):
    @transaction.atomic
    def resolver(self, info, source, data, **kwargs):
        queryset = self.django_model.objects.all()
        queryset = self.get_queryset(queryset=queryset, info=info, data=data, **kwargs)
        input_data = get_input_data(self.input_type, data)
        queryset.update(**input_data)
        update_m2m(queryset, data)
        return queryset

Upvote & Fund

Fund with Polar

soloman-rai commented 1 year ago

Hey dev team,

I am currently facing the same issue with the default update mutation and as @webcircles-franco pointed out here, the problem might be due to the model.save() method not being called. Please have a look into this and help us with a solution as you can. For the record, I'll be mentioning this in the discord server as well.

@webcircles-franco looks like it'll take a minute to resolve this. is there any workaround or fix i can try for the time being ? did you figure something out ?

webcircles-franco commented 1 year ago

Hi @soloman-rai, my first "solution" was to avoid file update. Users had to delete and recreate files. It was acceptable because my use case was to store documents related to a person, so the only field in my model was a FileField.

Today i run into this issue again for a more complex model. To solve the problem i implemented a custom DjangoUpdateMutation and used it in schema

class DjangoUpdateFileMutation(
    DjangoMutationBase,
    StrawberryDjangoFieldFilters,
    StrawberryDjangoFieldBase,
    StrawberryField,
):
    @transaction.atomic
    def resolver(self, info, source, data, **kwargs):
        queryset = self.django_model.objects.all()
        queryset = self.get_queryset(queryset=queryset, info=info, data=data, **kwargs)
        input_data = get_input_data(self.input_type, data)
        keys_to_remove = []
        for k, v in input_data.items():
            if isinstance(self.django_model._meta.get_field(k), FileField):
                for instance in queryset:
                    if getattr(instance, k):
                        getattr(instance, k).delete()
                    setattr(instance, k, v)
                    instance.save()
                    keys_to_remove.append(k)
        for k in keys_to_remove:
            input_data.pop(k)
        queryset.update(**input_data)
        update_m2m(queryset, data)
        return queryset
def update_file(input_type=UNSET, filters=UNSET, permission_classes=[], **kwargs) -> Any:
    return DjangoUpdateFileMutation(
        input_type, filters=filters, permission_classes=permission_classes, **kwargs
    )
updateWorkDimension: List[WorkDimension] = update_file(WorkDimensionPartialInput, permission_classes=[IsAuthenticated])

Probably isn't the best solution but it seems to work