用Django全栈开发——24. CMS的真正的Dashboard开发

大家好,这是皮爷给大家带来的最新的学习Python能干啥?之Django教程,从零开始,到最后成功部署上线的项目。这一节,用AdminLTE编写Dashboard。

Peekpa.com的官方地址:http://peekpa.com

皮爷的每一篇文章,都配置相对应的代码。这篇文章的代码Tag是Post_024

title.jpeg

目前,当我们登录我们的CMS界面,我们看到的是这个样子:

001.png

很丑,不美观。所以这几节,我们将要开发一款真正意义上的CMS Dashboard。具体开发完成长这个样子:

002.png

我们看到,具体分为这么几个部分:

  • 用户访问
  • 文章统计
  • 若是想加,还可以加其他的功能

这些东西我们其实都是可以做出来的,接下来的几节课,我们就来讲讲每一个部分都是怎么实现的。

用户访问统计

首先是用户访问统计,我们要统计每一个用户的访问请求,同时要记录每天网站访问人数,那么我们就需要创建模型了。

这个模型应该是属于全站使用,所以我们就在basefucntion/models.py下面的创建:

class UserIP(models.Model):
    ip_address = models.CharField(max_length=30)
    ip_location = models.CharField(max_length=30)
    end_point = models.CharField(default='/', max_length=30)
    day = models.DateField(default=timezone.now)


# 网站总访问次数
class VisitNumber(models.Model):
    count = models.IntegerField(default=0)  # 网站访问总次数


# 单日访问量统计
class DayNumber(models.Model):
    day = models.DateField(default=timezone.now)
    count = models.IntegerField(default=0)  # 网站访问总次数

接着,我们在base应用下,创建一个tracking_view.py,里面要放我们的更新方法,即最终网站访问数量的方法:

def peekpa_tracking(func):
    def wrapper(request, *args, **kwargs):
        tacking_info(request)
        return func(request, *args, **kwargs)
    return wrapper


def tacking_info(request):
    update_visit_number()
    update_user_ip(request)
    update_day_visit_number()


def update_visit_number():
    count_nums = VisitNumber.objects.filter(id=1)
    if count_nums:
        count_nums = count_nums[0]
        count_nums.count = F('count') + 1
    else:
        count_nums = VisitNumber()
        count_nums.count = 1
    count_nums.save()


def update_user_ip(request):
    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

    UserIP().objects.create(ip=client_ip, end_point=request.path, ip_address="TBA", day=timezone.now().date())


def update_day_visit_number():
    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()

首先,这里我们要通过装饰器的方式来实现,当request进来的时候,我们做了三步处理:

  1. 更新总网站访问量;
  2. 更新个人访问记录;
  3. 更新每天访问数量。

然后再将request交给传入的func去做接下来该做的事儿。

我们这个时候可以在文章的detail信息做一下验证:

@peekpa_tracking
def detail(request, time_id):

这个时候,我们再去打开之前发布的任何一个文章:

003.png

访问成功之后,我们看数据库里面:

004.png

就能看到我们跟踪的信息已经存到了数据库中了。说明成功了。接下来我们就要来做Dashboard的统计工作。

页面统计的显示

我们在最开始的预览图里面看到,我们的Dashboard页面是有网站点击相关统计的内容显示的,接下来,我们就将这些东西显示出来。

如果我们要将数据显示到Dashboard上,那么就应该在Dashboard的home视图函数中,先提前将数据准备好,然后再传递给页面即可。

所以,我们的主要逻辑就应该在cms_dashboard(request)视图方法中写。

@peekpa_login_required
def cms_dashboard(request):
    context = {}
    context.update(get_dashboard_top_data())
    context.update(get_dashboard_visitor_ip_table())
    return render(request, 'cms/home/home.html', context=context)
    
def get_dashboard_top_data():
    post_num = Post.objects.all().count()
    day_visit_ip_set = set()
    day_visit_ip_list = UserIP.objects.filter(day=timezone.now().date())
    if day_visit_ip_list:
        for user_ip_item in day_visit_ip_list:
            if user_ip_item.ip_address not in day_visit_ip_set:
                day_visit_ip_set.add(user_ip_item.ip_address)
    day_visit_ip_num = len(day_visit_ip_set)
    day_visit_num = DayNumber.objects.filter(day=timezone.now().date())[0].count
    total_visit_num = VisitNumber.objects.filter(id=1)[0].count
    context = {
        "post_num": post_num,
        "day_visit_ip_num": day_visit_ip_num,
        "day_visit_num": day_visit_num,
        "total_visit_num": total_visit_num
    }
    return context
    
def get_dashboard_visitor_ip_table():
    visitor_data = UserIP.objects.filter(day=timezone.now().date())
    if len(visitor_data):
        visitor_data = visitor_data[:7]
    context = {
        'visitor_data_list': visitor_data,
    }
    return context

可以看到,我们这里使用两个方法get_dashboard_top_dataget_dashboard_visitor_ip_table来分别获取顶部四个小模块的数据还有Visitor IP Table的数据。我们拿到数据之后,就展示到前端:

005.png

接下来,我们就要完善Chart的内容了。

图表显示

这里可以看到,我们的Dashboard用到了一个非常美丽的表单,这个是Chart.js,是一个非常好用的图表库。官网地址:

https://www.chartjs.org/

因为我们首先需要将值从后台传给前端,然后这个图标是个js文档,所以,我们应该在html代码中,编写一些js代码。我们的home_visit_chat.html代码就应该变成下面这样:

<script>
    $(document).ready(function () {
        var ticksStyle = {
            fontColor: '#495057',
            fontStyle: 'bold'
        }
        var $visitorsChart = $('#visitors-chart')
        var visitorsChart2 = new Chart($visitorsChart, {
            data: {
                labels: {{ date_time_list }},
                datasets: [{
                    type: 'line',
                    data: {{ week_data_list }},
                    backgroundColor: 'transparent',
                    borderColor: '#007bff',
                    pointBorderColor: '#007bff',
                    pointBackgroundColor: '#007bff',
                    fill: true
                }]
            },
            options: {
                maintainAspectRatio: false,
                tooltips: {
                    mode: 'index',
                    intersect: true
                },
                hover: {
                    mode: 'index',
                    intersect: true
                },
                legend: {
                    display: false
                },
                scales: {
                    yAxes: [{
                        // display: false,
                        gridLines: {
                            display: true,
                            lineWidth: '4px',
                            color: 'rgba(0, 0, 0, .2)',
                            zeroLineColor: 'transparent'
                        },
                        ticks: $.extend({
                            beginAtZero: false,
                            suggestedMax: {{ suggested_max }}
                        }, ticksStyle)
                    }],
                    xAxes: [{
                        display: true,
                        gridLines: {
                            display: false
                        },
                        ticks: ticksStyle
                    }]
                }
            }
        })
    })
</script>

可以看到,这里面有好多数据都是需要后盾传送给前端,所以,我们将这些数据都读取出来,添加给cms_dashboard()视图函数即可:

def get_dashboard_visitor_chart():
    days_list = []
    visit_list = []
    max_num = 0
    week_total_num = 0
    for index in range(6, -1, -1):
        day, format_date = get_before_date(index)
        days_list.append(int(day))
        day_visit_num = 0
        daynumber_item = DayNumber.objects.filter(day=format_date)
        if daynumber_item:
            day_visit_num = daynumber_item[0].count
        visit_list.append(day_visit_num)
        week_total_num += day_visit_num
        max_num = day_visit_num if day_visit_num > max_num else max_num
    context = {
        'visit_week_total_number': day_visit_num,
        'date_time_list': days_list,
        'week_data_list': visit_list,
        'suggested_max': max_num
    }
    return context

这样,我们的图表就制作完成了。看一下效果:

006.png

最后一个,就是我们的文章浏览情况了。

文章阅读情况

文章阅读,我们既然已经知道了每一天的访问量,而且他们访问的地址我们也知道,所以,文章的访问我们就能够很轻易的做出来。

同样,还是在cms_dashboard视图函数里面添加数据即可。

def get_dashboard_post_view_table():
    visitor_day_data = UserIP.objects.filter(day=timezone.now().date(), end_point__contains='/detail/')
    post_map = {}
    post_view_table_list = []
    if visitor_day_data:
        for item in visitor_day_data:
            post_id = item.end_point.split('/')[2]
            if post_id in post_map:
                post_map[post_id] += 1
            else:
                post_map[post_id] = 1
    if post_map:
        for key in post_map:
            key = key
            post_item = Post.objects.filter(time_id=key)
            if post_item:
                post_item[0].inscrease = post_map[key]
                post_view_table_list.append(post_item[0])
    if post_view_table_list:
        post_view_table_list.sort(key=lambda x: x.inscrease, reverse=True)
    context = {
        'post_view_table_list': post_view_table_list,
    }
    return context

这里我们操作比较繁琐,主要是经历了这么几步骤:

  1. 读取当天文章点击情况;
  2. 获取出来文章列表,并将这些数据存储在一个字典中,key是id,name是文章个数;
  3. 从Post里面捞出来这些文章,然后排序返回给前端展示。

最后,我们来看一下整体的效果:

007.png

看到整个页面分为四个板块,然后左侧还做了Monitor,里面分别对应的UserIP管理还有文章阅读详情。其实Dashboard的编写,还能更加多变灵活,关键还是要根据自己的实际需求来完成。

文章统计

最后我们再来加一章节,来说一下文章统计的事儿。

细心读文章的同学肯定发现了,我们之前的文章统计的数值是不对的,那么想要实现文章统计,我们有这么几个思路:

  1. 每一次请求文章的时候,我们从数据库读取文章对象,然后把Post里面的read_num喜加一,再存进去;
  2. 稍微设置一下缓存,通过的F表达式,来做懒加载处理;
  3. 将文章的“喜加一”功能,放到MemoryCache或者Reddis里面做处理。

这几种思路,他们的特点分别是:

  1. 实现简单,但是耗费数据库开销严重;
  2. 实现一般,可缓解一定的数据库开销,但是当网站规模增大的时候,高并发会出问题;
  3. 实现有难度,但是可以扛得住高并发的问题。

既然,我们的peekpa.com也不是什么一般的小网站,这里我就选择第二种实现方式给大家看看。

这里来简单说一下思路:

当用户访问文章详情的时候,会针对用户生成一个uid,这个uid在不同的文章里面,缓存1分钟。1分钟之内用户重复访问,不算访问量。然后懒加载更新数据库。

这回,我们就要用到了middleware了。首先在Post应用下,创建一个middleware python包,然后在里面实现一个叫做user_idmiddleware:

008.png
class UserIDMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        uid = self.generate_uid(request)
        request.uid = uid
        response = self.get_response(request)
        response.set_cookie(USER_KEY, uid, max_age=TEN_YEARS, httponly=True)
        return response

    def generate_uid(self, request):
        try:
            uid = request.COOKIES[USER_KEY]
        except KeyError:
            uid = uuid.uuid4().hex
        return uid

然后,我们要在settings.py文件里面的``中,在第一个位置添加这个middleware:

MIDDLEWARE = [
    'apps.poster.middleware.user_id.UserIDMiddleware',
]

最后,我们就在文章详情请求页里面,来加入我们上面所讲的逻辑:

@peekpa_tracking
def detail(request, time_id):
    handle_visited(request, time_id)
    return render(request, 'post/detail.html', context=context)
    
def handle_visited(request, time_id):
    increase_post_view = True
    uid = request.uid
    pv_key = 'pv:%s:%s' % (uid, request.path)
    if not cache.get(pv_key):
        increase_post_view = True
        cache.set(pv_key, 1, 2*60)

    if increase_post_view:
        Post.objects.filter(time_id=time_id).update(read_num=F('read_num') + 1)

这样,我们就完美的实现了页面访问喜加一的功能。

技术总结

最后总结一下,

编写CMS的Dashboard:

  1. 用户统计,需要创建模型来存储管理,我们这里创建了UserIP,VisitNumber还有DayVisitNumberr,分别管理单次点击,整个点击还有每天的点击量;
  2. Dashboard还是要拆开分结构,顶部四个小方块,然后拆成四个表格;
  3. cms_dashboard()这个视图函数里面添加数据返回前端;
  4. 添加数据,按照模块一步一步的处理;
  5. Chartjs,这里我们在html模板里面写了JavaScript代码,同样也是;
  6. 文章排序,则是通过每日访问里面的end_point值获取到文章id,然后再去Post里面根据ID找文章,最后展示出来;
  7. 页面喜加一,则是用到了MiddleWare来给request添加一个uid,然后当request到达视图函数的时候,通过uid是否在缓存里来判断是否需要喜加一;
  8. 完毕

获取代码的唯一途径:关注『皮爷撸码』,回复『代码』即可获得。

长按下图二维码关注,如文章对你有启发,欢迎在看与转发。

底部图片.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348