[snowy] Add note searching. Needs lots of UI love though.



commit 6419f018fc52d1d498406cf4df38a3b402aadf20
Author: Brad Taylor <brad getcoded net>
Date:   Fri Jul 24 16:59:43 2009 -0400

    Add note searching.  Needs lots of UI love though.

 TODO                                         |    1 -
 core/templatetags/truncate.py                |   48 ++++++++++++++++++++++++++
 notes/forms.py                               |   25 +++++++++++++
 notes/templates/notes/base.html              |   11 +++++-
 notes/templates/notes/note_list.html         |   37 +-------------------
 notes/templates/notes/note_list_snippet.html |   35 +++++++++++++++++++
 notes/templates/notes/note_search.html       |   24 +++++++++++++
 notes/urls.py                                |    1 +
 notes/views.py                               |   25 ++++++++++++--
 9 files changed, 166 insertions(+), 41 deletions(-)
---
diff --git a/TODO b/TODO
index 63fe53e..4d80258 100644
--- a/TODO
+++ b/TODO
@@ -28,7 +28,6 @@ TODO
 
  * Note View
    - Show which notebooks a note is part of
-   - Note searching
    - Better page to show when current user can't view a note
 
  * Note Editing
diff --git a/core/templatetags/truncate.py b/core/templatetags/truncate.py
new file mode 100644
index 0000000..7c47600
--- /dev/null
+++ b/core/templatetags/truncate.py
@@ -0,0 +1,48 @@
+#
+# From http://www.djangosnippets.org/snippets/1516/
+# Under the public domain as specified at:
+# http://www.djangosnippets.org/about/tos/
+#
+
+from django.template import Library
+from django.utils.encoding import force_unicode
+from django.utils.functional import allow_lazy
+from django.template.defaultfilters import stringfilter
+
+register = Library()
+
+def truncate_chars(s, num):
+    """
+    Template filter to truncate a string to at most num characters respecting word
+    boundaries.
+    """
+    s = force_unicode(s)
+    length = int(num)
+    if len(s) > length:
+        length = length - 3
+        if s[length-1] == ' ' or s[length] == ' ':
+            s = s[:length].strip()
+        else:
+            words = s[:length].split()
+            if len(words) > 1:
+                del words[-1]
+            s = u' '.join(words)
+        s += '...'
+    return s
+truncate_chars = allow_lazy(truncate_chars, unicode)
+
+def truncatechars(value, arg):
+    """
+    Truncates a string after a certain number of characters, but respects word boundaries.
+    
+    Argument: Number of characters to truncate after.
+    """
+    try:
+        length = int(arg)
+    except ValueError: # If the argument is not a valid integer.
+        return value # Fail silently.
+    return truncate_chars(value, length)
+truncatechars.is_safe = True
+truncatechars = stringfilter(truncatechars)
+
+register.filter(truncatechars)
diff --git a/notes/forms.py b/notes/forms.py
new file mode 100644
index 0000000..c93fe57
--- /dev/null
+++ b/notes/forms.py
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2009 Brad Taylor <brad getcoded net>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+from django import forms
+
+class SearchForm(forms.Form):
+    """
+    Form that allows users to search for text in the body and title of their
+    notes.
+    """
+    query = forms.fields.CharField(min_length=2, max_length=30)
diff --git a/notes/templates/notes/base.html b/notes/templates/notes/base.html
index 312f323..4f4cae4 100644
--- a/notes/templates/notes/base.html
+++ b/notes/templates/notes/base.html
@@ -3,7 +3,9 @@
 {% load i18n %}
 
 {% block sidebar %}
-<input type="text" id="search" class="dim" name="search" accesskey="s" value="{% trans "Search your notes" %}">
+<form method="GET" name="searchForm" action="{% url note_search request.user.username %}">
+    <input type="text" id="search" class="dim" name="query" accesskey="s" value="{% trans "Search your notes" %}">
+</form>
 {% endblock %}
 
 {% block extra_body %}
@@ -15,6 +17,13 @@ $(document).ready(function() {
                     .unbind("focus", undim);
     }
     $("#search").bind("focus", undim);
+
+    $('#search').keyup(function(e) {
+        // enter key
+        if (e.keyCode == 13) {
+            document.searchForm.submit();
+        }
+    });
 });
 </script>
 {% endblock %}
diff --git a/notes/templates/notes/note_list.html b/notes/templates/notes/note_list.html
index 98152a1..152dfb1 100644
--- a/notes/templates/notes/note_list.html
+++ b/notes/templates/notes/note_list.html
@@ -1,45 +1,10 @@
 {% extends 'notes/base.html' %}
 
 {% load i18n %}
-{% load humanize %}
-{% load pagination_tags %}
 
 {% block title %}{% trans "All Notes" %} | {{ block.super }}{% endblock %}
 
 {% block content-container %}
 <h1>{% trans "All Notes" %}</h1>
-<table class="object_list" cellspacing="0" cellpadding="0">
-    <thead>
-        <tr>
-            <th></th>
-            <th>{% trans "Title" %}</th>
-            <th>{% trans "Last Modified" %}</th>
-            <th>{% trans "Visibility" %}</th>
-        </tr>
-    </thead>
-    <tbody>
-{% autopaginate notes %}
-{% for note in notes %}
-        <tr class="{% cycle 'row-odd' 'row-even' %}">
-{% if note.pinned %}
-            <td class="icon"><img src="{{ MEDIA_URL }}img/pin-down_16.png" width="16" height="16" alt="Pinned"/></td>
-{% else %}
-            <td class="icon"><img src="{{ MEDIA_URL }}img/note_16.png" width="16" height="16" alt="Not pinned"/></td>
-{% endif %}
-            <td><a href="{{ note.get_absolute_url }}">{{ note.title }}</a></td>
-            <td>{{ note.user_modified|naturalday|title }}</td>
-            <td>{{ note.get_permissions_display }}</td>
-        </tr>
-{% endfor %}
-    </tbody>
-    <tfoot>
-        <tr>
-{% ifnotequal paginator.num_pages 1 %}
-            <td colspan="4">{% paginate %}</td>
-{% else %}
-            <td colspan="4">{% trans "Page 1 of 1" %}</td>
-{% endifnotequal %}
-        </tr>
-    </tfoot>
-</table>
+{% include "notes/note_list_snippet.html" %}
 {% endblock %}
diff --git a/notes/templates/notes/note_list_snippet.html b/notes/templates/notes/note_list_snippet.html
new file mode 100644
index 0000000..e94fc5f
--- /dev/null
+++ b/notes/templates/notes/note_list_snippet.html
@@ -0,0 +1,35 @@
+{% load i18n %}{% load humanize %}{% load pagination_tags %}
+<table class="object_list" cellspacing="0" cellpadding="0">
+    <thead>
+        <tr>
+            <th></th>
+            <th>{% trans "Title" %}</th>
+            <th>{% trans "Last Modified" %}</th>
+            <th>{% trans "Visibility" %}</th>
+        </tr>
+    </thead>
+    <tbody>
+{% autopaginate notes %}
+{% for note in notes %}
+        <tr class="{% cycle 'row-odd' 'row-even' %}">
+{% if note.pinned %}
+            <td class="icon"><img src="{{ MEDIA_URL }}img/pin-down_16.png" width="16" height="16" alt="Pinned"/></td>
+{% else %}
+            <td class="icon"><img src="{{ MEDIA_URL }}img/note_16.png" width="16" height="16" alt="Not pinned"/></td>
+{% endif %}
+            <td><a href="{{ note.get_absolute_url }}">{{ note.title }}</a></td>
+            <td>{{ note.user_modified|naturalday|title }}</td>
+            <td>{{ note.get_permissions_display }}</td>
+        </tr>
+{% endfor %}
+    </tbody>
+    <tfoot>
+        <tr>
+{% ifnotequal paginator.num_pages 1 %}
+            <td colspan="4">{% paginate %}</td>
+{% else %}
+            <td colspan="4">{% blocktrans count paginator.count as c %}{{ c }} result found{% plural %}{{ c }} results found{% endblocktrans %}</td>
+{% endifnotequal %}
+        </tr>
+    </tfoot>
+</table>
diff --git a/notes/templates/notes/note_search.html b/notes/templates/notes/note_search.html
new file mode 100644
index 0000000..1b85cb5
--- /dev/null
+++ b/notes/templates/notes/note_search.html
@@ -0,0 +1,24 @@
+{% extends 'notes/base.html' %}
+
+{% load i18n %}
+{% load truncate %}
+
+{% block title %}{% blocktrans with query|truncatechars:20 as q %}Notes matching "{{ q }}"{% endblocktrans %} | {{ block.super }}{% endblock %}
+
+{% block content-container %}
+<h1>{% trans "Search" %}</h1>
+<form method="GET">
+    <table class="input-form">
+        {{ form.as_table }}
+        <tfoot>
+            <tr>
+                <th></th>
+                <td>
+                    <input type="submit" value="{% trans "Search" %}"/>
+                </td>
+            </tr>
+        </tfoot>
+    </table>
+</form>
+{% include "notes/note_list_snippet.html" %}
+{% endblock %}
diff --git a/notes/urls.py b/notes/urls.py
index 6bc35cc..6f3a68f 100644
--- a/notes/urls.py
+++ b/notes/urls.py
@@ -21,6 +21,7 @@ from snowy.notes.models import Note
 urlpatterns = patterns('',
     url(r'^$', 'snowy.notes.views.note_index', name='note_index'),
     url(r'^list/$', 'snowy.notes.views.note_list', name='note_list'),
+    url(r'^search/$', 'snowy.notes.views.note_search', name='note_search'),
     url(r'^(?P<note_id>\d+)/$', 'snowy.notes.views.note_detail', name='note_detail_no_slug'),
     url(r'^(?P<note_id>\d+)/(?P<slug>[^/]+)/$', 'snowy.notes.views.note_detail', name='note_detail'),
 )
diff --git a/notes/views.py b/notes/views.py
index 24b8ba4..ec0abba 100644
--- a/notes/views.py
+++ b/notes/views.py
@@ -17,13 +17,15 @@
 
 from django.http import HttpResponseRedirect, HttpResponseForbidden, Http404
 from django.shortcuts import render_to_response, get_object_or_404
-from django.core.paginator import Paginator
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.urlresolvers import reverse
+from django.core.paginator import Paginator
 from django.contrib.auth.models import User
 from django.template import RequestContext
+from django.db.models import Q
 
 from snowy.notes.templates import CONTENT_TEMPLATES, DEFAULT_CONTENT_TEMPLATE
+from snowy.notes.forms import SearchForm
 from snowy.notes.models import *
 from snowy import settings
 
@@ -45,12 +47,29 @@ def note_index(request, username,
 def note_list(request, username,
               template_name='notes/note_list.html'):
     author = get_object_or_404(User, username=username)
-    notes = Note.objects.user_viewable(request.user, author) \
-                        .order_by('-user_modified')
+    notes = Note.objects.user_viewable(request.user, author)
     return render_to_response(template_name,
                               {'notes': notes},
                               context_instance=RequestContext(request))
 
+def note_search(request, username,
+                template_name='notes/note_search.html'):
+    author = get_object_or_404(User, username=username)
+    notes = []
+    if request.method == 'GET':
+        form = SearchForm(request.GET)
+        if form.is_valid():
+            query = request.GET['query']
+            notes = Note.objects.user_viewable(request.user, author) \
+                                .filter(Q(title__icontains=query) \
+                                        | Q(content__icontains=query))
+    else:
+        form = SearchForm()
+
+    return render_to_response(template_name,
+                              {'notes': notes, 'form': form},
+                              context_instance=RequestContext(request))
+
 def note_detail(request, username, note_id, slug='',
                 template_name='notes/note_detail.html'):
     def clean_content(xml, author):



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]