嘿,伙计们!今天咱们来聊聊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地址。
它和我们熟悉的 CharField、IntegerField 是亲戚,但业务能力更专精。你把它当成一个专门为IP地址定制的、带格式验证的字符串字段就行。
它的核心技能(字段参数):
protocol(协议限定): 这位侦探可以缩小侦查范围。
-
'both'(默认值): 通吃!IPv4和IPv6都支持。这是最常用的模式。'IPv4': 只侦查IPv4地址。如果你的环境还接触不到IPv6,用这个可以更严谨。'IPv6': 只侦查IPv6地址。面向未来,专精于此。
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。
它会让你的代码更干净,逻辑更清晰,数据处理能力更上一层楼。快去你的项目里试试吧!

被折叠的 条评论
为什么被折叠?



