Django基础教程(三十三)Django模型的数据字段之IP地址类字段:Django模型的“私家侦探”——IP地址字段:如何优雅地记录用户的数字足迹?

嘿,伙计们!今天咱们来聊聊Django模型里一个看似不起眼,实则非常“侦探”的字段——IP地址字段

想象一下这个场景:你的网站有个用户登录日志功能,老板一拍桌子:“咱们得知道用户是从哪儿登录的!” 你眉头一皱,心想这不就是记录IP地址嘛。但紧接着问题来了:IP地址有IPv4(比如 192.168.1.1)和IPv6(比如 2001:0db8:85a3:0000:0000:8a2e:0370:7334)两种,长度格式天差地别。自己用CharField来存?感觉有点山寨,而且查询和验证会麻烦到让你怀疑人生。

别担心!Django早就为你准备了一位超级好用的“私家侦探”——GenericIPAddressField。它就像是你安插在数据库里的一个专业情报员,专门负责处理IP地址这种棘手的“数字足迹”。

第一部分:认识我们的“侦探”——GenericIPAddressField

这位侦探可不是吃素的,它住在 django.db.models 这个“总部”里。它的核心职责非常明确:在数据库中存储并验证IPv4或IPv6地址

它和我们熟悉的 CharFieldIntegerField 是亲戚,但业务能力更专精。你把它当成一个专门为IP地址定制的、带格式验证的字符串字段就行。

它的核心技能(字段参数):

  1. protocol(协议限定): 这位侦探可以缩小侦查范围。
    • 'both'(默认值): 通吃!IPv4和IPv6都支持。这是最常用的模式。
    • 'IPv4': 只侦查IPv4地址。如果你的环境还接触不到IPv6,用这个可以更严谨。
    • 'IPv6': 只侦查IPv6地址。面向未来,专精于此。
  1. unpack_ipv4(解包技能): 这是一个高级技能!
    • 默认是 False
    • 当它为 True 时,如果遇到一个IPv6地址表示的IPv4映射地址(比如 ::ffff:192.0.2.1),这位侦探会非常智能地把它“解包”成我们熟悉的IPv4格式(192.0.2.1)再存起来。这能极大地提升数据的一致性和可读性,强烈推荐开启!

它在数据库里的“办公地点”:

  • PostgreSQL 里,它会用 inet 类型。这可是个神器!它原生支持IP地址网络操作。
  • MySQL 里,它会用 CHAR(39) 类型,足够放下最长的IPv6地址。
  • SQLite 里,它会用 VARCHAR(39) 类型。

你看,Django已经帮我们搞定了所有数据库的兼容性问题,我们只需要放心地用就行了。

第二部分:实战!打造一个用户登录日志系统

光说不练假把式。现在,我们就请出这位侦探,亲手构建一个简单的用户登录日志系统。

步骤一:创建模型

在你的 models.py 文件里,我们定义一个 UserLoginLog 模型。

from django.db import models
from django.contrib.auth.models import User # 导入Django内置的用户模型

class UserLoginLog(models.Model):
    """
    用户登录日志模型
    我们的IP侦探就在这里工作!
    """
    # 登录的用户,如果未登录则为空
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        verbose_name='登录用户'
    )
    
    # IP侦探登场!
    ip_address = models.GenericIPAddressField(
        protocol='both',       # 同时支持IPv4和IPv6
        unpack_ipv4=True,      # 开启智能解包,非常实用!
        verbose_name='IP地址'
    )
    
    # 登录时间
    login_time = models.DateTimeField(
        auto_now_add=True,     # 创建记录时自动设置为当前时间
        verbose_name='登录时间'
    )
    
    # 用户代理信息,可以知道用户用的什么浏览器
    user_agent = models.TextField(
        blank=True,
        verbose_name='用户代理'
    )

    class Meta:
        verbose_name = '用户登录日志'
        verbose_name_plural = '用户登录日志'
        ordering = ['-login_time'] # 按登录时间倒序排列,新的在前面

    def __str__(self):
        # 方便在后台识别
        if self.user:
            return f"{self.user.username} 于 {self.login_time} 来自 {self.ip_address}"
        return f"匿名用户 于 {self.login_time} 来自 {self.ip_address}"

代码解读:

  • 我们为 ip_address 字段配置了 protocol='both'unpack_ipv4=True,这几乎是最佳实践。它能应对绝大多数网络环境,并且保持数据的整洁。
  • auto_now_add=True 让我们在保存新记录时,无需手动设置时间,Django自动帮我们打上时间戳。

步骤二:生成并执行数据库迁移

模型定义好了,就得告诉数据库腾出地方来。在终端运行:

python manage.py makemigrations
python manage.py migrate

搞定!你的数据库里现在多了一张 user_login_log 表,我们的IP侦探已经正式入驻并开始“蹲点”了。

第三部分:在视图里调用我们的侦探

现在,我们模拟一个用户登录成功的场景,在视图里记录这次登录。

在你的 views.py 里:

from django.http import HttpResponse
from django.contrib.auth import authenticate, login
from .models import UserLoginLog

def user_login_view(request):
    """
    一个简单的用户登录视图
    """
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        
        # 验证用户
        user = authenticate(request, username=username, password=password)
        
        if user is not None:
            # 登录成功
            login(request, user)
            
            # !!!关键步骤:呼叫IP侦探,记录日志!!!
            try:
                # 从请求对象中获取IP地址
                # 注意:直接使用 request.META['REMOTE_ADDR'] 可能不准确,因为可能经过代理
                x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
                if x_forwarded_for:
                    # 如果经过代理,取第一个IP才是真实IP
                    ip = x_forwarded_for.split(',')[0].strip()
                else:
                    ip = request.META.get('REMOTE_ADDR')
                
                # 创建登录日志记录
                UserLoginLog.objects.create(
                    user=user,
                    ip_address=ip, # 我们的侦探在这里工作!
                    user_agent=request.META.get('HTTP_USER_AGENT', '')
                )
                
            except Exception as e:
                # 记录日志出错不应该影响用户登录,但我们要在后台看到错误
                # 在生产环境中,这里应该使用日志系统,如 logging
                print(f"记录登录日志失败: {e}")
            
            return HttpResponse("登录成功!")
        else:
            return HttpResponse("登录失败!")
    # ... 其他代码(比如返回登录表单)

获取IP的注意事项:
代码中展示了如何更可靠地获取用户真实IP。在真正的生产环境中,你的Django应用前面很可能有Nginx这样的反向代理。代理服务器会把客户端的真实IP放在 HTTP_X_FORWARDED_FOR 这个头信息里,而 REMOTE_ADDR 就变成了代理服务器的IP。所以,上面的逻辑是行业通用做法。

第四部分:让侦探大显身手——高级查询技巧

记录数据是为了分析。我们的IP侦探在查询方面也是一把好手!

假设我们想分析异常登录:

from django.db.models import Count
from .models import UserLoginLog

# 1. 查找所有来自特定IP段的登录记录(比如排查一个内网网段)
internal_network_logs = UserLoginLog.objects.filter(
    ip_address__startswith='192.168.1.'
)
print("内网登录记录:", internal_network_logs)

# 2. 查找最近一天内,登录最频繁的10个IP地址(可能发现爬虫或暴力破解)
from django.utils import timezone
from datetime import timedelta

one_day_ago = timezone.now() - timedelta(days=1)
suspicious_ips = UserLoginLog.objects.filter(
    login_time__gte=one_day_ago
).values('ip_address').annotate(
    login_count=Count('id')
).order_by('-login_count')[:10]

print("可疑IP排行:")
for item in suspicious_ips:
    print(f"IP: {item['ip_address']} - 登录次数: {item['login_count']}")

# 3. 查找某个特定用户的所有登录IP
user_logs = UserLoginLog.objects.filter(user__username='xiaoming').values('ip_address').distinct()
print("小明用过的IP:", list(user_logs))

特别提醒(针对PostgreSQL用户):
如果你用的是PostgreSQL,那你就中大奖了!因为Django会利用其 inet 类型,支持网络包含查询,强大到没朋友!

# 查询属于 192.168.0.0/16 这个网段的所有记录
# 注意:此功能仅在PostgreSQL下有效!
network_logs = UserLoginLog.objects.filter(ip_address__contained_by='192.168.0.0/16')

这个 __contained_by 查找就是IP字段的王牌功能,能让你像操作数学集合一样操作IP网段。

第五部分:在Django Admin后台优雅地展示

这么棒的数据,当然要有个好看的后台。在 admin.py 里简单配置一下:

from django.contrib import admin
from .models import UserLoginLog

@admin.register(UserLoginLog)
class UserLoginLogAdmin(admin.ModelAdmin):
    list_display = ('user', 'ip_address', 'login_time') # 列表页显示的字段
    list_filter = ('login_time', 'ip_address') # 右侧过滤器,居然可以直接按IP过滤!
    search_fields = ('user__username', 'ip_address') # 搜索框,支持按IP搜索
    date_hierarchy = 'login_time' # 按时间层级钻取
    readonly_fields = ('user', 'ip_address', 'login_time', 'user_agent') # 日志通常是只读的

现在,去Django Admin后台看看吧!一个功能强大、查询方便的用户登录日志系统已经完美呈现,所有的IP地址都被我们的“侦探”整理得清清楚楚。

总结

瞧,Django的 GenericIPAddressField 绝不是一个简单的字符串包装器。它是一个:

  • 专业的验证者:自动帮你校验IP格式是否正确。
  • 智能的协调员:无缝处理IPv4和IPv6的兼容性问题。
  • 高效的查询器:提供针对IP地址的专用查询方法,尤其是在PostgreSQL上威力无穷。

下次当你的项目需要记录任何形式的“数字足迹”时——无论是用户登录、操作审计、文章评论来源还是API调用——别再犹豫了,请毫不犹豫地派出这位可靠的“IP侦探”:GenericIPAddressField

它会让你的代码更干净,逻辑更清晰,数据处理能力更上一层楼。快去你的项目里试试吧!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值