#python #sql-server #django #django-models
Вопрос:
У меня есть 2 модели- AssetType
и Asset
(у обеих есть идентификаторы, а у актива есть внешняя ключевая точка для типа актива)
Я хочу массово вставлять строки в таблицу активов, используя ИМЯ типа активов вместо идентификатора.
Я попытался сделать df.to_sql (), но он не может работать, потому что мой df содержит «имя типа актива» вместо идентификатора типа актива. Есть ли способ легко преобразовать его?
Ожидаемые результаты
Таблица типов активов выглядит следующим образом:
id | name | description
1 | at1 | desc1
Таблица активов должна выглядеть следующим образом
asset_name | display_name | asset_type_id
n1 | d1 | 1
Кадр данных, который я хочу вставить в таблицу активов, выглядит следующим образом (Ввод): — обратите внимание, что я вставляю имя типа актива, и оно должно быть преобразовано в идентификатор типа актива
asset_name | display_name | asset_type_name
n1 | d1 | at1
Итак, я передаю at1
данные в своем фрейме данных, но хочу вставить его как «id=1» для типа активов. Возможно ли это в джанго?
Мои модели:
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 server
Комментарии:
1. Имя типа актива уникально. В моей модели типа активов написано
unique=True
Ответ №1:
Подход , который я могу придумать, заключается в использовании django in_bulk
для заполнения словаря, в котором имя типа актива используется в качестве ключа, а экземпляр типа актива-в качестве значения:
# 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)
Я не проверял это, так что может произойти сбой здесь и там, но суть здесь.
Комментарии:
1. Я просто обновил вопрос своим мнением. Я использую
.to_sql()
. Я не думаю, что он войдет в функцию «создать» в сериализаторе? Может быть, я смогу использовать созданный вами диктант, чтобы заменить имя идентификатором в моейbulk_create()
функции2. О, хорошо, я думал, что вы также используете сериализаторы для сохранения объектов. Да, я думаю, что это тоже должно быть хорошо. Но, глядя на ваше обновление, вы можете просто использовать
request.data
его в сериализаторе? Но решать вам3. У меня нет никакого мнения по этому поводу-довольно новое для джанго. Использование
request.data
в сериализаторе более эффективно, не так ли? Не могли бы вы помочь мне, как я мог бы это сделать?4. Я мало что знаю о пандах, но вы можете оценить скорость обоих подходов и сравнить. Обновлено ответом, чтобы показать, как вы можете использовать сериализатор для сохранения в БД.
Ответ №2:
Отвечаю на свой собственный вопрос для всех, у кого в будущем возникнет такая же проблема.
Сильно вдохновленный ответом @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)
Обновить
Основываясь на приведенном ниже комментарии @bdbd, этот метод работает, но in_bulk
экономит количество запросов к базе данных, поэтому его использование более предпочтительно. Оставляю этот ответ здесь на всякий случай.
Комментарии:
1. Хотя это будет работать, обратите внимание, что он будет попадать в базу данных для каждой строки
request.data['data']
только для того, чтобы получитьAssetType
. Причина, которую я использовалin_bulk
в своем ответе, заключается в том, чтобы избежать этого и повысить производительность. Но если это одноразовая вещь, то этого должно хватить.2. Хорошо, спасибо за совет-я изменил свой метод, чтобы использовать
in_bulk
только!