【问题标题】:django bulk insert using value from parent table instead of IDdjango 使用父表中的值而不是 ID 批量插入
【发布时间】:2021-08-19 05:04:20
【问题描述】:

我有 2 个模型——AssetTypeAsset(都有 ID,Asset 有一个指向 AssetType 的外键)

我想使用资产类型名称而不是 ID 在资产表中批量插入行。

我尝试执行 df.to_sql() 但它无法工作,因为我的 df 包含“资产类型名称”而不是资产类型 ID。有没有办法轻松转换?

预期输出

资产类型表如下所示:

id | name | description
1  | at1  | desc1

资产表应如下所示

asset_name | display_name | asset_type_id
n1         | d1           | 1

我想在 Asset 表中插入的数据框如下所示(输入): -- 注意我正在插入asset_type_name,它应该被转换为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 服务器

【问题讨论】:

  • Asset_type_name 是唯一的。在我的 AssetType 模型中,它显示 unique=True

标签: python sql-server django django-models


【解决方案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)

尚未对此进行测试,因此可能会在这里和那里失败,但要点就在这里。

【讨论】:

  • 我刚刚用我的观点更新了这个问题。我正在使用.to_sql()。我认为它不会进入序列化程序中的“创建”功能?也许我可以使用您创建的 dict 在我的 bulk_create() 函数中用 id 替换名称
  • 哦,好吧,我以为您也在使用序列化程序来保存对象。是的,我认为这也应该没问题。但是看看你的更新,你可以在序列化程序中使用request.data 吗?但取决于你
  • 我对此没有意见——对 django 来说相当新。在序列化程序中使用request.data 效率更高是吗?你能帮我怎么做吗?
  • 我对 pandas 了解不多,但你可以分析一下接近和比较的速度。由答案更新以显示如何使用序列化程序保存到数据库。
【解决方案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 将查询数保存到数据库,因此更可取。 在这里留下这个答案以防万一。

【讨论】:

  • 虽然这会起作用,但请注意,它会针对request.data['data'] 中的每一行访问数据库,只是为了获取AssetType。我在回答中使用in_bulk 的原因是为了避免这种情况并提高性能。但如果这是一次性的事情,那么应该这样做。
  • 好的,感谢您的提示 -- 我将方法更改为仅使用 in_bulk
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-18
  • 1970-01-01
  • 2019-11-05
  • 2020-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多