Массовая вставка django с использованием значения из родительской таблицы вместо ID

У меня есть 2 модели - AssetType и Asset (у обеих есть идентификаторы, а Asset имеет внешний ключ, указывающий на AssetType)

Я хочу массово вставлять строки в таблицу Asset, используя NAME типа актива, вместо ID.

Я пытался сделать df.to_sql(), но это не может сработать, потому что мой df содержит "имя типа актива" вместо id типа актива. Есть ли способ легко преобразовать его?

Ожидаемый выход

Таблица типов активов выглядит следующим образом:

id | name | description
1  | at1  | desc1

Таблица активов должна выглядеть следующим образом

asset_name | display_name | asset_type_id
n1         | d1           | 1

Фрейм данных, который я хочу вставить в таблицу Asset, выглядит следующим образом (Input): -- обратите внимание, я вставляю имя_типа_актива, и оно должно быть преобразовано в asset_type_id

asset_name | display_name | asset_type_name
n1         | d1           | at1

Итак, я передаю at1 в моем датафрейме, но хочу вставить его как "id=1" для типа актива. Возможно ли это в django?

Мои модели:

class AssetType(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=80, unique=True)
    description = models.CharField(max_length=80)

class Asset(models.Model):
    asset_type = models.ForeignKey(AssetType, on_delete=models.CASCADE)
    asset_name = models.CharField(max_length=80)
    display_name = models.CharField(max_length=80)

Мой сериализатор выглядит следующим образом:

class AssetTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = AssetType
        fields = "__all__"


class AssetSerializer(serializers.ModelSerializer):
    asset_type_name = serializers.CharField(source='asset_type.name')
    
    class Meta:
        model = Asset
        fields = ("id", "asset_type_name", "asset_name", "display_name")  

Мое представление выглядит примерно так:

class AssetViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    A simple ViewSet for viewing and editing accounts.
    """
    queryset = Asset.objects.select_related()
    serializer_class = AssetSerializer

    @action(methods=['POST'], detail=False)
    def bulk_create(self, request):

        df = pd.DataFrame.from_dict(dict(request.data['data']))
        df.to_sql(Asset._meta.db_table, con=engine, if_exists='append', index=False, chunksize=500)  

Версия Django - 3.2 База данных - SQL сервер

Подход, о котором я могу думать, заключается в использовании in_bulk в django, чтобы заполнить словарь, который использует имя типа актива как ключ и экземпляр типа актива как значение:

# This will create {'asset_type_name': asset_type_instance} mapping
asset_types = AssetType.objects.in_bulk(field_name='name')

Затем передайте его сериализатору как контекст:

serializer = AssetSerializer(
    data=request.data['data'], many=True, 
    context={'asset_types_map': asset_types},
)
serializer.is_valid(raise_exception=True)
serializer.save()

А затем используйте карту в пользовательском создании, используя имя типа актива в данных, чтобы получить экземпляр типа актива, как показано здесь:

class AssetSerializer(serializers.ModelSerializer):
    asset_type_name = serializers.CharField(source='asset_type.name')
    
    class Meta:
        model = Asset
        fields = ("id", "asset_type_name", "asset_name", "display_name") 

    def create(self, validated_data):
        asset_type_name = validated_data.pop('asset_type_name')
        validated_data['asset_type'] = self.context['asset_types_map'].get(asset_type_name)
        return super().create(validated_data)

Я не тестировал это, поэтому возможны сбои, но суть здесь.


Отвечаю на свой вопрос для тех, у кого в будущем возникнет такая же проблема.

Во многом вдохновлен ответом @bdbd, но не нуждался в словаре или контекстной части.

Вот код, который сработал:

Набор представлений был довольно простым. Принимаем данные запроса и передаем в сериализатор

class AssetViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    queryset = Asset.objects.select_related()
    serializer_class = AssetSerializer

    @action(methods=['POST'], detail=False)
    def bulk_create(self, request):
        serializer = AssetSerializer(
            data=request.data['data'], many=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data, status=status.HTTP_201_CREATED)

В сериализаторе запросите объект типа актива из названия и отправьте его как часть создания объекта актива.

class AssetSerializer(serializers.ModelSerializer):
    asset_type_name = serializers.CharField(source='asset_type.name')
    
    class Meta:
        model = Asset
        fields = ("id", "asset_type_name", "asset_name", "display_name")    

    def create(self, validated_data):
        asset_type = validated_data.pop('asset_type')
        asset_type = AssetType.objects.get(**asset_type)
        return Asset.objects.create(**validated_data, asset_type=asset_type)

Update

Основываясь на комментарии ниже от @bdbd, этот метод работает, но in_bulk сохраняет количество запросов к бд, поэтому его предпочтительнее использовать. Оставляю этот ответ здесь на всякий случай.

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