0

I need to get a FK info in logged User on ModelSerializer to add a new models.

In this case User->Business and Client->Business. When post client I need to set Business id using the logged user Business.

It's important to say all other models have the same behavior. I'm looking for some generic solution for this problem.

Client Model

class Client(SoftDeletionModel):
    object = ClientManager
    business = models.ForeignKey(Business, related_name='business_clients', on_delete=models.CASCADE)
    company_name = models.CharField(max_length=511, verbose_name=_('Company Name'))
    cnpj = models.CharField(max_length=14, verbose_name=_('CNPJ'))

User Model

class User(AbstractUser):
"""User model."""

    username = None
    email = models.EmailField(_('email address'), unique=True)
    business = models.ForeignKey(Business, related_name='business', on_delete=models.CASCADE, null=True)

ClientSerializer

class ClientSerializer(serializers.ModelSerializer):
    business = serializers.IntegerField() # here how can I get user.business?
    deleted_at = serializers.HiddenField(default=None)
    active = serializers.BooleanField(read_only=True)
    password = serializers.CharField(write_only=True, required=False, allow_blank=True)
    password_contract = Base64PDFFileField()

class Meta:
    model = Client
    fields = '__all__'
    validators = [
        UniqueTogetherValidator2(
            queryset=Client.objects.all(),
            fields=('cnpj', 'business'),
            message=_("CNPJ already exists"),
            key_field_name='cnpj'
        ),
        UniqueTogetherValidator2(
            queryset=Client.objects.all(),
            fields=('email', 'business'),
            message=_("Email already exists"),
            key_field_name='email'
        )
    ]
Lucas Weyne
  • 1,107
  • 7
  • 17

2 Answers2

1

Access request inside a serializer

Within the serializer you have access to the serializer context that can include the request instance

class ClientSerializer(serializers.ModelSerializer):
    ...

    def create(self, validated_data):
        return Client.objects.create(
            business=self.context['request'].user.business,
            **validated_data
        )

Request is only acessible if you pass it when instantiate the serializer

Pass extra arguments to a serializer via save()

It is also possible to pass extra arguments to a serializer during the save() method call

def create(self, request, **kwargs)
    serializer = ClientSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save(business=request.user.business)
    ...

Create a mixin to set business

Finally, a more reusable way is create a mixin for views that provides create and/or update actions, then overwrite perform_create() and perform_update() methods

class BusinessMixin:
    def perform_create(self, serializer):
        serializer.save(business=self.request.user.business)

    def perform_update(self, serializer):
        serializer.save(business=self.request.user.business)


class ClientViewSet(BusinessMixin, ModelViewSet):
    serializer_class = ClientSerializer
    queryset = Client.objects.all()
    ...

ModelViewSet (basicallyCreateModelMixin and UpdateModelMixin) use these methods to call the save() method from serializer when executing its actions (create(), update() and partial_update(), i.e. POST, PUT and PATCH)

Lucas Weyne
  • 1,107
  • 7
  • 17
  • Nice Lucas! I saw the first and second solution but really didn't like it. But your third solution is very nice and clever. In spite of that, my solution is bad for any reason ? Or yours better for some situations ? Thank you for your answer! – Vitor Hugo Morales Jan 08 '19 at 14:18
  • 1
    Set a `default` value for a serializer field does not prevent it from being supplied by a client request (check this [doc](https://www.django-rest-framework.org/api-guide/fields/#default)). My answer was based on this [similar question](https://stackoverflow.com/q/35518273/7727181) – Lucas Weyne Jan 08 '19 at 22:49
0

Inspired by serializers.CurrentUserDefault() magic I wrote CurrenUserBusinessDefault but set_context with current user business.

class CurrentUserBusinessDefault(object):
    def set_context(self, serializer_field):
        self.business =  serializer_field.context['request'].user.business

    def __call__(self):
        return self.business

    def __repr__(self):
        return unicode_to_repr('%s()' % self.__class__.__name__)

So it's accessible like the default method

class ClientSerializer(serializers.ModelSerializer):
    business = BusinessSerializer(default=CurrentUserBusinessDefault())