第九届CUIT信息安全大赛正常进行,围观地址:http://hack.myclover.org/


在论坛加了个类似微博的@功能,在回复帖子的时候可以@系统中的用户,被@的用户可以收到自己被@的通知可以做出相应的处理。


关于model

#-*- coding:utf-8 -*-
from django.db import models
from django.contrib.auth.models import User
import datetime
from geek.geekchallenge.models import *
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
import re
class Reply(models.Model):
    thread = models.ForeignKey(Thread, verbose_name=u"所属帖子")
    author = models.ForeignKey(Team, verbose_name=u"回复作者")
    content = models.TextField(verbose_name=u"回复内容")
    submit_time = models.DateTimeField(verbose_name=u"发表时间", auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=u"更新时间", blank=True, null=True, editable=False)
    events = generic.GenericRelation('Event')
    def __unicode__(self):
        return self.submit_time.strftime("%Y-%m-%d %H:%m:%S")
    class Meta:
        verbose_name_plural = u"帖子回复"
@receiver(post_save, sender=Reply, dispatch_uid="ashin_unique_identifier")
def post_save_handler(sender, instance, **kwargs):
    reply = instance
    team_name_pattern = re.compile('(?<=@)(\w+)', re.UNICODE)
    at_team_names = set(re.findall(team_name_pattern, reply.content))
    if at_team_names:
        for at_team_name in at_team_names:
            if at_team_name != reply.author.user.username:
                try:
                    at_team = User.objects.get(username=at_team_name)
                    event = Event(author=reply.author.user, event=reply, at_team=at_team)
                    event.save()
                except:
                    pass
    elif reply.author != reply.thread.author:
        event = Event(author=reply.author.user, event=reply, at_team=reply.thread.author.user)
        event.save()
class Event(models.Model):
    content_type = models.ForeignKey(ContentType, verbose_name=u"被触发的模型")
    object_id = models.PositiveIntegerField(verbose_name=u"被触发模型ID")
    author = models.ForeignKey(User, verbose_name=u"事件发起者", related_name="author")
    event = generic.GenericForeignKey('content_type', 'object_id')
    at_team = models.ForeignKey(User, verbose_name=u"提到的人", related_name="at_team")
    #两个外键都指向User必须使用related_name参数
    submit_time = models.DateTimeField(verbose_name=u"@时间", auto_now_add=True)
    is_readed = models.BooleanField(verbose_name=u"已读", default=False)
    is_deleted = models.BooleanField(verbose_name=u"已被删除", default=False)
    def __unicode__(self):
        return u"%s在回复%s的帖子《%s》中提到了%s"%(self.author, self.event.thread.author, self.event.thread, self.at_team)
    class Meta:
        verbose_name_plural = u"回复新闻"
        ordering = ["-submit_time"]


在用户按钮处显示有多少条未读@消息

<script>
    function get_new_at_num(){
        $.get('/forum/new-at-num/',function(data){
            if (data != 0){
                $('#at_tip').text('('+data+'条@我未读)');
                $('#id_global_at').text(' ('+data+')');
            }
            else{
                $('#at_tip').text('');
                $('#id_global_at').text('');
            }
            window.setTimeout(get_new_at_num, 5000)
        });
    };
    $(function(){
        get_new_at_num();
    });
</script>
<div class="btn-group pull-right">
    <a class="btn dropdown-toggle" data-toggle="dropdown" href="">
        <i class="icon-user"></i>{% if request.user.is_authenticated %} `request`.`user`.`username` <span id="at_tip"></span>{% else %} 游客{% endif %}
        <span class="caret"></span>
    </a>
    <ul class="dropdown-menu">
        {% if request.user.is_authenticated %}
        <li><a href="/team-info/"><i class="icon-cog"></i> 团队信息</a></li>
        <li><a href="/forum/my-threads/"><i class="icon-th-list"></i> 我的帖子</a></li>
        <li><a href="/forum/at-me/"><i class="icon-envelope"></i> @我的回复<font color="red" id="id_global_at"></font></a></li>
        <li class="divider"></li>
        <li><a href="/accounts/logout/"><i class="icon-off"></i> 退出</a></li>
        {% else %}
        <li><a href="/accounts/register/"><i class="icon-pencil"></i> 注册</a></li>
        <li class="divider"></li>
        <li><a href="/accounts/login/"><i class="icon-leaf"></i> 登录</a></li>
        {% endif %}
    </ul>
</div><!-- userbtn -->


ajax每隔5秒请求一次服务器返回未读消息的条数,有就显示在页面上


@login_required
def new_at_num(request):
    unread_count = Event.objects.filter(at_team = request.user, is_deleted=False, is_readed = False).count()
    return HttpResponse(unread_count)


@的消息页面

2000.jpg


页面代码

<div>
    <p>当前位置: <a href="/forum/">论坛首页</a> » @我的回复</p>
    <p>共 <b id="id_all_count">{{results|length}}</b> 条, 未读 <b id="id_unread_count">`unread_count`</b> 条</p>
</div>
<div class="span10">
            {% if paged_events.object_list %}
            <table class="table table-hover table-striped">
                <tr id="id_news_table_head">
                    <th>点击查看@我的回复</th>
                    <th>@我的时间</th>
                    <th>阅读状态</th>
                    <th>操作</th>
                </tr>
                {% for news in paged_events.object_list %}
                <tr id="id_news_`news`.`id`">
                    <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>
                    <td>{{ news.submit_time|date:"Y-m-d H:i:s"}}</td>
                    <td>{% if news.is_readed %}已读{% else %}<font color="red" id="id_unread_`news`.`id`">未读</font>{% endif %}</td>
                    <td><button id="id_readed_news_`news`.`id`" οnclick="readed_news('`news`.`id`');" class="btn btn-mini btn-success" {% if news.is_readed %}disabled{% endif %}>设为已读</button> <button id="id_delete_news" οnclick="delete_news('`news`.`id`');" class="btn btn-mini btn-warning">删除记录</button></td>
                </tr>
                {% endfor %}
            </table>
            {% endif %}
</div><!--span10-->
<script>
    function readed_news(id){
        var url = '/forum/readed-at-me/'+id+'/';
        $.post(url, {'csrfmiddlewaretoken':'` csrf_token `'}, function(data){
            if (data=='success'){
                $('#id_unread_'+id).attr('color', '').text('已读');
                $('#id_readed_news_'+id).attr('disabled', 'true');
                $('#id_unread_count').text($('#id_unread_count').text()-1)
            }else{
                alert('操作失败!');
            }
        });
    }
    function delete_news(id){
        var url = '/forum/delete-at-me/'+id+'/';
        $.post(url, {'csrfmiddlewaretoken':'` csrf_token `'}, function(data){
            if(data=='success'){
                if ($('#id_unread_'+id).text() == '未读'){
                    $('#id_unread_count').text($('#id_unread_count').text()-1)
                }
                $('#id_news_'+id).remove();
                $('#id_all_count').text($('#id_all_count').text()-1)
            }else{
                alert('操作失败!');
            }
        });
    }
</script>


处理函数

@login_required
def at_me(request):
    #最新帖子
    latest_threads = Thread.objects.all()[:5]
    #回复事件
    results = Event.objects.filter(at_team = request.user, is_deleted=False)
    unread_count = Event.objects.filter(at_team = request.user, is_deleted=False, is_readed = False).count()
    #分页
    events_paginator = Paginator(results, 15)
    try:
        page = int(request.GET.get('page', 1))
    except ValueError:
        page = 1
    try:
        paged_events = events_paginator.page(page)
    except (EmptyPage, InvalidPage):
        paged_events = events_paginator.page(events_paginator.num_pages)
    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))
@login_required
def readed_news(request, news_id):
    news = get_object_or_404(Event, id=news_id, at_team=request.user)
    if request.method == 'POST':
        news.is_readed=True
        news.save()
        return HttpResponse("success")
    return HttpResponse("error")
@login_required
def delete_news(request, news_id):
    news = get_object_or_404(Event, id=news_id, at_team=request.user)
    if request.method == 'POST':
        news.is_deleted = True
        news.save()
        return HttpResponse("success")
    return HttpResponse("error")


在回复中@用户

2000.jpg


实现代码:

<script src="/static/js/userAutoTips.js"></script>
<style type="text/css" >
    ol, ul { list-style: none outside none; }
    .recipients-tips{ font-family:Tahoma, Arial;position:absolute; background:#282828; z-index:2147483647; padding:2px; border:2px solid #33b5e5; display:none;}
    .recipients-tips li a{display:block; padding:2px 5px; cursor:pointer;}
    .autoSelected{background:#131517;}
</style>
<script type="text/javascript">
    userAutoTips({id:'id_content'});
</script>


用了网上找的一个别人写好的只要检测到textarea中有@就会显示列表的代码,自己该了一些设置和获取最近活跃的10位用户

@login_required
def nearest_users(request):
    threads = Thread.objects.all()
    replies = Reply.objects.all()
    users = [thread.author.user.username for thread in threads]
    users.extend([reply.author.user.username for reply in replies])
    users = set(users)
    if request.user.username in users:
        users.remove(request.user.username)
    user_name_list = []
    for user in users:
        user_name_list.append({}.fromkeys(('user', 'name'), user))
    show_count = 15
    if len(user_name_list) > show_count:
        user_name_list = user_name_list[:show_count]
    data = simplejson.dumps(user_name_list)
    return HttpResponse(data, mimetype="application/json")


UserAutoTips.js放到微盘了:

http://vdisk.weibo.com/s/naFdb


文章为阿小信的个人笔记,转载请注明出处。