Browse Source

Merge pull request #27 from caspervg/develop

Implements Flask-WhooshAlchemy search functionality
sh4nks 11 years ago
parent
commit
df9a978a3d

+ 2 - 0
.gitignore

@@ -18,6 +18,7 @@
 *.log
 *.log
 *.sql
 *.sql
 *.sqlite
 *.sqlite
+whoosh_index
 
 
 # OS generated files #
 # OS generated files #
 ######################
 ######################
@@ -40,6 +41,7 @@ __pycache__
 *.sublime-workspace
 *.sublime-workspace
 *.sublime-project
 *.sublime-project
 *.egg-info
 *.egg-info
+*.iml
 flaskbb/configs/production.py
 flaskbb/configs/production.py
 flaskbb/configs/development.py
 flaskbb/configs/development.py
 
 

+ 11 - 4
flaskbb/admin/views.py

@@ -16,6 +16,7 @@ from flask import (Blueprint, current_app, request, redirect, url_for, flash,
 from flask.ext.login import current_user
 from flask.ext.login import current_user
 
 
 from flaskbb import __version__ as flaskbb_version
 from flaskbb import __version__ as flaskbb_version
+from flaskbb.forum.forms import UserSearchForm
 from flaskbb.utils.helpers import render_template
 from flaskbb.utils.helpers import render_template
 from flaskbb.utils.decorators import admin_required
 from flaskbb.utils.decorators import admin_required
 from flaskbb.extensions import db
 from flaskbb.extensions import db
@@ -45,15 +46,21 @@ def overview():
                            post_count=post_count)
                            post_count=post_count)
 
 
 
 
-@admin.route("/users")
+@admin.route("/users", methods=['GET', 'POST'])
 @admin_required
 @admin_required
 def users():
 def users():
     page = request.args.get("page", 1, type=int)
     page = request.args.get("page", 1, type=int)
+    search_form = UserSearchForm()
+
+    if search_form.validate():
+        users = search_form.get_results().paginate(page, current_app.config['USERS_PER_PAGE'], False)
+        return render_template("admin/users.html", users=users, search_form=search_form)
+    else:
+        users = User.query. \
+            paginate(page, current_app.config['USERS_PER_PAGE'], False)
+        return render_template("admin/users.html", users=users, search_form=search_form)
 
 
-    users = User.query.\
-        paginate(page, current_app.config['USERS_PER_PAGE'], False)
 
 
-    return render_template("admin/users.html", users=users)
 
 
 
 
 @admin.route("/groups")
 @admin.route("/groups")

+ 10 - 0
flaskbb/app.py

@@ -24,9 +24,11 @@ from flaskbb.auth.views import auth
 from flaskbb.admin.views import admin
 from flaskbb.admin.views import admin
 # Import the forum blueprint
 # Import the forum blueprint
 from flaskbb.forum.views import forum
 from flaskbb.forum.views import forum
+from flaskbb.forum.models import Post, Topic, Category, Forum
 # extenesions
 # extenesions
 from flaskbb.extensions import db, login_manager, mail, cache, redis, \
 from flaskbb.extensions import db, login_manager, mail, cache, redis, \
     debugtoolbar, migrate, themes
     debugtoolbar, migrate, themes
+from flask.ext.whooshalchemy import whoosh_index
 # various helpers
 # various helpers
 from flaskbb.utils.helpers import format_date, time_since, crop_title, \
 from flaskbb.utils.helpers import format_date, time_since, crop_title, \
     is_online, render_markup, mark_online, forum_is_unread, topic_is_unread, \
     is_online, render_markup, mark_online, forum_is_unread, topic_is_unread, \
@@ -94,6 +96,14 @@ def configure_extensions(app):
     # Flask-And-Redis
     # Flask-And-Redis
     redis.init_app(app)
     redis.init_app(app)
 
 
+    # Flask-WhooshAlchemy
+    with app.app_context():
+        whoosh_index(app, Post)
+        whoosh_index(app, Topic)
+        whoosh_index(app, Forum)
+        whoosh_index(app, Category)
+        whoosh_index(app, User)
+
     # Flask-Login
     # Flask-Login
     login_manager.login_view = app.config["LOGIN_VIEW"]
     login_manager.login_view = app.config["LOGIN_VIEW"]
     login_manager.refresh_view = app.config["REAUTH_VIEW"]
     login_manager.refresh_view = app.config["REAUTH_VIEW"]

+ 39 - 3
flaskbb/forum/forms.py

@@ -9,10 +9,11 @@
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
 from flask.ext.wtf import Form
 from flask.ext.wtf import Form
-from wtforms import TextAreaField, TextField
-from wtforms.validators import Required
+from wtforms import TextAreaField, TextField, SelectMultipleField
+from wtforms.validators import Required, Optional, Length
 
 
-from flaskbb.forum.models import Topic, Post, Report
+from flaskbb.forum.models import Topic, Post, Report, Forum, Category
+from flaskbb.user.models import User
 
 
 
 
 class QuickreplyForm(Form):
 class QuickreplyForm(Form):
@@ -54,3 +55,38 @@ class ReportForm(Form):
     def save(self, user, post):
     def save(self, user, post):
         report = Report(**self.data)
         report = Report(**self.data)
         return report.save(user, post)
         return report.save(user, post)
+
+
+class UserSearchForm(Form):
+    search_query = TextField("Search", validators=[Optional(), Length(min=3, max=50)])
+
+    def get_results(self):
+        query = self.search_query.data
+        return User.query.whoosh_search(query)
+
+
+class SearchPageForm(Form):
+    search_query = TextField("Criteria", validators=[Required(), Length(min=3, max=50)])
+    search_types = SelectMultipleField("Content", validators=[Required()], choices=[
+        ('post', 'Post'), ('topic', 'Topic'), ('forum', 'Forum'), ('user', 'Users')])
+
+    def get_results(self):
+        # Because the DB is not yet initialized when this form is loaded, the query objects cannot be instantiated
+        # in the class itself
+        search_actions = {
+            'post': Post.query.whoosh_search,
+            'topic': Topic.query.whoosh_search,
+            'forum': Forum.query.whoosh_search,
+            'user': User.query.whoosh_search
+        }
+
+        query = self.search_query.data
+        types = self.search_types.data
+        results = {}
+
+        for type in search_actions.keys():
+            if type in types:
+                results[type] = search_actions[type](query)
+
+        return results
+

+ 4 - 0
flaskbb/forum/models.py

@@ -134,6 +134,7 @@ class Report(db.Model):
 
 
 class Post(db.Model):
 class Post(db.Model):
     __tablename__ = "posts"
     __tablename__ = "posts"
+    __searchable__ = ['content', 'username']
 
 
     id = db.Column(db.Integer, primary_key=True)
     id = db.Column(db.Integer, primary_key=True)
     topic_id = db.Column(db.Integer,
     topic_id = db.Column(db.Integer,
@@ -234,6 +235,7 @@ class Post(db.Model):
 
 
 class Topic(db.Model):
 class Topic(db.Model):
     __tablename__ = "topics"
     __tablename__ = "topics"
+    __searchable__ = ['title', 'username']
 
 
     query_class = TopicQuery
     query_class = TopicQuery
 
 
@@ -526,6 +528,7 @@ class Topic(db.Model):
 
 
 class Forum(db.Model):
 class Forum(db.Model):
     __tablename__ = "forums"
     __tablename__ = "forums"
+    __searchable__ = ['title', 'description']
 
 
     id = db.Column(db.Integer, primary_key=True)
     id = db.Column(db.Integer, primary_key=True)
     category_id = db.Column(db.Integer, db.ForeignKey("categories.id"),
     category_id = db.Column(db.Integer, db.ForeignKey("categories.id"),
@@ -640,6 +643,7 @@ class Forum(db.Model):
 
 
 class Category(db.Model):
 class Category(db.Model):
     __tablename__ = "categories"
     __tablename__ = "categories"
+    __searchable__ = ['title', 'description']
 
 
     id = db.Column(db.Integer, primary_key=True)
     id = db.Column(db.Integer, primary_key=True)
     title = db.Column(db.String(63), nullable=False)
     title = db.Column(db.String(63), nullable=False)

+ 21 - 6
flaskbb/forum/views.py

@@ -25,7 +25,7 @@ from flaskbb.utils.permissions import (can_post_reply, can_post_topic,
 from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
 from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
                                   TopicsRead)
                                   TopicsRead)
 from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
 from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
-                                 ReportForm)
+                                 ReportForm, UserSearchForm, SearchPageForm)
 from flaskbb.utils.helpers import get_forums
 from flaskbb.utils.helpers import get_forums
 from flaskbb.user.models import User
 from flaskbb.user.models import User
 
 
@@ -460,15 +460,19 @@ def who_is_online():
                            online_users=online_users)
                            online_users=online_users)
 
 
 
 
-@forum.route("/memberlist")
+@forum.route("/memberlist", methods=['GET', 'POST'])
 def memberlist():
 def memberlist():
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 
-    users = User.query.order_by(User.id).\
-        paginate(page, current_app.config['POSTS_PER_PAGE'], False)
+    search_form = UserSearchForm()
 
 
-    return render_template("forum/memberlist.html",
-                           users=users)
+    if search_form.validate():
+        users = search_form.get_results().paginate(page, current_app.config['USERS_PER_PAGE'], False)
+        return render_template("forum/memberlist.html", users=users, search_form=search_form)
+    else:
+        users = User.query. \
+            paginate(page, current_app.config['USERS_PER_PAGE'], False)
+        return render_template("forum/memberlist.html", users=users, search_form=search_form)
 
 
 
 
 @forum.route("/topictracker")
 @forum.route("/topictracker")
@@ -501,3 +505,14 @@ def untrack_topic(topic_id, slug=None):
     current_user.untrack_topic(topic)
     current_user.untrack_topic(topic)
     current_user.save()
     current_user.save()
     return redirect(topic.url)
     return redirect(topic.url)
+
+
+@forum.route("/search", methods=['GET', 'POST'])
+def search():
+    form = SearchPageForm()
+
+    if form.validate_on_submit():
+        result = form.get_results()
+        return render_template('forum/search_result.html', form=form, result=result)
+
+    return render_template('forum/search_form.html', form=form)

+ 29 - 11
flaskbb/templates/admin/users.html

@@ -1,12 +1,24 @@
 {% extends theme("admin/admin_layout.html") %}
 {% extends theme("admin/admin_layout.html") %}
 {% block admin_content %}
 {% block admin_content %}
 {% from theme('macros.html') import render_pagination %}
 {% from theme('macros.html') import render_pagination %}
+{% from theme("macros.html") import render_field, group_field %}
 
 
 <legend>Manage Users | <a href="{{ url_for('admin.add_user') }}">Add User</a></legend>
 <legend>Manage Users | <a href="{{ url_for('admin.add_user') }}">Add User</a></legend>
 
 
 <div class="pull-left" style="padding-bottom: 10px">
 <div class="pull-left" style="padding-bottom: 10px">
     {{ render_pagination(users, url_for('admin.users')) }}
     {{ render_pagination(users, url_for('admin.users')) }}
 </div>
 </div>
+<div class="pull-right" style="padding-bottom: 10px">
+    <form role="form" method="post">
+        <div class="input-group">
+            {{ search_form.hidden_tag() }}
+            {{ group_field(search_form.search_query) }}
+      <span class="input-group-btn">
+        <button class="btn btn-primary" type="button">Search</button>
+      </span>
+        </div>
+    </form>
+</div>
 
 
 <table class="table table-bordered">
 <table class="table table-bordered">
     <thead>
     <thead>
@@ -21,17 +33,23 @@
     </thead>
     </thead>
     <tbody>
     <tbody>
         {% for user in users.items %}
         {% for user in users.items %}
-        <tr>
-            <td>{{ user.id }}</td>
-            <td><a href="{{ url_for('user.profile', username=user.username) }}">{{ user.username }}</a></td>
-            <td>{{ user.post_count }}</td>
-            <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
-            <td>{{ user.primary_group.name }}</td>
-            <td>
-                <a href="{{ url_for('admin.edit_user', user_id = user.id) }}">Edit</a> |
-                <a href="{{ url_for('admin.delete_user', user_id = user.id) }}">Delete</a>
-            </td>
-        </tr>
+            <tr>
+                <td>{{ user.id }}</td>
+                <td><a href="{{ url_for('user.profile', username=user.username) }}">{{ user.username }}</a></td>
+                <td>{{ user.post_count }}</td>
+                <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
+                <td>{{ user.primary_group.name }}</td>
+                <td>
+                    <a href="{{ url_for('admin.edit_user', user_id = user.id) }}">Edit</a> |
+                    <a href="{{ url_for('admin.delete_user', user_id = user.id) }}">Delete</a>
+                </td>
+            </tr>
+        {% else %}
+            <tr>
+                <td colspan="6">
+                    No users found matching your search query
+                </td>
+            </tr>
         {% endfor %}
         {% endfor %}
     </tbody>
     </tbody>
 </table>
 </table>

+ 14 - 11
flaskbb/templates/forum/memberlist.html

@@ -2,24 +2,27 @@
 
 
 {% extends theme("layout.html") %}
 {% extends theme("layout.html") %}
 {% block content %}
 {% block content %}
-{% from theme('macros.html') import render_pagination %}
+{% from theme('macros.html') import render_pagination, group_field %}
 
 
 <ul class="breadcrumb">
 <ul class="breadcrumb">
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
     <li class="active">Memberlist</li>
     <li class="active">Memberlist</li>
 </ul>
 </ul>
 
 
-  <div class="pull-left" style="padding-bottom: 10px">
+<div class="pull-left" style="padding-bottom: 10px">
     {{ render_pagination(users, url_for('forum.memberlist')) }}
     {{ render_pagination(users, url_for('forum.memberlist')) }}
-  </div><!-- /.col-pull-left -->
-  <div class="pull-right" style="padding-bottom: 10px">
-    <div class="input-group">
-      <input type="text" class="form-control" placeholder="Search">
-      <span class="input-group-btn">
-        <button class="btn btn-default" type="button">Search</button>
-      </span>
-    </div><!-- /input-group -->
-</div><!-- /.row -->
+</div><!-- /.col-pull-left -->
+<div class="pull-right" style="padding-bottom: 10px">
+  <form role="form" method="post">
+      <div class="input-group">
+          {{ search_form.hidden_tag() }}
+          {{ group_field(search_form.search_query) }}
+          <span class="input-group-btn">
+            <button class="btn btn-primary" type="button">Search</button>
+          </span>
+      </div>
+  </form>
+</div>
 
 
 <table class="table table-bordered">
 <table class="table table-bordered">
     <thead>
     <thead>

+ 26 - 0
flaskbb/templates/forum/search_form.html

@@ -0,0 +1,26 @@
+{% set page_title = "Search" %}
+
+{% extends theme("layout.html") %}
+{% from theme("macros.html") import horizontal_field %}
+{% block content %}
+
+    <ul class="breadcrumb">
+        <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
+        <li class="active">Search</li>
+    </ul>
+
+    <form class="form-horizontal" role="form" method="post">
+        <h2>Search</h2>
+        <hr>
+        {{ form.hidden_tag() }}
+        {{ horizontal_field(form.search_types)}}
+        {{ horizontal_field(form.search_query)}}
+
+        <div class="form-group">
+            <div class="col-sm-offset-3 col-sm-9">
+                <button type="submit" class="btn btn-success">Search</button>
+            </div>
+        </div>
+    </form>
+
+{% endblock %}

+ 269 - 0
flaskbb/templates/forum/search_result.html

@@ -0,0 +1,269 @@
+{% set page_title = "Search" %}
+
+{% extends theme("layout.html") %}
+{% block content %}
+    {% from theme('macros.html') import render_pagination, group_field, topic_pages %}
+
+    <ul class="breadcrumb">
+        <li><a href="{{ url_for('forum.index') }}">Forum</a></li>
+        <li class="active">Search</li>
+    </ul>
+
+    {% if result['post'] %}
+        <h3>Posts</h3>
+
+        <table class="table table-bordered">
+            <tbody>
+            {% for post in result['post'].all() %}
+            <tr>
+                <td>
+                    <table class="table table-borderless">
+                        <tr>
+                            {% if post.user_id %}
+                                {% if post.user.avatar %}
+                                    <td width="1">
+                                        <img src="{{ post.user.avatar }}" alt="Avatar" height="100" width="100">
+                                    </td>
+                                {% endif %}
+                                <td>
+                                    <a href="{{ post.user.url }}">
+                                        <span style="font-weight:bold">{{ post.user.username }}</span> <!-- TODO: Implement userstyles -->
+                                    </a>
+                                    {%- if post.user|is_online %}
+                                        <span class="label label-success">Online</span>
+                                    {%- else %}
+                                        <span class="label label-default">Offline</span>
+                                    {%- endif %}
+                                    <div class="profile primary-group">
+                                        {{ post.user.primary_group.name }}
+                                    </div>
+                                </td>
+
+                                <td class="pull-right">
+                                    Posts: {{ post.user.post_count }}<br />
+                                    Registered since: {{ post.user.date_joined|format_date('%b %d %Y') }}<br />
+                                </td>
+                            {% else %}
+                                <td>
+                                    <strong>{{ post.username }}</strong>
+                                    <br />
+                                    Guest
+                                </td>
+                            {% endif %}
+                        </tr>
+                    </table>
+                </td>
+            </tr>
+
+            <tr>
+                <td>
+                    <div class="post_body" id="pid{{ post.id }}">
+                        {% autoescape false %}
+                        {{ post.content|markup }}
+                        {% endautoescape %}
+                    </div>
+                </td>
+            </tr>
+            {% else %}
+            <tr>
+                <td>No posts found matching your search criteria</td>
+            </tr>
+            {% endfor %}
+            </tbody>
+        </table>
+    {% endif %}
+
+    {% if result['user'] %}
+        <h3>Users</h3>
+
+        <table class="table table-bordered">
+            <thead>
+            <tr>
+                <th>#</th>
+                <th>Username</th>
+                <th>Posts</th>
+                <th>Date registered</th>
+                <th>Group</th>
+            </tr>
+            </thead>
+            <tbody>
+            {% for user in result['user'].all() %}
+                <tr>
+                    <td>{{ user.id }}</td>
+                    <td><a href="{{ user.url }}">{{ user.username }}</a></td>
+                    <td>{{ user.post_count }}</td>
+                    <td>{{ user.date_joined|format_date('%b %d %Y') }}</td>
+                    <td>{{ user.primary_group.name }}</td>
+                </tr>
+            {% else %}
+                <tr>
+                    <td colspan="5">No users found matching your search criteria</td>
+                </tr>
+            {% endfor %}
+            </tbody>
+        </table>
+    {% endif %}
+
+    {% if result['topic'] %}
+        <h3>Topics</h3>
+
+        <table class="table table-bordered">
+            <thead>
+            <tr>
+                <th colspan="2">Topic</th>
+
+                <th>Posts</th>
+
+                <th>Views</th>
+
+                <th>Last Post</th>
+            </tr>
+            </thead>
+
+            <tbody>
+            {% for topic in result['topic'].all() %}
+                <tr>
+                    <td width="4%">
+                        {% if topic.locked %}
+                            <span class="fa fa-locked" style="font-size: 2em"></span>
+                        {% else %}
+                            <span class="fa fa-comment-o" style="font-size: 2em"></span>
+                        {% endif %}
+
+                    </td>
+                    <td>
+                        <div>
+                            <a href="{{ topic.url }}">{{ topic.title }}</a>
+                            <!-- Topic Pagination -->
+                            {{ topic_pages(topic, config["POSTS_PER_PAGE"]) }}
+                            <br />
+                            {% if topic.user_id %}
+                                <small>by <a href="{{ topic.user.url }}">{{ topic.user.username }}</a></small>
+                            {% else %}
+                                <small>by {{ topic.username }}</small>
+                            {% endif %}
+                        </div>
+                    </td>
+                    <td>
+                        {{ topic.post_count }}
+                    </td>
+                    <td>
+                        {{ topic.views }}
+                    </td>
+                    <td>
+                        <a href="{{ topic.last_post.url }}">{{ topic.last_post.date_created|time_since }}</a><br />
+
+                        {% if topic.last_post.user_id %}
+                            <small>by <a href="{{ topic.last_post.user.url }}">{{ topic.last_post.user.username }}</a></small>
+                        {% else %}
+                            <small>{{ topic.last_post.username }}</small>
+                        {% endif %}
+                    </td>
+                </tr>
+            {% else %}
+                <tr>
+                    <td colspan="5">No topics found matching your search criteria</td>
+                </tr>
+            {% endfor %}
+            </tbody>
+        </table>
+    {% endif %}
+
+    {% if result['forum'] %}
+        <h3>Forums</h3>
+
+        <table class="table table-bordered">
+            <thead class="categoryhead">
+            <tr>
+                <th colspan="2"><strong>Forum</strong></th>
+                <th width="85" align="center" style="white-space: nowrap"><strong>Topics</strong></th>
+                <th width="85" align="center" style="white-space: nowrap"><strong>Posts</strong></th>
+                <th width="200" align="center" style="white-space: nowrap"><strong>Last Post</strong></th>
+            </tr>
+            </thead>
+            <tbody class="forumbody">
+            {% for forum in result['forum'].all() %}
+            <tr>
+                <td align="center" valign="center" width="4%">
+
+                    {% if forum.external %}
+                    <span class="fa fa-external-link" style="font-size: 2em"></span>
+                </td>
+
+                <td valign="top">
+                    <strong><a href="{{ forum.external }}">{{ forum.title }}</a></strong>
+
+                    <div class="forum-description">
+                        {% autoescape false %}
+                        {{ forum.description|markup }}
+                        {% endautoescape %}
+                    </div>
+                </td>
+
+                <td valign="top" align="center" style="white-space: nowrap">-</td>
+                <td valign="top" align="center" style="white-space: nowrap">-</td>
+                <td valign="top" align="right" style="white-space: nowrap">-</td>
+                <!-- End external -->
+                {% else %}
+
+                {% if forum.locked %}
+                    <span class="fa fa-lock" style="font-size: 2em"></span>
+                {% else %}
+                    <span class="fa fa-comments-o" style="font-size: 2em"></span>
+                {% endif %}
+
+                </td>
+
+                <td valign="top">
+                    <strong><a href="{{ forum.url }}">{{ forum.title }}</a></strong>
+
+                    <div class="forum-description">
+                        {% autoescape false %}
+                        {{ forum.description|markup }}
+                        {% endautoescape %}
+                        {% if forum.show_moderators %}
+                            <div class="forum-moderators">
+                                Moderators:
+                                {% for moderator in forum.moderators %}
+                                    <a href="{{ url_for('user.profile', username=moderator.username) }}">{{ moderator.username }}</a>{% if not loop.last %}, {% endif %}
+                                {% endfor %}
+                            </div>
+                        {% endif %}
+                    </div>
+                </td>
+
+                <td valign="top" align="center" style="white-space: nowrap">{{ forum.topic_count }}</td>
+                <td valign="top" align="center" style="white-space: nowrap">{{ forum.post_count }}</td>
+
+                <td valign="top" align="right" style="white-space: nowrap">
+                    {% if forum.last_post_id %}
+                        <a href="{{ forum.last_post.url }}" title="{{ forum.last_post.topic.title }}">
+                            <strong>{{ forum.last_post.topic.title|crop_title }}</strong>
+                        </a>
+                        <br />
+                        {{ forum.last_post.date_created|time_since }}<br />
+
+                        {% if forum.last_post.user_id %}
+                            by <a href="{{ url_for('user.profile', username=forum.last_post.user.username) }}">{{ forum.last_post.user.username }}</a>
+                        {% else %}
+                            {{ forum.last_post.username }}
+                        {% endif %}
+
+                    {% else %}
+                        No posts
+                    {% endif %}
+                    {% endif %}
+                </td>
+            </tr>
+            {% else %}
+            <tr>
+                <td colspan="5">
+                    No forums found matching your search criteria
+                </td>
+            </tr>
+            {% endfor %}
+            </tbody>
+        </table>
+    {% endif %}
+
+{% endblock %}

+ 1 - 5
flaskbb/templates/layout.html

@@ -46,6 +46,7 @@
                             {# active_forum_nav is set in {forum, category, topic}.html and new_{topic, post}.html #}
                             {# active_forum_nav is set in {forum, category, topic}.html and new_{topic, post}.html #}
                             {{ topnav(endpoint='forum.index', name='Forum', icon='fa fa-comment', active=active_forum_nav) }}
                             {{ topnav(endpoint='forum.index', name='Forum', icon='fa fa-comment', active=active_forum_nav) }}
                             {{ topnav(endpoint='forum.memberlist', name='Memberlist', icon='fa fa-user') }}
                             {{ topnav(endpoint='forum.memberlist', name='Memberlist', icon='fa fa-user') }}
+                            {{ topnav(endpoint='forum.search', name='Search', icon='fa fa-search') }}
                         </ul>
                         </ul>
 
 
                     {% if current_user and current_user.is_authenticated() %}
                     {% if current_user and current_user.is_authenticated() %}
@@ -93,11 +94,6 @@
                             </ul>
                             </ul>
                         </div>
                         </div>
                     {% endif %}
                     {% endif %}
-                        <form class="navbar-form navbar-right" role="search">
-                            <div class="form-group">
-                                <input type="text" class="form-control" placeholder="Search">
-                            </div>
-                        </form>
                     </div><!-- nav-collapse -->
                     </div><!-- nav-collapse -->
                 </div><!-- container -->
                 </div><!-- container -->
             </nav> <!-- navbar navbar-inverse -->
             </nav> <!-- navbar navbar-inverse -->

+ 23 - 0
flaskbb/templates/macros.html

@@ -102,6 +102,29 @@
 {%- endmacro -%}
 {%- endmacro -%}
 
 
 
 
+{%- macro group_field(field, label_text='', label_class='') -%}
+    <div class="form-group {%- if field.errors %} has-error{%- endif %}" style="margin-bottom: 0px;">
+        {{field.label(class="sr-only")}}
+
+        {%- if kwargs['required'] or field.flags.required -%}
+            {% if label_text %}
+                {{field(class='form-control form-grouped', placeholder=label_text, required="required", **kwargs)}}
+            {% else %}
+                {{field(class='form-control form-grouped', placeholder=field.label.text, required="required", **kwargs)}}
+            {% endif %}
+        {%- else -%}
+            {% if label_text %}
+                {{field(class='form-control form-grouped', placeholder=label_text, **kwargs)}}
+            {% else %}
+                {{field(class='form-control form-grouped', placeholder=field.label.text, **kwargs)}}
+            {% endif %}
+        {%- endif -%}
+        {{ field_description(field) }}
+        {{ field_errors(field) }}
+    </div>
+{%- endmacro -%}
+
+
 {%- macro horizontal_field(field, label_text='', label_class='') -%}
 {%- macro horizontal_field(field, label_text='', label_class='') -%}
 <div class="form-group row {%- if field.errors %} has-error{%- endif %}">
 <div class="form-group row {%- if field.errors %} has-error{%- endif %}">
 
 

+ 1 - 5
flaskbb/themes/bootstrap2/templates/layout.html

@@ -47,6 +47,7 @@
                             {# active_forum_nav is set in {forum, category, topic}.html and new_{topic, post}.html #}
                             {# active_forum_nav is set in {forum, category, topic}.html and new_{topic, post}.html #}
                             {{ topnav(endpoint='forum.index', name='Forum', icon='fa fa-comment', active=active_forum_nav) }}
                             {{ topnav(endpoint='forum.index', name='Forum', icon='fa fa-comment', active=active_forum_nav) }}
                             {{ topnav(endpoint='forum.memberlist', name='Memberlist', icon='fa fa-user') }}
                             {{ topnav(endpoint='forum.memberlist', name='Memberlist', icon='fa fa-user') }}
+                            {{ topnav(endpoint='forum.search', name='Search', icon='fa fa-search') }}
                         </ul>
                         </ul>
 
 
                     {% if current_user and current_user.is_authenticated() %}
                     {% if current_user and current_user.is_authenticated() %}
@@ -94,11 +95,6 @@
                             </ul>
                             </ul>
                         </div>
                         </div>
                     {% endif %}
                     {% endif %}
-                        <form class="navbar-form navbar-right" role="search">
-                            <div class="form-group">
-                                <input type="text" class="form-control" placeholder="Search">
-                            </div>
-                        </form>
                     </div><!-- nav-collapse -->
                     </div><!-- nav-collapse -->
                 </div><!-- container -->
                 </div><!-- container -->
             </nav> <!-- navbar navbar-inverse -->
             </nav> <!-- navbar navbar-inverse -->

+ 4 - 0
flaskbb/themes/bootstrap3/static/css/flaskbb.css

@@ -153,6 +153,10 @@ margin-bottom: 0px;
   }
   }
 }
 }
 
 
+.form-grouped {
+    border-radius: 5px 0px 0px 5px !important;
+}
+
 .form-signin {
 .form-signin {
   max-width: 330px;
   max-width: 330px;
   padding: 15px;
   padding: 15px;

+ 1 - 5
flaskbb/themes/bootstrap3/templates/layout.html

@@ -46,6 +46,7 @@
                             {# active_forum_nav is set in {forum, category, topic}.html and new_{topic, post}.html #}
                             {# active_forum_nav is set in {forum, category, topic}.html and new_{topic, post}.html #}
                             {{ topnav(endpoint='forum.index', name='Forum', icon='fa fa-comment', active=active_forum_nav) }}
                             {{ topnav(endpoint='forum.index', name='Forum', icon='fa fa-comment', active=active_forum_nav) }}
                             {{ topnav(endpoint='forum.memberlist', name='Memberlist', icon='fa fa-user') }}
                             {{ topnav(endpoint='forum.memberlist', name='Memberlist', icon='fa fa-user') }}
+                            {{ topnav(endpoint='forum.search', name='Search', icon='fa fa-search') }}
                         </ul>
                         </ul>
 
 
                     {% if current_user and current_user.is_authenticated() %}
                     {% if current_user and current_user.is_authenticated() %}
@@ -93,11 +94,6 @@
                             </ul>
                             </ul>
                         </div>
                         </div>
                     {% endif %}
                     {% endif %}
-                        <form class="navbar-form navbar-right" role="search">
-                            <div class="form-group">
-                                <input type="text" class="form-control" placeholder="Search">
-                            </div>
-                        </form>
                     </div><!-- nav-collapse -->
                     </div><!-- nav-collapse -->
                 </div><!-- container -->
                 </div><!-- container -->
             </nav> <!-- navbar navbar-inverse -->
             </nav> <!-- navbar navbar-inverse -->

+ 1 - 0
flaskbb/user/models.py

@@ -72,6 +72,7 @@ class Group(db.Model):
 
 
 class User(db.Model, UserMixin):
 class User(db.Model, UserMixin):
     __tablename__ = "users"
     __tablename__ = "users"
+    __searchable__ = ['username', 'email']
 
 
     id = db.Column(db.Integer, primary_key=True)
     id = db.Column(db.Integer, primary_key=True)
     username = db.Column(db.String(15), unique=True, nullable=False)
     username = db.Column(db.String(15), unique=True, nullable=False)

+ 1 - 0
setup.py

@@ -58,6 +58,7 @@ setup(
         'itsdangerous==0.23',
         'itsdangerous==0.23',
         'postmarkup==1.2.0',
         'postmarkup==1.2.0',
         'wsgiref==0.1.2',
         'wsgiref==0.1.2',
+        'Flask-WhooshAlchemy==0.55'
     ],
     ],
     classifiers=[
     classifiers=[
         'Development Status :: 4 - Beta',
         'Development Status :: 4 - Beta',