0

I am trying to use nested serializers in my app. I followed documentation seen on DRF website but it seems there is a problem with inner serializer visibility. The error message:

Exception Type: AttributeError
Exception Value:    
Got AttributeError when attempting to get a value for field `produkty` on serializer `ZamowieniaSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Zamowienie` instance.
Original exception text was: 'Zamowienie' object has no attribute 'produkty'.

models.py

class Zamowienie(models.Model):
    kontrahent = models.ForeignKey(Kontrahent, on_delete=models.PROTECT)
    my_id = models.CharField(max_length=60, unique=True)
    data_zal = models.DateTimeField()
    data_realizacji = models.DateField()
    timestamp = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=50, choices=STATUS_CHOICES, default="0")
    komentarz = models.CharField(max_length=100, unique=False, default="")
    uwagi = models.CharField(max_length=150, unique=False, default="")
    zam_knt_id = models.CharField(max_length=50, unique=False)

    class Meta:
        ordering = ['-data_zal']
        verbose_name_plural = 'Zamowienia'

    objects = models.Manager()

    def __str__(self):
        return str(self.my_id)

    def save(self, *args, **kwargs):
        licznik = Zamowienie.objects.filter(Q(kontrahent=self.kontrahent) &
                                            Q(timestamp__year=timezone.now().year)).count() + 1
        self.my_id = str(licznik) + "/" + self.kontrahent.nazwa + "/" + str(timezone.now().year)
        self.zam_knt_id = self.kontrahent.zam_knt_id
        super(Zamowienie, self).save(*args, **kwargs)


class Produkt(models.Model):
    zamowienie = models.ForeignKey(Zamowienie, on_delete=models.CASCADE)
    ean = models.CharField(max_length=13, unique=False, blank=True)
    model = models.CharField(max_length=50, unique=False)
    kolor_frontu = models.CharField(max_length=50, unique=False, blank=True)
    kolor_korpusu = models.CharField(max_length=50, unique=False, blank=True)
    kolor_blatu = models.CharField(max_length=50, unique=False, blank=True)
    symbol = models.CharField(max_length=50, unique=False)
    zam_twrid = models.CharField(max_length=10, unique=False, blank=True)
    ilosc = models.IntegerField(unique=False, default=0)
    ilosc_do_odebrania = models.IntegerField(unique=False, default=0)
    komentarz = models.CharField(max_length=100, unique=False, blank=True)
    update_time = models.DateTimeField(auto_now=True)
    waga = models.DecimalField(max_digits=5, decimal_places=1, blank=True, default=0)
    dlugosc = models.DecimalField(unique=False, blank=True, max_digits=4, decimal_places=1, null=True)
    szerokosc = models.DecimalField(unique=False, blank=True, max_digits=4, decimal_places=1, null=True)
    zakonczenie = models.CharField(max_length=15, choices=ZAKONCZENIE_CHOICES, blank=True)
    wykonczenie = models.CharField(max_length=10, choices=WYKONCZENIE_CHOICES, blank=True)

    class Meta:
        ordering = ['-update_time']
        verbose_name_plural = 'Produkty'

    objects = models.Manager()

    def __str__(self):
        return str(self.model) + ' ' + str(self.symbol) + ' ' + str(self.ean)

serializers.py

from rest_framework import serializers
from .models import Zamowienie, Produkt


class ProduktySerializer(serializers.ModelSerializer):
    class Meta:
        model = Produkt
        fields = ['ean', 'model', 'kolor_frontu', 'kolor_korpusu', 'kolor_blatu', 'zam_twrid', 'ilosc',
                  'komentarz', 'zakonczenie', 'wykonczenie']


class ZamowieniaSerializer(serializers.ModelSerializer):
    produkty = ProduktySerializer(many=True)

    class Meta:
        model = Zamowienie
        fields = ['data_zal', 'data_realizacji', 'status', 'komentarz', 'uwagi', 'produkty']

    def create(self, validated_data):
        produkty_dane = validated_data.pop('produkty')
        zamowienie = Zamowienie.objects.create(**validated_data)
        for produkt in produkty_dane:
            Produkt.objects.create(zamowienie=zamowienie, **produkt)
        return zamowienie

views.py

class ZamowienieViewSet(viewsets.ModelViewSet):
    serializer_class = ZamowieniaSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        user = self.request.user
        zamowienia = Zamowienie.objects.filter(kontrahent=user)
        return zamowienia

urls.py

from django.urls import path, include
from rest_framework import routers

from . import views

router = routers.DefaultRouter()
router.register(r'zam', views.ZamowienieViewSet, basename='Zamowienie')


urlpatterns = [
    path("", views.home, name="home"),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    path('api/', include(router.urls)),
]

I skipped most of the code that does not matter to DRF. I didn't notice any mistake compared to documentation. Any idea what did I do wrong?

It looks like the problem dissapears when I add produkty = ProduktySerializer(many=True, write_only=True) to outer serializer definition (specifically write_only=True statement). Why does it work only that way?

Jorhanc
  • 310
  • 2
  • 13

1 Answers1

0

The error said it all:

'Zamowienie' object has no attribute 'produkty'

If you want produkty to be available in Zamowienie model, you need to set a related_name in a ForeignKey in Produkty model like this:

zamowienie = models.ForeignKey(Zamowienie, on_delete=models.CASCADE, related_name='produkty')

This way, you can access your produkty list from zamowienie and your serializer should work again without using write_only=True.

Last, about why write_only=True did the trick, it's because when you're fetching the data (read), you told Django to not looking for produkty from Zamowienie model. Therefore, only when creating (write) was using produkty which you already handled it in create().

P.S. the related name should be plural but I'm not sure what it should be so I didn't change it to :)

Ref: What does related_name do?

Preeti Y.
  • 429
  • 4
  • 11