Django学习过程记录(二)

前言

本文章承接之前发布的Django学习过程记录(一)的内容,没有看过一的可以先去阅读之前的文章,这里的内容会一直包含到最后网站成品为止。

一、Django开发的高级操作

承接一里面Django开发的高级操作

1.1 前端开发的知识

说起做网站,那必然少不了前端知识,千万不要妄想着仅靠Django的内容去完成一个网站,Django知识一个后端框架,与以前java开发时候SSM,redis等类似,它用python完成,为我们前后端交互和所有逻辑的衔接提供辅助。这就好像做一台车子,车架已经给你装好了,里面的线路也都调试通过,但还是需要你把油箱、发动机等部件装进去,才能让车子跑起来,跑的远。

·用ajax替换原有的页面提交刷新方式。

Ajax——即异步Javascript和XML,它主要实现让网页无需整个刷新也能更新部分内容。通常我们在用爬虫截取网页数据的时候会发现数据大多采用这种方式加载,因此有时候网速慢的话会出现,网页整体框架都刷出来了,但是数据栏目却是空白的情况。来看看百度百科对Ajax的定义:
在这里插入图片描述
下面我们就用Ajax的方式来实现之前文章的评论提交。
(1)添加script的block
首先,在base.html {%block content %}后面添加一个block用于存放网页中的javascript代码,这是一个好习惯,因为肯定不只评论的网页需要用到ajax提交数据,这样以后网页需要添加javascript代码的时候就只需要在该block中添加即可。

{% block script_extends %}{% endblock %} # 用于存放每个网页的javascript代码

(2)添加javascript代码到评论页面blog_detail.html
关于ajax的方法,可以参考这篇文章——$.ajax方法详解

 <script type="text/javascript">
            $("#id_comment_form").off().submit(function () {
                // 更新数据到textarea
                CKEDITOR.instances['id_content'].updateElement();

                // 异步提交
                $.ajax({
                    url: "{% url 'submit_comment' %}",
                    type: 'POST',
                    data: $(this).serialize(),
                    cache: false,
                    success: function (data) {
                        console.log(data);
                    },
                    error: function (xhr) {
                        console.log(xhr);
                    }
                });
                return false;
            });

注意.off()方法不能省略,这保证你的script代码只执行一次。更新数据到textarea也不能省略,这让ajax可以直接获得第一手数据。
打开comment下的 views.py 修改submit_commit()方法如下:

def submit_comment(request):
    '''
    提交评论方法,该评论方法应对所有拥有评论的model提供
    :param request:
    :return:
    '''
    referer = request.META.get('HTTP_REFERER', reverse('index'))
    user = request.user
    comment_form = CommentForm(request.POST)  #request.POST 是一个字典
    data = {}
    if comment_form.is_valid() and user.is_authenticated:
        # 提交了一个有效评论
        comment = Comment(content = comment_form.cleaned_data['content'],
                          content_object = comment_form.cleaned_data['content_object'],
                          user=user)
        comment.save()
        # return redirect(referer)
        data['status'] = 'SUCCESS'
        data['submit_data'] = request.POST
    else:
        # 提交无效评论,如评论内容为空
        data['status'] = 'FAIL'
    
    return JsonResponse(data)

这时候我们获取的数据通过JsonResponse传输给ajax,而不是用redirect重定向的方式刷新页面。
在控制台打印输出提交的表单数据如下:
在这里插入图片描述
既然获取到了数据,接下来就是将数据在下面的列表进行显示:

success: function (data) {
                        {#console.log(data);#}
                        if (data.status == "SUCCESS") { //也可以用data['status']
                            // 成功获取到数据,插入到列表中
                            var comment_content = data.username + ' '+data.created_time+ ':'+ data.content;
                            $("#id_comment_list").prepend(comment_content);
                            // 点了提交后,清空编辑框
                            CKEDITOR.instances['id_content'].set_Data('');
                        }else{
                            //获取数据失败,比如内容为空,显示错误信息
                        }
                    },

prepend方法用于在指定id的标签中添加内容,这里我们将用户名、时间、评论内容组合后加到评论列表标签中即可。
(3)补充错误信息
有时候用户直接点击了提交或出现了提交错误,我们需要给予用户提示,
1、在CommentForm中设置error_message:
在这里插入图片描述
这个错误信息会存储到CommentForm的实例中,通过object.errors取到。
2、在views中获取该错误信息,传到data里:
在这里插入图片描述
errors是一个字典类型,我们将他的值转换为列表可以看到效果如下:
在这里插入图片描述
目前来看定义的错误就只有这一种,后面可能会加什么(比如你的评论包含粗鄙之语之类的233)
3、用jsp将该错误信息插入到定义好的标签中,类似于之前插入数据到评论列表中:

if (data.status == "SUCCESS") { //也可以用data['status']
                            // 成功获取到数据,插入到列表中
                            var comment_content = data.username + ' '+data.created_time+ ':'+ data.content;
                            $("#id_comment_list").prepend(comment_content);
                            // 点了提交后,清空编辑框
                            CKEDITOR.instances['id_content'].set_Data('');
                        }else{
                            //获取数据失败,比如内容为空,显示错误信息
                            $("#id_comment_error").text(data['error_message']);
                        }

4、查看效果:
在这里插入图片描述

1.2 自定义模板标签

在django 中我们可以用模板标签来让我们的代码更加独立,将许多操作与逻辑分开。拿get_blog_detail这个逻辑函数而言:

def get_blog_detail(request, blog_id):
    '''
    获取博客完整内容
    :param request:
    :param blog_id: 博客id
    :return:
    '''
    context = {}
    current_blog = Blog.objects.get(id=blog_id)
    # 阅读一次该博客进行阅读数加1
    read_key = read_once(request, current_blog)
    # 获取所有的评论
    blog_ct = ContentType.objects.get_for_model(current_blog)
    # 根据blog的content_type 和blog id找出评论中所有blog的评论
    comments = Comment.objects.filter(content_type=blog_ct, object_id=current_blog.id,
                                      parent=None)  # parent=None说明是原始评论
    context['comments'] = comments.order_by('-created_time')  # 将comments传送至前端,按照时间倒叙排序
    context['comment_num'] = comments = Comment.objects.filter(content_type=blog_ct, object_id=current_blog.id).count() # 评论数
    context['comment_form'] = CommentForm(initial={'content_type': blog_ct.model, 'object_id': blog_id,'reply_id':0})
    context['previous_blog'] = Blog.objects.filter(created_time__gt=current_blog.created_time).last()
    # 因为按照时间顺序,时间新的排在前面,日期比他大排在他前面,所以取比他大的最后一篇,下面取第一篇同理
    context['next_blog'] = Blog.objects.filter(created_time__lt=current_blog.created_time).first()
    context['blog'] = current_blog

可以看到在获取博客详情页的逻辑中出现了许多获取评论信息的代码,这对于整个代码的耦合性会有所提升,并且不利于阅读。
下面以计算评论数为例,介绍如何使用django中的自定义模板标签:
(1)在你想要添加的app下创建templatetags包
在该目录下创建一个comment_tags.py 这个文件用于存放你comment的模板方法。
在这里插入图片描述
(2)在模板中注册你的方法,具体代码如下

from django import template

register = template.Library()  # 获取注册器

@register.simple_tag  # simple_tag允许传入多个参数
def test(a):
    return "this is a test %s" % a

代码添加后必须重启本地服务,保证它已经注册
(3)注册完成后就可以使用该模板
找到你想要调用该方法的html文件,像之前{% load staticfiles %},load你的模板文件
在这里插入图片描述(4)在某个位置测试你的方法
找到你需要调用方法的位置用{% function args%} 这种样式调用
在这里插入图片描述
在这里插入图片描述

(5) 用该方法获取评论数传给html
修改模板方法:

@register.simple_tag  # simple_tag允许传入多个参数
def get_comment_num(obj):
    content_type = ContentType.objects.get_for_model(obj)  # 通过对象获取content_type
    comment_num = Comment.objects.filter(content_type=content_type, object_id=obj.id).count()  # 评论数
    return comment_num

在这里插入图片描述
在这里插入图片描述
可以看到结果是一样的。
同样地,在博客列表中我们也可以用这个方法显示评论数
在这里插入图片描述更多关于自定义模板地用法参考官方文档——自定义模板标签

二、网站开发中值得记录的地方(个人)

这篇内容主要是记载我在网站开发中实现某一个功能的过程,主要是怕日后遗忘之前完成的步骤,影响日后开发的调试、运维。同样记录这些,这样在日后开发有类似的内容则可以通过这里进行借鉴。

2.1 网站的评论回复功能

回复是基本上所有网站都会考虑加入的功能,本身回复也是一种评论,之前(Django学习过程记录一)里面已经讲述了评论的制作过程,并且在本文的1.1中第一条ajax的使用也进行了评论提交的优化。实现回复之前,评论的回复对于评论而言就是叶子结点,回复可以再回复,同一条评论也可以有多个回复,因此在原本评论的 model基础上,我们只需要添加父结点这一属性,但是为了更好了效果,多添加了root(所有回复针对的那个评论),to_user(回复针对的人)两个属性。
下面是实现回复功能的过程:
(1)修改comment的models中Comment Model
在这里插入图片描述
这时候显示一般评论从views中获取的时候就要指定一下parent属性为None,即一般评论没有父级了已经是root。

comments = Comment.objects.filter(content_type=blog_ct, object_id=current_blog.id,
                                      parent=None)  # parent=None说明是原始评论

(2)在html页面通过root评论显示其所有的回复
可以通过外键关联的root_comment属性获取root_comment为一样的所有回复
顺便添加一个回复按钮,回复按钮需要实现点击后滚动到评论输入框(避免再设计表单进行提交)。

{% for reply in comment.root_comment.all%}
  {{ reply.user.username }}
    {{ reply.created_time| date:"Y-m-d h:i:s" }}
    回复
    {{ reply.to_user.username }}
    {{ reply.content }}
    <br>
    <a href="javascript:reply({{ reply.id }})">回复</a>
    <br>
{% endfor %}

在这里插入图片描述

(3)实现点击回复跳转到输入栏
之前只是后台创建了回复的数据,下面要实现点击回复会自动滚动到评论输入框,同时要显示正在对谁的什么时候的什么评论进行回复。
咱们一一实现:
1、点击回复回滚到评论输入框
创建一个jsp function reply(),滚动基于整个html标签,用animate方法具体代码如下:
在这里插入图片描述在这里插入图片描述
2、显示针对的评论所有的内容
虽然滚动到了评论输入栏,但是应该表明是针对哪个评论进行回复,首先需要获取针对评论的id(通过在点击评论时调用的reply参数传入)。同时需要在评论的form中添加reply_id这一信息,如果是普通评论则id为0,否则id为获取到的reply_id.
在评论输入栏表单前面加入一个隐藏的div用于存放动态获取的评论内容,如下代码:

在这里插入图片描述
想想我们需要的内容在下面的评论列表里,因此那的内容需要用个标签存储,通过id访问得到(因为我们有了reply_id所以这个标签应该与其关联),然后构成html代码,通过获取到id_replied,html()方法将html代码加入。
先给需要的内容添加标签
在这里插入图片描述
jsp通过该标签获取,
在这里插入图片描述效果显示:
在这里插入图片描述
(3)通过评论输入栏写入回复到数据库
这个时候可以发现评论输入栏的内容多了reply_id这一项要提交,需要对这个属性进行验证,验证就需要在froms中CommentForm中进行clean操作:

def clean_reply_id(self):
    # 对reply_id进行验证
    reply_id = self.cleaned_data['reply_id']
    if reply_id < 0:
        raise forms.ValidationError('哦豁,好像出现错误了哦')
    elif reply_id ==  0:
        # 是0说明是一般评论,不是回复
        self.cleaned_data['parent'] = None
    else:
        if Comment.objects.filter(id = reply_id).exists():
            # 如果reply_id对应的评论或回复存在
            self.cleaned_data['parent'] = Comment.objects.get(id=reply_id)
        else:
            # 该条评论或回复可能被删除了
            raise forms.ValidationError('该评论找不到了哦,试试刷新页面')
    return reply_id

在views逻辑中判断提交的评论是否为回复,如果是需要添加root,parent,to_user属性
在这里插入图片描述
(4)添加回复后应该插入到对应评论下面
这时候就需要修改我们的jsp代码,之前jsp在成功获取到数据后是通过id找到对应标签,将数据添加进去,这里同理,但是我们需要找到该条评论的标签(因为所有的回复都是在该评论下,回复时间越晚越在下面)。
在这里插入图片描述修改原来的jsp success代码:

success: function (data) {
        {#console.log(data);#}
        if (data.status == "SUCCESS") { //也可以用data['status']
            // 成功获取到数据,插入到列表中

            if ($('#id_reply').val() == '0') {
                // 插入评论
                var comment_html = data.username + ' ' + data.created_time + ':' + data.content;
                $("#id_comment_list").prepend(comment_html);
            } else {
                //插入回复到评论下
                var reply_html = '<br>'+data.username + ' ' + data.created_time + ' ' +
                    '回复' + ' ' + data.to_user.username + '<br>' + data.content;
                $("#id_root_"+data.root_id).append(reply_html);
            }
            // 点了提交后,清空编辑框
            CKEDITOR.instances['id_content'].set_Data('');
        } else {
            //获取数据失败,比如内容为空,显示错误信息
            $("#id_comment_error").text(data['error_message']);
        }
    },

到这里已经基本实现评论的回复功能了

  • 小结
    之所以特地把这部分记录一下,是因为这个功能穿插了从html-jsp-django多个内容,详细到view,form和ajax操作等。值得注意的小技巧有以下几个:
    1、jsp通过id查找标签
 $("#id_comment_list").prepend(comment_html);

这个方式是经常用到的。
2、forms清理验证数据

    def clean_reply_id(self):
        # 对reply_id进行验证
        reply_id = self.cleaned_data['reply_id']
        if reply_id < 0:
            raise forms.ValidationError('哦豁,好像出现错误了哦')
        elif reply_id ==  0:
            # 是0说明是一般评论,不是回复
            self.cleaned_data['parent'] = None
        else:
            if Comment.objects.filter(id = reply_id).exists():
                # 如果reply_id对应的评论或回复存在
                self.cleaned_data['parent'] = Comment.objects.get(id=reply_id)
            else:
                # 该条评论或回复可能被删除了
                raise forms.ValidationError('该评论找不到了哦,试试刷新页面')
        return reply_id

3、views的逻辑判断
这里注意views是前端和后台数据逻辑交互的地方,html-jsp可以通过url来调用views中的方法,views可以通过render,JsonResponse等方式给前端传值

三、部署项目到服务器

当我们的网站开发的差不多了之后,就需要上线部署,让我们不仅能在本地访问,也能在任意一台主机上通过ip或域名访问到。

  • 1、购买一台服务器
    服务器可以选择国内或者国外,国内的诸如xx云。但我个人推荐购买国外的服务器,因为除了可以用于部署项目之外,还可以用来科学上网,你懂得OVO。
    购买服务器的流程在此不多赘述,网上有很多资料。

  • 2、移植项目到服务器上
    这时候就凸显出用虚拟环境搭建项目的好处了,在移植项目的过程中,只需要拷贝项目目录,创建虚拟环境,再pip install -r requirements.txt就大功告成。
    当项目可以在服务器上正常运行之后,尝试用其他机器访问,如下:
    在这里插入图片描述
    注意我开放的是80端口,与平常使用的8000端口不同。80端口专为http协议开放,作为网页服务器的访问端口,默认是80。此时再用我服务器的ip进行访问即可看到效果。
    在这里插入图片描述但是这种方式要求程序不能关闭,并没有达到我们期望的目标。

  • 3、web服务器软件部署
    在之前我们只是简单的将项目移植到服务器上运行,现在改用web服务器来开启项目。这里采用Nginx+uwsgi的方式实现。
    (1)安装uwsgi
    直接输入以下命令安装uwsgi

    pip3 install uwsgi
    

    安装完成后小测试一下:
    创建一个test.py,输入代码如下(代码参考uWSGI中文文档

    def application(env, start_response):
        start_response('200 OK', [('Content-Type','text/html')])
        return [b"Hello World"] # python3
    

    运行uWSGI:

    uwsgi --http :8000 --wsgi-file test.py
    
    • http:8000:使用http协议,开8000端口
    • wsgi-file test.py :加载指定文件 test.py
      如果提示已经有uWSGI占用端口,可以用如下命令关闭uWSGI相关进程:
    ps -aux | grep uwsgi | awk '{print $2}' | xargs kill -9
    

    在这里插入图片描述
    (2)用uWSGI启动我们的项目
    之前是单个文件用uWSGI启动,现在用uWSGI启动整个django项目,根据我项目的路径和虚拟环境的路径命令如下:

    uwsgi --chdir /home/projects/MyWebSite --home /root/.virtualenvs/MyWebSite_Env/ --http :8000 --module MyWebSite.wsgi:application  # 这是一条命令
    

    其中

    • –chdir /home/projects/MyWebSite:指定项目的根目录
    • –home /root/.virtualenvs/MyWebSite_Env/:指定项目的虚拟环境
    • –module MyWebSite.wsgi:application: 指定采用的wsgi

访问结果 :
在这里插入图片描述
但是样式都没有加载,是因为wsgi只提供链接服务,不提供静态文件加载。因此需要Nginx来提供完整的web服务。(Nginx提供静态文件服务,uWSGI提供动态数据服务)
(3)安装Nginx

apt-get install nginx

安装完成后进入/etc/nginx目录查看:
在这里插入图片描述
其中

  • sites-available:可用配置
  • sites-enabled:已用配置
    接下来我们进行Nginx的配置,进入sites-available目录,新建MyWebSite.conf
server{
        listen 80;
        server_name MyWebSite;
        charset utf-8;
        client_max_body_size 75M;

        location /static{
                alias /home/projects/MyWebSite/static;
        }

        location /media{
                alias /home/projects/MyWebSite/media;
        }

        # 动态数据处理
        location / {
                uwsgi_pass 127.0.0.1:8001;
                include /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
        }
}

这个配置文件告诉nginx提供来自文件系统的媒体和静态文件,以及处理那些需要Django干预的请求。对于一个大型部署,让一台服务器处理静态/媒体文件,让另一台处理Django应用,被认为是一种很好的做法,但是现在,这样就好了。
注意我们之前启动uWSGI的时候要输入项目路径,虚拟环境路径,module等信息,这太麻烦了,我们设置一个配置文件用于存储这些信息。
(4)配置uWSGI
针对该项目的uWSGI建议放在与项目同目录下,我放在了/home/projects/(我项目的根目录)中,创建一个ini配置文件,输入如下内容:

[uwsgi]
chdir=/home/projects/MyWebSite  # 项目目录
home=/root/.virtualenvs/MyWebSite_Envs  # 虚拟环境目录
module=MyWebSite.wsgi:application  # 采用的wsgi

master=True  # 启动主程序
processes=4  #使用进程数
max-requests=5000  # 最大请求数

socket=127.0.0.1:8001
uid=1000
gid=2000  # 用Nginx用户组,不用root

pidfile=/home/projects/MyWebSite_uWSGI/master.pid  # 允许对进程进行启动和关闭
daemonize=/home/projects/MyWebSite_uWSGI/MyWebSite.log  # 运行日志
vacuum=True  

用uwsgi命令启动:

uwsgi --ini /home/projects/MyWebSite_uWSGI/MyWebSite.ini
发布了46 篇原创文章 · 获赞 99 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/GentleCP/article/details/88081096