Django模拟新浪微博的@功能 (转载)
在论坛加了个类似微博的@功能,在回复帖子的时候可以@系统中的用户,被@的用户可以收到自己被@的通知可以做出相应的处理。
关于model
1 #-*- coding:utf-8 -*- 2 from django.db import models 3 from django.contrib.auth.models import User 4 import datetime 5 from geek.geekchallenge.models import * 6 from django.db.models.signals import post_save 7 from django.dispatch import receiver 8 from django.contrib.contenttypes.models import ContentType 9 from django.contrib.contenttypes import generic 10 import re 11 class Reply(models.Model): 12 thread = models.ForeignKey(Thread, verbose_name=u"所属帖子") 13 author = models.ForeignKey(Team, verbose_name=u"回复作者") 14 content = models.TextField(verbose_name=u"回复内容") 15 submit_time = models.DateTimeField(verbose_name=u"发表时间", auto_now_add=True) 16 update_time = models.DateTimeField(verbose_name=u"更新时间", blank=True, null=True, editable=False) 17 events = generic.GenericRelation('Event') 18 def __unicode__(self): 19 return self.submit_time.strftime("%Y-%m-%d %H:%m:%S") 20 class Meta: 21 verbose_name_plural = u"帖子回复" 22 @receiver(post_save, sender=Reply, dispatch_uid="ashin_unique_identifier") 23 def post_save_handler(sender, instance, **kwargs): 24 reply = instance 25 team_name_pattern = re.compile('(?<=@)(\w+)', re.UNICODE) 26 at_team_names = set(re.findall(team_name_pattern, reply.content)) 27 if at_team_names: 28 for at_team_name in at_team_names: 29 if at_team_name != reply.author.user.username: 30 try: 31 at_team = User.objects.get(username=at_team_name) 32 event = Event(author=reply.author.user, event=reply, at_team=at_team) 33 event.save() 34 except: 35 pass 36 elif reply.author != reply.thread.author: 37 event = Event(author=reply.author.user, event=reply, at_team=reply.thread.author.user) 38 event.save() 39 class Event(models.Model): 40 content_type = models.ForeignKey(ContentType, verbose_name=u"被触发的模型") 41 object_id = models.PositiveIntegerField(verbose_name=u"被触发模型ID") 42 author = models.ForeignKey(User, verbose_name=u"事件发起者", related_name="author") 43 event = generic.GenericForeignKey('content_type', 'object_id') 44 at_team = models.ForeignKey(User, verbose_name=u"提到的人", related_name="at_team") 45 #两个外键都指向User必须使用related_name参数 46 submit_time = models.DateTimeField(verbose_name=u"@时间", auto_now_add=True) 47 is_readed = models.BooleanField(verbose_name=u"已读", default=False) 48 is_deleted = models.BooleanField(verbose_name=u"已被删除", default=False) 49 def __unicode__(self): 50 return u"%s在回复%s的帖子《%s》中提到了%s"%(self.author, self.event.thread.author, self.event.thread, self.at_team) 51 class Meta: 52 verbose_name_plural = u"回复新闻" 53 ordering = ["-submit_time"]
在用户按钮处显示有多少条未读@消息
1 <script> 2 function get_new_at_num(){ 3 $.get('/forum/new-at-num/',function(data){ 4 if (data != 0){ 5 $('#at_tip').text('('+data+'条@我未读)'); 6 $('#id_global_at').text(' ('+data+')'); 7 } 8 else{ 9 $('#at_tip').text(''); 10 $('#id_global_at').text(''); 11 } 12 window.setTimeout(get_new_at_num, 5000) 13 }); 14 }; 15 $(function(){ 16 get_new_at_num(); 17 }); 18 </script> 19 <div class="btn-group pull-right"> 20 <a class="btn dropdown-toggle" data-toggle="dropdown" href=""> 21 <i class="icon-user"></i>{% if request.user.is_authenticated %} {{request.user.username}} <span id="at_tip"></span>{% else %} 游客{% endif %} 22 <span class="caret"></span> 23 </a> 24 <ul class="dropdown-menu"> 25 {% if request.user.is_authenticated %} 26 <li><a href="/team-info/"><i class="icon-cog"></i> 团队信息</a></li> 27 <li><a href="/forum/my-threads/"><i class="icon-th-list"></i> 我的帖子</a></li> 28 <li><a href="/forum/at-me/"><i class="icon-envelope"></i> @我的回复<font color="red" id="id_global_at"></font></a></li> 29 <li class="divider"></li> 30 <li><a href="/accounts/logout/"><i class="icon-off"></i> 退出</a></li> 31 {% else %} 32 <li><a href="/accounts/register/"><i class="icon-pencil"></i> 注册</a></li> 33 <li class="divider"></li> 34 <li><a href="/accounts/login/"><i class="icon-leaf"></i> 登录</a></li> 35 {% endif %} 36 </ul> 37 </div><!-- userbtn -->
ajax每隔5秒请求一次服务器返回未读消息的条数,有就显示在页面上
1 @login_required 2 def new_at_num(request): 3 unread_count = Event.objects.filter(at_team = request.user, is_deleted=False, is_readed = False).count() 4 return HttpResponse(unread_count)
@的消息页面
页面代码
1 <div> 2 <p>当前位置: <a href="/forum/">论坛首页</a> » @我的回复</p> 3 <p>共 <b id="id_all_count">{{results|length}}</b> 条, 未读 <b id="id_unread_count">{{unread_count}}</b> 条</p> 4 </div> 5 <div class="span10"> 6 {% if paged_events.object_list %} 7 <table class="table table-hover table-striped"> 8 <tr id="id_news_table_head"> 9 <th>点击查看@我的回复</th> 10 <th>@我的时间</th> 11 <th>阅读状态</th> 12 <th>操作</th> 13 </tr> 14 {% for news in paged_events.object_list %} 15 <tr id="id_news_{{news.id}}"> 16 <td><a href="/forum/{{news.event.thread.forum.id}}/{{news.event.thread.id}}/?reading={{news.id}}#reply-{{news.event.id}}">{{ news.author }}在回复{{ news.event.thread.author}}的帖子《{{news.event.thread.title}}》中提到了您</a></td> 17 <td>{{ news.submit_time|date:"Y-m-d H:i:s"}}</td> 18 <td>{% if news.is_readed %}已读{% else %}<font color="red" id="id_unread_{{news.id}}">未读</font>{% endif %}</td> 19 <td><button id="id_readed_news_{{news.id}}" onclick="readed_news('{{news.id}}');" class="btn btn-mini btn-success" {% if news.is_readed %}disabled{% endif %}>设为已读</button> <button id="id_delete_news" onclick="delete_news('{{news.id}}');" class="btn btn-mini btn-warning">删除记录</button></td> 20 </tr> 21 {% endfor %} 22 </table> 23 {% endif %} 24 </div><!--span10--> 25 <script> 26 function readed_news(id){ 27 var url = '/forum/readed-at-me/'+id+'/'; 28 $.post(url, {'csrfmiddlewaretoken':'{{ csrf_token }}'}, function(data){ 29 if (data=='success'){ 30 $('#id_unread_'+id).attr('color', '').text('已读'); 31 $('#id_readed_news_'+id).attr('disabled', 'true'); 32 $('#id_unread_count').text($('#id_unread_count').text()-1) 33 }else{ 34 alert('操作失败!'); 35 } 36 }); 37 } 38 function delete_news(id){ 39 var url = '/forum/delete-at-me/'+id+'/'; 40 $.post(url, {'csrfmiddlewaretoken':'{{ csrf_token }}'}, function(data){ 41 if(data=='success'){ 42 if ($('#id_unread_'+id).text() == '未读'){ 43 $('#id_unread_count').text($('#id_unread_count').text()-1) 44 } 45 $('#id_news_'+id).remove(); 46 $('#id_all_count').text($('#id_all_count').text()-1) 47 }else{ 48 alert('操作失败!'); 49 } 50 }); 51 } 52 </script>
处理函数
1 @login_required 2 def at_me(request): 3 #最新帖子 4 latest_threads = Thread.objects.all()[:5] 5 #回复事件 6 results = Event.objects.filter(at_team = request.user, is_deleted=False) 7 unread_count = Event.objects.filter(at_team = request.user, is_deleted=False, is_readed = False).count() 8 #分页 9 events_paginator = Paginator(results, 15) 10 try: 11 page = int(request.GET.get('page', 1)) 12 except ValueError: 13 page = 1 14 try: 15 paged_events = events_paginator.page(page) 16 except (EmptyPage, InvalidPage): 17 paged_events = events_paginator.page(events_paginator.num_pages) 18 return render_to_response('at-me.html', {"results":results,'unread_count':unread_count, 'latest_threads':latest_threads, "paged_events":paged_events, "events_paginator":events_paginator}, context_instance=RequestContext(request)) 19 @login_required 20 def readed_news(request, news_id): 21 news = get_object_or_404(Event, id=news_id, at_team=request.user) 22 if request.method == 'POST': 23 news.is_readed=True 24 news.save() 25 return HttpResponse("success") 26 return HttpResponse("error") 27 @login_required 28 def delete_news(request, news_id): 29 news = get_object_or_404(Event, id=news_id, at_team=request.user) 30 if request.method == 'POST': 31 news.is_deleted = True 32 news.save() 33 return HttpResponse("success") 34 return HttpResponse("error")
在回复中@用户
实现代码:
1 <script src="/static/js/userAutoTips.js"></script> 2 <style type="text/css" > 3 ol, ul { list-style: none outside none; } 4 .recipients-tips{ font-family:Tahoma, Arial;position:absolute; background:#282828; z-index:2147483647; padding:2px; border:2px solid #33b5e5; display:none;} 5 .recipients-tips li a{display:block; padding:2px 5px; cursor:pointer;} 6 .autoSelected{background:#131517;} 7 </style> 8 <script type="text/javascript"> 9 userAutoTips({id:'id_content'}); 10 </script>
用了网上找的一个别人写好的只要检测到textarea中有@就会显示列表的代码,自己该了一些设置和获取最近活跃的10位用户
1 @login_required 2 def nearest_users(request): 3 threads = Thread.objects.all() 4 replies = Reply.objects.all() 5 users = [thread.author.user.username for thread in threads] 6 users.extend([reply.author.user.username for reply in replies]) 7 users = set(users) 8 if request.user.username in users: 9 users.remove(request.user.username) 10 user_name_list = [] 11 for user in users: 12 user_name_list.append({}.fromkeys(('user', 'name'), user)) 13 show_count = 15 14 if len(user_name_list) > show_count: 15 user_name_list = user_name_list[:show_count] 16 data = simplejson.dumps(user_name_list) 17 return HttpResponse(data, mimetype="application/json")
UserAutoTips.js放到微盘了:
http://vdisk.weibo.com/s/naFdb