Django实现统计网站访问次数、访问 ip 、受访页面

想知道网站每天的访问量,都有哪些人访问,都是来自什么地方的访客,都访问了哪些端点。

编写 models.py

# 访问网站的 ip 地址、端点和次数
class UserIP(models.Model):
    ip = models.CharField(verbose_name='IP 地址', max_length=30)
    ip_addr = models.CharField(verbose_name='IP 地理位置', max_length=30)
    end_point = models.CharField(verbose_name='访问端点', default='/', max_length=30)
    count = models.IntegerField(verbose_name='访问次数', default=0)
    
    class Meta:
        verbose_name = '访问用户信息'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return self.ip
        
# 网站总访问次数
class VisitNumber(models.Model):
    count = models.IntegerField(verbose_name='网站访问总次数', default=0) # 网站访问总次数
    
    class Meta:
        verbose_name = '网站访问总次数'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return str(self.count)

# 单日访问量统计
class DayNumber(models.Model):
    day = models.DateField(verbose_name='日期', default=timezone.now)
    count = models.IntegerField(verbose_name='网站访问次数', default=0) # 网站访问总次数
    
    class Meta:
        verbose_name = '网站日访问量统计'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return str(self.day)

使用 geoip2 实现 IP 转换成现实地理位置
安装 geoip2 库

pip install geoip2

下载 Maxmind 网站的 IP 信息库

地址:GeoLite2 Free Geolocation Data | MaxMind Developer Portal

下载完之后,解压,将解压包中的 mmdb 文件复制到项目的应用程序中

下载个GeoLite2 City使用。

然后我们的python代码如下:

#!/usr/bin/python
#-*-coding:utf-8-*-
import geoip2.database
reader = geoip2.database.Reader('./GeoLite2-City.mmdb')
ip = raw_input("输入你要查询的IP:\n")
response = reader.city(ip)
# 有多种语言,我们这里主要输出英文和中文
print("你查询的IP的地理位置是:")

print("地区:{}({})".format(response.continent.names["es"],
                                       response.continent.names["zh-CN"]))

print("国家:{}({}) ,简称:{}".format(response.country.name,
                                                        response.country.names["zh-CN"],
                                                        response.country.iso_code))

print("洲/省:{}({})".format(response.subdivisions.most_specific.name,
                                          response.subdivisions.most_specific.names["zh-CN"]))

print("城市:{}({})".format(response.city.name, 
                                          response.city.names["zh-CN"]))

print("经度:{},纬度{}".format(response.location.longitude,
                                            response.location.latitude))

print("时区:{}".format(response.location.time_zone))

print("邮编:{}".format(response.postal.code))

试一试

(venv) allenwoo@~/renren/code$ python test.py 
输入你要查询的IP:
112.74.207.96
你查询的IP的地理位置是:
地区:Asia(亚洲)
国家:China(中国) ,简称:CN
洲/省:Zhejiang(浙江省)
城市:Hangzhou(杭州)
经度:120.1614,纬度30.2936
时区:Asia/Shanghai
邮编:None

再来一个:

(venv) allenwoo@~/renren/code$ python test.py 
输入你要查询的IP:
223.192.2.165
你查询的IP的地理位置是:
地区:Asia(亚洲)
国家:China(中国) ,简称:CN
洲/省:Beijing(北京市)
城市:Beijing(北京)
经度:116.3883,纬度39.9289
时区:Asia/Shanghai
邮编:None

2.至于在线库的使用是需要一个license_key
只有前面两步不太一样
连接: client = geoip2.webservice.Client(42, <license_key>)
查询 IP: response = client.insights(<IP>)

编写将 IP 转换为现实地理位置的函数

ip_convert_addr.py

import geoip2.database


def ip_to_addr(ip):
    """
    IP 转换成现实中的地理位置
    country = 国家
    province = 省
    city = 城市
    """
    reader = geoip2.database.Reader('blog/GeoLite2-City.mmdb')
    response = reader.city(ip)
    #print(response)
    # 因为有些IP的省份和城市未知,所以设置默认为空
    province = ''
    city = ''
    try:
        # 国家、省份、城市
        country = response.country.names["zh-CN"]
        province = response.subdivisions.most_specific.names["zh-CN"]
        city = response.city.names["zh-CN"]
    except:
        pass
    if country != '中国':
        return country
    if province and city:
        if province == city or city in province:
            return province
        return '%s%s' %(province, city)
    elif province and not city:
        return province
    else:
        return country

编写 visit_info.py 实现更新网站访问量和访问 ip 等信息

from django.utils import timezone

from .models import UserIP, VisitNumber, DayNumber
from .ip_convert_addr import ip_to_addr


# 自定义的函数,不是视图
def change_info(request, end_point):
    """
    # 修改网站访问量和访问 ip 等信息
    # 每一次访问,网站总访问次数加一
    """
    count_nums = VisitNumber.objects.filter(id=1)
    if count_nums:
        count_nums = count_nums[0]
        count_nums.count += 1
    else:
        count_nums = VisitNumber()
        count_nums.count = 1
    count_nums.save()

    # 记录访问 ip 和每个 ip 的次数
    if 'HTTP_X_FORWARDED_FOR' in request.META:  # 获取 ip
        client_ip = request.META['HTTP_X_FORWARDED_FOR']
        client_ip = client_ip.split(",")[0]  # 所以这里是真实的 ip
    else:
        client_ip = request.META['REMOTE_ADDR']  # 这里获得代理 ip
    # print(client_ip)

    ip_exist = UserIP.objects.filter(ip=str(client_ip), end_point=end_point)
    if ip_exist:  # 判断是否存在该 ip
        uobj = ip_exist[0]
        uobj.count += 1
    else:
        uobj = UserIP()
        uobj.ip = client_ip
        uobj.end_point = end_point
        try:
            uobj.ip_addr = ip_to_addr(client_ip)
        except:
            uobj.ip_addr = 'unknown'
        uobj.count = 1
    uobj.save()
    
    # 增加今日访问次数
    date = timezone.now().date()
    today = DayNumber.objects.filter(day=date)
    if today:
        temp = today[0]
        temp.count += 1
    else:
        temp = DayNumber()
        temp.dayTime = date
        temp.count = 1
    temp.save()

将上面的函数导入到视图中,在需要时直接调用

from .visit_info import change_info


...
def post_detail(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    change_info(request, '/')
    # 记得在顶部引入 markdown 模块
    post.body = markdown.markdown(post.body, extensions=['extra', 'codehilite', 'toc',
                                    'tables', 'sane_lists'])
    return render(request, 'post_detail.html', context={'post': post})

Django 数据库抽象 API

 描述了如何创建、检索、更新和删除独立的对象。但是,有时你会需要处理一些有关对象的集合的统计。本文描述如何使用 Django 查询来处理统计。

本文我们将使用以下模型。这些模型用于在线书店图书清单:

class Author(models.Model):
   name = models.CharField(max_length=100)
   age = models.IntegerField()
   friends = models.ManyToManyField('self', blank=True)

class Publisher(models.Model):
   name = models.CharField(max_length=300)
   num_awards = models.IntegerField()

class Book(models.Model):
   isbn = models.CharField(max_length=9)
   name = models.CharField(max_length=300)
   pages = models.IntegerField()
   price = models.DecimalField(max_digits=10, decimal_places=2)
   rating = models.FloatField()
   authors = models.ManyToManyField(Author)
   publisher = models.ForeignKey(Publisher)
   pubdate = models.DateField()

class Store(models.Model):
   name = models.CharField(max_length=300)
   books = models.ManyToManyField(Book)

生成整个查询集的统计

Django 提供两种方法来产生统计。

第一种方法是产生整个 查询集 的统计。假设我们要统计所有书的平均价格。 Djnago 中查询所有书的语句为:

>>> Book.objects.all()

在这个语句后加上一个 aggregate() 子句就行了:

>>> from django.db.models import Avg

>>> Book.objects.all().aggregate(Avg('price'))

{'price__avg': 34.35}

上例中的 all() 是多余的。所以可以简写为:

>>> Book.objects.aggregate(Avg('price'))

{'price__avg': 34.35}

aggregate() 子句的参数代表我们要统计的内容,本例中我们要统计 Book 模型中 price 字段的平均值。

aggregate() 是一个 查询集 的未端子句,调用后会返回一个由名称-值配对组成的字典。名称是指统计的名称,值就是统计的值。名称由字段名称配双下划线加上函数名自动组成。如果你想手动指定统计名称,可以象下例在统计子句中定义:

>>> Book.objects.aggregate(average_price=Avg('price'))

{'average_price': 34.35}

如果你想要生成多个统计,那么只要在统计子句后加上另外的统计子句就可以了。例如,如果要计算所有书价中最高价和最低价,可以这样写:

>>> from django.db.models import Avg, Max, Min, Count, Sum

>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))

{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

生成查询集中每一个项目的统计

第二种方法是为 查询集 中每个独立的对象生成统计。例如,当你检索一个书单时,可能想知道每本书有几个作者。每本书与每个作者之间是一个多对多的关系,我们要为每本书总结这个关系。

要产生每个对象的统计可以使用 annotate() 子句。当定义一个 annotate() 子句后, 查询集 中的每个对象就可以与特定值关联,相当于每个对象有一个 “注释”。

这种注释的语法与 aggregate() 相同。 annotate() 的每个参数代表一个统计。例如,要计算每本书的作者人数:

生成查询集中每一个项目的统计

第二种方法是为 查询集 中每个独立的对象生成统计。例如,当你检索一个书单时,可能想知道每本书有几个作者。每本书与每个作者之间是一个多对多的关系,我们要为每本书总结这个关系。

要产生每个对象的统计可以使用 annotate() 子句。当定义一个 annotate() 子句后, 查询集 中的每个对象就可以与特定值关联,相当于每个对象有一个 “注释”。

这种注释的语法与 aggregate() 相同。 annotate() 的每个参数代表一个统计。例如,要计算每本书的作者人数:

>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price')) 

联合的深度是无限的。例如,要统计所有书的作者的最小年龄,你可以这样: 

>>> Store.objects.aggregate(youngest_age=Min('books__authors__age')) 

统计和其他查询子句 

filter() 和 exclude()

在过滤器中也可以使用统计。任何用于一般模型的 filter() (或 exclude() )也可与统计联用。

当与 annotate() 子句联用时,过滤器作用于被统计的对象上。例如要统计书名以 "Django" 开头的书: 

>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors')) 

当与 aggregate() 子句联用时,过滤器作用于被统计的所有对象上。例如要统计书名以 "Django" 开头的书的平均价格: 

>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price')) 

过滤统计的值

统计出来的值也可以被过滤。和其他模型一样,统计的结果也可以使用 filter() 和 exclude() 子句来过滤。

例如,要产生一个由两个以上作者的书单可以这样:

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

上例先进行统计,然后在统计的结果上使用了过滤器。 

annotate() 和 filter() 子句的顺序

当使用同时包含 annotate() 和 filter() 子句的复杂查询时,要特别小心两种子句的顺序。

当一个 annotate() 子句作用于查询时,该统计只对子句之前的查询起作用。也就是说 filter() 和 annotate() 顺序不同,查询就不同了。查询:

>>> Publisher.objects.annotate(num_books=Count('book')).filter(book__rating__gt=3.0)

和查询:

>>> Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))

是不同的。两个查询都会返回至少有一本好书(评分大于 3.0 )的出版商。但是,第一个查询中的统计会提供出版商的所有书的数量;第二个查询中的统计只返回好书的数量。第一个查询中统计先于过滤器,所以过滤器对统计没有作用。而第二个查询过滤器先于统计,所以统计的对象是已经过滤过的。

order_by()

统计可以作为排序的基础。当你定义一个 order_by 子句时,可以引用 annotate() 子句中的统计。

例如,要依据书的作者人数进行排序,可以这样:

>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

values()

通常,统计会针对 查询集 中每一个对象返回一个结果。但是,当使用 values 子句来约束要统计的列时,返回的结果会有所不同。原先统计结果中,统计字段的值相同的项会分组合并统计。

例如,要统计每个作者各自所写的书的平均评分:

>>> Author.objects.annotate(average_rating=Avg('book__rating'))

返回的结果会包含每一个作者及其所写的书的平均计分。

但是,如果使用 values() 子句,返回的结果会有所不同:

Author.objects.values('name').annotate(average_rating=Avg('book__rating'))

这个例子中会把作者按名字分组统计,返回的结果中不会有重复的作者名字。名字相同的作者在统计中会作为同一个作者来统计,同名作者所写的书的评分会合并为一个作者的书来统计。

annotate() 和 values() 子句的顺序

当使用 filter() 子句时, annotate() 和 values() 子句的顺序是非常重要的。如果 values() 子句先于 annotate() 子句,会按照前文所述的方式统计。

但是,如果 annotate() 子句先于 values() 子句,那么统计会作用于整个查询集,而 values() 子句只约束统计输出的字段。

例如,如果我们把前一个例子中的 values() 和 annotate() 子句调换顺序:

>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

这个例子会为每一个作者生成唯一的结果。但是在输了的数据中只会包含作者名和 average_rating 的统计。

你可以注意到 average_rating 在例子中显示地定义了。在 annotate() 和 values() 子句的顺序处于这种情况是必须显式定义。

如果 values() 子句先于 annotate() 子句,那么任何统计会自动添加到输出结果中。但是 values() 子句在 annotate() 子句之后,那么必须显式定义统计列。

缺省排序或 order_by() 子句的副作用

一个查询集中 order_by() 子句中的字段(或一个模型中缺省排序字段)会对输了数据产生影响,即使在 values() 中没有这些字段的定义时也同样会影响。这些特殊的字段会影响统计结果,这种情况在计数统计时尤为明显。

假设有一个这样的模型:

class Item(models.Model):
    name = models.CharField(max_length=10)
    data = models.IntegerField()

    class Meta:
        ordering = ["name"]
Author.objects.values('name').annotate(average_rating=Avg('book__rating'))

这个例子中会把作者按名字分组统计,返回的结果中不会有重复的作者名字。名字相同的作者在统计中会作为同一个作者来统计,同名作者所写的书的评分会合并为一个作者的书来统计。

annotate() 和 values() 子句的顺序

当使用 filter() 子句时, annotate() 和 values() 子句的顺序是非常重要的。如果 values() 子句先于 annotate() 子句,会按照前文所述的方式统计。

但是,如果 annotate() 子句先于 values() 子句,那么统计会作用于整个查询集,而 values() 子句只约束统计输出的字段。

例如,如果我们把前一个例子中的 values() 和 annotate() 子句调换顺序:

>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

这个例子会为每一个作者生成唯一的结果。但是在输了的数据中只会包含作者名和 average_rating 的统计。

你可以注意到 average_rating 在例子中显示地定义了。在 annotate() 和 values() 子句的顺序处于这种情况是必须显式定义。

如果 values() 子句先于 annotate() 子句,那么任何统计会自动添加到输出结果中。但是 values() 子句在 annotate() 子句之后,那么必须显式定义统计列。

缺省排序或 order_by() 子句的副作用

一个查询集中 order_by() 子句中的字段(或一个模型中缺省排序字段)会对输了数据产生影响,即使在 values() 中没有这些字段的定义时也同样会影响。这些特殊的字段会影响统计结果,这种情况在计数统计时尤为明显。

假设有一个这样的模型:

class Item(models.Model):
    name = models.CharField(max_length=10)
    data = models.IntegerField()

    class Meta:
        ordering = ["name"]

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Django中,可以使用权限系统来控制用户对不同页面访问权限。权限系统基于用户和用户组的概念来进行权限管理。 首先,需要在数据库中创建权限记录。可以使用Django提供的`manage.py`命令来创建权限记录,也可以在Django Admin后台手动创建。例如,可以创建一个名为"访问页面A"的权限记录。 接下来,可以在视图函数或类中使用`@permission_required`装饰器来限制页面访问权限。通过该装饰器,只有拥有特定权限的用户才能访问被装饰的视图。例如,可以在视图函数中加上`@permission_required('app_name.访问页面A')`。 此外,还可以在模板文件中使用`{% if user.has_perm %}`来判断用户是否拥有特定权限,从而动态展示页面上的内容。例如,可以在模板中使用`{% if user.has_perm 'app_name.访问页面A' %} 显示页面A的内容 {% endif %}`。 如果要限制整个应用中的所有视图都需要特定权限才能访问,可以在项目的URL配置中使用`login_required`装饰器和`PermissionRequiredMixin`类。例如,可以在`urls.py`文件中对整个应用添加如下代码: ``` from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import PermissionRequiredMixin urlpatterns = [ path('app_name/', login_required(PermissionRequiredMixin.as_view( permission_required='app_name.访问页面A')), name='app_name'), # ... ] ``` 以上是使用Django权限系统来控制不同页面访问权限的基本方法。通过创建权限记录、使用装饰器和在模板中判断权限,可以实现在不同页面中控制用户的访问权限。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

菲宇

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值