AttributeError: объект 'dict' не имеет атрибута 'id'

Я пытаюсь получить доступ к диктонарию внутри сериализатора jsonfield "assigned_facilities". Но я получаю следующую ошибку:

AttributeError: 'dict' object has no attribute 'facility_id'

Я пытаюсь создать объект "LeadFacilityAssign" для каждого элемента в моем json, чтобы у меня был объект "LeadFacilityAssign" для каждого объекта, который я хочу добавить к лиду.

json

{
    "facilities": [{
            "facility_id": "1",
            "datetime": "2018-12-19 09:26:03.478039"
        },
        {
            "facility_id": "1",
            "datetime": "2018-12-19 09:26:03.478039"
        }
    ]
}

serializers.py

class LeadUpdateSerializer(serializers.ModelSerializer):
    is_owner = serializers.SerializerMethodField()
    assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)

    class Meta:
        model = Lead
        fields = (
            "id",
            "first_name",
            "last_name",
            "PrimaryAddress",
            "City",
            "PostalCode",
            "RegionOrState",
            "pc_email",
            "Cell",
            "secphone",
            "client_cell",
            "client_secphone",
            "birthday",
            "curr_client_address",
            "curr_client_city",
            "curr_client_zip",
            "ideal_address",
            "ideal_city",
            "ideal_zip",
            "ideal_state",
            "budget",
            "client_email",
            "client_first_name",
            "client_last_name",
            "lead_status",
            "created_at",
            "agent",
            "is_owner",
            "relationship",
            "marital_status",
            "gender",
            "pets",
            "assigned_facilities",
        )
        read_only_fields = ("id", "created_at", "agent", "is_owner")

    def get_is_owner(self, obj):
        user = self.context["request"].user
        return obj.agent == user
    
    def create(self, validated_data):
        
        assigned_facilities = validated_data.pop("assigned_facilities")
        instance = Lead.objects.create(**validated_data)
        for facilities in assigned_facilities:
            instance.leadfacility.create(assigned_facilities_id=assigned_facilities.facility_id,datetime=assigned_facilities.datetime)
        return instance

models.py

class Facility(models.Model):
    name = models.CharField(max_length=150, null=True, blank=False)

    def __str__(self):
        return self.name

class Lead(models.Model):
    first_name = models.CharField(max_length=40, null=True, blank=True)
    last_name = models.CharField(max_length=40, null=True, blank=True)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

class LeadFacilityAssign(models.Model):
    assigned_facilities = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name='leadfacility')
    lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='leadfacility')
    datetime = models.DateTimeField()

views.py

class LeadCreateView(CreateAPIView):
    permission_classes = [IsAuthenticated, IsLeadOwner]
    serializer_class = LeadUpdateSerializer

    def perform_create(self, serializer):
        serializer.save(agent=self.request.user)

class LeadUpdateView(UpdateAPIView):
    permission_classes = [IsAuthenticated, IsLeadOwner]
    serializer_class = LeadUpdateSerializer

    def get_queryset(self):
        return Lead.objects.all()

Ваш образец JSON включает "facilities", но ваш сериализатор имеет assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True).

class LeadUpdateSerializer(serializers.ModelSerializer):
    is_owner = serializers.SerializerMethodField()
    facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)

    class Meta:
        model = Lead
        fields = (
            "id",
            "first_name",
            "last_name",
            "PrimaryAddress",
            "City",
            "PostalCode",
            "RegionOrState",
            "pc_email",
            "Cell",
            "secphone",
            "client_cell",
            "client_secphone",
            "birthday",
            "curr_client_address",
            "curr_client_city",
            "curr_client_zip",
            "ideal_address",
            "ideal_city",
            "ideal_zip",
            "ideal_state",
            "budget",
            "client_email",
            "client_first_name",
            "client_last_name",
            "lead_status",
            "created_at",
            "agent",
            "is_owner",
            "relationship",
            "marital_status",
            "gender",
            "pets",
            "facilities",
        )
        read_only_fields = ("id", "created_at", "agent", "is_owner")

    def get_is_owner(self, obj):
        user = self.context["request"].user
        return obj.agent == user
    
    def create(self, validated_data):
        
        facilities = validated_data.pop("facilities", None)
        instance = Lead.objects.create(**validated_data)
        for facilities in assigned_facilities:
             LeadFacilityAssign.objects.create(assigned_facilities_id=assigned_facilities.get("facility_id"), datetime=assigned_facilities.get("datetime"), lead=instance)

        return instance

Также вы добавили обязательный параметр False для объектов, поэтому validated.pop("facilities") может вызвать ошибку, если в запросе нет объектов. Вам следует добавить еще один параметр в метод pop. validated.pop("facilities", None)

У вас есть три таблицы:

  1. Таблица Lead, в которой lead_id не нулевой, поскольку является первичным ключом
  2. Таблица Facility, в которой facility_id не нулевой, так как является первичным ключом
  3. Таблица LeadFacility, в которой lead_facility_id не является нулевым, но два внешних ключа (lead_id и facility_id) являются нулевыми. И вы присваиваете эти нулевые значения своему не нулевому полю.

Возможно, вы пытаетесь сделать : lead_falility_id (table: LeadFacility talbe) = facility_id (table: Facility)

Но по ошибке вы делаете : lead_falility_id (table: LeadFacility talbe) = facility_id (table: LeadFacility).

И из-за этого вы делаете : lead_facility_id = null для не нулевого поля.

Ваш JSON не соответствует вашему сериализатору. Поля вашего сериализатора не соответствуют полям вашей модели. Ваши представления не соответствуют вашим моделям или вашим сериализаторам.

Итак, давайте начнем с самого начала.

Если я правильно понял, вы хотите создать объект LeadFacilityAssign одновременно с созданием или обновлением объекта Lead. Есть несколько подходов к решению этой проблемы, например, использование сигнала post_save сразу после запроса на создание Lead, но давайте следовать вашему примеру...

Из вашего сериализатора Lead это "нормально":

class LeadUpdateSerializer(serializers.ModelSerializer):
    is_owner = serializers.SerializerMethodField()
    assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)

    class Meta:
        model = Lead
        fields = (
            "id",
            "first_name",
            "last_name",
            "is_owner",
            "assigned_facilities",
        )
        read_only_fields = ("id", "is_owner")

Но это:

    def get_is_owner(self, obj):
        user = self.context["request"].user
        return obj.agent == user

В последней строке есть оператор сравнения (==), что означает, что она может вернуть значение True или False (?), плюс вы больше нигде не используете поле "agent", оно не объявлено в сериализаторе и даже не является полем в ваших моделях. Просто избавьтесь от этой функции или добавьте поле "agent" в модель LeadFacilityAssign (предполагается, что вы будете назначать Lead, Facility и Agent для этого отношения).

Полагаю, что вы ожидаете от JSON-вызова информацию об "объектах". Из ваших объявленных выше полей я предполагаю, что вы должны ожидать поле "assigned_facilities", которое не отображается в ваших данных JSON, но давайте предположим, что ваш API получит поле "assigned_facilities" вместо поддикта "facilities" с множеством объектов, связанных с одним Lead.

Я не тестировал приведенный ниже код, но согласно документации REST API, теперь вы должны определить два метода в вашем сериализаторе, один для CREATE и один для UPDATE.

Создать:

def create(self, validated_data):
    lead = Lead.objects.create(first_name=validated_data['first_name'], last_name=validated_data['last_name'] #Here you will create the Lead object that you will reference later in your LeadFacilityAssign relationship with the dictionary information from the received data, so let's save it:
    lead.save()
    #Now we need to create all facilities relationships to this Lead:
    facilities = validated_data['assigned_facilities'] #This will create a "facilities" sub-dict from your received data with a facility_id and a datetime field in key-value pair.
    for item in facilities:
        facility = Facility.objects.get(id=item['facility_id']) #Get a single facility object for each ID in your JSON. If this fails, try converting it to int().
        datetime = item['datetime']  #Again, if it fails because it's taken as string, try converting it to datetime object.
        entry = LeadFacilityAssign.objects.create(assigned_facilities=facility, lead=lead, datetime=datetime) #Create the entry.
        entry.save() #Save the entry
    return #Exit your function

Метод Update должен выглядеть более или менее одинаково.

В представлении, если вы не используете поле "агент", просто разберите пользователя для безопасности или просто используйте его позже, если вы хотите включить его как владельца в вашу модель.

class LeadCreateView(CreateAPIView):
    permission_classes = [IsAuthenticated, IsLeadOwner]
    serializer_class = LeadUpdateSerializer

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

Ваш запрос будет выглядеть следующим образом

instance.leadfacility.create(assigned_facilities_id__id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get("datetime"))

ЗАМЕТКА-

because assigned_facilities_id return full object of foreign key & assigned_facilities_id__id return value of id foreign key object

Вернуться на верх