Skip to content
This repository has been archived by the owner on Apr 8, 2023. It is now read-only.

Commit

Permalink
add search through Minutes option
Browse files Browse the repository at this point in the history
  • Loading branch information
julkw committed Feb 26, 2018
1 parent 1a91e72 commit 3aa4aba
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 14 deletions.
4 changes: 4 additions & 0 deletions _1327/minutes/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ class Meta:


Guest.Form = GuestForm


class SearchForm(forms.Form):
search_phrase = forms.CharField()
24 changes: 16 additions & 8 deletions _1327/minutes/templates/minutes_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
{% block sidebar %}
<div class="toc hidden-print">
<ul>
<form class="pb-2" action="{% url 'minutes:search' group_id %}" method="post" id="text_search">
{% csrf_token %}
{{ search_form }}
</form>
{% for year, minutes in minutes_list %}
<li><a href="#year{{ year }}">{{ year }}</a></li>
{% endfor %}
Expand All @@ -19,7 +23,7 @@
{% for year, minutes in minutes_list %}
<h3 id="year{{ year }}">{{ year }}</h3>
<table class="table table-striped">
{% for minute in minutes %}
{% for minute, lines in minutes %}
<tr>
<td style="width: 15%;">
<a href="{{ minute.get_view_url }}">{{ minute.date | date:"d.m.Y" }}</a>
Expand All @@ -46,6 +50,8 @@ <h3 id="year{{ year }}">{{ year }}</h3>
</td>
<td style="width: 55%;">
<a href="{{ minute.get_view_url }}">{{ minute.title }}</a>
{% block linepreview %}
{% endblock %}
</td>
<td style="width: 5%; text-align: center;">
{% if minute.attachments.count > 0 %}
Expand All @@ -58,12 +64,14 @@ <h3 id="year{{ year }}">{{ year }}</h3>
{% endfor %}
</table>
{% empty %}
<em>{% trans "No minutes available." %}</em>
{% url "login" as anchor_url %}
{% if not user.is_authenticated %}
<em>
{% blocktrans with anchor='<a href="'|add:anchor_url|add:'">'|safe anchor_end='</a>'|safe %}You might have to {{ anchor }} login {{ anchor_end }} first.{% endblocktrans %}
</em>
{% endif %}
{% block minutesempty %}
<em>{% trans "No minutes available." %}</em>
{% url "login" as anchor_url %}
{% if not user.is_authenticated %}
<em>
{% blocktrans with anchor='<a href="'|add:anchor_url|add:'">'|safe anchor_end='</a>'|safe %}You might have to {{ anchor }} login {{ anchor_end }} first.{% endblocktrans %}
</em>
{% endif %}
{% endblock %}
{% endfor %}
{% endblock %}
14 changes: 14 additions & 0 deletions _1327/minutes/templates/minutes_with_lines_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends 'minutes_list.html' %}
{% load i18n %}

{% block linepreview %}
{% for line in lines %}
<li>{{ line }}</li>
{% endfor %}
{% endblock %}

{% block minutesempty %}
<em>
{% blocktrans %} No documents containing "{{ phrase }}" found.{% endblocktrans %}
</em>
{% endblock %}
133 changes: 133 additions & 0 deletions _1327/minutes/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,139 @@ def test_no_minutes_available_text(self):
self.assertNotIn('You might have to <a href="/login"> login </a> first.', response.body.decode('utf-8'))


class TestSearchMinutes(WebTest):
csrf_checks = False

@classmethod
def setUpTestData(cls):
cls.user = mommy.make(UserProfile, is_superuser=True)

text1 = "both notO \n Case notB notO \n two notB notO \n two lines notB notO"
text2 = "in both minutes notO \n one notB \n substring notB notO"
text3 = "this will never show up notB notO"
text4 = "<script>alert(Hello);</script> something else"

cls.minutes_document1 = mommy.make(MinutesDocument, text=text1, title="MinutesOne")
cls.minutes_document2 = mommy.make(MinutesDocument, text=text2, title="MinutesTwo")
cls.minutes_document3 = mommy.make(MinutesDocument, text=text3, title="MinutesThree")
cls.minutes_document4 = mommy.make(MinutesDocument, text=text4, title="MinutesFour")
cls.group = mommy.make(Group)
cls.minutes_document1.set_all_permissions(cls.group)
cls.minutes_document2.set_all_permissions(cls.group)
cls.minutes_document3.set_all_permissions(cls.group)
cls.minutes_document4.set_all_permissions(cls.group)

def test_two_minutes_results(self):
search_string = "both"

response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user)

form = response.forms[0]
form.set('search_phrase', search_string)

response = form.submit()

self.assertIn('MinutesOne', response)
self.assertIn('MinutesTwo', response)
self.assertNotIn('MinutesThree', response)

self.assertIn('<b>both</b> notO', response)
self.assertIn('in <b>both</b> minutes notO', response)
self.assertNotIn('notB', response.body.decode('utf-8'))

def test_one_minute_results(self):
search_string = "one"

response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user)

form = response.forms[0]
form.set('search_phrase', search_string)

response = form.submit()

self.assertIn('MinutesTwo', response)
self.assertNotIn('MinutesOne', response)
self.assertNotIn('MinutesThree', response)

self.assertIn('<b>one</b> notB', response)
self.assertNotIn('notO', response)

def test_two_line_results(self):
search_string = "two"

response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user)

form = response.forms[0]
form.set('search_phrase', search_string)

response = form.submit()

self.assertIn('MinutesOne', response)
self.assertNotIn('MinutesTwo', response)
self.assertNotIn('MinutesThree', response)

self.assertIn('<b>two</b> notB notO', response)
self.assertIn('<b>two</b> lines notB notO', response)

def test_case_insensitive_result(self):
search_string = "case"

response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user)

form = response.forms[0]
form.set('search_phrase', search_string)

response = form.submit()

self.assertIn('MinutesOne', response)
self.assertNotIn('MinutesTwo', response)
self.assertNotIn('MinutesThree', response)

self.assertIn('<b>Case</b> notB notO', response)

def test_substring_result(self):
search_string = "bstrin"

response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user)

form = response.forms[0]
form.set('search_phrase', search_string)

response = form.submit()

self.assertIn('MinutesTwo', response)
self.assertNotIn('MinutesOne', response)
self.assertNotIn('MinutesThree', response)

self.assertIn('su<b>bstrin</b>g notB notO', response)

def test_nothing_found_message(self):
search_string = "not in the minutes"

response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user)

form = response.forms[0]
form.set('search_phrase', search_string)

response = form.submit()

self.assertIn('No documents containing "not in the minutes" found.', response.body.decode('utf-8'))
self.assertNotIn('notB', response)
self.assertNotIn('notO', response)

def test_correct_escaping(self):
search_string = "<script>alert(Hello);</script>"

response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user)

form = response.forms[0]
form.set('search_phrase', search_string)

response = form.submit()

self.assertIn('<b>&lt;script&gt;alert(Hello);&lt;/script&gt;</b> something else', response.body.decode('utf-8'))


class TestNewMinutesDocument(WebTest):
csrf_checks = False

Expand Down
1 change: 1 addition & 0 deletions _1327/minutes/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
urlpatterns = [
url(r"list/(?P<groupid>[\d]+)$", views.list, name='list'),
url(r"(?P<title>[\w\-/]+)/edit$", document_views.edit, name='edit'),
url(r"search/(?P<groupid>[\d]+)$", views.search, name='search'),
]
urlpatterns.extend(document_urls.document_urlpatterns)
urlpatterns.extend([
Expand Down
75 changes: 69 additions & 6 deletions _1327/minutes/views.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import re

from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import Http404, render
from django.shortcuts import Http404, redirect, render
from django.utils.html import escape
from django.utils.safestring import mark_safe
from guardian.core import ObjectPermissionChecker

from _1327.minutes.forms import SearchForm
from _1327.minutes.models import MinutesDocument


def list(request, groupid):
def get_permitted_minutes(minutes, request, groupid):
groupid = int(groupid)
try:
group = Group.objects.get(id=groupid)
except ObjectDoesNotExist:
raise Http404
result = {}

own_group = request.user.is_superuser or group in request.user.groups.all()
minutes = MinutesDocument.objects.all().prefetch_related('labels').order_by('-date')

# Prefetch group permissions
group_checker = ObjectPermissionChecker(group)
group_checker.prefetch_perms(minutes)
Expand All @@ -30,6 +34,7 @@ def list(request, groupid):
ip_range_group = Group.objects.get(name=ip_range_group_name)
ip_range_group_checker = ObjectPermissionChecker(ip_range_group)

permitted_minutes = []
for m in minutes:
# we show all documents for which the requested group has edit permissions
# e.g. if you request FSR minutes, all minutes for which the FSR group has edit rights will be shown
Expand All @@ -38,10 +43,68 @@ def list(request, groupid):
# we only show documents for which the user has view permissions
if not user_checker.has_perm(MinutesDocument.get_view_permission(), m) and (not ip_range_group_name or not ip_range_group_checker.has_perm(MinutesDocument.get_view_permission(), m)):
continue
permitted_minutes.append(m)

return permitted_minutes, own_group


def search(request, groupid):
if request.method == 'POST':
form = SearchForm(request.POST)
if form.is_valid():
search_text = form.cleaned_data['search_phrase']
else:
# redirect to minutes list
return redirect("minutes:list", groupid=groupid)

# filter for documents that contain the searched for string
minutes = MinutesDocument.objects.filter(text__icontains=search_text).prefetch_related('labels').order_by('-date')

# only show permitted documents
minutes, own_group = get_permitted_minutes(minutes, request, groupid)

# find lines containing the searched for string
result = {}
for m in minutes:
# find lines with the searched for string and mark it as bold
lines = m.text.splitlines()
lines = [
mark_safe(
re.sub(
r'(' + re.escape(escape(search_text)) + ')',
r'<b>\1</b>', escape(line),
flags=re.IGNORECASE
)
)
for line in lines if (line.casefold().find(search_text.casefold()) != -1)
]

if m.date.year not in result:
result[m.date.year] = []

result[m.date.year].append((m, lines))

return render(request, "minutes_with_lines_list.html", {
'minutes_list': sorted(result.items(), reverse=True),
'own_group': own_group,
'group_id': groupid,
'search_form': SearchForm(),
'phrase': search_text,
})


def list(request, groupid):
minutes = MinutesDocument.objects.all().prefetch_related('labels').order_by('-date')
minutes, own_group = get_permitted_minutes(minutes, request, groupid)

result = {}
for m in minutes:
if m.date.year not in result:
result[m.date.year] = []
result[m.date.year].append(m)
result[m.date.year].append((m, []))
return render(request, "minutes_list.html", {
'minutes_list': sorted(result.items(), reverse=True),
'own_group': own_group
'own_group': own_group,
'group_id': groupid,
'search_form': SearchForm(),
})
3 changes: 3 additions & 0 deletions _1327/static/less/1327.less
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,9 @@ table.diff > thead th:nth-of-type(4) { width: 45%; }
z-index: 99999;
}

.pb-2 {
padding-bottom: 16px;
}

// jquery nestable for menu item sorting
.dd { position: relative; display: block; margin: 0; padding: 0 0 20px 0; list-style: none; font-size: 13px; line-height: 20px; }
Expand Down

0 comments on commit 3aa4aba

Please sign in to comment.