How to post foreign key with APIView

Hi,
I’m a bit newbie with django.
I want to do a POST in a endpoint and validate if the foreign key exists or not.
My models:

# TABELA DOS FORNECEDORES
class Fornecedor(models.Model):
    nome = models.CharField(max_length=100, null=False, blank=False)
    endereco = models.CharField(max_length=100, null=False, blank=False)
    codigo_postal = models.CharField(max_length=8, null=False, blank=False)
    cidade = models.CharField(max_length=50, null=False, blank=False)
    nif = models.IntegerField(
        null=False,
        blank=False,
        unique=True,
        validators=[
            RegexValidator(r"[1-9]\d*"),
            MinLengthValidator(9),
            MaxLengthValidator(9),
            MaxValueValidator(999999999),
            MinValueValidator(1),
        ],
    )
    email = models.EmailField(max_length=50, null=False, blank=False)
    data_registo = models.DateTimeField(auto_now_add=True)
    data_ultimo_login = models.DateTimeField(auto_now=True)
    ativo = models.BooleanField(default=True, null=False, blank=False)

    def __str__(self):
        return self.nome

# TABELA DAS MARCAS
class Marca(models.Model):
    nome = models.CharField(max_length=55, null=False, blank=False)
    fornecedor = models.ForeignKey(
        Fornecedor, on_delete=models.CASCADE, null=False, blank=False
    )

    def __str__(self):
        return self.nome

My serializers

class FornecedorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Fornecedor
        fields = "__all__"


class MarcaSerializer(serializers.ModelSerializer):
    class Meta:
        model = Marca
        fields = "__all__"

My views

class FornecedorPOST(APIView):
    @swagger_auto_schema(
        operation_summary="Criar um Fornecedor",
        operation_description="Criar um novo Fornecedor",
        request_body=FornecedorSerializer,
        responses={
            status.HTTP_201_CREATED: response_201(FornecedorSerializer),
            status.HTTP_400_BAD_REQUEST: response_400(FornecedorSerializer),
        },
    )
    def post(self, request, format=None):
        fornecedor = FornecedorSerializer(data=request.data)
        if fornecedor.is_valid():
            fornecedor.save()
            return Response(fornecedor.data, status=status.HTTP_201_CREATED)
        return Response(fornecedor.errors, status=status.HTTP_400_BAD_REQUEST)

class MarcaPOST(APIView):
    @swagger_auto_schema(
        operation_summary="Criar uma marca",
        operation_description="Criar uma nova marca",
        request_body=MarcaSerializer,
        responses={
            status.HTTP_201_CREATED: response_201(MarcaSerializer),
            status.HTTP_400_BAD_REQUEST: response_400(MarcaSerializer),
        },
    )
    def post(self, request, format=None):
        marca = MarcaSerializer(data=request.data)
        if marca.is_valid():
            marca.save()
            return Response(marca.data, status=status.HTTP_201_CREATED)
        return Response(marca.errors, status=status.HTTP_400_BAD_REQUEST)

My urls

urlpatterns = [
    # URLS API ENDPOINTS
    path("api/fornecedor/", views.FornecedorPOST.as_view(), name="add_single_supplier_api"),
    path("api/marca/", views.MarcaPOST.as_view(), name="add_single_brand_api"),

    path("api/docs", schema_view.with_ui("swagger", cache_timeout=0)),
]

Basically I want to add new Marca and use nif field from Fornecedor instead of Fornecedor pk and ofc validate if exists.

Thanks in advance.

G’day,

I would try and make your life a bit easier by using either a ModelViewset which gives you PUT, POST, DELETE and GET or using

from rest_framework.generics import CreateAPIView

class FornecedorCreateView(CreateAPIView):
    permission_classes = [IsAuthenticated]
    serializer_class = FornecedorSerializer
    model = Fornecedor

# URLs
    path("create-fornecedor", views.LoginView.as_view(), name="create_fornecedor"),

And that is that, you can now post to your URL and create your model. Alternatively, use a ModelViewSet which gives you all the HTTP verbs.

class FornecedorCreateViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated, IsAdminUser]
    model = Fornecedor
    serializer_class = FornecedorSerializer
    queryset = models.Fornecedor.objects.all()

URL

router = SimpleRouter(trailing_slash=False)

visit_router.register(
    "fornecedor",
    FornecedorCreateViewSet,
    basename="fornecedor_viewset",
)

urls = path("fornecedor/", include(vouter.urls)),

Now you can GET all objects and CREATE a objects single object at /fornecedor and DELETE, PUT and GET a single object at fornecedor/objects_pk

note: You can overide all of the classes function to include custom behaviour.

Have a look at Classy Based Views for DRF which shows all the classes and their methods: https://www.cdrf.co/

Hope that helps

Hi @conor thanks for help.
I changed all of my code for english to make it more understandable.
So, now Marca is Brand and Fornecedor is Supplier.
Maybe I do not explane correctly. I want the validation when I POST a new Marca in the endpoint.
All the routers and URLs are ok, I can POST new Marca using the default body, but I don’t want to use default body, I want to change the body to use nif field from Supplier instead of Supplier pk.
The body to create Marca is like this:
{ "name": "Teste", "Supplier": 1 }
On POST a new Marca I want to use nif instead of pk from Fornecedor and validate if exists or not.

You’re welcome. Just a thought, given that nif is unique and null=False, why not use it as the PK? If that’s possible, then it should make it a bit easier. If this is possible, then I recommend it, as you then can use the code as I wrote, which is the out-of-the-box way of doing it with DRF.

If you want to create a Fornecerdo and a Marca simultaneously, I would first create a Fornecerdo with one HTTP call, then using the return data, create the Marca.

Another thing to be aware of is that you can write your own custom save() method in the serializer. Something along these lines:

class MarcaSerializer(serializers.ModelSerializer):
    class Meta:
        model = Marca
        fields = "__all__"

  def save(self, **kwargs):
    fornecerdo_nif = **kwargs["nif"]
    try:
      fornecerdo_obj = Fernecerdo.objects.get(nif=fornecerdo_nif)
    except Fornecerdo.DoesNotExist
       # handle error
    new_kwargs = deepcopy(kwargs)  # copy the existing kwargs into new variable
    new_kwargs["nif"] = fornecerdo_obj.id # update the new kwargs with the FK to Fornecerdo
    marca = Marca.objects.create(**new_kwargs)  # create the object
    return marca  # return the new Marca

I don’t know your use case or requirements, but if I could I avoid having to overwrite the serializers save methods and use nif as the PK I would. You can view the source code here: ModelSerializer -- Classy DRF

Also note the original save() method is a lot more robust than the one I have written, so it is worth taking a look at it for some inspiration.

The last alternative is that you write a view function without DRF and write the whole thing by hand. DRF tightly couples the serializer to the view, and it is the serializer which performs the creation, deletion, modifying etc of objects. By writing your own function based view, you can put all your logic in your own function and avoid the tight coupling between DRF its views and its serializers.

Cheers,

Conor

1 Like