开发一个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=,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=)
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=)
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 = ; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(, name.length + ) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + ));
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') #、获取群组,qqgroup_set这种方法适用于自己没有和别的表关联,但是别的表和自己关联了
#、如果不使用上述方法,就需要从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=)
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=,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=)
表结构创建完成之后,利用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==){
var msg_text=$("textarea").val();
if($.trim(msg_text).length>){
//SendMsg(msg_text);
console.log(msg_text);
//AddSendMsgIntoBox(msg_text);
//$("textarea").val(''); }
}
})
</script>
{% endblock %}