开发一个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
webchatmodel.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)),
webchaturls.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'), )
webchatviews.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 %}