Просмотр исходного кода

add notice(增加消息通知)

honmaple 8 лет назад
Родитель
Сommit
86e93a7933

+ 1 - 1
forums/__init__.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-01-25 20:10:50 (CST)
-# Last Update:星期三 2017-3-29 11:25:18 (CST)
+# Last Update:星期五 2017-3-31 17:37:56 (CST)
 #          By:
 # Description:
 # **************************************************************************

+ 42 - 0
forums/admin/message.py

@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: message.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-04-01 18:52:43 (CST)
+# Last Update:星期六 2017-4-1 18:55:37 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from .views import BaseView
+from forums.extension import db
+from forums.api.message.models import MessageText, Message
+
+
+class MessageTextView(BaseView):
+    pass
+
+
+class MessageView(BaseView):
+    pass
+    # column_searchable_list = ['name']
+    # form_excluded_columns = ('topics', 'followers')
+
+
+def register_message(admin):
+    admin.add_view(
+        MessageView(
+            Message,
+            db.session,
+            name='管理通知',
+            endpoint='admin_message',
+            category='管理通知'))
+    admin.add_view(
+        MessageTextView(
+            MessageText,
+            db.session,
+            name='管理内容',
+            endpoint='admin_message_text',
+            category='管理通知'))

+ 3 - 1
forums/admin/urls.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-10-28 10:15:42 (CST)
-# Last Update:星期三 2017-3-29 13:13:11 (CST)
+# Last Update:星期六 2017-4-1 18:55:17 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -15,8 +15,10 @@ from .forums import register_forums
 # from .permission import register_permission
 from .user import register_user
 from .topic import register_topic
+from .message import register_message
 
 register_forums(admin)
 register_user(admin)
 register_topic(admin)
+register_message(admin)
 # register_permission(admin)

+ 3 - 1
forums/api/collect/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-28 16:15:08 (CST)
-# Last Update:星期三 2017-3-29 19:1:59 (CST)
+# Last Update:星期六 2017-4-1 20:38:59 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -25,6 +25,7 @@ from forums.api.topic.models import Topic
 from forums.common.serializer import Serializer
 from forums.common.utils import gen_filter_dict, gen_order_by
 from forums.common.views import IsAuthMethodView as MethodView
+from forums.api.message.models import MessageClient
 
 from .models import Collect
 
@@ -106,6 +107,7 @@ class AddToCollectView(MethodView):
                     topics__id=topic.id, author_id=user.id).exists():
                 collect.topics.append(topic)
                 collect.save()
+            MessageClient.collect(topic)
         return redirect(url_for('topic.topic', topicId=topic.id))
 
     # def delete(self, topicId):

+ 6 - 2
forums/api/follow/views.py

@@ -6,18 +6,19 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-22 21:49:05 (CST)
-# Last Update:星期三 2017-3-29 12:40:41 (CST)
+# Last Update:星期六 2017-4-1 19:52:14 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from flask import render_template, request
 
 from forums.api.tag.models import Tags
-from forums.api.topic.models import  Topic
+from forums.api.topic.models import Topic
 from forums.api.collect.models import Collect
 from forums.api.user.models import User
 from forums.common.response import HTTPResponse
 from forums.common.views import IsAuthMethodView as MethodView
+from forums.api.message.models import MessageClient
 
 
 class FollowingTagsView(MethodView):
@@ -71,6 +72,8 @@ class FollowingTopicsView(MethodView):
             topic = Topic.query.filter_by(id=topic_id).first_or_404()
             user.following_topics.append(topic)
             user.save()
+            # notice
+            MessageClient.follow(topic)
         return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
 
     def delete(self):
@@ -102,6 +105,7 @@ class FollowingUsersView(MethodView):
             if not f_user.is_followed(user):
                 user.following_users.append(f_user)
                 user.save()
+            MessageClient.follow(f_user)
         return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
 
     def delete(self):

+ 1 - 1
forums/api/forums/models.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-25 18:48:33 (CST)
-# Last Update:星期四 2017-3-30 14:49:19 (CST)
+# Last Update:星期五 2017-3-31 15:53:1 (CST)
 #          By:
 # Description:
 # **************************************************************************

+ 12 - 0
forums/api/message/__init__.py

@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: __init__.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-04-01 18:33:33 (CST)
+# Last Update:星期六 2017-4-1 18:33:34 (CST)
+#          By:
+# Description:
+# **************************************************************************

+ 280 - 0
forums/api/message/models.py

@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: models.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-04-01 18:33:37 (CST)
+# Last Update:星期六 2017-4-1 20:49:46 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from flask import url_for
+from flask_login import current_user
+from forums.extension import db
+from forums.filters import safe_markdown
+from forums.common.models import CommonTimeMixin
+
+
+class MessageText(CommonTimeMixin, db.Model):
+    __tablename__ = 'message_text'
+
+    MESSAGE_TYPE_PUBLIC = '0'
+    MESSAGE_TYPE_TOPIC = '1'
+    MESSAGE_TYPE_REPLY = '2'
+    MESSAGE_TYPE_PRIVATE = '3'
+
+    MESSAGE_TYPE = (('0', '系统消息'), ('1', '主题相关'), ('2', '回复相关'), ('3', '私信'))
+
+    STATUS_SUBMIT = '0'
+    STATUS_PUBLISH = '1'
+    STATUS_UNDO = '2'
+
+    STATUS = (('0', '未发布'), ('1', '已发布'), ('2', '撤销发布'))
+
+    READ_STATUS_UNREAD = '0'
+    READ_STATUS_READ = '1'
+    READ_STATUS = (('0', '未读'), ('1', '已读'))
+
+    title = db.Column(db.String(128), nullable=False, doc='站内信标题')
+    content = db.Column(db.String(1024), nullable=False, doc='站内信内容')
+    status = db.Column(
+        db.String(128), nullable=False, default=STATUS_SUBMIT, doc='站内信状态')
+    message_type = db.Column(
+        db.String(128),
+        nullable=False,
+        default=MESSAGE_TYPE_PUBLIC,
+        doc='站内信类型')
+    sender_id = db.Column(db.Integer, db.ForeignKey('users.id'))
+    sender = db.relationship(
+        'User',
+        backref=db.backref(
+            'send_messages', lazy='dynamic'),
+        lazy='joined',
+        uselist=False)
+
+    @classmethod
+    def get_choice_dict(cls):
+        return dict(messagetext=dict(
+            status=dict(cls.STATUS),
+            message_type=dict(cls.MESSAGE_TYPE),
+            read_status=dict(cls.READ_STATUS)))
+
+    def __str__(self):
+        return self.title
+
+    def __repr__(self):
+        return "<MessageText %r>" % self.title
+
+    @property
+    def read_status(self):
+        '''
+        判断站内信是否已读
+        '''
+        message = Message.query.filter_by(message_text_id=self.id).first()
+        if message:
+            return '已读' if message.status == Message.STATUS_READ else '已删除'
+        return '未读'
+
+
+class Message(CommonTimeMixin, db.Model):
+    __tablename__ = 'message'
+
+    STATUS_UNREAD = '0'
+    STATUS_READ = '1'
+    STATUS_DELETE = '2'
+
+    STATUS = (('0', '未读'), ('1', '已读'), ('2', '删除'))
+
+    status = db.Column(
+        db.String(128), nullable=False, default=STATUS_UNREAD, doc='站内信状态')
+    message_text_id = db.Column(db.Integer, db.ForeignKey('message_text.id'))
+    message_text = db.relationship(
+        MessageText,
+        backref=db.backref(
+            "messages", cascade='all,delete', lazy='dynamic'),
+        uselist=False,
+        lazy='joined')
+    receiver_id = db.Column(db.Integer, db.ForeignKey('users.id'))
+    receiver = db.relationship(
+        'User',
+        backref=db.backref(
+            'receive_messages', lazy='dynamic'),
+        lazy='joined',
+        uselist=False)
+
+    def __str__(self):
+        return self.status
+
+    def __repr__(self):
+        return "<Message %r>" % self.status
+
+    @property
+    def title(self):
+        return safe_markdown(self.message_text.title)
+        # return self.message_text.title
+
+    @property
+    def content(self):
+        return self.message_text.content
+
+    @classmethod
+    def get_choice_dict(cls):
+        return dict(message=dict(status=dict(cls.STATUS)))
+
+
+class MessageClient(object):
+    def system():
+        '''
+        系统消息
+        '''
+
+    @classmethod
+    def topic(cls, reply, sender=None):
+        '''
+        回复主题
+        '''
+        if sender is None:
+            sender = current_user
+        topic = reply.topic
+        receiver = topic.author
+        if sender.id == receiver.id:
+            return
+        title = '[{}]({})回复了你创建的主题:[{}]({})'.format(
+            sender.username,
+            url_for(
+                'user.user', username=sender.username),
+            topic.title,
+            url_for(
+                'topic.topic', topicId=topic.id))
+        content = reply.content
+        message_text = MessageText(
+            sender_id=sender.id, title=title, content=content)
+        message_text.save()
+        message = Message(receiver=receiver, message_text=message_text)
+        message.save()
+        receiver.message_count = 1
+
+    @classmethod
+    def collect(cls, topic, sender=None):
+        '''
+        收藏
+        '''
+        if sender is None:
+            sender = current_user
+        receiver = topic.author
+        if sender.id == receiver.id:
+            return
+        title = '[{}]({})收藏了你创建的主题:[{}]({})'.format(
+            sender.username,
+            url_for(
+                'user.user', username=sender.username),
+            topic.title,
+            url_for(
+                'topic.topic', topicId=topic.id))
+        content = 'a'
+        message_text = MessageText(
+            sender_id=sender.id, title=title, content=content)
+        message_text.save()
+        message = Message(receiver=receiver, message_text=message_text)
+        message.save()
+        receiver.message_count = 1
+
+    @classmethod
+    def follow(cls, following, sender=None):
+        '''
+        关注用户,关注主题,关注收藏
+        '''
+        if sender is None:
+            sender = current_user
+        if following.__class__.__name__ == 'Topic':
+            receiver = following.author
+            title = '[{}]({})关注了你创建的主题:[{}]({})'.format(
+                sender.username,
+                url_for(
+                    'user.user', username=sender.username),
+                following.title,
+                url_for(
+                    'topic.topic', topicId=following.id))
+        elif following.__class__.__name__ == 'Collect':
+            receiver = following.author
+            title = '[{}]({})关注了你创建的收藏:[{}]({})'.format(
+                sender.username,
+                url_for(
+                    'user.user', username=sender.username),
+                following.title,
+                url_for(
+                    'collect.collect', pk=following.id))
+        elif following.__class__.__name__ == 'User':
+            receiver = following
+            title = '[{}]({})关注了你'.format(
+                sender.username,
+                url_for(
+                    'user.user', username=sender.username))
+        if sender.id == receiver.id:
+            return
+        content = 'a'
+        message_text = MessageText(
+            sender_id=sender.id, title=title, content=content)
+        message_text.save()
+        message = Message(receiver=receiver, message_text=message_text)
+        message.save()
+        receiver.message_count = 1
+
+    @classmethod
+    def reply(cls, reply, sender=None):
+        '''
+        子回复
+        '''
+        if sender is None:
+            sender = current_user
+        receiver = reply.author
+        if sender.id == receiver.id:
+            return
+        topic = reply.topic
+        title = '[{}]({})在[{}]({})回复了你'.format(
+            sender.username,
+            url_for(
+                'user.user', username=sender.username),
+            topic.title,
+            url_for(
+                'topic.topic', topicId=topic.id))
+        content = reply.content
+        message_text = MessageText(
+            sender_id=sender.id, title=title, content=content)
+        message_text.save()
+        message = Message(receiver=receiver, message_text=message_text)
+        message.save()
+        receiver.message_count = 1
+
+    @classmethod
+    def like(cls, reply, sender=None):
+        '''
+        点赞
+        '''
+        if sender is None:
+            sender = current_user
+        receiver = reply.author
+        if sender.id == receiver.id:
+            return
+        topic = reply.topic
+        title = '[{}]({})在[{}]({})赞了你的回复'.format(
+            sender.username,
+            url_for(
+                'user.user', username=sender.username),
+            topic.title,
+            url_for(
+                'topic.topic', topicId=topic.id))
+        content = reply.content
+        message_text = MessageText(
+            sender_id=sender.id, title=title, content=content)
+        message_text.save()
+        message = Message(receiver=receiver, message_text=message_text)
+        message.save()
+        receiver.message_count = 1
+
+    def private(cls, message, sender=None):
+        '''
+        私信
+        '''

+ 20 - 0
forums/api/message/urls.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: urls.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-04-01 18:34:38 (CST)
+# Last Update:星期六 2017-4-1 20:6:3 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from flask import Blueprint
+from .views import MessageListView
+
+site = Blueprint('message', __name__, url_prefix='/message')
+
+message_list = MessageListView.as_view('list')
+
+site.add_url_rule('', view_func=message_list)

+ 32 - 0
forums/api/message/views.py

@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: views.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-04-01 18:34:07 (CST)
+# Last Update:星期六 2017-4-1 20:48:15 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from flask import render_template, request
+
+from forums.common.views import IsAuthMethodView as MethodView
+from forums.count import Count
+
+from .models import Message, MessageText
+
+
+class MessageListView(MethodView):
+    def get(self):
+        query_dict = request.data
+        user = request.user
+        status = query_dict.pop('status', '0')
+        page, number = self.page_info
+        messages = Message.query.filter_by(
+            receiver_id=user.id,
+            status=status).order_by('-created_at').paginate(page, number, True)
+        data = {'title': 'Notice', 'messages': messages}
+        Count.user_message_count(user.id, clear=True)
+        return render_template('forums/message.html', **data)

+ 12 - 0
forums/api/search/__init__.py

@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: __init__.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-03-31 17:25:57 (CST)
+# Last Update:星期五 2017-3-31 17:25:58 (CST)
+#          By:
+# Description:
+# **************************************************************************

+ 18 - 0
forums/api/search/urls.py

@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: urls.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-03-31 17:27:30 (CST)
+# Last Update:星期五 2017-3-31 17:28:36 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from flask import Blueprint
+from .views import SearchView
+
+site = Blueprint('search', __name__)
+
+site.add_url_rule('/search', view_func=SearchView.as_view('search'))

+ 27 - 0
forums/api/search/views.py

@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: views.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-03-31 17:26:28 (CST)
+# Last Update:星期五 2017-3-31 17:48:23 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from flask import request
+from forums.common.views import BaseMethodView as MethodView
+from forums.api.topic.models import Topic
+
+
+class SearchView(MethodView):
+    def get(self):
+        query_dict = request.data
+        search = query_dict.pop('key', None)
+        results = Topic.query.whoosh_search('第一').all()
+        print(results)
+        return ''
+
+    def post(self):
+        pass

+ 2 - 1
forums/api/topic/models.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 20:52:07 (CST)
-# Last Update:星期四 2017-3-30 15:5:44 (CST)
+# Last Update:星期五 2017-3-31 17:30:1 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -31,6 +31,7 @@ topic_follower = db.Table(
 
 class Topic(db.Model, ModelMixin):
     __tablename__ = 'topics'
+    __searchable__ = ['title', 'content']
 
     CONTENT_TYPE_TEXT = '0'
     CONTENT_TYPE_MARKDOWN = '1'

+ 5 - 1
forums/api/topic/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 22:07:39 (CST)
-# Last Update:星期四 2017-3-30 16:39:56 (CST)
+# Last Update:星期六 2017-4-1 19:50:27 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -31,6 +31,7 @@ from .models import Reply, Topic
 from .permissions import (like_permission, reply_list_permission,
                           reply_permission, topic_list_permission,
                           topic_permission, edit_permission)
+from forums.api.message.models import MessageClient
 
 
 class TopicAskView(IsConfirmedMethodView):
@@ -174,6 +175,8 @@ class ReplyListView(MethodView):
         reply = Reply(content=content, topic_id=topic.id)
         reply.author = user
         reply.save()
+        # notice
+        MessageClient.topic(reply)
         # count
         topic.board.post_count = 1
         reply.author.reply_count = 1
@@ -208,6 +211,7 @@ class LikeView(MethodView):
         reply = Reply.query.filter_by(id=replyId).first_or_404()
         reply.likers.append(user)
         reply.save()
+        MessageClient.like(reply)
         serializer = Serializer(reply, many=False)
         return HTTPResponse(
             HTTPResponse.NORMAL_STATUS, data=serializer.data).to_response()

+ 6 - 3
forums/api/urls.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-01-25 20:12:58 (CST)
-# Last Update:星期二 2017-3-28 17:40:36 (CST)
+# Last Update:星期六 2017-4-1 18:45:19 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -19,8 +19,10 @@ from .setting.urls import site as setting_site
 from .follow.urls import site as follow_site
 from .upload.urls import site as upload_site
 from .collect.urls import site as collect_site
+from .message.urls import site as message_site
+
+# from .search.urls import site as search_site
 # from .permission.urls import site as perm_site
-# from .mine.urls import site as mine_site
 
 
 def api_routers(app):
@@ -33,5 +35,6 @@ def api_routers(app):
     app.register_blueprint(follow_site)
     app.register_blueprint(upload_site)
     app.register_blueprint(collect_site)
+    app.register_blueprint(message_site)
+    # app.register_blueprint(search_site)
     # app.register_blueprint(perm_site)
-    # app.register_blueprint(mine_site)

+ 10 - 1
forums/api/user/models.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 21:09:08 (CST)
-# Last Update:星期四 2017-3-30 15:10:19 (CST)
+# Last Update:星期六 2017-4-1 20:28:26 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -88,6 +88,15 @@ class User(db.Model, UserMixin, ModelMixin):
     def reply_count(self, value):
         return Count.user_reply_count(self.id, value)
 
+    @property
+    def message_count(self):
+        # return self.receive_messages.filter_by(status='0').count()
+        return Count.user_message_count(self.id)
+
+    @message_count.setter
+    def message_count(self, value):
+        return Count.user_message_count(self.id, value)
+
     def __str__(self):
         return self.username
 

+ 2 - 2
forums/common/response.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-10-25 21:07:00 (CST)
-# Last Update:星期三 2017-1-25 21:43:35 (CST)
+# Last Update:星期六 2017-4-1 18:51:49 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -65,7 +65,7 @@ class HTTPResponse(object):
             'description': self.description,
         }
         if self.pageinfo is not None:
-            response.update(pageinfo=self.pageinfo)
+            response.update(pageinfo=self.pageinfo.as_dict())
         return response
 
     def to_response(self):

+ 12 - 1
forums/count.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-29 21:28:52 (CST)
-# Last Update:星期四 2017-3-30 14:59:51 (CST)
+# Last Update:星期六 2017-4-1 20:47:11 (CST)
 #          By:
 # Description: 一些统计信息
 # **************************************************************************
@@ -76,3 +76,14 @@ class Count(object):
             pipe.hincrby(key, 'replies', value)
             pipe.execute()
         return redis_data.hget(key, 'replies') or 0
+
+    @classmethod
+    def user_message_count(cls, userId, value=None, clear=False):
+        key = 'count:user:%s' % str(userId)
+        if value is not None:
+            pipe = redis_data.pipeline()
+            pipe.hincrby(key, 'message', value)
+            pipe.execute()
+        if clear:
+            redis_data.hset(key, 'message', 0)
+        return redis_data.hget(key, 'message') or 0

+ 3 - 2
forums/filters.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-11-07 21:00:32 (CST)
-# Last Update:星期四 2017-3-30 15:10:35 (CST)
+# Last Update:星期六 2017-4-1 20:16:51 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -21,7 +21,7 @@ from misaka import HtmlRenderer, Markdown
 
 
 def safe_clean(text):
-    tags = ['b', 'i', 'font', 'br', 'blockquote', 'div', 'h2', 'a']
+    tags = ['b', 'i', 'font', 'br', 'blockquote', 'div', 'h2', 'a', 'p']
     attrs = {'*': ['style', 'id', 'class'], 'font': ['color'], 'a': ['href']}
     styles = ['color']
     return Markup(clean(text, tags=tags, attributes=attrs, styles=styles))
@@ -68,6 +68,7 @@ def show_time():
     else:
         return 'UTC:' + format_datetime(datetime.utcnow())
 
+
 def hot_tags():
     from forums.api.tag.models import Tags
     tags = Tags.query.limit(9).all()

+ 2 - 2
manager.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-10-25 22:08:39 (CST)
-# Last Update:星期三 2017-3-29 23:38:31 (CST)
+# Last Update:星期六 2017-4-1 18:47:53 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -35,7 +35,7 @@ def init_db():
     """
     Drops and re-creates the SQL schema
     """
-    db.drop_all()
+    # db.drop_all()
     db.configure_mappers()
     db.create_all()
     db.session.commit()

+ 1 - 1
static/assets/home.js

@@ -21,7 +21,7 @@ function Follow(obj,data,url){if(obj.hasClass('active')){$.ajax({type:"DELETE",u
 {alert('fail');}}});}else{$.ajax({type:"POST",url:url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {obj.text('取消关注').addClass('active');}else
 {alert('fail');}}});}}
-$(document).ready(function(){$('button.topic-following').click(function(){var _$this=$(this);var url="/user/following/topics";var data=JSON.stringify({topicId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.tag-following').click(function(){var _$this=$(this);var url="/user/following/tags";var data=JSON.stringify({tagId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.user-following').click(function(){var _$this=$(this);var url="/user/following/users";var data=JSON.stringify({userId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.collect-following').click(function(){var _$this=$(this);var url="/user/following/collects";var data=JSON.stringify({collectId:_$this.attr("data-id"),});Follow(_$this,data,url);});});function DoCollect(collect_url){$(document).ready(function(){$('button#edit-collect').click(function(){var data=JSON.stringify({name:$('#name').val(),description:$('#description').val(),is_hidden:$("input[name='is_hidden']:checked").val()});$.ajax({type:"PUT",url:collect_url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
+$(document).ready(function(){$('button.topic-following').click(function(){var _$this=$(this);var url="/following/topics";var data=JSON.stringify({topicId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.tag-following').click(function(){var _$this=$(this);var url="/following/tags";var data=JSON.stringify({tagId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.user-following').click(function(){var _$this=$(this);var url="/following/users";var data=JSON.stringify({userId:_$this.attr("data-id"),});Follow(_$this,data,url);});$('button.collect-following').click(function(){var _$this=$(this);var url="/following/collects";var data=JSON.stringify({collectId:_$this.attr("data-id"),});Follow(_$this,data,url);});});function DoCollect(collect_url){$(document).ready(function(){$('button#edit-collect').click(function(){var data=JSON.stringify({name:$('#name').val(),description:$('#description').val(),is_hidden:$("input[name='is_hidden']:checked").val()});$.ajax({type:"PUT",url:collect_url,data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {window.location.href=collect_url;}}});});$('button#delete-collect').click(function(){$.ajax({type:"DELETE",url:collect_url,data:JSON.stringify(),contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {window.location.href=collect_url;}}});});$('#delete-from-collect').click(function(){var _$this=$(this);var topicId=_$this.attr('data-id');var data=JSON.stringify({topicId:topicId});$.ajax({type:"DELETE",url:'/topic/'+topicId+'/collect',data:data,contentType:'application/json;charset=UTF-8',success:function(response){if(response.status==='200')
 {_$this.parent().remove();}}});});});}

+ 4 - 4
static/styles/following.js

@@ -29,7 +29,7 @@ function Follow(obj,data,url){
 $(document).ready(function(){
   $('button.topic-following').click(function(){
     var _$this = $(this);
-    var url = "/user/following/topics";
+    var url = "/following/topics";
     var data = JSON.stringify({
       topicId:_$this.attr("data-id"),
     });
@@ -37,7 +37,7 @@ $(document).ready(function(){
   });
   $('button.tag-following').click(function(){
     var _$this = $(this);
-    var url = "/user/following/tags";
+    var url = "/following/tags";
     var data = JSON.stringify({
       tagId:_$this.attr("data-id"),
     });
@@ -45,7 +45,7 @@ $(document).ready(function(){
   });
   $('button.user-following').click(function(){
     var _$this = $(this);
-    var url = "/user/following/users";
+    var url = "/following/users";
     var data = JSON.stringify({
       userId:_$this.attr("data-id"),
     });
@@ -53,7 +53,7 @@ $(document).ready(function(){
   });
   $('button.collect-following').click(function(){
     var _$this = $(this);
-    var url = "/user/following/collects";
+    var url = "/following/collects";
     var data = JSON.stringify({
       collectId:_$this.attr("data-id"),
     });

+ 48 - 34
templates/base/base.html

@@ -4,57 +4,71 @@
 {% import 'base/link.html' as link %}
 {% import 'base/panel.html' as panel_base %}
 {% from 'base/head.html' import breadcrumb %}
+
+{% block title -%}
+  {{ title }} {{ SITE['title'] }} - {{ _(SITE['description']) }}
+{%- endblock title %}
+
 {% block script -%}
-{{ super() }}
+  {{ super() }}
 {%- endblock script %}
 
 {% block style -%}
-{{ super() }}
-<link href="http://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
+  {{ super() }}
+  <link href="http://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
 {%- endblock style %}
 
 {% block main %}
-{% include "base/header.html" %}
-<div class="col-md-offset-1 col-md-10" style="padding:0;margin-top:60px">
+  {% include "base/header.html" %}
+  <div class="col-md-offset-1 col-md-10" style="padding:0;margin-top:60px">
     {% with messages = get_flashed_messages(with_categories=true) %}
-    {% if messages %}
-    {% for category,message in messages %}
-    {% if category == 'message' -%}
-    {% set category = 'info' %}
-    {%- endif %}
-    <div class="alert alert-{{ category }}" style="padding:8px">
-        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
-            <span aria-hidden="true">&times;</span>
-        </button>
-        <ul>
-            <li>{{ message }} </li>
-        </ul>
-    </div>
-    {% endfor %}
-    {% endif %}
+      {% if messages %}
+        {% for category,message in messages %}
+          {% if category == 'message' -%}
+            {% set category = 'info' %}
+          {%- endif %}
+          <div class="alert alert-{{ category }}" style="padding:8px">
+            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+              <span aria-hidden="true">&times;</span>
+            </button>
+            <ul>
+              <li>{{ message }} </li>
+            </ul>
+          </div>
+        {% endfor %}
+      {% endif %}
     {% endwith %}
     {{ dropdown() }}
     {% block content %}{% endblock %}
-</div>
+  </div>
 {% endblock %}
 
 {% macro dropdown() -%}
-{% if g.user.is_authenticated %}
-<div class="btn-group pull-right">
-    <button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+  {% if g.user.is_authenticated %}
+    <div class="btn-group pull-right">
+      <button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
         {{ g.user.username }} <span class="caret"></span>
-    </button>
-    <ul class="dropdown-menu">
+      </button>
+      <ul class="dropdown-menu">
         <li><a href="{{ url_for('user.user',username=g.user.username) }}">{{ _('Home Page') }}</a></li>
         <li><a href="{{ url_for('setting.setting') }}">{{ _('Setting')}}</a></li>
         <li role="separator" class="divider"></li>
         <li><a href="{{ url_for('auth.logout') }}">{{ _('Logout')}}</a></li>
-    </ul>
-</div>
-{% else  %}
-<a href="{{ url_for('auth.register') }}" class="btn btn-sm btn-primary pull-right">{{ _('Register') }}</a>
-<a href="{{ url_for('auth.login') }}" class="btn btn-sm btn-primary pull-right">{{ _('Login') }}</a>
-{% endif %}
-<a href="{{ url_for('tag.list') }}" class="btn btn-sm btn-primary pull-right">{{ _('TagList') }}</a>
-<a href="{{ url_for('user.list') }}" class="btn btn-sm btn-primary pull-right">{{ _('UserList') }}</a>
+      </ul>
+    </div>
+    <a href="{{ url_for('message.list') }}" class="btn btn-sm btn-primary pull-right">
+      {{ _('NoticeList') }}
+      {%- set n = current_user.message_count -%}
+      {%- if n and n != '0' -%}
+        <span class="badge" style="padding:2px 5px;">
+          {{ n }}
+        </span>
+      {%- endif -%}
+    </a>
+  {% else  %}
+    <a href="{{ url_for('auth.register') }}" class="btn btn-sm btn-primary pull-right">{{ _('Register') }}</a>
+    <a href="{{ url_for('auth.login') }}" class="btn btn-sm btn-primary pull-right">{{ _('Login') }}</a>
+  {% endif %}
+  <a href="{{ url_for('tag.list') }}" class="btn btn-sm btn-primary pull-right">{{ _('TagList') }}</a>
+  <a href="{{ url_for('user.list') }}" class="btn btn-sm btn-primary pull-right">{{ _('UserList') }}</a>
 {%- endmacro %}

+ 35 - 0
templates/forums/message.html

@@ -0,0 +1,35 @@
+{% extends 'base/base.html' %}
+{% block content %}
+  {{ breadcrumb(active=_('Notices'))}}
+  <div class="row">
+    <div class="col-md-9">
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <button class="pull-right btn btn-sm btn-default ingore-all" style="padding:0 5px">{{ _('mark all to is read')}}</button>
+          {{ _('Notices') }}
+        </div>
+        {% if messages.items %}
+          {% for message in messages.items %}
+            <div class="panel-body" style="border-bottom:1px solid #eee;">
+              <small class="pull-right"> {{ message.created_at | timesince }} </small>
+              <small>{{ message.title }}</small>
+              <p style="margin-bottom:0px">
+                {{ message.content }}
+              </p>
+            </div>
+          {% endfor %}
+          {{ p_footer(messages, 'message.list')}}
+        {% else %}
+          <div class="panel-body">
+            <span class="text-center" style="display:block;width:100%;color:#999">
+              {{ _('No Notices')}}
+            </span>
+          </div>
+        {% endif %}
+      </div>
+    </div>
+    <div class="col-md-3" style="padding-left:0">
+      {{ panel_base.index() }}
+    </div>
+  </div>
+{% endblock %}

+ 0 - 32
templates/forums/notice.html

@@ -1,32 +0,0 @@
-{% extends 'base/base.html' %}
-{% block content %}
-{{ breadcrumb(active=_('Notices'))}}
-<div class="row">
-    <div class="col-md-9">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <button class="pull-right btn btn-sm btn-default ingore-all" style="padding:0 5px">{{ _('mark all to is read')}}</button>
-                {{ _('Notices') }}
-            </div>
-            {% if notices.items %}
-            {% for notice in notices.items %}
-            <div class="panel-body" style="border-bottom:1px solid #eee;">
-                {% import 'base/notice.html' as notice_base %}
-                {{ notice_base[notice.category](notice) }}
-            </div>
-            {% endfor %}
-            {{ p_footer(notices, 'forums.notice')}}
-            {% else %}
-            <div class="panel-body">
-                <span class="text-center" style="display:block;width:100%;color:#999">
-                    {{ _('No Notices')}}
-                </span>
-            </div>
-            {% endif %}
-        </div>
-    </div>
-    <div class="col-md-3" style="padding-left:0">
-        {{ panel_base.index() }}
-    </div>
-</div>
-{% endblock %}

+ 26 - 26
templates/tag/_macro.html

@@ -1,30 +1,30 @@
 {% macro title(tag) -%}
-<div class="media">
-  <div class="media-left">
-    <a href="{{ request.path }}">
-      <img class="media-object img-circle" src="{{ url_for('avatar',text=tag.name)}}" alt="avatar" style="width:56px;height:56px">
-    </a>
-  </div>
-  <div class="media-body">
-    <h4 class="media-heading"><strong class="text-capitalize">{{ tag.name }}</strong></h4>
-    {% set description = tag.description %}
-    {% if description -%}
-    {{ description }}
-    {%- endif %}
-  </div>
-  <div class="media-right">
-    <span class="rss">
-      <a href="{{ url_for('tag.feed',name=tag.name)}}">
-        <i class="icon-rss" style="padding:2px;white-space:nowrap;">Rss</i>
+  <div class="media">
+    <div class="media-left">
+      <a href="{{ request.path }}">
+        <img class="media-object img-circle" src="{{ url_for('avatar',text=tag.name)}}" alt="avatar" style="width:56px;height:56px">
       </a>
-    </span>
-    {% if g.user.is_authenticated %}
-    {% if tag.is_followed() %}
-    <button class="btn btn-sm btn-default tag-following active" data-id="{{ tag.id}}" style="padding:0 5px">取消关注</button>
-    {% else %}
-    <button class="btn btn-sm btn-default tag-following" data-id="{{ tag.id}}" style="padding:0 5px">关注</button>
-    {% endif %}
-    {% endif %}
+    </div>
+    <div class="media-body">
+      <h4 class="media-heading"><strong class="text-capitalize">{{ tag.name }}</strong></h4>
+      {% set description = tag.description %}
+      {% if description -%}
+        {{ description }}
+      {%- endif %}
+    </div>
+    <div class="media-right">
+      <span class="rss">
+        <a href="{{ url_for('tag.feed',name=tag.name)}}">
+          <i class="icon-rss" style="padding:2px;white-space:nowrap;">Rss</i>
+        </a>
+      </span>
+      {% if g.user.is_authenticated %}
+        {% if tag.is_followed() %}
+          <button class="btn btn-sm btn-default tag-following active" data-id="{{ tag.id}}" style="padding:0 5px">取消关注</button>
+        {% else %}
+          <button class="btn btn-sm btn-default tag-following" data-id="{{ tag.id}}" style="padding:0 5px">关注</button>
+        {% endif %}
+      {% endif %}
+    </div>
   </div>
-</div>
 {%- endmacro %}

+ 14 - 14
templates/tag/tag.html

@@ -1,20 +1,20 @@
 {% extends 'base/base.html' %}
 {% block content %}
-{% from 'tag/_macro.html' import title %}
-{{ breadcrumb(hrefs={_('All Tags'):url_for('tag.list')},active=tag.name)}}
-<div class="row">
-  <div class="col-md-9">
-    <div class="panel panel-default">
-      <div class="panel-body" style="border-bottom: 1px solid #ddd;">
-        {{ title(tag) }}
+  {% from 'tag/_macro.html' import title %}
+  {{ breadcrumb(hrefs={_('All Tags'):url_for('tag.list')},active=tag.name)}}
+  <div class="row">
+    <div class="col-md-9">
+      <div class="panel panel-default">
+        <div class="panel-body" style="border-bottom: 1px solid #ddd;">
+          {{ title(tag) }}
+        </div>
+        {% include "topic/_topic.html" %}
+        {{ p_footer(topics,'tag.tag',dict(name=tag.name))}}
       </div>
-      {% include "topic/_topic.html" %}
-      {{ p_footer(topics,'tag.tag',dict(name=tag.name))}}
+    </div>
+    <div class="col-md-3" style="padding-left:0">
+      {% from 'tag/panel.html' import tag as tag_panel %}
+      {{ tag_panel(tag) }}
     </div>
   </div>
-  <div class="col-md-3" style="padding-left:0">
-    {% from 'tag/panel.html' import tag as tag_panel %}
-    {{ tag_panel(tag) }}
-  </div>
-</div>
 {% endblock %}

+ 17 - 17
templates/tag/tag_list.html

@@ -1,25 +1,25 @@
 {% extends 'base/base.html' %}
 {% block content %}
-{{ breadcrumb(active=_('All Tags'))}}
-<div class="row">
+  {{ breadcrumb(active=_('All Tags'))}}
+  <div class="row">
     <div class="col-md-9">
-        <div class="panel panel-primary">
-            <div class="panel-heading">
-                {{ _('All Tags') }}
-            </div>
-            <div class="panel-body">
-                {% for tag in tags.items %}
-                <a class="tag" href="{{ url_for('tag.tag',name=tag.name)}}">{{ tag.name }}</a>
-                {% else %}
-                <span class="text-center">
-                    {{ _('No Tags') }}
-                </span>
-                {% endfor %}
-            </div>
+      <div class="panel panel-primary">
+        <div class="panel-heading">
+          {{ _('All Tags') }}
         </div>
+        <div class="panel-body">
+          {% for tag in tags.items %}
+            <a class="tag" href="{{ url_for('tag.tag',name=tag.name)}}">{{ tag.name }}</a>
+          {% else %}
+            <span class="text-center">
+              {{ _('No Tags') }}
+            </span>
+          {% endfor %}
+        </div>
+      </div>
     </div>
     <div class="col-md-3" style="padding-left:0">
-        {{ panel_base.tag_list() }}
+      {{ panel_base.tag_list() }}
     </div>
-</div>
+  </div>
 {% endblock %}