Browse Source

add some permission and update models

honmaple 8 years ago
parent
commit
e50cd483e3
44 changed files with 538 additions and 546 deletions
  1. 3 1
      forums/__init__.py
  2. 2 2
      forums/admin/permission.py
  3. 3 3
      forums/admin/urls.py
  4. 9 1
      forums/admin/views.py
  5. 8 3
      forums/api/auth/views.py
  6. 19 11
      forums/api/collect/models.py
  7. 3 2
      forums/api/collect/views.py
  8. 9 9
      forums/api/follow/views.py
  9. 13 11
      forums/api/forms.py
  10. 13 1
      forums/api/forums/models.py
  11. 8 3
      forums/api/forums/views.py
  12. 0 236
      forums/api/permission/models.py
  13. 0 22
      forums/api/permission/urls.py
  14. 0 42
      forums/api/permission/views.py
  15. 2 2
      forums/api/setting/views.py
  16. 28 20
      forums/api/tag/models.py
  17. 43 14
      forums/api/topic/models.py
  18. 72 0
      forums/api/topic/permissions.py
  19. 39 9
      forums/api/topic/views.py
  20. 4 4
      forums/api/upload/views.py
  21. 18 11
      forums/api/user/models.py
  22. 2 8
      forums/api/user/views.py
  23. 52 0
      forums/api/utils.py
  24. 20 1
      forums/app.py
  25. 12 1
      forums/common/middleware.py
  26. 2 2
      forums/common/utils.py
  27. 19 3
      forums/common/views.py
  28. 17 38
      forums/filters.py
  29. 81 3
      forums/permission.py
  30. 2 5
      static/assets/home.js
  31. 3 9
      static/styles/forums.js
  32. 1 1
      static/styles/upload.js
  33. 2 2
      templates/base/link.html
  34. 7 7
      templates/board/_macro.html
  35. 1 2
      templates/setting/_macro.html
  36. 1 1
      templates/tag/_macro.html
  37. 6 9
      templates/topic/_list_macro.html
  38. 0 28
      templates/topic/item/_macro.html
  39. 2 2
      templates/topic/panel.html
  40. 1 1
      templates/topic/reply/_macro.html
  41. 2 3
      templates/topic/reply/itemlist.html
  42. 4 4
      templates/user/base.html
  43. 4 8
      templates/user/info.html
  44. 1 1
      templates/user/replies.html

+ 3 - 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-27 19:41:1 (CST)
+# Last Update:星期三 2017-3-29 11:25:18 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -17,6 +17,7 @@ from forums.admin.urls import admin
 
 from .filters import register_jinja2
 from .logs import register_logging
+from .app import register_app
 
 
 def create_app(config):
@@ -36,6 +37,7 @@ def register(app):
     register_router(app)
     register_logging(app)
     register_jinja2(app)
+    register_app(app)
 
 
 def register_router(app):

+ 2 - 2
forums/admin/permission.py

@@ -6,13 +6,13 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-17 09:39:36 (CST)
-# Last Update:星期六 2017-3-25 18:16:4 (CST)
+# Last Update:星期三 2017-3-29 13:12:47 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from .views import BaseView
 from forums.extension import db
-from forums.api.permission.models import Group, Router, Permission
+from flask_maple.permission.models import Group, Router, Permission
 
 __all__ = ['register_permission']
 

+ 3 - 3
forums/admin/urls.py

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

+ 9 - 1
forums/admin/views.py

@@ -6,12 +6,14 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-17 13:15:10 (CST)
-# Last Update:星期六 2016-12-17 13:15:20 (CST)
+# Last Update:星期三 2017-3-29 11:22:15 (CST)
 #          By:
 # Description:
 # **************************************************************************
+from flask import abort
 from flask_admin.contrib.sqla import ModelView
 from flask_wtf import Form
+from forums.permission import super_permission
 
 
 class BaseForm(Form):
@@ -26,3 +28,9 @@ class BaseView(ModelView):
     page_size = 10
     can_view_details = True
     form_base_class = BaseForm
+
+    def is_accessible(self):
+        return super_permission.can()
+
+    def inaccessible_callback(self, name, **kwargs):
+        abort(404)

+ 8 - 3
forums/api/auth/views.py

@@ -6,20 +6,21 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-10-28 10:26:10 (CST)
-# Last Update:星期六 2017-3-25 18:26:32 (CST)
+# Last Update:星期三 2017-3-29 11:24:40 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from random import sample
 from string import ascii_letters, digits
 
-from flask import redirect, render_template, request, url_for
+from flask import current_app, redirect, render_template, request, url_for
 from flask.views import MethodView
 from flask_babelex import gettext as _
 from flask_login import current_user, login_required, login_user, logout_user
+from flask_principal import Identity, identity_changed, AnonymousIdentity
+
 from flask_maple.auth.forms import (ForgetForm, LoginForm, RegisterForm,
                                     form_validate)
-
 from forums.api.user.models import User
 from forums.common.response import HTTPResponse
 from forums.common.serializer import Serializer
@@ -42,6 +43,8 @@ class LoginView(MethodView):
         user = User.query.filter_by(username=username).first()
         if user and user.check_password(password):
             login_user(user, remember=remember)
+            identity_changed.send(
+                current_app._get_current_object(), identity=Identity(user.id))
             serializer = Serializer(user, many=False, depth=1)
             return HTTPResponse(
                 HTTPResponse.NORMAL_STATUS, data=serializer.data).to_response()
@@ -53,6 +56,8 @@ class LogoutView(MethodView):
     @login_required
     def get(self):
         logout_user()
+        identity_changed.send(
+            current_app._get_current_object(), identity=AnonymousIdentity())
         return redirect(request.args.get('next') or '/')
 
 

+ 19 - 11
forums/api/collect/models.py

@@ -6,24 +6,25 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-28 17:58:59 (CST)
-# Last Update:星期二 2017-3-28 18:4:27 (CST)
+# Last Update:星期三 2017-3-29 19:10:23 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from datetime import datetime
 from flask_maple.models import ModelMixin, ModelTimeMixin, ModelUserMixin
+from flask_login import current_user
 from forums.api.user.models import User
 from forums.extension import db
 
-topics_collects = db.Table(
-    'topics_collects',
-    db.Column('topics_id', db.Integer, db.ForeignKey('topics.id')),
-    db.Column('collects_id', db.Integer, db.ForeignKey('collects.id')))
+topic_collect = db.Table(
+    'topic_collect',
+    db.Column('topic_id', db.Integer, db.ForeignKey('topics.id')),
+    db.Column('collect_id', db.Integer, db.ForeignKey('collects.id')))
 
-collect_follow_users = db.Table(
-    'collects_follow_users',
-    db.Column('collects_id', db.Integer, db.ForeignKey('collects.id')),
-    db.Column('follow_users_id', db.Integer, db.ForeignKey('users.id')))
+collect_follower = db.Table(
+    'collect_follower',
+    db.Column('collect_id', db.Integer, db.ForeignKey('collects.id')),
+    db.Column('follower_id', db.Integer, db.ForeignKey('users.id')))
 
 
 class Collect(db.Model, ModelMixin):
@@ -47,18 +48,25 @@ class Collect(db.Model, ModelMixin):
 
     topics = db.relationship(
         'Topic',
-        secondary=topics_collects,
+        secondary=topic_collect,
         backref=db.backref(
             'collects', lazy='dynamic'),
         lazy='dynamic')
 
     followers = db.relationship(
         'User',
-        secondary=collect_follow_users,
+        secondary=collect_follower,
         backref=db.backref(
             'following_collects', lazy='dynamic'),
         lazy='dynamic')
 
+    def is_followed(self, user=None):
+        if user is None:
+            user = current_user
+        return db.session.query(collect_follower).filter(
+            collect_follower.c.collect_id == self.id,
+            collect_follower.c.follower_id == user.id).exists()
+
     def __str__(self):
         return self.name
 

+ 3 - 2
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-28 21:57:24 (CST)
+# Last Update:星期三 2017-3-29 19:1:59 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -37,7 +37,8 @@ class CollectListView(MethodView):
         page, number = self.page_info
         keys = ['name']
         order_by = gen_order_by(query_dict, keys)
-        filter_dict = gen_filter_dict(query_dict, keys, user)
+        filter_dict = gen_filter_dict(query_dict, keys)
+        filter_dict.update(author_id=user.id)
         collects = Collect.query.filter_by(
             **filter_dict).order_by(*order_by).paginate(page, number, True)
         data = {'title': 'Collect', 'collects': collects, 'form': form}

+ 9 - 9
forums/api/follow/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-22 21:49:05 (CST)
-# Last Update:星期二 2017-3-28 18:2:43 (CST)
+# Last Update:星期三 2017-3-29 12:40:41 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -97,22 +97,22 @@ class FollowingUsersView(MethodView):
         user = request.user
         post_data = request.data
         user_id = post_data.pop('userId', None)
-        if user_id is not None and not User.query.filter_by(
-                following_users__id=user_id).exists():
+        if user_id is not None:
             f_user = User.query.filter_by(id=user_id).first_or_404()
-            user.following_users.append(f_user)
-            user.save()
+            if not f_user.is_followed(user):
+                user.following_users.append(f_user)
+                user.save()
         return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
 
     def delete(self):
         user = request.user
         post_data = request.data
         user_id = post_data.pop('userId', None)
-        if user_id is not None and User.query.filter_by(
-                following_users__id=user_id).exists():
+        if user_id is not None:
             f_user = User.query.filter_by(id=user_id).first_or_404()
-            user.following_users.remove(f_user)
-            user.save()
+            if f_user.is_followed(user):
+                user.following_users.remove(f_user)
+                user.save()
         return HTTPResponse(HTTPResponse.NORMAL_STATUS).to_response()
 
 

+ 13 - 11
forums/api/forms.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-28 12:53:02 (CST)
-# Last Update:星期二 2017-3-28 17:59:13 (CST)
+# Last Update:星期三 2017-3-29 13:39:45 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -76,16 +76,18 @@ class LoginForm(BaseForm):
     remember = BooleanField(_('Remember me'), default=False)
 
 
+WITHIN = [(0, _('All Topics')), (1, _('One Day')), (2, _('One Week')),
+          (3, _('One Month'))]
+
+ORDERBY = [(0, _('Publish')), (1, _('Author'))]
+
+DESC = [(0, _('Desc')), (1, _('Asc'))]
+
+
 class SortForm(Form):
-    within = SelectField(
-        _('Choice'),
-        coerce=int,
-        choices=[(0, _('All Topics')), (1, _('One Day')), (2, _('One Week')),
-                 (3, _('One Month'))])
-    orderby = SelectField(
-        'orderby', coerce=int, choices=[(0, _('Publish')), (1, _('Author'))])
-    desc = SelectField(
-        'Up and Down', coerce=int, choices=[(0, _('Desc')), (1, _('Asc'))])
+    within = SelectField(_('Choice'), coerce=int, choices=WITHIN)
+    orderby = SelectField('orderby', coerce=int, choices=ORDERBY)
+    desc = SelectField('Up and Down', coerce=int, choices=DESC)
 
 
 class SearchForm(Form):
@@ -130,7 +132,7 @@ class AvatarForm(Form):
 
 class PrivacyForm(Form):
     online_status = SelectField(
-        _('Login status:'), coerce=str, choices=choices)
+        _('Online status:'), coerce=str, choices=choices)
     topic_list = SelectField(_('Topic List:'), coerce=str, choices=choices)
 
     rep_list = SelectField(_('Reply List:'), coerce=str, choices=choices)

+ 13 - 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-25 18:48:35 (CST)
+# Last Update:星期三 2017-3-29 19:41:28 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -33,6 +33,18 @@ class Board(db.Model, ModelMixin):
         lazy='joined',
         uselist=False)
 
+    @property
+    def newest_topic(self):
+        return self.topics.order_by('-id').first()
+
+    @property
+    def topic_count(self):
+        return self.topics.count()
+
+    @property
+    def post_count(self):
+        return self.topics.count()
+
     def __str__(self):
         return self.name
 

+ 8 - 3
forums/api/forums/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-17 20:45:08 (CST)
-# Last Update:星期一 2017-3-27 19:47:49 (CST)
+# Last Update:星期三 2017-3-29 13:46:30 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -71,7 +71,12 @@ class BoardView(MethodView):
         return render_template('board/board.html', **data)
 
     def topics(self, boardId):
+        query_dict = request.data
         page, number = self.page_info
-        filter_dict = dict(board_id=boardId)
-        topics = Topic.get_list(page, number, filter_dict)
+        keys = ['title']
+        order_by = gen_order_by(query_dict, keys)
+        filter_dict = gen_filter_dict(query_dict, keys)
+        filter_dict.update(board_id=boardId)
+        topics = Topic.query.filter_by(
+            **filter_dict).order_by(*order_by).paginate(page, number, True)
         return topics

+ 0 - 236
forums/api/permission/models.py

@@ -1,236 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# **************************************************************************
-# Copyright © 2016 jianglin
-# File Name: models.py
-# Author: jianglin
-# Email: xiyang0807@gmail.com
-# Created: 2016-12-17 09:13:38 (CST)
-# Last Update:星期六 2017-3-25 18:17:15 (CST)
-#          By:
-# Description:
-# **************************************************************************
-from flask_maple.models import db, ModelMixin
-from sqlalchemy import event
-from sqlalchemy.orm import object_session
-from forums.api.user.models import User
-
-group_user = db.Table(
-    'group_user',
-    db.Column('group_id', db.Integer, db.ForeignKey('groups.id')),
-    db.Column('user_id', db.Integer, db.ForeignKey('users.id')))
-
-group_permission = db.Table(
-    'group_permission',
-    db.Column('group_id', db.Integer, db.ForeignKey('groups.id')),
-    db.Column('permission_id', db.Integer, db.ForeignKey('permissions.id')))
-
-router_permission = db.Table(
-    'router_permission',
-    db.Column('router_id', db.Integer, db.ForeignKey('routers.id')),
-    db.Column('permission_id', db.Integer, db.ForeignKey('permissions.id')))
-
-
-class Permission(db.Model, ModelMixin):
-    __tablename__ = 'permissions'
-
-    METHOD_GET = '0'
-    METHOD_POST = '1'
-    METHOD_PUT = '2'
-    METHOD_DELETE = '3'
-    METHOD_PATCH = '4'
-    METHOD_ALL = '5'
-
-    METHOD = (('0', 'GET 方式'), ('1', 'POST 方式'), ('2', 'PUT 方式'),
-              ('3', 'DELETE 方式'), ('4', 'PATCH 方式'), ('5', '所有方式'))
-
-    PERMISSION_DENY = '0'
-    PERMISSION_ALLOW = '1'
-
-    PERMISSION = (('0', '禁止'), ('1', '允许'))
-
-    id = db.Column(db.Integer, primary_key=True)
-    name = db.Column(db.String(512), nullable=False, unique=True)
-    allow = db.Column(db.String(10), nullable=False, default=PERMISSION_ALLOW)
-    method = db.Column(db.String(16), nullable=False, default=METHOD_GET)
-
-    def __str__(self):
-        return self.name
-
-    def __repr__(self):
-        return "<Permission %r>" % self.name
-
-    def is_allowed(self):
-        if self.allow == self.PERMISSION_ALLOW:
-            return True
-        return False
-
-    def is_denied(self):
-        if self.allow == self.PERMISSION_DENY:
-            return True
-        return False
-
-# class Callback(db.Model, ModelMixin):
-#     __tablename__ = 'callbacks'
-
-#     CALLBACK_TYPE_HTTP = '0'
-#     CALLBACK_TYPE_JSON = '1'
-#     CALLBACK_TYPE_REDIRECT = '2'
-
-#     CALLBACK_TYPE = (('0', '403 Forbidden'), ('1', 'Json'), ('2', 'Redirect'))
-
-#     id = db.Column(db.Integer, primary_key=True)
-#     callback = db.Column(db.String(512), nullable=False, unique=True)
-#     callback_type = db.Column(
-#         db.String(10), nullable=False, default=CALLBACK_TYPE_HTTP)
-#     description = db.Column(db.String(128), nullable=True)
-
-#     def __str__(self):
-#         return self.callback
-
-#     def __repr__(self):
-#         return "<Callback %r>" % self.callback
-
-
-class Group(db.Model, ModelMixin):
-    __tablename__ = 'groups'
-    id = db.Column(db.Integer, primary_key=True)
-    name = db.Column(db.String(512), nullable=False, unique=True)
-    permissions = db.relationship(
-        Permission,
-        secondary=group_permission,
-        backref=db.backref(
-            'groups', lazy='dynamic'),
-        lazy='dynamic')
-    users = db.relationship(
-        User,
-        secondary=group_user,
-        backref=db.backref(
-            'groups', lazy='dynamic'),
-        lazy='dynamic')
-
-    def __str__(self):
-        return self.name
-
-    def __repr__(self):
-        return "<Group %r>" % self.name
-
-    def get_permissions(self):
-        return self.permissions.all()
-
-    def has_perm(self, perm):
-        if perm in self.get_permissions():
-            return True
-        return False
-
-    def has_perms(self, perm_list):
-        router_perm_list = set(perm_list)
-        group_perm_list = set(self.get_permissions())
-        common_perm_list = router_perm_list & group_perm_list
-        if not common_perm_list:
-            return False
-        return True
-
-
-class Router(db.Model, ModelMixin):
-    __tablename__ = 'routers'
-
-    URL_TYPE_HTTP = '0'
-    URL_TYPE_ENDPOINT = '1'
-    URL_TYPE = (('0', 'HTTP'), ('1', 'Endpoint'))
-
-    id = db.Column(db.Integer, primary_key=True)
-    url = db.Column(db.String(512), nullable=False, unique=True)
-    url_type = db.Column(db.String(10), nullable=False, default=URL_TYPE_HTTP)
-    description = db.Column(db.String(128), nullable=True)
-    # callback_id = db.Column(db.Integer, db.ForeignKey('callbacks.id'))
-    # callback = db.relationship(
-    #     Callback, backref=db.backref(
-    #         'routers', lazy='dynamic'),
-    #     lazy='joined')
-    permissions = db.relationship(
-        Permission,
-        secondary=router_permission,
-        backref=db.backref(
-            'routers', lazy='dynamic'),
-        lazy='dynamic')
-
-    def __repr__(self):
-        return "<Router %r>" % self.url
-
-    def _get_filter_dict(self, method):
-        filter_dict = {}
-        if method == "HEAD":
-            method = "GET"
-        if hasattr(Permission, 'METHOD_' + method):
-            filter_dict.update(method=getattr(Permission, 'METHOD_' + method))
-        return filter_dict
-
-    def get_permissions(self):
-        return self.permissions.all()
-
-    def get_allow_permissions(self):
-        return self.permissions.filter_by(
-            allow=Permission.PERMISSION_ALLOW).all()
-
-    def get_deny_permissions(self):
-        return self.permissions.filter_by(allow=Permission.PERMISSION_DENY)
-
-    def get_method_permissions(self, method):
-        filter_dict = self._get_filter_dict(method)
-        return self.permissions.filter_by(**filter_dict).all()
-
-    def get_allow_method_permissions(self, method):
-        filter_dict = self._get_filter_dict(method)
-        filter_dict.update(allow=Permission.PERMISSION_ALLOW)
-        return self.permissions.filter_by(**filter_dict).all()
-
-    def get_deny_method_permissions(self, method):
-        filter_dict = self._get_filter_dict(method)
-        filter_dict.update(allow=Permission.PERMISSION_DENY)
-        return self.permissions.filter_by(**filter_dict).all()
-
-
-@event.listens_for(Group, 'after_insert')
-def add_group_permission(mapper, connection, target):
-    method_list = ['GET', 'POST', 'PUT', 'DELETE']
-    perm_list = []
-    for method in method_list:
-        name = target.name + '组' + '允许' + method + '请求'
-        perm = Permission.query.filter_by(name=name).first()
-        if perm is None:
-            perm = Permission()
-            perm.name = name
-            perm.allow = Permission.PERMISSION_ALLOW
-            perm.method = getattr(Permission, 'METHOD_' + method)
-            object_session(target).add(perm)
-        perm_list.append(perm)
-
-        name = target.name + '组' + '禁止' + method + '请求'
-        perm = Permission.query.filter_by(name=name).first()
-        if perm is None:
-            perm = Permission()
-            perm.name = name
-            perm.allow = Permission.PERMISSION_DENY
-            perm.method = getattr(Permission, 'METHOD_' + method)
-            object_session(target).add(perm)
-        perm_list.append(perm)
-    for perm in perm_list:
-        target.permissions.append(perm)
-
-
-@event.listens_for(Group, 'before_delete')
-def delete_group_permission(mapper, connection, target):
-    method_list = ['GET', 'POST', 'PUT', 'DELETE']
-    for method in method_list:
-        name = target.name + '组' + '允许' + method + '请求'
-        perm = Permission.query.filter_by(
-            name=name, allow=Permission.PERMISSION_ALLOW).first()
-        if perm is not None:
-            object_session(target).delete(perm)
-
-        name = target.name + '组' + '禁止' + method + '请求'
-        perm = Permission.query.filter_by(
-            name=name, allow=Permission.PERMISSION_DENY).first()
-        if perm is not None:
-            object_session(target).delete(perm)

+ 0 - 22
forums/api/permission/urls.py

@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# **************************************************************************
-# Copyright © 2016 jianglin
-# File Name: urls.py
-# Author: jianglin
-# Email: xiyang0807@gmail.com
-# Created: 2016-12-17 09:36:57 (CST)
-# Last Update:星期六 2016-12-17 9:39:16 (CST)
-#          By:
-# Description:
-# **************************************************************************
-from flask import Blueprint
-from .views import GroupListView, GroupView
-
-site = Blueprint('perm', __name__)
-
-group_list = GroupListView.as_view('list')
-group = GroupView.as_view('group')
-
-site.add_url_rule('/group', view_func=group_list)
-site.add_url_rule('/group/<name>', view_func=group)

+ 0 - 42
forums/api/permission/views.py

@@ -1,42 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# **************************************************************************
-# Copyright © 2016 jianglin
-# File Name: views.py
-# Author: jianglin
-# Email: xiyang0807@gmail.com
-# Created: 2016-12-17 09:31:49 (CST)
-# Last Update:星期三 2017-1-25 21:46:14 (CST)
-#          By:
-# Description:
-# **************************************************************************
-from flask_maple.serializer import FlaskSerializer as Serializer
-from flask_maple.response import HTTPResponse
-from common.views import BaseMethodView as MethodView
-from .models import Group
-
-
-class GroupListView(MethodView):
-    def get(self):
-        page, number = self.page_info
-        users = Group.get_list(page, number)
-        serializer = Serializer(users, many=True)
-        return HTTPResponse(HTTPResponse.NORMAL_STATUS,
-                            **serializer.data).to_response()
-
-    def post(self):
-        return 'post'
-
-
-class GroupView(MethodView):
-    def get(self, username):
-        user = Group.get(username=username)
-        serializer = Serializer(user, many=False)
-        return HTTPResponse(
-            HTTPResponse.NORMAL_STATUS, data=serializer.data).to_response()
-
-    def put(self, username):
-        return 'put'
-
-    def delete(self, username):
-        return 'delete'

+ 2 - 2
forums/api/setting/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-20 22:16:04 (CST)
-# Last Update:星期二 2017-3-28 15:53:0 (CST)
+# Last Update:星期三 2017-3-29 18:17:0 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -14,7 +14,7 @@ from flask import render_template, redirect, url_for, flash, request
 from flask_login import current_user, logout_user
 from forums.api.forms import (ProfileForm, PasswordForm, PrivacyForm,
                               AvatarForm, BabelForm)
-from forums.common.views import IsAuthMethodView as MethodView
+from forums.common.views import IsConfirmedMethodView as MethodView
 from flask_maple.auth.forms import form_validate
 
 

+ 28 - 20
forums/api/tag/models.py

@@ -6,27 +6,23 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 20:46:13 (CST)
-# Last Update:星期六 2017-3-25 18:17:15 (CST)
+# Last Update:星期三 2017-3-29 19:17:5 (CST)
 #          By:
 # Description:
 # **************************************************************************
+from flask_login import current_user
 from flask_maple.models import ModelMixin
 from forums.api.topic.models import Topic
 from forums.api.user.models import User
 from forums.extension import db
 
-tags_follow_users = db.Table(
-    'tags_follow_users',
-    db.Column('tags_id', db.Integer, db.ForeignKey('tags.id')),
-    db.Column('follow_users_id', db.Integer, db.ForeignKey('users.id')))
+tag_follower = db.Table(
+    'tag_follower', db.Column('tag_id', db.Integer, db.ForeignKey('tags.id')),
+    db.Column('follower_id', db.Integer, db.ForeignKey('users.id')))
 
-tags_topics = db.Table(
-    'tags_topics', db.Column('tags_id', db.Integer, db.ForeignKey('tags.id')),
-    db.Column('topics_id', db.Integer, db.ForeignKey('topics.id')))
-
-tags_parents = db.Table(
-    'tags_parents', db.Column('tag_id', db.Integer, db.ForeignKey('tags.id')),
-    db.Column('parent_id', db.Integer, db.ForeignKey('tags.id')))
+tag_topic = db.Table(
+    'tag_topic', db.Column('tag_id', db.Integer, db.ForeignKey('tags.id')),
+    db.Column('topic_id', db.Integer, db.ForeignKey('topics.id')))
 
 
 class Tags(db.Model, ModelMixin):
@@ -34,27 +30,39 @@ class Tags(db.Model, ModelMixin):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(64), nullable=False)
     description = db.Column(db.String(128), nullable=False)
-    parents = db.relationship(
+    parent_id = db.Column(
+        db.Integer, db.ForeignKey(
+            'tags.id', ondelete="CASCADE"))
+    parent = db.relationship(
         'Tags',
-        secondary=tags_parents,
-        primaryjoin=(id == tags_parents.c.tag_id),
-        secondaryjoin=(id == tags_parents.c.parent_id),
+        remote_side=[id],
         backref=db.backref(
-            'children', lazy='joined'),
-        lazy='joined')
+            'children',
+            remote_side=[parent_id],
+            cascade='all,delete-orphan',
+            lazy='dynamic'),
+        lazy='joined',
+        uselist=False)
     topics = db.relationship(
         Topic,
-        secondary=tags_topics,
+        secondary=tag_topic,
         backref=db.backref(
             'tags', lazy='dynamic'),
         lazy='dynamic')
     followers = db.relationship(
         User,
-        secondary=tags_follow_users,
+        secondary=tag_follower,
         backref=db.backref(
             'following_tags', lazy='dynamic'),
         lazy='dynamic')
 
+    def is_followed(self, user=None):
+        if user is None:
+            user = current_user
+        return db.session.query(tag_follower).filter(
+            tag_follower.c.tag_id == self.id,
+            tag_follower.c.follower_id == user.id).exists()
+
     def __str__(self):
         return self.name
 

+ 43 - 14
forums/api/topic/models.py

@@ -6,24 +6,25 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 20:52:07 (CST)
-# Last Update:星期二 2017-3-28 17:58:52 (CST)
+# Last Update:星期三 2017-3-29 19:40:39 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from datetime import datetime
 
 from flask import current_app
-from flask_maple.models import ModelMixin, ModelTimeMixin, ModelUserMixin
-from forums.common.models import CommonUserMixin
+from flask_login import current_user
 
+from flask_maple.models import ModelMixin, ModelTimeMixin, ModelUserMixin
 from forums.api.forums.models import Board
 from forums.api.user.models import User
+from forums.common.models import CommonUserMixin
 from forums.extension import db
 
-topics_follow_users = db.Table(
-    'topics_follow_users',
-    db.Column('topics_id', db.Integer, db.ForeignKey('topics.id')),
-    db.Column('follow_users_id', db.Integer, db.ForeignKey('users.id')))
+topic_follower = db.Table(
+    'topic_follower',
+    db.Column('topic_id', db.Integer, db.ForeignKey('topics.id')),
+    db.Column('follower_id', db.Integer, db.ForeignKey('users.id')))
 
 
 class Topic(db.Model, ModelMixin):
@@ -65,11 +66,35 @@ class Topic(db.Model, ModelMixin):
         lazy='joined')
     followers = db.relationship(
         User,
-        secondary=topics_follow_users,
+        secondary=topic_follower,
         backref=db.backref(
             'following_topics', lazy='dynamic'),
         lazy='dynamic')
 
+    def is_followed(self, user=None):
+        if user is None:
+            user = current_user
+        return db.session.query(topic_follower).filter(
+            topic_follower.c.topic_id == self.id,
+            topic_follower.c.follower_id == user.id).exists()
+
+    def is_collected(self, user=None):
+        if user is None:
+            user = current_user
+        return self.collects.filter_by(author_id=user.id).exists()
+
+    @property
+    def newest_reply(self):
+        return self.replies.order_by('-id').first()
+
+    @property
+    def reply_count(self):
+        return self.replies.count()
+
+    @property
+    def read_count(self):
+        return self.replies.count()
+
     def __str__(self):
         return self.title
 
@@ -77,11 +102,10 @@ class Topic(db.Model, ModelMixin):
         return "<Topic %r>" % self.title
 
 
-
-replies_likers = db.Table(
-    'replies_likers',
-    db.Column('replies_id', db.Integer, db.ForeignKey('replies.id')),
-    db.Column('likers_id', db.Integer, db.ForeignKey('users.id')))
+reply_liker = db.Table(
+    'reply_liker',
+    db.Column('reply_id', db.Integer, db.ForeignKey('replies.id')),
+    db.Column('liker_id', db.Integer, db.ForeignKey('users.id')))
 
 
 class Reply(db.Model, ModelMixin):
@@ -108,11 +132,16 @@ class Reply(db.Model, ModelMixin):
 
     likers = db.relationship(
         User,
-        secondary=replies_likers,
+        secondary=reply_liker,
         backref=db.backref(
             'like_replies', lazy='dynamic'),
         lazy='dynamic')
 
+    def is_liked(self, user=None):
+        if user is None:
+            user = current_user
+        return self.likers.filter_by(id=user.id).exists()
+
     def __str__(self):
         return self.content[:10]
 

+ 72 - 0
forums/api/topic/permissions.py

@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: permissions.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-03-29 15:53:37 (CST)
+# Last Update:星期三 2017-3-29 18:38:9 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from flask import request
+from flask_login import login_required
+
+from forums.permission import (ReplyPermission, RestfulView, TopicPermission,
+                               is_confirmed)
+
+
+class TopicList(RestfulView):
+    @is_confirmed
+    def post(self):
+        return True
+
+
+class Topic(RestfulView):
+    @is_confirmed
+    def put(self, topicId):
+        permission = TopicPermission(topicId)
+        if not permission.can():
+            return self.callback()
+        return True
+
+    @is_confirmed
+    def delete(self, topicId):
+        permission = TopicPermission(topicId)
+        if not permission.can():
+            return self.callback()
+        return True
+
+
+class ReplyList(RestfulView):
+    @is_confirmed
+    def post(self, topicId):
+        return True
+
+
+class Reply(RestfulView):
+    @is_confirmed
+    def put(self, replyId):
+        return True
+
+    @is_confirmed
+    def delete(self, replyId):
+        return True
+
+
+class Like(RestfulView):
+    @is_confirmed
+    def post(self, replyId):
+        return True
+
+    @is_confirmed
+    def delete(self, replyId):
+        return True
+
+
+topic_list_permission = TopicList()
+topic_permission = Topic()
+reply_list_permission = ReplyList()
+reply_permission = Reply()
+like_permission = Like()

+ 39 - 9
forums/api/topic/views.py

@@ -6,30 +6,34 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 22:07:39 (CST)
-# Last Update:星期二 2017-3-28 22:24:9 (CST)
+# Last Update:星期三 2017-3-29 18:59:44 (CST)
 #          By:
 # Description:
 # **************************************************************************
-from flask import redirect, render_template, request, url_for, Markup
+from flask import Markup, redirect, render_template, request, url_for
 from flask_babelex import gettext as _
 from flask_login import current_user, login_required
 
 from flask_maple.auth.forms import form_validate
 from flask_maple.response import HTTPResponse
+from forums.api.forms import (CollectForm, ReplyForm, TopicForm,
+                              collect_error_callback, error_callback,
+                              form_board)
 from forums.api.forums.models import Board
 from forums.api.tag.models import Tags
 from forums.common.serializer import Serializer
 from forums.common.utils import gen_filter_dict, gen_order_by
 from forums.common.views import BaseMethodView as MethodView
+from forums.common.views import IsAuthMethodView, IsConfirmedMethodView
 from forums.filters import safe_markdown
 
-from forums.api.forms import (CollectForm, ReplyForm, TopicForm,
-                              collect_error_callback, error_callback,
-                              form_board)
 from .models import Reply, Topic
+from .permissions import (like_permission, reply_list_permission,
+                          reply_permission, topic_list_permission,
+                          topic_permission)
 
 
-class TopicAskView(MethodView):
+class TopicAskView(IsConfirmedMethodView):
     def get(self):
         boardId = request.args.get('boardId', type=int)
         form = form_board()
@@ -39,7 +43,7 @@ class TopicAskView(MethodView):
         return render_template('topic/ask.html', **data)
 
 
-class TopicEditView(MethodView):
+class TopicEditView(IsConfirmedMethodView):
     def get(self, topicId):
         topic = Topic.query.filter_by(id=topicId).first_or_404()
         form = form_board()
@@ -51,7 +55,7 @@ class TopicEditView(MethodView):
         return render_template('topic/edit.html', **data)
 
 
-class TopicPreviewView(MethodView):
+class TopicPreviewView(IsConfirmedMethodView):
     @login_required
     def post(self):
         post_data = request.data
@@ -63,6 +67,8 @@ class TopicPreviewView(MethodView):
 
 
 class TopicListView(MethodView):
+    decorators = (topic_list_permission, )
+
     def get(self):
         query_dict = request.data
         page, number = self.page_info
@@ -106,10 +112,26 @@ class TopicListView(MethodView):
 
 
 class TopicView(MethodView):
+    per_page = 10
+
+    decorators = (topic_permission, )
+
     def get(self, topicId):
         form = ReplyForm()
+        query_dict = request.data
         topic = Topic.query.filter_by(id=topicId).first_or_404()
-        data = {'title': topic.title, 'form': form, 'topic': topic}
+        page, number = self.page_info
+        keys = ['title']
+        order_by = gen_order_by(query_dict, keys)
+        filter_dict = gen_filter_dict(query_dict, keys)
+        replies = topic.replies.filter_by(
+            **filter_dict).order_by(*order_by).paginate(page, number, True)
+        data = {
+            'title': topic.title,
+            'form': form,
+            'topic': topic,
+            'replies': replies
+        }
         return render_template('topic/topic.html', **data)
 
     # def put(self, topicId):
@@ -132,6 +154,8 @@ class TopicView(MethodView):
 
 
 class ReplyListView(MethodView):
+    decorators = (reply_list_permission, )
+
     @form_validate(ReplyForm, error=error_callback, f='')
     def post(self, topicId):
         topic = Topic.query.filter_by(id=topicId).first_or_404()
@@ -144,6 +168,9 @@ class ReplyListView(MethodView):
 
 
 class ReplyView(MethodView):
+
+    decorators = (reply_permission, )
+
     def put(self, replyId):
         post_data = request.data
         reply = Reply.query.filter_by(id=replyId).first()
@@ -164,6 +191,9 @@ class ReplyView(MethodView):
 
 
 class LikeView(MethodView):
+
+    decorators = (like_permission, )
+
     def post(self, replyId):
         user = request.user
         reply = Reply.query.filter_by(id=replyId).first_or_404()

+ 4 - 4
forums/api/upload/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-21 21:56:41 (CST)
-# Last Update:星期二 2017-3-28 15:58:18 (CST)
+# Last Update:星期三 2017-3-29 18:19:47 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -30,9 +30,9 @@ class AvatarView(MethodView):
         AvatarForm, error=lambda: redirect(url_for('setting.setting')), f='')
     def post(self):
         form = AvatarForm()
+        user = request.user
         file = request.files[form.avatar.name]
-        filename = secure_filename(file.filename)
-        filename = current_user.username + '-' + str(int(time())) + str(
+        filename = user.username + '-' + str(int(time())) + str(
             randint(1000, 9999))
         img = Image.open(file)
         size = 150, 150
@@ -45,7 +45,7 @@ class AvatarView(MethodView):
             os.mkdir(avatar_path)
         img.save(avatar)
         img.close()
-        info = current_user.info
+        info = user.info
         if info.avatar:
             ef = os.path.join(avatar_path, info.avatar)
             if os.path.exists(ef):

+ 18 - 11
forums/api/user/models.py

@@ -6,12 +6,12 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 21:09:08 (CST)
-# Last Update:星期三 2017-1-25 20:25:9 (CST)
+# Last Update:星期三 2017-3-29 19:11:0 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from flask import current_app
-from flask_login import UserMixin
+from flask_login import UserMixin, current_user
 from flask_maple.models import ModelMixin
 from flask_mail import Message
 from threading import Thread
@@ -24,10 +24,10 @@ from forums.extension import db, mail
 from pytz import all_timezones
 from datetime import datetime
 
-users_follow_users = db.Table(
-    'users_follow_users',
-    db.Column('users_id', db.Integer, db.ForeignKey('users.id')),
-    db.Column('follow_users_id', db.Integer, db.ForeignKey('users.id')))
+user_follower = db.Table(
+    'user_follower',
+    db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
+    db.Column('follower_id', db.Integer, db.ForeignKey('users.id')))
 
 
 class User(db.Model, UserMixin, ModelMixin):
@@ -43,13 +43,20 @@ class User(db.Model, UserMixin, ModelMixin):
 
     followers = db.relationship(
         'User',
-        secondary=users_follow_users,
-        primaryjoin=(id == users_follow_users.c.users_id),
-        secondaryjoin=(id == users_follow_users.c.follow_users_id),
+        secondary=user_follower,
+        primaryjoin=(id == user_follower.c.user_id),
+        secondaryjoin=(id == user_follower.c.follower_id),
         backref=db.backref(
             'following_users', lazy='dynamic'),
         lazy='dynamic')
 
+    def is_followed(self, user=None):
+        if user is None:
+            user = current_user
+        return db.session.query(user_follower).filter(
+            user_follower.c.user_id == self.id,
+            user_follower.c.follower_id == user.id).exists()
+
     def __str__(self):
         return self.username
 
@@ -156,7 +163,7 @@ class UserInfo(db.Model, ModelMixin):
         return "<UserInfo %r>" % str(self.id)
 
     def __str__(self):
-        return "%s's info" % self.user.username
+        return "%s's info" % self.user_id
 
 
 class UserSetting(db.Model, ModelMixin):
@@ -203,7 +210,7 @@ class UserSetting(db.Model, ModelMixin):
         return "<UserSetting %r>" % str(self.id)
 
     def __str__(self):
-        return "%s's setting" % self.user.username
+        return "%s's setting" % self.user_id
 
 
 @event.listens_for(User, 'before_insert')

+ 2 - 8
forums/api/user/views.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-12-15 22:08:06 (CST)
-# Last Update:星期二 2017-3-28 22:33:11 (CST)
+# Last Update:星期三 2017-3-29 11:53:37 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -88,15 +88,9 @@ class UserReplyListView(MethodView):
 
 class UserFollowerListView(MethodView):
     def get(self, username):
-        query_dict = request.data
         user = User.query.filter_by(username=username).first_or_404()
         page, number = self.page_info
-        keys = ['title']
-        order_by = gen_order_by(query_dict, keys)
-        filter_dict = gen_filter_dict(query_dict, keys)
-        filter_dict.update(author_id=user.id)
-        followers = Collect.query.filter_by(
-            **filter_dict).order_by(*order_by).paginate(page, number, True)
+        followers = user.followers.paginate(page, number, True)
         data = {'followers': followers, 'user': user}
         return render_template('user/followers.html', **data)
 

+ 52 - 0
forums/api/utils.py

@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# Copyright © 2017 jianglin
+# File Name: utils.py
+# Author: jianglin
+# Email: xiyang0807@gmail.com
+# Created: 2017-03-29 13:33:03 (CST)
+# Last Update:星期三 2017-3-29 13:43:40 (CST)
+#          By:
+# Description:
+# **************************************************************************
+from datetime import datetime, timedelta
+
+one_day = datetime.now() + timedelta(days=-1)
+one_month = datetime.now() + timedelta(days=-30)
+one_year = datetime.now() + timedelta(years=-1)
+
+
+def gen_topic_filter(query_dict=dict(), keys=[], equal_key=[], user=None):
+    filter_dict = {}
+    keys = list(set(keys) & set(query_dict.keys()))
+    for k in keys:
+        if k in equal_key:
+            filter_dict.update(**{k: query_dict[k]})
+        else:
+            new_k = '%s__contains' % k
+            filter_dict.update(**{new_k: query_dict[k]})
+    if user is not None and user.is_authenticated:
+        filter_dict.update(user__id=user.id)
+    within = query_dict.pop('within', None)
+    if within is not None:
+        if within == 1:
+            filter_dict.update(created_at__gte=one_day)
+        elif within == 2:
+            filter_dict.update(created_at__gte=one_month)
+        elif within == 3:
+            filter_dict.update(created_at__gte=one_year)
+    return filter_dict
+
+
+def gen_topic_orderby(query_dict=dict(), keys=[], date_key=True):
+    keys.append('id')
+    if date_key:
+        keys += ['created_at', 'updated_at']
+    order_by = ['id']
+    descent = query_dict.pop('orderby', None)
+    if descent is not None:
+        descent = descent.split(',')
+        descent = list(set(keys) & set(descent))
+        order_by = ['-%s' % i for i in descent]
+    return tuple(order_by)

+ 20 - 1
forums/app.py

@@ -6,12 +6,13 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-28 16:11:07 (CST)
-# Last Update:星期二 2017-3-28 16:11:19 (CST)
+# Last Update:星期三 2017-3-29 17:35:46 (CST)
 #          By:
 # Description:
 # **************************************************************************
 from flask_principal import RoleNeed, UserNeed, identity_loaded
 from flask_login import current_user
+from .permission import TopicNeed, ReplyNeed, CollectNeed
 
 
 def register_app(app):
@@ -27,6 +28,24 @@ def register_app(app):
             if current_user.is_superuser:
                 identity.provides.add(RoleNeed('super'))
 
+        if hasattr(current_user, 'is_confirmed'):
+            if current_user.is_confirmed:
+                identity.provides.add(RoleNeed('confirmed'))
+
         if hasattr(current_user, 'is_authenticated'):
             if current_user.is_authenticated:
                 identity.provides.add(RoleNeed('auth'))
+            else:
+                identity.provides.add(RoleNeed('guest'))
+
+        if hasattr(current_user, 'topics'):
+            for topic in current_user.topics:
+                identity.provides.add(TopicNeed(topic.id))
+
+        if hasattr(current_user, 'replies'):
+            for reply in current_user.replies:
+                identity.provides.add(ReplyNeed(reply.id))
+
+        if hasattr(current_user, 'collects'):
+            for collect in current_user.collects:
+                identity.provides.add(CollectNeed(collect.id))

+ 12 - 1
forums/common/middleware.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2016-11-12 13:29:17 (CST)
-# Last Update:星期二 2017-3-28 16:53:28 (CST)
+# Last Update:星期三 2017-3-29 13:29:17 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -15,10 +15,21 @@ from flask_login import current_user
 from forums.api.forms import SortForm, SearchForm
 
 
+def set_form(form):
+    within = request.args.get('within', 0, type=int)
+    orderby = request.args.get('orderby', 0, type=int)
+    desc = request.args.get('desc', 0, type=int)
+    form.within.data = within
+    form.orderby.data = orderby
+    form.desc.data = desc
+    return form
+
+
 class GlobalMiddleware(object):
     def preprocess_request(self):
         g.user = current_user
         g.sort_form = SortForm()
+        g.sort_form = set_form(g.sort_form)
         g.search_form = SearchForm()
         request.user = current_user._get_current_object()
         if request.method == 'GET':

+ 2 - 2
forums/common/utils.py

@@ -6,7 +6,7 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-13 13:40:38 (CST)
-# Last Update:星期二 2017-3-28 16:59:0 (CST)
+# Last Update:星期三 2017-3-29 13:46:43 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -21,7 +21,7 @@ def gen_order_by(query_dict=dict(), keys=[], date_key=True):
     keys.append('id')
     if date_key:
         keys += ['created_at', 'updated_at']
-    order_by = ['id']
+    order_by = ['-id']
     descent = query_dict.pop('orderby', None)
     if descent is not None:
         descent = descent.split(',')

+ 19 - 3
forums/common/views.py

@@ -6,13 +6,25 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-13 13:29:37 (CST)
-# Last Update:星期一 2017-3-13 13:31:23 (CST)
+# Last Update:星期三 2017-3-29 13:54:44 (CST)
 #          By:
 # Description:
 # **************************************************************************
-from flask import request, current_app
+from flask import request, current_app, flash, redirect, url_for
 from flask.views import MethodView
-from flask_login import login_required
+from flask_login import login_required, current_user
+from forums.permission import confirm_permission
+
+
+def is_confirmed(func):
+    def _is_confirmed(*args, **kwargs):
+        if confirm_permission.can():
+            ret = func(*args, **kwargs)
+            return ret
+        flash('请验证你的帐号', 'warning')
+        return redirect(url_for('user.user', username=current_user.username))
+
+    return _is_confirmed
 
 
 class BaseMethodView(MethodView):
@@ -32,3 +44,7 @@ class BaseMethodView(MethodView):
 
 class IsAuthMethodView(BaseMethodView):
     decorators = [login_required]
+
+
+class IsConfirmedMethodView(BaseMethodView):
+    decorators = [is_confirmed, login_required]

+ 17 - 38
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-28 21:12:1 (CST)
+# Last Update:星期三 2017-3-29 19:27:23 (CST)
 #          By:
 # Description:
 # **************************************************************************
@@ -118,42 +118,25 @@ def recent_tags():
     return tags
 
 
-def is_liked(replyId):
-    from forums.api.topic.models import Reply
-    return Reply.query.filter_by(
-        likers__id=current_user.id, id=replyId).exists()
-
-
-def topic_is_followed(topicId):
-    from forums.api.topic.models import Topic
-    return Topic.query.filter_by(
-        followers__id=current_user.id, id=topicId).exists()
-
-
-def tag_is_followed(pk):
-    from forums.api.tag.models import Tags
-    return Tags.query.filter_by(followers__id=current_user.id, id=pk).exists()
-
-
-def user_is_followed(pk):
-    from forums.api.user.models import User
-    return User.query.filter_by(followers__id=current_user.id, id=pk).exists()
-
-
-def is_collected(topicId):
-    from forums.api.collect.models import Collect
-    return Collect.query.filter_by(
-        topics__id=topicId, author_id=current_user.id).exists()
-
-
-def is_online(username):
+def is_online(user):
+    from forums.api.user.models import UserSetting
     from forums.common.records import load_online_sign_users
-    online_users = load_online_sign_users()
-    if username in online_users:
-        return True
+    setting = user.setting
+    if setting.online_status == UserSetting.STATUS_ALLOW_ALL:
+        return user.username in load_online_sign_users()
+    elif setting.online_status == UserSetting.STATUS_ALLOW_AUTHENTICATED:
+        return user.username in load_online_sign_users(
+        ) and current_user.is_authenticated
+    elif setting.online_status == UserSetting.STATUS_ALLOW_OWN:
+        return current_user.id == user.id
     return False
 
 
+def is_not_confirmed(user):
+    return (not user.is_confirmed and
+            user.id == current_user.id)
+
+
 class Title(object):
     setting = {'title': 'Honmaple', 'picture': '', 'description': '爱生活,更爱自由'}
     title = setting['title']
@@ -174,9 +157,5 @@ def register_jinja2(app):
     app.jinja_env.filters['timesince'] = timesince
     app.jinja_env.filters['markdown'] = safe_markdown
     app.jinja_env.filters['safe_clean'] = safe_clean
-    app.jinja_env.filters['is_collected'] = is_collected
     app.jinja_env.filters['is_online'] = is_online
-    app.jinja_env.filters['is_liked'] = is_liked
-    app.jinja_env.filters['topic_is_followed'] = topic_is_followed
-    app.jinja_env.filters['tag_is_followed'] = tag_is_followed
-    app.jinja_env.filters['user_is_followed'] = tag_is_followed
+    app.jinja_env.filters['is_not_confirmed'] = is_not_confirmed

+ 81 - 3
forums/permission.py

@@ -6,11 +6,89 @@
 # Author: jianglin
 # Email: xiyang0807@gmail.com
 # Created: 2017-03-28 16:02:43 (CST)
-# Last Update:星期二 2017-3-28 16:13:3 (CST)
+# Last Update:星期三 2017-3-29 17:49:46 (CST)
 #          By:
 # Description:
 # **************************************************************************
-from flask_principal import Permission, RoleNeed
+from collections import namedtuple
+from functools import partial, wraps
+
+from flask import abort, current_app, flash, redirect, request, url_for
+from flask_login import current_user, login_required
+from flask_principal import (Need, Permission, RoleNeed, UserNeed,
+                             identity_loaded)
 
 super_permission = Permission(RoleNeed('super'))
-auth_permission = Permission(RoleNeed('auth'))
+confirm_permission = Permission(RoleNeed('confirmed')).union(super_permission)
+auth_permission = Permission(RoleNeed('auth')).union(confirm_permission)
+guest_permission = Permission(RoleNeed('guest')).union(auth_permission)
+
+_TopicNeed = namedtuple('Topic', ['method', 'value'])
+TopicNeed = partial(_TopicNeed, 'PUT')
+
+_ReplyNeed = namedtuple('Reply', ['method', 'value'])
+ReplyNeed = partial(_ReplyNeed, 'edit')
+
+_CollectNeed = namedtuple('Collect', ['method', 'value'])
+CollectNeed = partial(_CollectNeed, 'edit')
+
+
+class TopicPermission(Permission):
+    def __init__(self, pk):
+        need = TopicNeed(pk)
+        super(TopicPermission, self).__init__(need)
+
+
+class ReplyPermission(Permission):
+    def __init__(self, pk):
+        need = ReplyNeed(pk)
+        super(ReplyPermission, self).__init__(need)
+
+
+class CollectPermission(Permission):
+    def __init__(self, pk):
+        need = CollectNeed(pk)
+        super(CollectPermission, self).__init__(need)
+
+
+def is_confirmed(func):
+    @wraps(func)
+    def _is_confirmed(*args, **kwargs):
+        if not current_user.is_authenticated:
+            return redirect(url_for('auth.login', next=request.path))
+        if confirm_permission.can():
+            return func(*args, **kwargs)
+        flash('请验证你的帐号', 'warning')
+        return redirect(url_for('user.user', username=current_user.username))
+
+    return _is_confirmed
+
+
+class RestfulView(object):
+    decorators = ()
+
+    def __call__(self, func):
+        f = self.method(func)
+        if self.decorators:
+            for dec in reversed(self.decorators):
+                f = dec(f)
+        return f
+
+    def method(self, func):
+        @wraps(func)
+        def decorator(*args, **kwargs):
+            meth = getattr(self, request.method.lower(), None)
+            if request.method == 'HEAD':
+                meth = getattr(self, 'get', None)
+            if meth is not None:
+                check = meth(*args, **kwargs)
+                if isinstance(check, bool) and check:
+                    return func(*args, **kwargs)
+                elif check:
+                    return check or self.callback()
+            return func(*args, **kwargs)
+
+        return decorator
+
+    def callback(self):
+        abort(403)

+ 2 - 5
static/assets/home.js

@@ -9,12 +9,9 @@ if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires j
 else
 {$("#showerror").show();$("#changeCode").attr("src",url.captcha+"?code="+Math.random());$("#captcha").val("");if(response.description!==""){$("#error").text(response.description);}
 else{$("#error").text(response.message);}}}
-$('button#login').click(function(){$.ajax({type:"POST",url:url.login,data:JSON.stringify({username:$('#username').val(),password:$('#password').val(),captcha:$("#captcha").val(),remember:$("#remember").is(':checked')}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});$('button#register').click(function(){$.ajax({type:"POST",url:url.register,data:JSON.stringify({username:$('#username').val(),email:$('#email').val(),password:$('#password').val(),captcha:$("#captcha").val()}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});$('button#forget').click(function(){$.ajax({type:"POST",url:url.forget,data:JSON.stringify({email:$('#email').val(),captcha:$("#captcha").val()}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});});function loadFile(event){var _file=document.getElementById("avatar");var i=_file.value.lastIndexOf('.');var len=_file.value.length;var extEndName=_file.value.substring(i+1,len);var extName="JPG,PNG";if(extName.indexOf(extEndName.toUpperCase())==-1){alert("您只能上传"+extName+"格式的文件");$('#avatar').val('');}else{var reader=new FileReader();reader.onload=function(){var icon='<i class="icon-exchange"></i>'+'\n';var img='<img src="'+reader.result+'" title="avatar" class="avatar img-circle">';$("#show-avatar").html(icon+img);};reader.readAsDataURL(event.target.files[0]);}}
+$('button#login').click(function(){$.ajax({type:"POST",url:url.login,data:JSON.stringify({username:$('#username').val(),password:$('#password').val(),captcha:$("#captcha").val(),remember:$("#remember").is(':checked')}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});$('button#register').click(function(){$.ajax({type:"POST",url:url.register,data:JSON.stringify({username:$('#username').val(),email:$('#email').val(),password:$('#password').val(),captcha:$("#captcha").val()}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});$('button#forget').click(function(){$.ajax({type:"POST",url:url.forget,data:JSON.stringify({email:$('#email').val(),captcha:$("#captcha").val()}),contentType:'application/json;charset=UTF-8',success:function(response){return AuthCallBack(response);}});});});function loadFile(event){var _file=document.getElementById("avatar");var i=_file.value.lastIndexOf('.');var len=_file.value.length;var extEndName=_file.value.substring(i+1,len);var extName="JPG,PNG";if(extName.indexOf(extEndName.toUpperCase())==-1){alert("您只能上传"+extName+"格式的文件");$('#avatar').val('');}else{var reader=new FileReader();reader.onload=function(){var icon='<i class="fa fa-exchange"></i>'+'\n';var img='<img src="'+reader.result+'" title="avatar" class="avatar img-circle">';$("#show-avatar").html(icon+img);};reader.readAsDataURL(event.target.files[0]);}}
 function getQueryParams(k){var p={};location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(s,k,v){p[k]=v;});return k?p[k]:p;}
-function SortFuntion(){var within=$('select#within').val();var orderby=$('select#orderby').val();var desc=$('select#desc').val();var params=getQueryParams();if(within!='0'){params.within=within;}
-if(orderby!='0'){params.orderby=orderby;}
-if(desc!='0'){params.desc=desc;}
-window.location.href=window.location.pathname+'?'+$.param(params);}
+function SortFuntion(){var within=$('select#within').val();var orderby=$('select#orderby').val();var desc=$('select#desc').val();var params=getQueryParams();params.within=within;params.orderby=orderby;params.desc=desc;window.location.href=window.location.pathname+'?'+$.param(params);}
 $(document).ready(function(){$('select#within').change(function(){SortFuntion();});$('select#orderby').change(function(){SortFuntion();});$('select#desc').change(function(){SortFuntion();});$('span#email-confirm').click(function(){$.ajax({type:"POST",url:"/confirm-email",data:JSON.stringify({}),contentType:'application/json;charset=UTF-8',success:function(result){if(result.judge===true)
 {alert(result.error);}else
 {alert(result.error);}}});});});function dispatch(){var q=document.getElementById("search");if(q.value!==""){var url='https://www.google.com/search?q=site:forums.honmaple.org%20'+q.value;if(navigator.userAgent.indexOf('iPad')>-1||navigator.userAgent.indexOf('iPod')>-1||navigator.userAgent.indexOf('iPhone')>-1){location.href=url;}else{window.open(url,"_blank");}

+ 3 - 9
static/styles/forums.js

@@ -8,15 +8,9 @@ function SortFuntion(){
   var orderby =  $('select#orderby').val();
   var desc =  $('select#desc').val();
   var params = getQueryParams();
-  if (within != '0'){
-    params.within = within;
-  }
-  if (orderby != '0'){
-    params.orderby = orderby;
-  }
-  if (desc != '0'){
-    params.desc = desc;
-  }
+  params.within = within;
+  params.orderby = orderby;
+  params.desc = desc;
   window.location.href = window.location.pathname + '?' + $.param(params);
 }
 $(document).ready(function(){

+ 1 - 1
static/styles/upload.js

@@ -10,7 +10,7 @@ function loadFile(event) {
   }else{
     var reader = new FileReader();
     reader.onload = function(){
-      var icon = '<i class="icon-exchange"></i>' + '\n';
+      var icon = '<i class="fa fa-exchange"></i>' + '\n';
       var img = '<img src="' + reader.result + '" title="avatar" class="avatar img-circle">';
       $("#show-avatar").html(icon + img);
     };

+ 2 - 2
templates/base/link.html

@@ -38,9 +38,9 @@
 {% macro user_avatar(user,width=64) -%}
 {% set info = user.info %}
 {% if not info.avatar -%}
-<img class="media-object img-circle" src="{{ url_for('avatar',text=user.username) }}" style="width:{{ width }}px;height:{{ width }}px">
+<img class="img-circle" src="{{ url_for('avatar',text=user.username) }}" style="width:{{ width }}px;height:{{ width }}px">
 {% else %}
-<img class="media-object img-circle" src="{{ url_for('upload.avatar_file',filename=info.avatar) }}" style="width:{{ width }}px;height:{{ width }}px">
+<img class="img-circle" src="{{ url_for('upload.avatar_file',filename=info.avatar) }}" style="width:{{ width }}px;height:{{ width }}px">
 {%- endif %}
 {%- endmacro %}
 

+ 7 - 7
templates/board/_macro.html

@@ -7,19 +7,19 @@
         <dd>{{ link.board(board) }}</dd>
         <dd style="font-size:12px;">
           <span style="color:#999;">{{_('Topics:')}}</span>
-          <span>1</span>
+          <span>{{ board.topic_count }}</span>
           <span style="color:#999;">{{ _('Posts:')}}</span>
-          <span>2</span>
+          <span>{{ board.post_count }}</span>
         </dd>
       </dl>
     </div>
     <div class="col-md-offset-8 col-md-2" style="font-size:12px">
-      {% set last_topic = board.topics.first() %}
-      {% if last_topic  %}
+      {% set newest_topic = board.newest_topic %}
+      {% if newest_topic  %}
       <dl style="margin:0;">
-        <dt style="word-wrap: break-word;word-break: normal;">{{ link.topic(last_topic)}}</dt>
-        <dd>{{ _('published by %(author)s',author=link.user(last_topic.author.username)) }}</dd>
-        <dd>{{ last_topic.created_at | timesince }}</dd>
+        <dt style="word-wrap: break-word;word-break: normal;">{{ link.topic(newest_topic)}}</dt>
+        <dd>{{ _('published by %(author)s',author=link.user(newest_topic.author)) }}</dd>
+        <dd>{{ newest_topic.created_at | timesince }}</dd>
       </dl>
       {% else %}
       {{_('No Topic')}}

+ 1 - 2
templates/setting/_macro.html

@@ -17,8 +17,7 @@
             <span>
                 {{ link.user_avatar(g.user,100) }}
             </span>
-            <span id="show-avatar">
-            </span>
+            <span id="show-avatar"></span>
             {{ form.avatar(class="form-control",onchange="loadFile(event)")}}
         </div>
     </div>

+ 1 - 1
templates/tag/_macro.html

@@ -19,7 +19,7 @@
       </a>
     </span>
     {% if g.user.is_authenticated %}
-    {% if tag.id | tag_is_followed %}
+    {% 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>

+ 6 - 9
templates/topic/_list_macro.html

@@ -74,21 +74,18 @@
 
 {% macro body_read(topic) -%}
 <div class="col-md-2  hidden-xs" style="padding:5px 5px 5px 20px;margin:0">
-  {% set rep,read = topic.id,1 %}
-  <span>{{ rep }}</span> /
-  <span style="font-size:12px;color:#999;">
-    {{ read }}
-  </span>
+  <span>{{ topic.reply_count }}</span> /
+  <span style="font-size:12px;color:#999;">{{ topic.read_count }}</span>
 </div>
 {%- endmacro %}
 
 {% macro body_reply(topic) -%}
 <div class="col-md-2 hidden-xs" style="padding:5px;margin:0;">
-  {% set last_reply = topic.replies.first() %}
-  {% if last_reply %}
-  <span style="font-size:12px">{{ link.user(last_reply.author) }}</span>
+  {% set newest_reply = topic.newest_reply %}
+  {% if newest_reply %}
+  <span style="font-size:12px">{{ link.user(newest_reply.author) }}</span>
   <br/>
-  <span style="font-size:12px;color:#999;">{{ last_reply.created_at | timesince }}</span>
+  <span style="font-size:12px;color:#999;">{{ newest_reply.created_at | timesince }}</span>
   {% else %}
   <span style="font-size:12px">{{ link.user(topic.author) }}</span>
   <br/>

+ 0 - 28
templates/topic/item/_macro.html

@@ -1,32 +1,5 @@
 {% import 'base/link.html' as link %}
 
-{% macro votes(topic) -%}
-<div class="votes">
-  {% if topic.vote and topic.vote > 0 -%}
-  {{ vote_a('up',topic.vote) }}
-  {% else %}
-  {{ vote_a('up') }}
-  {%- endif %}
-  {% if topic.vote and topic.vote < 0 %}
-  {{ vote_a('down',topic.vote) }}
-  {% else %}
-  {{ vote_a('down') }}
-  {%- endif %}
-</div>
-{%- endmacro %}
-
-{% macro vote_a(vo,count=None) -%}
-{% if count -%}
-<a id="topic-{{ vo }}-vote" class="vote" href="javascript:void(0)" style="text-decoration:none;">
-  <i class="fa fa-chevron-{{ vo }}">{{ count }}</i>
-</a>
-{% else %}
-<a id="topic-{{ vo }}-vote" class="vote" href="javascript:void(0)" style="text-decoration:none;">
-  <i class="fa fa-chevron-{{ vo }}"></i>
-</a>
-{%- endif %}
-{%- endmacro %}
-
 {% macro tags(topic) -%}
 {% for tag in topic.tags %}
 {{ link.tag(tag) }}
@@ -46,7 +19,6 @@
 {%- endmacro %}
 
 {% macro title(topic) -%}
-{{ votes(topic) }}
 {{ tags(topic) }}
 {{ last_reply(topic) }}
 {%- endmacro %}

+ 2 - 2
templates/topic/panel.html

@@ -1,6 +1,6 @@
 {% if g.user.is_authenticated %}
 <ul class="list-group">
-  {% if topic.id | topic_is_followed %}
+  {% if topic.is_followed() %}
   <button type="button" class="btn btn-info btn-block topic-following active" data-id="{{topic.id}}">取消关注</button>
   {% else %}
   <button type="button" class="btn btn-info btn-block topic-following" data-id="{{topic.id}}">关注问题</button>
@@ -10,7 +10,7 @@
     编辑问题
   </a>
   {%- endif %}
-  {% if topic.id | is_collected %}
+  {% if topic.is_collected() %}
   <span class="btn btn-default btn-block">已收藏</span>
   {% else %}
   <span class="btn btn-default btn-block" data-toggle="modal" data-target="#sidecollect">收藏问题</span>

+ 1 - 1
templates/topic/reply/_macro.html

@@ -1,5 +1,5 @@
 {% macro like(reply) -%}
-{% if reply.id | is_liked %}
+{% if reply.is_liked() %}
 <a href="javascript:void(0);" style="padding:0" class="like-reply btn btn-sm like-active" data-id="{{ reply.id}}" title="取消赞">
   <i class="fa fa-thumbs-up"></i>
   <span class="reply-count">{{ reply.likers.count() }}</span>

+ 2 - 3
templates/topic/reply/itemlist.html

@@ -1,5 +1,4 @@
 {% from 'topic/reply/_macro.html' import like,no_replies %}
-{% set replies = topic.replies.paginate(1,10) %}
 <style>
  .like-active {
      color:#a40004;
@@ -30,8 +29,9 @@
     </ul>
   </div>
   {% if replies.items %}
-  {% set num = 1 %}
+  {% set num = 0 %}
   {% for reply in replies.items %}
+  {% set num = num + 1 %}
   {% set user = reply.author %}
   <div class="panel-body media" id="reply-{{ reply.id }}" style="border-bottom:1px solid #eee;margin:0">
     <div class="media-left">
@@ -58,7 +58,6 @@
       </a>
     </div>
   </div>
-  {% set num = num + 1 %}
   {% endfor %}
   {{ p_footer(replies,'topic.topic',dict(topicId=topic.id))}}
   {% else %}

+ 4 - 4
templates/user/base.html

@@ -9,10 +9,10 @@
     <div  style="background:#fff;border:1px solid #ddd;border-radius:5px">
       <ul class="nav nav-pills nav-stacked" >
         {% set name = user.username %}
-        <li role="presentation" class="topics"><a href="{{ url_for('user.user',username=user.username)}}">{{ _("topics of %(n)s",n = name) }}</a></li>
-        <li role="presentation" class="replies"><a href="{{ url_for('user.reply',username=user.username)}}">{{ _("replies of %(n)s",n = name) }}</a></li>
-        <li role="presentation" class="collects"><a href="{{ url_for('user.collect',username=user.username)}}">{{ _("collects of %(n)s",n = name) }}</a></li>
-        <li role="presentation" class="followers"><a href="{{ url_for('user.follower',username=user.username)}}">{{ _("followers of %(n)s",n = name) }}</a></li>
+        <li role="presentation" {% if request.path.endswith('topics') %}class="active"{% endif %}><a href="{{ url_for('user.topic',username=user.username)}}">{{ _("topics of %(n)s",n = name) }}</a></li>
+        <li role="presentation" {% if request.path.endswith('replies') %}class="active"{% endif %}><a href="{{ url_for('user.reply',username=user.username)}}">{{ _("replies of %(n)s",n = name) }}</a></li>
+        <li role="presentation" {% if request.path.endswith('collects') %}class="active"{% endif %}><a href="{{ url_for('user.collect',username=user.username)}}">{{ _("collects of %(n)s",n = name) }}</a></li>
+        <li role="presentation" {% if request.path.endswith('followers') %}class="active"{% endif %}><a href="{{ url_for('user.follower',username=user.username)}}">{{ _("followers of %(n)s",n = name) }}</a></li>
       </ul>
     </div>
   </div>

+ 4 - 8
templates/user/info.html

@@ -2,19 +2,18 @@
 {% if current_user.is_authenticated and current_user.id != user.id %}
 <div class="list-group-item">
   <span class="text-right" style="display:block">
-    {% if user.id | user_is_followed %}
+    {% if user.is_followed() %}
     <button class="btn btn-sm btn-default user-following active" data-id="{{ user.id}}">取消关注</button>
     {% else %}
-    <button class="btn btn-sm btn-default user-following" data-id="{{ user.id}}">关注他</button>
+    <button class="btn btn-sm btn-default user-following" data-id="{{ user.id }}">关注他</button>
     {% endif %}
-    <button class="btn btn-sm btn-default" id="{{ user.id}}" title="私信" data-toggle="modal" data-target="#send-message"><i class="fa fa-comments" style="font-size:16px;"></i></button>
   </span>
 </div>
 {% endif %}
 {%- endmacro %}
 
 {% macro mine(user) -%}
-{% if g.user.is_authenticated and current_user.id == user.id and not user.is_confirmed %}
+{% if g.user.is_authenticated and user | is_not_confirmed %}
 <div class="alert alert-info" style="padding:6px;font-size:12px;margin-top:10px;">
   <button type="button" class="close" data-dismiss="alert" aria-label="Close">
     <span aria-hidden="true">&times;</span>
@@ -26,10 +25,7 @@
 {%- endmacro %}
 
 {% macro online(user) -%}
-{% set setting = user.setting %}
-{%- set _a = (setting.online_status == 1 and user.username | is_online) %}
-{%- set _b = (setting.online_status == 2 and current_user.is_authenticated and user.username | is_online) %}
-{%- if _a or _b %}
+{% if user | is_online %}
 <span class="online">{{ _('ONLINE') }} </span>
 {%- else %}
 <span class="online">{{ _('OUTLINE') }}</span>

+ 1 - 1
templates/user/replies.html

@@ -30,7 +30,7 @@
   </span>
   <p>{{ reply.content | safe_clean }}</p>
   <span style="font-size:12px;color:#999;">
-    {{_('replied time:')}}{{ reply.publish | timesince }}
+    {{_('replied time:')}}{{ reply.created_at | timesince }}
   </span>
 </div>
 {% else %}