阿里山QQ

导航

web聊天室

开发一个web聊天室

功能需求:

1、用户可以与好友一对一聊天

2、群聊

 

所需知识

1、Django

2、bootstrap

3、CSS

4、ajax

 

涉及到的新的知识点

1、如果设计表结构的时候,一张表中有一个以上的字段关联另外一张相同的表(外键),那么直接关联会出错,合适的方法是使用related_name指定一个名字就可以解决,如下members和admins.

class QQgroup(models.Model):
    name =models.CharField(max_length=64,unique=True)
    members=models.ManyToManyField(UserProfile,blank=True)  #null =True无效
    admins=models.ManyToManyField(UserProfile,related_name='group_admins')   #在model中存在同时存在两个字段关联一张表,这样的会出错,需要使用related_name将名字修改一下来解决
    max_member_nums=models.IntegerField(default=200)

2、在views或者js代码中,如果需要调用不同的函数,指向函数的url可以通过name名直接调用函数,而无需写url的真实路径,多个name可以同时指向同一个url,如下new_msg,另外,url建议使用名词,养成良好的编码习惯。

urlpatterns = patterns('',

    url(r'dashboard/$', views.dashboard,name='web_chat'),
    url(r'contacts/$', views.contacts,name='load_contact_list'),    #url全部使用名词,符合规范
    url(r'msg/$', views.new_msg,name='send_msg'),    #url全部使用名词,符合规范
    url(r'msg/$', views.new_msg,name='get_new_msgs'),
                       )

3、使用外键关联表自己的时候,不管是ForeignKey还是ManyToManyField,都需要related_name,如下friends字段:

class UserProfile(models.Model):
    '''账户信息表'''
    user=models.OneToOneField(User)   #继承自带的User表,但是原生的user表中的字段较少,可以继承之后可以扩展字段;只能使用onetoone,否则就会使得多个用户同时关联一个账户onetoone是在代码层面进行限制的,其实就是讲两张表进行拼接了
    name = models.CharField(max_length=32)
    groups=models.ManyToManyField('UserGroup')
    friends=models.ManyToManyField('self',related_name='my_friends')
    def __unicode__(self):
        return self.name
contacts=request.user.userprofile.friends.select_related().values('id','name')     #通过一个字段查看多对多,select_related查看所有的朋友,values表示需要查看的字段,为列表形式,元素为字典

 

4、如果开启了csrf验证功能,那么在Django form提交的时候,在模本中增加{% csrf_token %}即可,如果是ajax提交的数据,有两种方式让提交的数据添加csrf token;

a、在html模板中添加{% csrf_token %},将其埋在页面中,默认是hide的,通过页面元素审查,找到对应的input下的name为csrfmiddlewaretoken,将其value值通过函数在POST提交的时候,添加到提交的数据中,但是这种方式,每次POST数据都需要获取token并添加提交,不方便;

    function GetCsrfToken(){
        return $("input[name='csrfmiddlewaretoken']").val();   //获取csrftoken的值
        //每次post提交数据都需要将csrf token的值获取出来进行post提交,有点low
    }


 function SendMsg(msg_text){   //通过ajax 将数据进行提交
        var contact_id = $(".chat-header span").attr("contact_id");
        var contact_type = $(".chat-header span").attr("contact_type");
        var msg_dic={
            'contact_type':contact_type,
            'to':contact_id,
            'from':"{{ user.userprofile.id }}",
            'from_name':"{{ user.userprofile.name }}",
            'msg':msg_text
        };
        console.log("{{ user.userprofile.id }}");
        console.log("{{ user.userprofile.name }}");
        //$.post("{% url 'send_msg' %}",{'data':JSON.stringify(msg_dic),'csrfmiddlewaretoken':GetCsrfToken()},function(callback){    //csrfmiddlewaretoken提交的时候讲csrf的值一起提交
        $.post("{% url 'send_msg' %}",{'data':JSON.stringify(msg_dic)},function(callback){    //使用插件,csrfmiddlewaretoken提交的时候讲csrf的值一起提交
             console.log(callback);     //函数的返回值为callback,采用回调函数
        });    //end post  JSON.stringify(msg_dic)将字典转换为json格式
    }

b、使用插件提交

   //csrf ref
    //获取csrf token并添加到每一次post的数据中
    function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');


    function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});
    // end csrf ref

5、正向或者反向获取数据库外键关联表的字段值

    contacts=request.user.userprofile.friends.select_related().values('id','name')     #通过一个字段查看多对多,select_related(外键关联的所有实体)查看所有的朋友,values表示需要查看的字段,为列表形式,元素为字典
    print contacts
    contact_dic['contact_list']=list(contacts)    #显示为列表,其实还是django的对象,需要强制转换为列表
    groups=request.user.userprofile.qqgroup_set.select_related().values('id','name','max_member_nums')    #1、获取群组,qqgroup_set这种方法适用于自己没有和别的表关联,但是别的表和自己关联了
                                                                    #2、如果不使用上述方法,就需要从QQgroup表的member中过滤出包含自己名字的组名,添加
                                                                    #a=models.QQgroup.object.all(); for i in a:print i.members.select_related()

 

 

 

 

 

聊天室几种实现方式:

http请求是短链接、无状态

客户端cookie中保存的是session id,每次,对于同义词session请求,客户端会携带session id与服务器进行交互,这样对于无状态的http请求,服务端就会知道这次请求与上次请求的关系;

客户端连接上服务器之后,会阻塞,如果客户端有新消息发送过来的时候,就会唤醒,属于一种长轮询方式;

另外一种解决方式是WebSocket(长连接),通过浏览器(支持h5的浏览器)和服务器(支持长连接)端建立socket来快速实现;Django不支持长连接;

 

表结构设计:

class UserProfile(models.Model):
    '''账户信息表'''
    user=models.OneToOneField(User)   #继承自带的User表,但是原生的user表中的字段较少,可以继承之后可以扩展字段;只能使用onetoone,否则就会使得多个用户同时关联一个账户onetoone是在代码层面进行限制的,其实就是讲两张表进行拼接了
    name = models.CharField(max_length=32)
    groups=models.ManyToManyField('UserGroup')
    friends=models.ManyToManyField('self',related_name='my_friends')  #每一个用户都有自己的多个好友,好友也可以有多个好友;
    def __unicode__(self):
        return self.name

webchat\model.py

from django.db import models
from web.models import UserProfile
# Create your models here.

class QQgroup(models.Model):
    name =models.CharField(max_length=64,unique=True)
    members=models.ManyToManyField(UserProfile,blank=True)  #null =True无效
    admins=models.ManyToManyField(UserProfile,related_name='group_admins')   #在model中存在同时存在两个字段关联一张表,这样的会出错,需要使用related_name将名字修改一下来解决
    max_member_nums=models.IntegerField(default=200)

表结构创建完成之后,利用admin进行创建数据 

 

WEB聊天室页面布局

将聊天的url分发到自己的应用当中

from django.conf.urls import patterns, include, url
from django.contrib import admin
from web import views
from webchat import urls as chat_urls

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'js.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),

    url(r'^admin/', include(admin.site.urls)),
    url(r'^chat/', include(chat_urls)),

webchat\urls.py

from django.conf.urls import patterns, include, url
from django.contrib import admin
import views

urlpatterns = patterns('',

    url(r'dashboard/', views.dashboard,name='web_chat'),
                       )

webchat\views.py

from django.shortcuts import render
def dashboard(request):
    return render(request,'web_chat/dashboard.html')

dashboard.html:整个包含在一个大的div中,分为左右两个小的div(加row属性),占3/12,右边占9/12,右边的div分为3部分,顶部chat-header,中部chat-content,和底部chat-msg-sendbox(分为左右两部分);

{% extends 'index.html' %}

{% block page-container %}
<h1>撩妹专区.....</h1>
<div class="chat-container row">
    <div class="contact-list col-md-3">
        contact list
    </div>
    <div class="chat-box col-md-9">
        chat box
        <div class="chat-header"> talking with ...now</div>
        <div class="chat-content">content</div>
        <div class="chat-msg-sendbox row">
            <div class="msg-box col-md-10">
                <textarea></textarea>
            </div>
            <div class="msg-box-tn col-md-2">
                <button type="button" class="btn btn-success">发送</button>
            </div>
        </div>
    </div>

</div>
{% endblock %}
{% block bottom-js %}
    <script>
        $(document).delegate("textarea","keydown", function (e) {
            if(e.which==13){
                var msg_text=$("textarea").val();
                if($.trim(msg_text).length>0){
                    //SendMsg(msg_text);
                    console.log(msg_text);
                    //AddSendMsgIntoBox(msg_text);
                    //$("textarea").val('');

                }
            }
        })
    </script>
{% endblock %}

 

posted on 2016-08-04 10:09  阿里山QQ  阅读(383)  评论(0编辑  收藏  举报