Browse Source

Display unread messages in the navbar

sh4nks 9 years ago
parent
commit
65353d369a

+ 2 - 8
flaskbb/app.py

@@ -132,14 +132,8 @@ def configure_extensions(app):
     def load_user(user_id):
         """Loads the user. Required by the `login` extension."""
 
-        unread_count = db.session.query(db.func.count(Conversation.id)).\
-            filter(Conversation.unread,
-                   Conversation.user_id == user_id).subquery()
-        u = db.session.query(User, unread_count).filter(User.id == user_id).\
-            first()
-
-        if u:
-            user_instance, user_instance.pm_unread = u
+        user_instance = User.query.filter_by(id=user_id).first()
+        if user_instance:
             return user_instance
         else:
             return None

+ 16 - 6
flaskbb/themes/aurora/src/flaskbb.scss

@@ -76,9 +76,10 @@ body {
 }
 
 
-.index-view .row,
-.forum-view .row {
-    margin: 0;
+.index-view, .forum-view {
+    .forum-row, .category-row {
+        margin: 0;
+    }
 }
 
 // default values for the panels
@@ -119,9 +120,9 @@ body {
         .topic-info {
             position: relative;
             .topic-status {
+                float: left;
                 font-size: 1em;
                 padding-right: 0.75em;
-                float: left;
             }
             .topic-name {
                 font-weight: bold;
@@ -143,8 +144,8 @@ body {
     .category-row {
         .forum-info {
             position: relative;
-            float: left;
             .forum-status {
+                float: left;
                 font-size: 2em;
                 padding-right: 0.5em;
             }
@@ -160,7 +161,6 @@ body {
                 font-weight: bold;
             }
         }
-
     }
 }
 
@@ -188,3 +188,13 @@ p.flaskbb-stats {
 .inline-form .btn-link {
     padding: 0px;
 }
+
+.dropdown-messages {
+    min-width: 15em;
+    .message-subject {
+        font-style: italic;
+    }
+    .author-name {
+        font-weight: bold;
+    }
+}

+ 12 - 6
flaskbb/themes/aurora/static/css/flaskbb.css

@@ -53,8 +53,7 @@ body {
   border: 1px solid #cad7e1;
   border-radius: 0; }
 
-.index-view .row,
-.forum-view .row {
+.index-view .forum-row, .index-view .category-row, .forum-view .forum-row, .forum-view .category-row {
   margin: 0; }
 
 .category-panel, .forum-panel, .topic-panel, .widget-panel {
@@ -81,9 +80,9 @@ body {
 .forum-body .forum-row .topic-info {
   position: relative; }
   .forum-body .forum-row .topic-info .topic-status {
+    float: left;
     font-size: 1em;
-    padding-right: 0.75em;
-    float: left; }
+    padding-right: 0.75em; }
   .forum-body .forum-row .topic-info .topic-name {
     font-weight: bold; }
   .forum-body .forum-row .topic-info .topic-pages {
@@ -92,9 +91,9 @@ body {
 .category-body .category-meta .forum-name, .category-body .category-meta .forum-stats, .category-body .category-meta .forum-last-post {
   font-weight: bold; }
 .category-body .category-row .forum-info {
-  position: relative;
-  float: left; }
+  position: relative; }
   .category-body .category-row .forum-info .forum-status {
+    float: left;
     font-size: 2em;
     padding-right: 0.5em; }
   .category-body .category-row .forum-info .forum-name {
@@ -122,4 +121,11 @@ p.flaskbb-stats {
 .inline-form .btn-link {
   padding: 0px; }
 
+.dropdown-messages {
+  min-width: 15em; }
+  .dropdown-messages .message-subject {
+    font-style: italic; }
+  .dropdown-messages .author-name {
+    font-weight: bold; }
+
 /*# sourceMappingURL=flaskbb.css.map */

+ 18 - 8
flaskbb/themes/aurora/templates/navigation.html

@@ -1,7 +1,7 @@
-<div class="navbar navbar-default forum-navigation">
+<div class="navbar navbar-default forum-navigation" role="navigation">
     <!-- Brand and toggle get grouped for better mobile display -->
     <div class="navbar-header">
-      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
+      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse" aria-expanded="false">
         <span class="sr-only">Toggle navigation</span>
         <span class="icon-bar"></span>
         <span class="icon-bar"></span>
@@ -29,17 +29,27 @@
             <!-- Inbox -->
             <li class="dropdown {{ is_active('message.inbox') }}">
                 <a href="#" class="dropdown-toggle" data-toggle="dropdown">
-                    <span class="fa fa-envelope"></span> Inbox <span class="label label-info">{{ current_user.pm_unread }}</span>
+                    <span class="fa fa-envelope"></span> Inbox <span class="label label-info">{{ current_user.unread_count }}</span>
                 </a>
-                <ul class="dropdown-menu">
-                    <li><a href="{{ url_for('message.inbox') }}"><span class="fa fa-envelope"></span> {% trans %}Private Messages{% endtrans %}</a></li>
+                <ul class="dropdown-menu dropdown-messages">
+                    {% for message in current_user.unread_messages %}
+                    <li>
+                        <a href="{{ url_for('message.view_conversation', conversation_id=message.id) }}">
+                            <div>
+                                <span class="author-name">{{ message.from_user.username }}</span> <span class="pull-right text-muted">{{ message.last_message.date_created|time_since }}</span>
+                                <div class="message-subject">{{ message.subject }}</div>
+                            </div>
+                        </a>
+                    </li>
+                    {% else %}
+                    <li><a href="#">No unread messages.</a></li>
+                    <li class="divider"></li>
+                    {% endfor %}
+                    <li><a href="{{ url_for('message.inbox') }}"><span class="fa fa-envelope"></span> {% trans %}Inbox{% endtrans %}</a></li>
                     <li><a href="{{ url_for('message.new_conversation') }}"><span class="fa fa-pencil"></span> {% trans %}New Message{% endtrans %}</a></li>
                 </ul>
             </li>
 
-            <!-- Topic Tracker -->
-            {{ topnav(endpoint='forum.topictracker', name=_('Topic Tracker'), icon='fa fa-book') }}
-
             <!-- User Menu -->
             <li>
                 <div class="btn-group navbar-btn user-btn">

+ 44 - 19
flaskbb/user/models.py

@@ -123,27 +123,37 @@ class User(db.Model, UserMixin, CRUDMixin):
     # Properties
     @property
     def last_post(self):
-        """Returns the latest post from the user"""
+        """Returns the latest post from the user."""
 
         return Post.query.filter(Post.user_id == self.id).\
             order_by(Post.date_created.desc()).first()
 
     @property
     def url(self):
-        """Returns the url for the user"""
+        """Returns the url for the user."""
         return url_for("user.profile", username=self.username)
 
     @property
     def permissions(self):
-        """Returns the permissions for the user"""
+        """Returns the permissions for the user."""
         return self.get_permissions()
 
     @property
     def groups(self):
-        """Returns user groups"""
+        """Returns the user groups."""
         return self.get_groups()
 
     @property
+    def unread_messages(self):
+        """Returns the unread messages for the user."""
+        return self.get_unread_messages()
+
+    @property
+    def unread_count(self):
+        """Returns the unread message count for the user."""
+        return len(self.unread_messages)
+
+    @property
     def days_registered(self):
         """Returns the amount of days the user is registered."""
         days_registered = (datetime.utcnow() - self.date_joined).days
@@ -153,17 +163,17 @@ class User(db.Model, UserMixin, CRUDMixin):
 
     @property
     def topic_count(self):
-        """Returns the thread count"""
+        """Returns the thread count."""
         return Topic.query.filter(Topic.user_id == self.id).count()
 
     @property
     def posts_per_day(self):
-        """Returns the posts per day count"""
+        """Returns the posts per day count."""
         return round((float(self.post_count) / float(self.days_registered)), 1)
 
     @property
     def topics_per_day(self):
-        """Returns the topics per day count"""
+        """Returns the topics per day count."""
         return round((float(self.topic_count) / float(self.days_registered)), 1)
 
     # Methods
@@ -174,11 +184,11 @@ class User(db.Model, UserMixin, CRUDMixin):
         return "<{} {}>".format(self.__class__.__name__, self.username)
 
     def _get_password(self):
-        """Returns the hashed password"""
+        """Returns the hashed password."""
         return self._password
 
     def _set_password(self, password):
-        """Generates a password hash for the provided password"""
+        """Generates a password hash for the provided password."""
         self._password = generate_password_hash(password)
 
     # Hide password encryption by exposing password field only.
@@ -187,7 +197,7 @@ class User(db.Model, UserMixin, CRUDMixin):
                                               _set_password))
 
     def check_password(self, password):
-        """Check passwords. If passwords match it returns true, else false"""
+        """Check passwords. If passwords match it returns true, else false."""
 
         if self.password is None:
             return False
@@ -195,7 +205,7 @@ class User(db.Model, UserMixin, CRUDMixin):
 
     @classmethod
     def authenticate(cls, login, password):
-        """A classmethod for authenticating users
+        """A classmethod for authenticating users.
         It returns true if the user exists and has entered a correct password
 
         :param login: This can be either a username or a email address.
@@ -238,7 +248,7 @@ class User(db.Model, UserMixin, CRUDMixin):
 
     def verify_reset_token(self, token):
         """Verifies a reset token. It returns three boolean values based on
-        the state of the token (expired, invalid, data)
+        the state of the token (expired, invalid, data).
 
         :param token: The reset token that should be checked.
         """
@@ -272,7 +282,7 @@ class User(db.Model, UserMixin, CRUDMixin):
             paginate(page, flaskbb_config['TOPICS_PER_PAGE'], False)
 
     def track_topic(self, topic):
-        """Tracks the specified topic
+        """Tracks the specified topic.
 
         :param topic: The topic which should be added to the topic tracker.
         """
@@ -282,7 +292,7 @@ class User(db.Model, UserMixin, CRUDMixin):
             return self
 
     def untrack_topic(self, topic):
-        """Untracks the specified topic
+        """Untracks the specified topic.
 
         :param topic: The topic which should be removed from the
                       topic tracker.
@@ -293,7 +303,7 @@ class User(db.Model, UserMixin, CRUDMixin):
             return self
 
     def is_tracking_topic(self, topic):
-        """Checks if the user is already tracking this topic
+        """Checks if the user is already tracking this topic.
 
         :param topic: The topic which should be checked.
         """
@@ -322,7 +332,7 @@ class User(db.Model, UserMixin, CRUDMixin):
             return self
 
     def in_group(self, group):
-        """Returns True if the user is in the specified group
+        """Returns True if the user is in the specified group.
 
         :param group: The group which should be checked.
         """
@@ -366,8 +376,23 @@ class User(db.Model, UserMixin, CRUDMixin):
                     perms[c.name] = getattr(group, c.name)
         return perms
 
-    def invalidate_cache(self):
-        """Invalidates this objects cached metadata."""
+    @cache.memoize(timeout=max_integer)
+    def get_unread_messages(self):
+        """Returns all unread messages for the user."""
+        unread_messages = Conversation.query.\
+            filter(Conversation.unread, Conversation.user_id == self.id).all()
+        return unread_messages
+
+    def invalidate_cache(self, permissions_only=True):
+        """Invalidates this objects cached metadata.
+
+        :param permissions_only: If set to ``True`` it will only invalidate
+                                 the permissions cache. Otherwise it will
+                                 also invalidate the user's unread message
+                                 cache.
+        """
+        if not permissions_only:
+            cache.delete_memoized(self.get_unread_messages, self)
 
         cache.delete_memoized(self.get_permissions, self)
         cache.delete_memoized(self.get_groups, self)
@@ -463,7 +488,7 @@ class Guest(AnonymousUserMixin):
 
     @cache.memoize(timeout=max_integer)
     def get_permissions(self, exclude=None):
-        """Returns a dictionary with all permissions the user has"""
+        """Returns a dictionary with all permissions the user has."""
         exclude = exclude or []
         exclude.extend(['id', 'name', 'description'])