使用包含地理类型的 postgres 复合字段进行 django ORM 使用

Dip*_*att 6 python django postgresql postgis psycopg2

所以我有一个复合字段,我想在我的 django 模型中使用 postgres 作为数据库。

CREATE TYPE address_verbose AS (

    contact_person_name string_not_null,
    mobile string_no_spaces,
    mobile_cc string_no_spaces,
    email email_with_check,
    address text,
    city text,
    state text,
    country text,
    pincode string_no_spaces,
    location geography

);

Run Code Online (Sandbox Code Playgroud)

string_not_null和只是我为验证而创建的域string_no_spacesemail_with_check

如果我使用 SQL,一切都会很棒,但问题是想在 django 模型中使用这个字段,并且我想使用 Django ORM 至少创建、更新和删除包含该字段的对象。

因此,经过一些谷歌搜索后,我能够想出一个自定义模型字段。

from typing import Optional

from django.contrib.gis.geos import Point
from django.db import connection
from django.db import models
from psycopg2.extensions import register_adapter, adapt, AsIs
from psycopg2.extras import register_composite

address_verbose = register_composite(
    'address_verbose',
    connection.cursor().cursor,
    globally=True
).type


def address_verbose_adapter(value):

    return AsIs("(%s, %s, %s, %s, %s, %s, %s, %s, %s, ST_GeomFromText('POINT(%s %s)', 4326))::address_verbose" % (
        adapt(value.contact_person_name).getquoted().decode('utf-8'),
        adapt(value.mobile).getquoted().decode('utf-8'),
        adapt(value.mobile_cc).getquoted().decode('utf-8'),
        adapt(value.email).getquoted().decode('utf-8'),
        adapt(value.address).getquoted().decode('utf-8'),
        adapt(value.city).getquoted().decode('utf-8'),
        adapt(value.state).getquoted().decode('utf-8'),
        adapt(value.country).getquoted().decode('utf-8'),
        adapt(value.pincode).getquoted().decode('utf-8'),
        adapt(value.location).getquoted().decode('utf-8'),

    ))


register_adapter(address_verbose, address_verbose_adapter)


class AddressVerbose:
    def __init__(self, contact_person_name: str, mobile: str, mobile_cc: str, email: str, address: str, city: str,
                 state: str, country: str, pincode: str, location: Optional[Point] = None):
        self.contact_person_name = contact_person_name
        self.mobile = mobile
        self.mobile_cc = mobile_cc
        self.email = email
        self.address = address
        self.city = city
        self.state = state
        self.country = country
        self.pincode = pincode
        self.location = location


class AddressVerboseField(models.Field):
    def from_db_value(self, value, expression, connection):
        if value is None:
            return value
        return AddressVerbose(
            value.contact_person_name,
            value.mobile,
            value.mobile_cc,
            value.email,
            value.address,
            value.city,
            value.state,
            value.country,
            value.pincode,
            value.location,
        )

    def to_python(self, value):
        if isinstance(value, AddressVerbose):
            return value

        if value is None:
            return value

        return AddressVerbose(
            value.contact_person_name,
            value.mobile,
            value.mobile_cc,
            value.email,
            value.address,
            value.city,
            value.state,
            value.country,
            value.pincode,
            value.location,
        )


    def get_prep_value(self, value):
        if not isinstance(value, AddressVerbose):
            return None

        return (
            value.contact_person_name,
            value.mobile,
            value.mobile_cc,
            value.email,
            value.address,
            value.city,
            value.state,
            value.country,
            value.pincode,
            # Attempt 1
            value.location,
            # Attempt 2
            # "ST_GeomFromText('POINT(%s %s)', 4326)" % (value.location[0], value.location[1]) 
        )

    def db_type(self, connection):
        return 'address_verbose'
Run Code Online (Sandbox Code Playgroud)

使用上述字段的最小模型是

class Shipment(models.Model):
    pickup_detail = AddressVerboseField()
    drop_detail = AddressVerboseField()
Run Code Online (Sandbox Code Playgroud)

如果我要删除位置字段,那么在创建、更新或获取货件对象时一切都工作得很好。但是当您将位置字段包含在AddressVerboseField. 从 django doc 中,我得到了摆弄的想法get_prep_value,我尝试了第一种方法,直接发送元组中的位置。但它引发了以下错误。

在此输入图像描述

psycopg2 的适应函数似乎无法解析 Point 字段,因此考虑在尝试 2 中直接发送点字段的 SQL 文本。这让我很接近,但生成的 sql 中存在额外的引号。

在此输入图像描述

我在 google 中搜索了很多有关将 postgres 的复合字段适应 django 的信息,但所有结果并不真正包含 postgres 的 postgis 部分中存在的字段。这是我第一次编写 django 自定义字段,因此我可能会丢失或不正确的某些逻辑。如果有人能帮助我解决这个问题或者为我指出正确的方向,那将非常有帮助。谢谢