From bbdfc339e59e004d0ebfc2e8d7c26f6efcbe4f48 Mon Sep 17 00:00:00 2001
From: Jorran de Wit <jorrandewit@outlook.com>
Date: Fri, 18 Aug 2017 11:58:31 +0200
Subject: [PATCH] Search overhaul

---
 .bootstraprc                                  |   2 +
 SciPost_v1/settings/base.py                   |   5 +
 commentaries/search_indexes.py                |  19 ++
 .../partials/commentaries/search_card.html    |   1 +
 comments/search_indexes.py                    |  17 ++
 .../partials/comments/search_card.html        |   1 +
 common/forms.py                               |  93 +++++++++
 journals/search_indexes.py                    |  20 ++
 .../partials/journals/search_card.html        |   1 +
 scipost/forms.py                              |  34 ++++
 scipost/static/scipost/SciPost.css            |  66 -------
 .../scipost/assets/config/preconfig.scss      |  13 +-
 .../static/scipost/assets/css/_buttons.scss   |   4 +
 scipost/static/scipost/assets/css/_form.scss  |  25 ++-
 scipost/static/scipost/assets/css/_grid.scss  |   6 +
 scipost/static/scipost/assets/css/_nav.scss   |   5 +
 scipost/static/scipost/assets/js/scripts.js   |   4 +
 scipost/templates/scipost/footer.html         |  46 ++---
 scipost/templates/scipost/header.html         |   2 +-
 scipost/templates/scipost/navbar.html         |   2 +-
 scipost/templates/scipost/search.html         |   1 +
 scipost/templates/scipost/search2.html        | 182 ++++++++++++++++++
 scipost/urls.py                               |   2 +
 scipost/views.py                              |  23 ++-
 submissions/search_indexes.py                 |  19 ++
 .../partials/submissions/search_card.html     |   1 +
 templates/search/search.html                  | 108 +++++++++++
 theses/search_indexes.py                      |  20 ++
 .../partials/theses/search_card.html          |   1 +
 29 files changed, 624 insertions(+), 99 deletions(-)
 create mode 100644 commentaries/search_indexes.py
 create mode 100644 commentaries/templates/partials/commentaries/search_card.html
 create mode 100644 comments/search_indexes.py
 create mode 100644 comments/templates/partials/comments/search_card.html
 create mode 100644 common/forms.py
 create mode 100644 journals/search_indexes.py
 create mode 100644 journals/templates/partials/journals/search_card.html
 create mode 100644 scipost/templates/scipost/search2.html
 create mode 100644 submissions/search_indexes.py
 create mode 100644 submissions/templates/partials/submissions/search_card.html
 create mode 100644 templates/search/search.html
 create mode 100644 theses/search_indexes.py
 create mode 100644 theses/templates/partials/theses/search_card.html

diff --git a/.bootstraprc b/.bootstraprc
index c3cf26502..924360a98 100644
--- a/.bootstraprc
+++ b/.bootstraprc
@@ -14,6 +14,7 @@
         # First tables, due to self dependencies
         "tables": true,
         "alert": true,
+        "badge": true,
         "button-group": true,
         "buttons": true,
         "breadcrumb": true,
@@ -40,6 +41,7 @@
         "alert": true,
         "collapse": true,
         "modal": true,
+        "scrollspy": true,
         "tab": true,
         "tooltip": true,
         "util": true,
diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py
index 3abb27c0d..70d622cb4 100644
--- a/SciPost_v1/settings/base.py
+++ b/SciPost_v1/settings/base.py
@@ -98,13 +98,18 @@ INSTALLED_APPS = (
     'webpack_loader',
 )
 
+
 HAYSTACK_CONNECTIONS = {
     'default': {
         'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
         'PATH': 'local_files/haystack/',
     },
+    'base_search': {
+        'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
+    }
 }
 
+
 SPHINXDOC_BASE_TEMPLATE = 'scipost/base.html'
 SPHINXDOC_PROTECTED_PROJECTS = {
     'scipost': ['scipost.can_view_docs_scipost'],
diff --git a/commentaries/search_indexes.py b/commentaries/search_indexes.py
new file mode 100644
index 000000000..603ee07d4
--- /dev/null
+++ b/commentaries/search_indexes.py
@@ -0,0 +1,19 @@
+# import datetime
+
+from haystack import indexes
+
+from .models import Commentary
+
+
+class CommentaryIndex(indexes.SearchIndex, indexes.Indexable):
+    text = indexes.CharField(document=True, model_attr='title', use_template=True)
+    authors = indexes.CharField(model_attr='author_list')
+    date = indexes.DateTimeField(model_attr='pub_date')
+    abstract = indexes.CharField(model_attr='pub_abstract')
+
+    def get_model(self):
+        return Commentary
+
+    def index_queryset(self, using=None):
+        """Used when the entire index for model is updated."""
+        return self.get_model().objects.vetted()
diff --git a/commentaries/templates/partials/commentaries/search_card.html b/commentaries/templates/partials/commentaries/search_card.html
new file mode 100644
index 000000000..70d753159
--- /dev/null
+++ b/commentaries/templates/partials/commentaries/search_card.html
@@ -0,0 +1 @@
+{% extends 'commentaries/_commentary_card_content.html' %}
diff --git a/comments/search_indexes.py b/comments/search_indexes.py
new file mode 100644
index 000000000..3ccccda8f
--- /dev/null
+++ b/comments/search_indexes.py
@@ -0,0 +1,17 @@
+# import datetime
+
+from haystack import indexes
+
+from .models import Comment
+
+
+class CommentIndex(indexes.SearchIndex, indexes.Indexable):
+    text = indexes.CharField(document=True, model_attr='comment_text', use_template=True)
+    date = indexes.DateTimeField(model_attr='date_submitted')
+
+    def get_model(self):
+        return Comment
+
+    def index_queryset(self, using=None):
+        """Used when the entire index for model is updated."""
+        return self.get_model().objects.vetted()
diff --git a/comments/templates/partials/comments/search_card.html b/comments/templates/partials/comments/search_card.html
new file mode 100644
index 000000000..ccda3bf31
--- /dev/null
+++ b/comments/templates/partials/comments/search_card.html
@@ -0,0 +1 @@
+{% extends 'comments/_comment_card_content.html' %}
diff --git a/common/forms.py b/common/forms.py
new file mode 100644
index 000000000..b4f7d0c5b
--- /dev/null
+++ b/common/forms.py
@@ -0,0 +1,93 @@
+import datetime
+import re
+
+from django.forms.widgets import Widget, Select
+from django.utils.dates import MONTHS
+from django.utils.safestring import mark_safe
+
+__all__ = ('MonthYearWidget',)
+
+RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
+
+
+class MonthYearWidget(Widget):
+    """
+    A Widget that splits date input into two <select> boxes for month and year,
+    with 'day' defaulting to the first of the month.
+
+    Based on SelectDateWidget, in
+
+    django/trunk/django/forms/extras/widgets.py
+
+
+    """
+    none_value = (0, '---')
+    month_field = '%s_month'
+    year_field = '%s_year'
+
+    def __init__(self, attrs=None, years=True, months=True, required=False):
+        self.attrs = attrs or {}
+        self.required = required
+        self.today = datetime.date.today()
+        if years:
+            this_year = self.today.year
+            self.year_choices = [(i, i) for i in range(this_year - 4, this_year + 1)]
+            if not self.required:
+                self.year_choices.insert(0, self.none_value)
+        if months:
+            self.month_choices = dict(MONTHS.items())
+            if not self.required:
+                self.month_choices[self.none_value[0]] = self.none_value[1]
+            self.month_choices = sorted(self.month_choices.items())
+
+    def render(self, name, value, attrs=None):
+        try:
+            year_val, month_val = value.year, value.month
+        except AttributeError:
+            year_val = month_val = None
+            if isinstance(value, (str, bytes)):
+                match = RE_DATE.match(value)
+                if match:
+                    year_val, month_val, day_val = [int(v) for v in match.groups()]
+
+        output = []
+
+        if 'id' in self.attrs:
+            id_ = self.attrs['id']
+        else:
+            id_ = 'id_%s' % name
+
+        if hasattr(self, 'month_choices'):
+            local_attrs = self.build_attrs(id=self.month_field % id_)
+            s = Select(choices=self.month_choices, attrs={'class': 'form-control'})
+            select_html = s.render(self.month_field % name, month_val, local_attrs)
+            output.append(select_html)
+
+        if hasattr(self, 'year_choices'):
+            local_attrs = self.build_attrs(id=self.year_field % id_)
+            s = Select(choices=self.year_choices, attrs={'class': 'form-control'})
+            select_html = s.render(self.year_field % name, year_val, local_attrs)
+            output.append(select_html)
+
+        return mark_safe(u'\n'.join(output))
+
+    @classmethod
+    def id_for_label(self, id_):
+        return '%s_month' % id_
+
+    def value_from_datadict(self, data, files, name):
+        if hasattr(self, 'year_choices'):
+            y = data.get(self.year_field % name)
+        else:
+            y = self.today.year
+
+        if hasattr(self, 'month_choices'):
+            m = data.get(self.month_field % name)
+        else:
+            m = self.today.month
+
+        if y == "0" or m == "0":
+            return None
+        if y and m:
+            return '%s-%s-%s' % (y, m, 1)
+        return data.get(name, None)
diff --git a/journals/search_indexes.py b/journals/search_indexes.py
new file mode 100644
index 000000000..df45d5f97
--- /dev/null
+++ b/journals/search_indexes.py
@@ -0,0 +1,20 @@
+# import datetime
+
+from haystack import indexes
+
+from .models import Publication
+
+
+class PublicationIndex(indexes.SearchIndex, indexes.Indexable):
+    text = indexes.CharField(document=True, model_attr='title', use_template=True)
+    authors = indexes.CharField(model_attr='author_list')
+    date = indexes.DateTimeField(model_attr='publication_date')
+    abstract = indexes.CharField()
+    doi_label = indexes.CharField()
+
+    def get_model(self):
+        return Publication
+
+    def index_queryset(self, using=None):
+        """Used when the entire index for model is updated."""
+        return self.get_model().objects.published()
diff --git a/journals/templates/partials/journals/search_card.html b/journals/templates/partials/journals/search_card.html
new file mode 100644
index 000000000..ff17dfb32
--- /dev/null
+++ b/journals/templates/partials/journals/search_card.html
@@ -0,0 +1 @@
+{% extends 'journals/_publication_card_content.html' %}
diff --git a/scipost/forms.py b/scipost/forms.py
index ef56c9cf1..8b79ae454 100644
--- a/scipost/forms.py
+++ b/scipost/forms.py
@@ -1,3 +1,5 @@
+import datetime
+
 from django import forms
 from django.contrib.auth import authenticate
 from django.contrib.auth.models import User, Group
@@ -5,6 +7,7 @@ from django.contrib.auth.password_validation import validate_password
 from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse_lazy
 from django.utils import timezone
+from django.utils.dates import MONTHS
 from django.utils.http import is_safe_url
 
 from django_countries import countries
@@ -13,12 +16,18 @@ from django_countries.fields import LazyTypedChoiceField
 from captcha.fields import ReCaptchaField
 
 from ajax_select.fields import AutoCompleteSelectField
+from haystack.forms import ModelSearchForm as HayStackSearchForm
 
 from .constants import SCIPOST_DISCIPLINES, TITLE_CHOICES, SCIPOST_FROM_ADDRESSES
 from .decorators import has_contributor
 from .models import Contributor, DraftInvitation, RegistrationInvitation,\
                     UnavailabilityPeriod, PrecookedEmail
 
+from commentaries.models import Commentary
+from comments.models import Comment
+from submissions.models import Submission
+from theses.models import ThesisLink
+from common.forms import MonthYearWidget
 from partners.decorators import has_contact
 from journals.models import Publication
 # from mailing_lists.models import MailchimpList, MailchimpSubscription
@@ -425,6 +434,31 @@ class SearchForm(forms.Form):
     q = forms.CharField(max_length=100)
 
 
+def get_date_filter_choices():
+    today = datetime.date.today()
+    empty = [(0, '---')]
+    months = empty + list(MONTHS.items())
+    years = empty + [(i, i) for i in range(today.year - 4, today.year + 1)]
+    return months, years
+
+
+class Search2Form(HayStackSearchForm):
+    # The date filters doesn't function well...
+    # start_1 = forms.DateField(widget=MonthYearWidget(years=False), required=False)  # Month
+    # start_2 = forms.DateField(widget=MonthYearWidget(months=False), required=False)  # Year
+    # end_1 = forms.DateField(widget=MonthYearWidget(years=False), required=False)  # Month
+    # end_2 = forms.DateField(widget=MonthYearWidget(months=False), required=False)  # Years
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        models = self.fields['models'].choices
+        models = filter(lambda x: x[0] != 'sphinxdoc.document', models)
+        self.fields['models'].choices = models
+
+    def search(self):
+        return super().search().using('base_search')
+
+
 class EmailGroupMembersForm(forms.Form):
     group = forms.ModelChoiceField(queryset=Group.objects.all())
     email_subject = forms.CharField(widget=forms.Textarea(), label='')
diff --git a/scipost/static/scipost/SciPost.css b/scipost/static/scipost/SciPost.css
index b4248ef26..59a65ec9a 100644
--- a/scipost/static/scipost/SciPost.css
+++ b/scipost/static/scipost/SciPost.css
@@ -4,39 +4,6 @@ General style sheet for SciPost
 
 @import url(//fonts.googleapis.com/css?family=Merriweather+Sans);
 
-* {
-    box-sizing: border-box;
-}
-
-.row:after {
-    content: "";
-    clear: both;
-    display: block;
-}
-
-[class*="col-"] {
-    /*float: left;
-    padding: 5px;*/
-}
-/* For desktop: */
-.col-1 {width: 8.33%;}
-.col-2 {width: 16.66%;}
-.col-3 {width: 25%;}
-.col-4 {width: 33.33%;}
-.col-5 {width: 41.66%;}
-.col-6 {width: 50%;}
-.col-7 {width: 58.33%;}
-.col-8 {width: 66.66%;}
-.col-9 {width: 75%;}
-.col-10 {width: 83.33%;}
-.col-11 {width: 91.66%;}
-.col-12 {width: 100%;}
-@media only screen and (max-width: 768px) {
-    /* For mobile phones: */
-    [class*="col-"] {
-        width: 100%;
-    }
-}
 
 hr.hr6 {
     height: 6px;
@@ -307,16 +274,6 @@ ul.personalTabMenu li a.inactive {
   padding: 0px 4px;
 }
 
-body {
-/*  font-family: Merriweather, sans-serif; */
-  font-family: 'Merriweather Sans';
-  font-weight: lighter;
-  font-size: 0.8rem;
-  margin: 0px;
-  padding: 0px;
-  background-color: #ffffff;
-}
-
 body section div.sectionbox {
   width: 300px;
   background-color: #f4f4f4;
@@ -326,16 +283,6 @@ body section div.sectionbox {
   vertical-align: top;
 }
 
-a {
-  color: #eeeeee;
-/*  text-decoration: underline; */
-  target: _parent;
-}
-a:hover {
-  color: #ffffff;
-  text-decoration: underline;
-}
-
 .submitButton {
   background-color: #002B49;
   color: #ffffff;
@@ -372,19 +319,6 @@ header p {
   margin: 25px;
 }
 
-nav {
-  background-color: #f0f0f0;
-  /*color: #002B49;
-  margin:0px;
-  padding:0px;
-  float:center;
-  clear:both;*/
-  /*font-size: 12px;*/
-  /*font-weight: 500;*/
-}
-
-
-
 ol {
   list-style-type: none;
   counter-reset: item;
diff --git a/scipost/static/scipost/assets/config/preconfig.scss b/scipost/static/scipost/assets/config/preconfig.scss
index a67c0d9ce..b0e0d4443 100644
--- a/scipost/static/scipost/assets/config/preconfig.scss
+++ b/scipost/static/scipost/assets/config/preconfig.scss
@@ -27,6 +27,7 @@ $container-max-widths: (
 //
 $scipost-light: #C3D7EE;
 $scipost-lightblue: #6885c3;
+$scipost-lightestblue: #d3e3f6;
 $scipost-darkblue: #002b49;
 $scipost-orange: #f6a11a;
 $scipost-white: #f9f9f9;
@@ -38,7 +39,7 @@ $body-color: $scipost-darkblue;
 $white: #fff;
 $blue: $scipost-lightblue;  // Primary
 $green: #6ebb6e;
-$cyan: $scipost-lightblue;
+$cyan: $scipost-lightestblue;
 $yellow: $scipost-orange;
 $gray-600: #ccc;
 $text-muted: #636c72;
@@ -71,12 +72,16 @@ $breadcrumb-divider-color: $scipost-orange;
 
 // Forms
 //
-$input-btn-padding-x: .5rem;
-$input-btn-padding-y: .25rem;
-$input-btn-padding-y-sm: .15rem;
+$input-btn-padding-y-sm: 0.1rem;
+$input-btn-padding-x: 0.5rem;
+$input-btn-padding-y: 0.25rem;
+$input-btn-padding-y-lg: 0.25rem;
+$input-btn-padding-x-lg: 0.75rem;
+$input-btn-line-height-lg: 1.4;
 $input-border-radius-sm: $base-border-radius;
 $input-border-radius: $base-border-radius;
 $input-border-radius-lg: $base-border-radius;
+$enable-rounded: true !important;
 $btn-transition: none;
 
 $input-height: calc(1.5rem + 2px);
diff --git a/scipost/static/scipost/assets/css/_buttons.scss b/scipost/static/scipost/assets/css/_buttons.scss
index aa1038e1d..b499a56ee 100644
--- a/scipost/static/scipost/assets/css/_buttons.scss
+++ b/scipost/static/scipost/assets/css/_buttons.scss
@@ -31,6 +31,10 @@
     }
 }
 
+.btn-info {
+    color: $scipost-darkblue;
+}
+
 .category-group,
 .voting-group {
     border-radius: $card-border-radius;
diff --git a/scipost/static/scipost/assets/css/_form.scss b/scipost/static/scipost/assets/css/_form.scss
index 7ed22071c..25694c465 100644
--- a/scipost/static/scipost/assets/css/_form.scss
+++ b/scipost/static/scipost/assets/css/_form.scss
@@ -4,6 +4,7 @@
  */
 .form-control {
     font-family: inherit;
+    border-radius: $base-border-radius;
 }
 
 .has-error .form-control {
@@ -53,6 +54,8 @@ input[type="file"] {
 }
 
 .search-nav-form {
+    min-width: 260px;
+
     input {
         margin-right: 0 !important;
         outline: 0;
@@ -61,12 +64,14 @@ input[type="file"] {
 
     input[name="q"] {
         border-right: 0;
-        min-width: 200px;
+        width: 200px;
         border-radius: $base-border-radius 0 0 $base-border-radius;
     }
 
     input[type="submit"] {
         background: #f4f4f4;
+        width: 60px;
+        text-align: center;
         border-radius: 0 $base-border-radius $base-border-radius 0;
 
         &:hover {
@@ -75,6 +80,24 @@ input[type="file"] {
     }
 }
 
+.form-group.checkboxes {
+    [type="checkbox"] {
+        display: none;
+    }
+
+    [type="checkbox"]:checked + .btn-info {
+        color: $white;
+        background-color: $scipost-lightblue;
+        border-color: $scipost-lightblue;
+
+    }
+
+    [type="checkbox"]:checked + .btn-primary {
+        background-color: $scipost-darkblue;
+        border-color: $scipost-darkblue;
+    }
+}
+
 // Formset
 //
 .delete-form-group {
diff --git a/scipost/static/scipost/assets/css/_grid.scss b/scipost/static/scipost/assets/css/_grid.scss
index e7ef90aaf..7b5c41d37 100644
--- a/scipost/static/scipost/assets/css/_grid.scss
+++ b/scipost/static/scipost/assets/css/_grid.scss
@@ -22,6 +22,12 @@ img {
     display: none;
 }
 
+// footer.footer {
+//     position: fixed;
+//     width: 100%;
+//     bottom: 0;
+// }
+
 footer.secondary {
     color: $scipost-darkblue;
     background: transparent;
diff --git a/scipost/static/scipost/assets/css/_nav.scss b/scipost/static/scipost/assets/css/_nav.scss
index 4ff137786..b904d97c8 100644
--- a/scipost/static/scipost/assets/css/_nav.scss
+++ b/scipost/static/scipost/assets/css/_nav.scss
@@ -1,3 +1,8 @@
+.nav,
+nav {
+    background-color: #f0f0f0;
+}
+
 .nav.btn-group .nav-item {
     padding: 0 !important;
 }
diff --git a/scipost/static/scipost/assets/js/scripts.js b/scipost/static/scipost/assets/js/scripts.js
index 6dfe09de3..0c8bf6045 100644
--- a/scipost/static/scipost/assets/js/scripts.js
+++ b/scipost/static/scipost/assets/js/scripts.js
@@ -39,4 +39,8 @@ $(function(){
         var tab_name = e.target.hash.substring(1)
         window.history.replaceState({}, null, '?tab=' + tab_name);
     });
+
+    // $('footer').each(function () {
+      var $spy = $('body').scrollspy({ target: '.footer' })
+    // })
 });
diff --git a/scipost/templates/scipost/footer.html b/scipost/templates/scipost/footer.html
index a69ef2bf8..f792aebbd 100644
--- a/scipost/templates/scipost/footer.html
+++ b/scipost/templates/scipost/footer.html
@@ -1,26 +1,28 @@
 {% load staticfiles %}
-<footer class="container-fluid py-2">
-  <div class="row mt-2">
+<footer class="footer">
+    <div class="container-fluid py-1">
+      <div class="row my-3">
 
-    <div class="col-md-4">
-      Copyright &copy; <a href="{% url 'scipost:foundation' %}" target="_">SciPost Foundation</a>.
-      <br/>
-      Contact the <a href="mailto:admin@scipost.org">administrators</a> or <a href="mailto:techsupport@scipost.org">tech support</a>
-      <br/>
-      <a href="{% url 'scipost:terms_and_conditions' %}">Terms and conditions.</a>
-    </div>
-    <div class="col-md-3">
-      Follow us:<br/>
-      <table>
-    	<tr>
-    	  <td><a href="//www.facebook.com/scipost" target="_blank" title="Facebook"><img src="{% static 'scipost/images/FB-f-Logo__white_29.png' %}" width="20" alt="Facebook"/></a></td>
-    	  <td><a href="//twitter.com/scipost_dot_org" target="_blank" title="Twitter"><img src="{% static 'scipost/images/Twitter_Logo_Blue.png' %}" width="32" alt="Twitter"/></a></td>
-    	  <td><a style="float: right;" href="{% url 'scipost:feeds' %}"><img src="{% static 'scipost/images/feed-icon-28x28.png' %}" alt="Feed logo" width="20"></a></td>
-    	</tr>
-      </table>
-    </div>
-    <div class="col-md-5">
-      <a rel="license" href="//creativecommons.org/licenses/by/4.0/" target="_blank"><img alt="Creative Commons License" style="border-width:0" src="//i.creativecommons.org/l/by/4.0/80x15.png" /></a><br />Except where otherwise noted, all content on SciPost is licensed under a <a rel="license" href="//creativecommons.org/licenses/by/4.0/" target="_blank">Creative Commons Attribution 4.0 International License</a>.
-    </div>
+        <div class="col-md-4 mb-3 mb-md-0">
+          Copyright &copy; <a href="{% url 'scipost:foundation' %}" target="_">SciPost Foundation</a>
+          <br/>
+          Contact the <a href="mailto:admin@scipost.org">administrators</a> or <a href="mailto:techsupport@scipost.org">tech support</a>
+          <br/>
+          <a href="{% url 'scipost:terms_and_conditions' %}">Terms and conditions</a>
+        </div>
+        <div class="col-md-3 mb-3 mb-md-0">
+          Follow us:<br/>
+          <table>
+        	<tr>
+        	  <td><a href="//www.facebook.com/scipost" target="_blank" title="Facebook"><img src="{% static 'scipost/images/FB-f-Logo__white_29.png' %}" width="20" alt="Facebook"/></a></td>
+        	  <td><a href="//twitter.com/scipost_dot_org" target="_blank" title="Twitter"><img src="{% static 'scipost/images/Twitter_Logo_Blue.png' %}" width="32" alt="Twitter"/></a></td>
+        	  <td><a style="float: right;" href="{% url 'scipost:feeds' %}"><img src="{% static 'scipost/images/feed-icon-28x28.png' %}" alt="Feed logo" width="20"></a></td>
+        	</tr>
+          </table>
+        </div>
+        <div class="col-md-5">
+          <a rel="license" href="//creativecommons.org/licenses/by/4.0/" target="_blank"><img alt="Creative Commons License" style="border-width:0" src="//i.creativecommons.org/l/by/4.0/80x15.png" /></a><br />Except where otherwise noted, all content on SciPost is licensed under a <a rel="license" href="//creativecommons.org/licenses/by/4.0/" target="_blank">Creative Commons Attribution 4.0 International License</a>.
+        </div>
+      </div>
   </div>
 </footer>
diff --git a/scipost/templates/scipost/header.html b/scipost/templates/scipost/header.html
index 2f9ddf2da..c85dd9963 100644
--- a/scipost/templates/scipost/header.html
+++ b/scipost/templates/scipost/header.html
@@ -5,7 +5,7 @@
       <div class="flex-logobox">
 	<a href="{% url 'scipost:index' %}"><img src="{% static 'scipost/images/logo_scipost_RGB_HTML_groot.png' %}" alt="SciPost logo" width="240" /></a>
       </div>
-      <div class="flex-blurbbox hidden-sm-down">
+      <div class="flex-blurbbox d-none d-md-block">
 	<p>
 	  <i>The complete scientific publication portal</i><br />
 	  <i>Managed by professional scientists</i><br />
diff --git a/scipost/templates/scipost/navbar.html b/scipost/templates/scipost/navbar.html
index f38d7da1d..ed33013d1 100644
--- a/scipost/templates/scipost/navbar.html
+++ b/scipost/templates/scipost/navbar.html
@@ -50,7 +50,7 @@
 
         </ul>
         <form action="{% url 'scipost:search' %}" method="get" class="form-inline search-nav-form">
-            <input class="form-control" id="id_q" maxlength="100" name="q" type="text" required="required" value="{{search_query}}">
+            <input class="form-control" id="id_q" maxlength="100" name="q" type="text" required="required" value="{{search_query|default:''}}">
             <input class="btn btn-secondary" type="submit" value="Search">
         </form>
     </div>
diff --git a/scipost/templates/scipost/search.html b/scipost/templates/scipost/search.html
index dd9d6d630..f80596b41 100644
--- a/scipost/templates/scipost/search.html
+++ b/scipost/templates/scipost/search.html
@@ -6,6 +6,7 @@
 
 <div class="row">
     <div class="col-12">
+        <h1>Search results: <span class="text-muted">{{ search_term|default:'' }}</span></h1>
         <h1>Search results{% if search_term %}: <small><i>{{ search_term }}</i></small>{% endif %}</h1>
         {% if not publication_search_list and not commentary_search_list and not submission_search_list and not thesislink_search_list and not comment_search_link and not comment_search_list %}
             <p>Your search query did not return any result.</p>
diff --git a/scipost/templates/scipost/search2.html b/scipost/templates/scipost/search2.html
new file mode 100644
index 000000000..5453a7c2e
--- /dev/null
+++ b/scipost/templates/scipost/search2.html
@@ -0,0 +1,182 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: list{% endblock pagetitle %}
+
+{% block content %}
+
+
+<h1>Search results</h1>
+<form method="get" class="my-2">
+    <div class="form-group row">
+        <div class="col-md-5 col-lg-3 mb-2 mb-md-0 pr-md-1">
+            <input class="form-control" id="{{form.q.auto_id}}" maxlength="100" name="{{form.q.name}}" type="text" value="{{form.q.value}}" required="required">
+        </div>
+        <div class="col-md-7 col-lg-9 pl-md-1">
+            <input class="btn btn-primary" type="submit" value="Search">
+        </div>
+    </div>
+</form>
+
+
+{% if not publication_search_list and not commentary_search_list and not submission_search_list and not thesislink_search_list and not comment_search_link and not comment_search_list %}
+    <p>Your search query did not return any result.</p>
+{% endif %}
+
+{% if publication_search_list %}
+<hr>
+<div class="row">
+    <div class="col-12">
+        <h2 class="highlight">Publications ({{publication_search_list|length}})</h2>
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        {% for publication in publication_search_list %}
+            <div class="card card-publication">
+                {% include 'journals/_publication_card_content.html' with publication=publication %}
+            </div>
+        {% endfor %}
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+          <div class="pagination">
+            <span class="step-links">
+              {% if publication_search_list.has_previous %}
+              <a href="?publication_search_list_page={{ publication_search_list.previous_page_number }}">previous</a>
+              {% endif %}
+              <span class="current">
+        	Page {{ publication_search_list.number }} of {{ publication_search_list.paginator.num_pages }}.
+              </span>
+              {% if publication_search_list.has_next %}
+              <a href="?publication_search_list_page={{ publication_search_list.next_page_number }}">next</a>
+              {% endif %}
+            </span>
+          </div>
+    </div>
+</div>
+{% endif %}
+
+
+{% if commentary_search_list %}
+<hr>
+<div class="row">
+    <div class="col-12">
+        <h2 class="highlight">Commentaries ({{commentary_search_list|length}})</h2>
+    </div>
+</div>
+<div class="row">
+    <div class="col-12">
+        <ul class="list-group list-group-flush">
+            {% for commentary in commentary_search_list %}
+                <li class="list-group-item">
+                    {% include 'commentaries/_commentary_card_content.html' with commentary=commentary %}
+                </li>
+            {% endfor %}
+        </ul>
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+          <div class="pagination">
+            <span class="step-links">
+              {% if commentary_search_list.has_previous %}
+              <a href="?commentary_search_list_page={{ commentary_search_list.previous_page_number }}">previous</a>
+              {% endif %}
+              <span class="current">
+        	Page {{ commentary_search_list.number }} of {{ commentary_search_list.paginator.num_pages }}.
+              </span>
+              {% if commentary_search_list.has_next %}
+              <a href="?commentary_search_list_page={{ commentary_search_list.next_page_number }}">next</a>
+              {% endif %}
+            </span>
+          </div>
+    </div>
+</div>
+{% endif %}
+
+
+{% if submission_search_list %}
+<hr>
+<div class="row">
+    <div class="col-12">
+        <h2 class="highlight">Submissions ({{submission_search_list|length}})</h2>
+    </div>
+</div>
+<div class="row">
+    <div class="col-12">
+        <ul class="list-group list-group-flush">
+            {% for submission in submission_search_list %}
+                <li class="list-group-item">
+                    {% include 'submissions/_submission_card_content.html' with submission=submission %}
+                </li>
+            {% endfor %}
+        </ul>
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+      <div class="pagination">
+        <span class="step-links">
+          {% if submission_search_list.has_previous %}
+          <a href="?submission_search_list_page={{ submission_search_list.previous_page_number }}">previous</a>
+          {% endif %}
+          <span class="current">
+    	Page {{ submission_search_list.number }} of {{ submission_search_list.paginator.num_pages }}
+          </span>
+          {% if submission_search_list.has_next %}
+          <a href="?submission_search_list_page={{ submission_search_list.next_page_number }}">next</a>
+          {% endif %}
+        </span>
+      </div>
+    </div>
+</div>
+{% endif %}
+
+{% if thesislink_search_list %}
+<hr>
+<div class="row">
+    <div class="col-12">
+        <h2 class="highlight">Theses ({{thesislink_search_list|length}})</h2>
+    </div>
+</div>
+<div class="row">
+    <div class="col-12">
+        <ul class="list-group list-group-flush">
+            {% for thesislink in thesislink_search_list %}
+                <li class="list-group-item">
+                    {% include 'theses/_thesislink_card_content.html' with thesislink=thesislink %}
+                </li>
+            {% endfor %}
+        </ul>
+    </div>
+</div>
+{% endif %}
+
+{% if comment_search_list %}
+<hr>
+<div class="row">
+    <div class="col-12">
+        <h2 class="highlight">Comments ({{comment_search_list|length}})</h2>
+    </div>
+</div>
+<div class="row">
+    <div class="col-12">
+        <ul class="list-group list-group-flush">
+            {% for comment in comment_search_list %}
+                <li class="list-group-item">
+                    {% include 'comments/_comment_card_extended_content.html' with comment=comment %}
+                </li>
+            {% endfor %}
+        </ul>
+    </div>
+</div>
+{% endif %}
+
+{% endblock content %}
diff --git a/scipost/urls.py b/scipost/urls.py
index 71d914230..709d8ddfe 100644
--- a/scipost/urls.py
+++ b/scipost/urls.py
@@ -63,6 +63,8 @@ urlpatterns = [
 
     # Search
     url(r'^search$', views.search, name='search'),
+    url(r'^search2', views.SearchView.as_view(), name='search2'),
+    url(r'^search3', include('haystack.urls')),
 
     ################
     # Contributors:
diff --git a/scipost/views.py b/scipost/views.py
index 58aeea2e4..d55d44726 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -11,16 +11,15 @@ from django.core import mail
 from django.core.mail import EmailMessage, EmailMultiAlternatives
 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
 from django.core.urlresolvers import reverse
-from django.db.models import Q
+from django.db.models import Q, Prefetch
 from django.shortcuts import redirect
 from django.template import Context, Template
 from django.views.decorators.http import require_POST
 from django.views.generic.list import ListView
 
-from django.db.models import Prefetch
-
 from guardian.decorators import permission_required
 from guardian.shortcuts import assign_perm, get_objects_for_user
+from haystack.generic_views import SearchView
 
 from .constants import SCIPOST_SUBJECT_AREAS, subject_areas_raw_dict, SciPost_from_addresses_dict
 from .decorators import has_contributor
@@ -31,7 +30,8 @@ from .forms import AuthenticationForm, DraftInvitationForm, UnavailabilityPeriod
                    RegistrationForm, RegistrationInvitationForm, AuthorshipClaimForm,\
                    ModifyPersonalMessageForm, SearchForm, VetRegistrationForm, reg_ref_dict,\
                    UpdatePersonalDataForm, UpdateUserDataForm, PasswordChangeForm,\
-                   EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm
+                   EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm,\
+                   Search2Form
 from .utils import Utils, EMAIL_FOOTER, SCIPOST_SUMMARY_FOOTER, SCIPOST_SUMMARY_FOOTER_HTML
 
 from commentaries.models import Commentary
@@ -115,6 +115,21 @@ def documentsSearchResults(query):
     return context
 
 
+class SearchView(SearchView):
+    template_name = 'search/search.html'
+    form_class = Search2Form
+
+    def get_context_data(self, *args, **kwargs):
+        ctx = super().get_context_data(*args, **kwargs)
+        # Add context variable to fill navbar form
+        ctx['suggestion'] = kwargs['object_list'].spelling_suggestion()
+        ctx['search_query'] = self.request.GET.get('q')
+        ctx['stats_results'] = kwargs['object_list'].stats_results()
+        ctx['results_count'] = kwargs['object_list'].count()
+        # raise
+        return ctx
+
+
 def search(request):
     """ For the global search form in navbar """
     form = SearchForm(request.GET or None)
diff --git a/submissions/search_indexes.py b/submissions/search_indexes.py
new file mode 100644
index 000000000..8e602429e
--- /dev/null
+++ b/submissions/search_indexes.py
@@ -0,0 +1,19 @@
+# import datetime
+
+from haystack import indexes
+
+from .models import Submission
+
+
+class SubmissionIndex(indexes.SearchIndex, indexes.Indexable):
+    text = indexes.CharField(document=True, model_attr='title', use_template=True)
+    authors = indexes.CharField(model_attr='authors')
+    date = indexes.DateTimeField(model_attr='submission_date')
+    abstract = indexes.CharField(model_attr='abstract')
+
+    def get_model(self):
+        return Submission
+
+    def index_queryset(self, using=None):
+        """Used when the entire index for model is updated."""
+        return self.get_model().objects.public()
diff --git a/submissions/templates/partials/submissions/search_card.html b/submissions/templates/partials/submissions/search_card.html
new file mode 100644
index 000000000..537ed070c
--- /dev/null
+++ b/submissions/templates/partials/submissions/search_card.html
@@ -0,0 +1 @@
+{% extends 'submissions/_submission_card_content.html' %}
diff --git a/templates/search/search.html b/templates/search/search.html
new file mode 100644
index 000000000..ade8ef6ae
--- /dev/null
+++ b/templates/search/search.html
@@ -0,0 +1,108 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+{# {% load highlight %}#}
+
+{% block pagetitle %}: list{% endblock pagetitle %}
+
+{% block content %}
+
+
+<div class="row">
+    <div class="col-md-4">
+        <h1>Filter</h1>
+        <form method="get">
+            <div class="form-group">
+                <label for="{{form.q.auto_id}}">{{form.q.label}}</label>
+                <input type="text" name="{{form.q.name}}" class="form-control form-control-lg" id="{{form.q.auto_id}}" aria-describedby="search_help" placeholder="Search term" value="{{form.q.value}}" required="required">
+                <small id="search_help" class="form-text text-muted">{{form.q.help_text}}</small>
+            </div>
+
+            <div class="form-group">
+                <label>Type</label>
+            </div>
+            <div class="form-group checkboxes">
+                {% for field in form.models %}
+                    <input type="checkbox" name="{{field.name}}" id="{{field.id_for_label}}" value="{{field.choice_value|stringformat:'s'}}" {% if field.choice_value in form.models.value %}checked="checked"{% endif %}>
+                    <label class="btn btn-info" for="{{field.id_for_label}}">
+                        {{field.choice_label}}
+                    </label>
+
+                {% endfor %}
+            </div>
+
+            {% comment %}
+                {# DISABLED DATE SEARCH: DOES NOT WORK OK #}
+            <label>Date from</label>
+            <div class="form-row">
+                <div class="form-group col-md-6">
+                    {{form.start_1}}
+                </div>
+                <div class="form-group col-md-6">
+                    {{form.start_2}}
+                </div>
+            </div>
+
+            <label>Date until</label>
+            <div class="form-row">
+                <div class="form-group col-md-6">
+                    {{form.end_1}}
+                </div>
+                <div class="form-group col-md-6">
+                    {{form.end_2}}
+                </div>
+            </div>
+            {% endcomment %}
+
+            <div class="form-group">
+                {# <input type="reset" class="btn btn-danger" value="Reset">#}
+                <input type="submit" class="btn btn-primary" value="Search">
+            </div>
+        </form>
+    </div>
+    <div class="col-md-8">
+
+        {% if query %}
+            <h1 class="mb-3">Search results</h1>
+            <ul class="list-group list-group-flush">
+                {% for result in object_list %}
+                    <li class="list-group-item{% if result.content_type == 'journals.publication' %} border-0{% endif %}">
+                        {% if result.content_type == 'submissions.submission' %}
+                            {% include 'partials/submissions/search_card.html' with submission=result.object %}
+                        {% elif result.content_type == 'commentaries.commentary' %}
+                            {% include 'partials/commentaries/search_card.html' with commentary=result.object %}
+                        {% elif result.content_type == 'theses.thesislink' %}
+                            {% include 'partials/theses/search_card.html' with thesislink=result.object %}
+                        {% elif result.content_type == 'comments.comment' %}
+                            <div class="py-2">
+                                {% include 'partials/comments/search_card.html' with comment=result.object %}
+                            </div>
+                        {% elif result.content_type == 'journals.publication' %}
+                            <div class="card card-publication">
+                                {% include 'partials/journals/search_card.html' with publication=result.object %}
+                            </div>
+                        {% else %}
+                            <a href="{{ result.object.get_absolute_url }}">{{ result.object.title }}</a>
+                        {% endif %}
+                    </li>
+                {% empty %}
+                    <p>Your search query did not return any result.</p>
+                {% endfor %}
+
+                {% if is_paginated %}
+                    <p>
+                      {% if page_obj.has_previous %}
+                        <a href="?q={{ query }}&amp;page={{ page_obj.previous_page_number }}">Previous</a>
+                      {% endif %}
+                      Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
+                      {% if page_obj.has_next %}
+                        <a href="?q={{ query }}&amp;page={{ page_obj.next_page_number }}">Next</a>
+                      {% endif %}
+                    </p>
+                {% endif %}
+            </ul>
+        {% endif %}
+    </div>
+</div>
+
+{% endblock content %}
diff --git a/theses/search_indexes.py b/theses/search_indexes.py
new file mode 100644
index 000000000..b75bb610a
--- /dev/null
+++ b/theses/search_indexes.py
@@ -0,0 +1,20 @@
+# import datetime
+
+from haystack import indexes
+
+from .models import ThesisLink
+
+
+class ThesisIndex(indexes.SearchIndex, indexes.Indexable):
+    text = indexes.CharField(document=True, model_attr='title', use_template=True)
+    authors = indexes.CharField(model_attr='author_list')
+    supervisor = indexes.CharField()
+    date = indexes.DateTimeField(model_attr='defense_date')
+    abstract = indexes.CharField(model_attr='abstract')
+
+    def get_model(self):
+        return ThesisLink
+
+    def index_queryset(self, using=None):
+        """Used when the entire index for model is updated."""
+        return self.get_model().objects.vetted()
diff --git a/theses/templates/partials/theses/search_card.html b/theses/templates/partials/theses/search_card.html
new file mode 100644
index 000000000..dc78efd39
--- /dev/null
+++ b/theses/templates/partials/theses/search_card.html
@@ -0,0 +1 @@
+{% extends 'theses/_thesislink_card_content.html' %}
-- 
GitLab