Массовая вставка 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
сохраняет количество запросов к бд, поэтому его предпочтительнее использовать.
Оставляю этот ответ здесь на всякий случай.