You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将第三方API响应保存为关联User的Artist模型实例?

解决User保存时自动关联第三方API地理数据到UserGeoData的问题

现有代码的核心问题

你当前的实现存在几个关键问题:

  1. save方法里的self.ship = obj是笔误,应该关联self.usergeo字段
  2. User模型中usergeo设置了null=False,但创建用户时未预先处理关联,会直接抛出数据库错误
  3. 同步调用第三方API会阻塞用户创建流程,影响接口响应速度
  4. 未处理API调用失败的异常情况,导致用户创建失败

以下是两种可行的实现方案:


方案一:修正并重写User的save方法(简单直接)

只在用户首次创建或未关联地理数据时调用API,添加异常处理保证用户能正常保存:

import uuid
import requests
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .managers import CustomUserManager

class UserGeoData(models.Model):
    ip_address = models.GenericIPAddressField(default="0.0.0.0")
    city = models.CharField(max_length=50, blank=True)
    region_iso_code = models.CharField(max_length=5, blank=True)
    country_code = models.CharField(max_length=5, blank=True)
    longitude = models.DecimalField(max_digits=9, decimal_places=6, default=0.0)
    latitude = models.DecimalField(max_digits=9, decimal_places=6, default=0.0)

class User(AbstractBaseUser, PermissionsMixin):
    pkid = models.BigAutoField(primary_key=True, editable=False)
    id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    username = models.CharField(verbose_name=_('Username'), max_length=255, unique=True)
    first_name = models.CharField(verbose_name=_('First Name'), max_length=50)
    last_name = models.CharField(verbose_name=_('Last Name'), max_length=50)
    email = models.EmailField(verbose_name=_('Email Address'), unique=True)
    usergeo = models.OneToOneField(UserGeoData, on_delete=models.CASCADE, null=False)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username", "first_name", "last_name"]

    objects = CustomUserManager()

    class Meta:
        verbose_name = _('User')
        verbose_name_plural = _('Users')

    def __str__(self):
        return self.username

    @property
    def get_full_name(self):
        return f"{self.first_name} {self.last_name}"

    def get_short_name(self):
        return self.username
    
    def save(self, *args, **kwargs):
        # 仅在未关联地理数据时调用API
        if not self.usergeo_id:
            try:
                url = "https://ipgeolocation.abstractapi.com/v1/?api_key=d77f75a5593e4073a6eb1bef9c35e929"
                response = requests.get(url, timeout=5)
                response.raise_for_status()  # 触发HTTP错误
                data = response.json()
                
                geo_data, _ = UserGeoData.objects.get_or_create(
                    ip_address=data["ip_address"],
                    defaults={
                        "city": data.get("city", ""),
                        "region_iso_code": data.get("region_iso_code", ""),
                        "country_code": data.get("country_code", ""),
                        "longitude": data.get("longitude", 0.0),
                        "latitude": data.get("latitude", 0.0),
                    },
                )
                self.usergeo = geo_data
            except (requests.exceptions.RequestException, KeyError):
                # API调用失败时创建默认地理数据
                geo_data, _ = UserGeoData.objects.get_or_create(
                    ip_address="0.0.0.0",
                    defaults={
                        "city": "",
                        "region_iso_code": "",
                        "country_code": "",
                        "longitude": 0.0,
                        "latitude": 0.0,
                    },
                )
                self.usergeo = geo_data
        super().save(*args, **kwargs)

修改要点

  • 修正了字段关联的笔误
  • 添加API请求超时和异常捕获,保证用户创建流程不中断
  • 仅在必要时调用API,避免重复请求
  • 为UserGeoData字段添加默认值和允许空值,兼容API返回缺失数据的情况

方案二:使用Django信号(推荐,解耦逻辑)

通过post_save信号在用户创建后异步处理地理数据关联,不耦合User模型的save方法:

# 在models.py末尾添加信号处理
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def sync_user_geo_data(sender, instance, created, **kwargs):
    # 仅在用户首次创建且未关联地理数据时执行
    if created and not instance.usergeo_id:
        try:
            url = "https://ipgeolocation.abstractapi.com/v1/?api_key=d77f75a5593e4073a6eb1bef9c35e929"
            response = requests.get(url, timeout=5)
            response.raise_for_status()
            data = response.json()
            
            geo_data, _ = UserGeoData.objects.get_or_create(
                ip_address=data["ip_address"],
                defaults={
                    "city": data.get("city", ""),
                    "region_iso_code": data.get("region_iso_code", ""),
                    "country_code": data.get("country_code", ""),
                    "longitude": data.get("longitude", 0.0),
                    "latitude": data.get("latitude", 0.0),
                },
            )
            instance.usergeo = geo_data
            instance.save(update_fields=["usergeo"])
        except (requests.exceptions.RequestException, KeyError):
            geo_data, _ = UserGeoData.objects.get_or_create(
                ip_address="0.0.0.0",
                defaults={
                    "city": "",
                    "region_iso_code": "",
                    "country_code": "",
                    "longitude": 0.0,
                    "latitude": 0.0,
                },
            )
            instance.usergeo = geo_data
            instance.save(update_fields=["usergeo"])

同时需要修改CustomUserManager,预先创建默认地理数据避免null=False报错:

# 在CustomUserManager的create_user方法中添加
def create_user(
    self, username, first_name, last_name, email, password, **extra_fields
):
    # ... 原有代码 ...
    
    # 先创建默认地理数据
    default_geo, _ = UserGeoData.objects.get_or_create(
        ip_address="0.0.0.0",
        defaults={
            "city": "",
            "region_iso_code": "",
            "country_code": "",
            "longitude": 0.0,
            "latitude": 0.0,
        },
    )
    extra_fields["usergeo"] = default_geo
    
    user = self.model(
        username=username,
        first_name=first_name,
        last_name=last_name,
        email=email,
        **extra_fields
    )
    
    # ... 原有代码 ...

信号方案优势

  • 解耦用户模型和地理数据获取逻辑,代码更清晰
  • 仅在用户创建时触发,不影响后续更新操作
  • 支持批量创建用户的场景

额外优化建议

  1. 不要硬编码API密钥:将密钥存入环境变量,用os.getenv("ABSTRACT_API_KEY")读取
  2. 异步处理API请求:使用Celery等异步任务框架,避免阻塞用户注册流程
  3. 缓存地理数据:对相同IP的查询结果进行缓存,减少API调用次数

内容的提问来源于stack exchange,提问作者Krystian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 10:27:03