From 4d0236f8a74103f9345db3cdbf6acad8a457d08f Mon Sep 17 00:00:00 2001 From: Juliane Waack Date: Mon, 30 Apr 2018 20:16:13 +0200 Subject: [PATCH 1/4] add search for all documents --- _1327/documents/models.py | 9 ++++ _1327/documents/utils.py | 41 ++++++++++++++++++ _1327/main/templates/searched_list.html | 55 +++++++++++++++++++++++++ _1327/main/views.py | 51 +++++++++++++++++++++++ _1327/minutes/views.py | 47 ++------------------- _1327/templates/base.html | 24 +++++++++++ _1327/urls.py | 2 + 7 files changed, 186 insertions(+), 43 deletions(-) create mode 100644 _1327/main/templates/searched_list.html diff --git a/_1327/documents/models.py b/_1327/documents/models.py index 0522006c..26d561c0 100644 --- a/_1327/documents/models.py +++ b/_1327/documents/models.py @@ -35,6 +35,11 @@ def get_hash(): DOCUMENT_LINK_REGEX = r'\[(?P[^\[]+)\]\(document:(?P<id>\d+)\)' VIEW_PERMISSION_NAME = DOCUMENT_VIEW_PERMISSION_NAME + PLURAL_CONTENT_TYPE_NAME = { + 'Information document': _('Information documents'), + 'Minutes': _('Minutes'), + 'poll': _('Polls') + } class Meta: verbose_name = _("Document") @@ -103,6 +108,10 @@ def authors(self): authors.add(version.revision.user) return authors + def plural_content_type(self): + content_type = ContentType.objects.get_for_model(self) + return Document.PLURAL_CONTENT_TYPE_NAME[str(content_type)] + @classmethod def generate_new_title(cls): return _("New Page from {}").format(str(datetime.now())) diff --git a/_1327/documents/utils.py b/_1327/documents/utils.py index 07e029fe..7d84520b 100644 --- a/_1327/documents/utils.py +++ b/_1327/documents/utils.py @@ -4,10 +4,14 @@ from django.conf import settings +from django.contrib.auth.models import Group from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import SuspiciousOperation from django.db import transaction +from django.shortcuts import Http404 from django.utils import timezone +from guardian.core import ObjectPermissionChecker from reversion import revisions from reversion.models import Version @@ -174,3 +178,40 @@ def delete_cascade_to_json(cascade): "name": str(cascade_item), }) return items + + +def get_permitted_documents(documents, request, groupid): + groupid = int(groupid) + try: + group = Group.objects.get(id=groupid) + except ObjectDoesNotExist: + raise Http404 + + own_group = request.user.is_superuser or group in request.user.groups.all() + + # Prefetch group permissions + group_checker = ObjectPermissionChecker(group) + group_checker.prefetch_perms(documents) + + # Prefetch user permissions + user_checker = ObjectPermissionChecker(request.user) + user_checker.prefetch_perms(documents) + + # Prefetch ip group permissions + ip_range_group_name = request.user._ip_range_group_name if hasattr(request.user, '_ip_range_group_name') else None + if ip_range_group_name: + ip_range_group = Group.objects.get(name=ip_range_group_name) + ip_range_group_checker = ObjectPermissionChecker(ip_range_group) + + permitted_documents = [] + for d in documents: + # we show all documents for which the requested group has edit permissions + # e.g. if you request FSR documents, all documents for which the FSR group has edit rights will be shown + if not group_checker.has_perm(d.edit_permission_name, d): + continue + # we only show documents for which the user has view permissions + if not user_checker.has_perm(Document.get_view_permission(), d) and (not ip_range_group_name or not ip_range_group_checker.has_perm(Document.get_view_permission(), d)): + continue + permitted_documents.append(d) + + return permitted_documents, own_group diff --git a/_1327/main/templates/searched_list.html b/_1327/main/templates/searched_list.html new file mode 100644 index 00000000..1bb4270c --- /dev/null +++ b/_1327/main/templates/searched_list.html @@ -0,0 +1,55 @@ +{% extends 'base_with_sidebar.html' %} +{% load i18n %} + +{% block title %} + {% blocktrans count counter=2 %}Documents{% plural %}Documents{% endblocktrans %} +{% endblock %} + +{% block sidebar %} + <div class="toc hidden-print"> + <ul> + {% for type, documents in searched_documents %} + <li><a href="#type{{ type }}">{{ type }}</a></li> + {% endfor %} + </ul> + </div> +{% endblock %} + +{% block content %} + {% for type, documents in searched_documents %} + <h3 id="type{{ type }}">{{ type }}</h3> + <table class="table table-striped"> + {% for document, lines in documents %} + <tr> + <td style="width: 55%;"> + <a href="{{ document.get_view_url }}">{{ document.title}} {{ document.date| date:"d.m.Y" }}</a> + <ul class="minutes-lines"> + {% for line in lines %} + <li>{{ line }}</li> + {% endfor %} + </ul> + </td> + <td style="width: 5%; text-align: center;"> + {% if document.attachments.count > 0 %} + <span class="text-gray" data-toggle="tooltip" data-placement="left" data-container="body" title="{{ document.attachments.all|join:', ' }}"> + <span class="glyphicon glyphicon-file" aria-hidden="true"></span> + </span> + {% endif %} + </td> + </tr> + {% endfor %} + </table> + {% empty %} + {% block searched_documentsempty %} + <em> + {% blocktrans %}No documents containing "{{ phrase }}" found.{% endblocktrans %} + </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 %} diff --git a/_1327/main/views.py b/_1327/main/views.py index 389ed0ab..1e8289aa 100644 --- a/_1327/main/views.py +++ b/_1327/main/views.py @@ -1,5 +1,7 @@ import json +import re + from django.conf import settings from django.contrib import messages from django.contrib.auth.models import Group @@ -9,6 +11,8 @@ from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, Http404, redirect, render from django.urls import reverse +from django.utils.html import escape +from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from django.views.decorators.http import require_POST from guardian.shortcuts import get_objects_for_user @@ -170,3 +174,50 @@ def abbreviation_explanation_edit(request): return redirect('abbreviation_explanation') else: return render(request, "abbreviation_explanation.html", dict(formset=formset)) + + +def search(request): + search_text = None + if request.method == 'POST': + search_text = request.POST.get('search_phrase') + + if not search_text: + return render(request, "searched_list.html", { + 'searched_documents': [], + 'phrase': "", + }) + + # filter for documents that contain the searched for string + documents = Document.objects.filter(text__icontains=search_text) + + # find documents and lines containing the searched for string + result = {} + for d in documents: + # check if the user has permission to view the document + if not (request.user.has_perm(d.view_permission_name) or request.user.has_perm(d.view_permission_name, d)): + continue + # find lines with the searched for string and mark it as bold + lines = d.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) + ] + content_type = d.plural_content_type() + + if content_type not in result: + result[content_type] = [] + + result[content_type].append((d, lines)) + + if 'Minutes' in result: + result['Minutes'].sort(key=lambda minute: minute[0].date, reverse=True) + return render(request, "searched_list.html", { + 'searched_documents': result.items(), + 'phrase': search_text, + }) diff --git a/_1327/minutes/views.py b/_1327/minutes/views.py index 94e51112..58fb82d6 100644 --- a/_1327/minutes/views.py +++ b/_1327/minutes/views.py @@ -1,53 +1,14 @@ import re -from django.contrib.auth.models import Group -from django.core.exceptions import ObjectDoesNotExist -from django.shortcuts import Http404, redirect, render +from django.shortcuts import redirect, render from django.utils.html import escape from django.utils.safestring import mark_safe -from guardian.core import ObjectPermissionChecker +from _1327.documents.utils import get_permitted_documents from _1327.minutes.forms import SearchForm from _1327.minutes.models import MinutesDocument -def get_permitted_minutes(minutes, request, groupid): - groupid = int(groupid) - try: - group = Group.objects.get(id=groupid) - except ObjectDoesNotExist: - raise Http404 - - own_group = request.user.is_superuser or group in request.user.groups.all() - - # Prefetch group permissions - group_checker = ObjectPermissionChecker(group) - group_checker.prefetch_perms(minutes) - - # Prefetch user permissions - user_checker = ObjectPermissionChecker(request.user) - user_checker.prefetch_perms(minutes) - - # Prefetch ip group permissions - ip_range_group_name = request.user._ip_range_group_name if hasattr(request.user, '_ip_range_group_name') else None - if ip_range_group_name: - 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 - if not group_checker.has_perm(m.edit_permission_name, m): - continue - # 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) @@ -61,7 +22,7 @@ def search(request, groupid): 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) + minutes, own_group = get_permitted_documents(minutes, request, groupid) # find lines containing the searched for string result = {} @@ -95,7 +56,7 @@ def search(request, groupid): def list(request, groupid): minutes = MinutesDocument.objects.all().prefetch_related('labels').order_by('-date') - minutes, own_group = get_permitted_minutes(minutes, request, groupid) + minutes, own_group = get_permitted_documents(minutes, request, groupid) result = {} for m in minutes: diff --git a/_1327/templates/base.html b/_1327/templates/base.html index adade878..88479853 100644 --- a/_1327/templates/base.html +++ b/_1327/templates/base.html @@ -95,6 +95,18 @@ </ul> </li> {% endif %} + {# Search form #} + <li class ="pull-right"> + <a href="#" id="popover" data-placement="bottom"> + <span class="glyphicon glyphicon-search" aria-hidden="true"></span> + </a> + <div id="popover-content" class="hide"> + <form action='/search' method='post'> + {% csrf_token %} + <input type='text' name='search_phrase'> + </form> + </div> + </li> </ul> </nav> @@ -244,5 +256,17 @@ $(this).parent().removeClass('menu-hover'); }); }); + + // initialize popovers + $(function () { + $('[data-toggle="popover"]').popover() + }); + + $('#popover').popover({ + html : true, + content: function() { + return $("#popover-content").html(); + } + }); </script> {% endblock %} diff --git a/_1327/urls.py b/_1327/urls.py index 606881f5..e9316b2b 100644 --- a/_1327/urls.py +++ b/_1327/urls.py @@ -27,6 +27,8 @@ url(r"^menu_item_delete$", main_views.menu_item_delete, name="menu_item_delete"), url(r"^menu_item/update_order$", main_views.menu_items_update_order, name="menu_items_update_order"), + url(r"search$", main_views.search, name='search'), + url(r'^shortlinks$', shortlinks_views.shortlinks_index, name='shortlinks_index'), url(r'^shortlink/create$', shortlinks_views.shortlink_create, name='shortlink_create'), url(r'^shortlink/delete$', shortlinks_views.shortlink_delete, name='shortlink_delete'), From e6b4698e5720b1e10d36773574fa3de3ea900277 Mon Sep 17 00:00:00 2001 From: Juliane Waack <jkwaack@gmail.com> Date: Mon, 30 Jul 2018 19:06:13 +0200 Subject: [PATCH 2/4] add tests --- _1327/main/templates/menu_item_edit.html | 2 +- _1327/main/tests.py | 125 +++++++++++++++++++-- _1327/minutes/templates/minutes_list.html | 2 +- _1327/minutes/tests.py | 14 +-- _1327/polls/templates/polls_vote.html | 2 +- _1327/polls/tests.py | 2 +- _1327/templates/base.html | 2 +- _1327/user_management/templates/login.html | 2 +- _1327/user_management/tests.py | 12 +- 9 files changed, 132 insertions(+), 31 deletions(-) diff --git a/_1327/main/templates/menu_item_edit.html b/_1327/main/templates/menu_item_edit.html index b528f610..d767bbc5 100644 --- a/_1327/main/templates/menu_item_edit.html +++ b/_1327/main/templates/menu_item_edit.html @@ -13,7 +13,7 @@ {% endblock %} {% block content %} - <form method="post" class="form-horizontal" role="form" enctype="multipart/form-data"> + <form method="post" class="form-horizontal" role="form" enctype="multipart/form-data" id="menu_item_edit"> {% bootstrap_form form layout='horizontal' %} {% csrf_token %} {% if formset %} diff --git a/_1327/main/tests.py b/_1327/main/tests.py index 9643d0ea..f69ebf32 100644 --- a/_1327/main/tests.py +++ b/_1327/main/tests.py @@ -16,6 +16,7 @@ from _1327.information_pages.models import InformationDocument from _1327.main.utils import find_root_menu_items from _1327.minutes.models import MinutesDocument +from _1327.polls.models import Poll from _1327.user_management.models import UserProfile from .context_processors import mark_selected from .models import MenuItem @@ -105,7 +106,7 @@ def test_create_menu_item_as_superuser_no_document_and_link(self): self.assertEqual(response.status_code, 200) self.assertIn("Link", response.body.decode('utf-8')) - form = response.form + form = response.forms['menu_item_edit'] form['group'].select(text=self.staff_group.name) response = form.submit() @@ -118,7 +119,7 @@ def test_create_menu_item_as_superuser_document_and_link(self): document = mommy.make(InformationDocument) response = self.app.get(reverse('menu_item_create'), user=self.root_user) - form = response.form + form = response.forms['menu_item_edit'] form['link'] = 'polls:index' form['document'].select(text=document.title) form['group'].select(text=self.staff_group.name) @@ -132,7 +133,7 @@ def test_create_menu_item_as_superuser_with_link(self): menu_item_count = MenuItem.objects.count() response = self.app.get(reverse('menu_item_create'), user=self.root_user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['link'] = 'polls:index' form['group'].select(text=self.staff_group.name) @@ -146,7 +147,7 @@ def test_create_menu_item_as_superuser_with_link_and_param(self): menu_item_count = MenuItem.objects.count() response = self.app.get(reverse('menu_item_create'), user=self.root_user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['link'] = 'minutes:list?groupid={}'.format(self.staff_group.id) form['group'].select(text=self.staff_group.name) @@ -160,7 +161,7 @@ def test_create_menu_item_as_superuser_wrong_link(self): menu_item_count = MenuItem.objects.count() response = self.app.get(reverse('menu_item_create'), user=self.root_user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['link'] = 'polls:index?kekse?kekse2' form['group'].select(text=self.staff_group.name) @@ -174,7 +175,7 @@ def test_create_menu_item_as_superuser_wrong_link_2(self): menu_item_count = MenuItem.objects.count() response = self.app.get(reverse('menu_item_create'), user=self.root_user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['link'] = 'www.example.com' form['group'].select(text=self.staff_group.name) @@ -189,7 +190,7 @@ def test_create_menu_item_as_superuser_with_document(self): document = mommy.make(InformationDocument) response = self.app.get(reverse('menu_item_create'), user=self.root_user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['document'].select(text=document.title) form['group'].select(text=self.staff_group.name) @@ -208,7 +209,7 @@ def test_create_menu_item_as_normal_user_no_document_and_link(self): menu_item_count = MenuItem.objects.count() response = self.app.get(reverse('menu_item_create'), user=self.user) - form = response.form + form = response.forms['menu_item_edit'] form['group'].select(text=self.staff_group.name) response = form.submit() @@ -221,7 +222,7 @@ def test_create_menu_item_as_normal_user_with_document(self): document = mommy.make(InformationDocument) response = self.app.get(reverse('menu_item_create'), user=self.user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['document'].select(text=document.title) form['group'].select(text=self.staff_group.name) @@ -237,7 +238,7 @@ def test_create_menu_item_as_normal_user_with_document_without_parent(self): document = mommy.make(InformationDocument) response = self.app.get(reverse('menu_item_create'), user=self.user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['document'].select(text=document.title) form['group'].select(text=self.staff_group.name) @@ -253,7 +254,7 @@ def test_create_menu_wrong_group(self): group = mommy.make(Group) response = self.app.get(reverse('menu_item_create'), user=self.user) - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'test title' form['document'].select(text=document.title) form['group'].force_value(group.id) @@ -401,7 +402,7 @@ def test_menu_item_edit(self): original_menu_item = self.sub_item - form = response.form + form = response.forms['menu_item_edit'] form['title'] = 'Lorem Ipsum' form['document'] = document.pk @@ -522,3 +523,103 @@ def test_remind_users_about_due_unpublished_minutes_documents(self): management.call_command('send_reminders') self.assertEqual(len(mail.outbox), 2) + + +class TestSearch(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 \n all three notB notO" + text2 = "in both minutes notO \n one notB \n substring notB notO \n all three notB notO" + text3 = "all three notB notO" + text4 = "<script>alert(Hello);</script> something else" + text5 = "this will never show up notB notO" + + cls.minutes_document = mommy.make(MinutesDocument, text=text1, title="TestMinute") + cls.poll = mommy.make(Poll, text=text2, title="TestPoll") + cls.information_document = mommy.make(InformationDocument, text=text3, title="TestInformationDocument") + cls.minutes_document_w_script = mommy.make(MinutesDocument, text=text4, title="TestMinuteWithScript") + cls.information_document_never = mommy.make(InformationDocument, text=text5, title="TestInformationDocumentNever") + cls.group = mommy.make(Group) + cls.minutes_document.set_all_permissions(cls.group) + cls.poll.set_all_permissions(cls.group) + cls.information_document.set_all_permissions(cls.group) + cls.minutes_document_w_script.set_all_permissions(cls.group) + + def test_all_types_of_documents(self): + search_string = "all three" + + response = self.app.get(reverse('index'), user=self.user) + + form = response.forms["general_search"] + form.set('search_phrase', search_string) + + response = form.submit() + + self.assertIn('TestMinute', response) + self.assertIn('TestPoll', response) + self.assertIn('TestInformationDocument', response) + self.assertNotIn('TestInformationDocumentNever', response) + + def test_case_insensitive_result(self): + search_string = "case" + + response = self.app.get(reverse('index'), user=self.user) + + form = response.forms["general_search"] + form.set('search_phrase', search_string) + + response = form.submit() + + self.assertIn('TestMinute', response) + self.assertNotIn('TestPoll', response) + self.assertNotIn('TestInformationDocument', response) + self.assertNotIn('TestInformationDocumentNever', response) + + self.assertIn('<b>Case</b> notB notO', response) + + def test_substring_result(self): + search_string = "bstrin" + + response = self.app.get(reverse('index'), user=self.user) + + form = response.forms["general_search"] + form.set('search_phrase', search_string) + + response = form.submit() + + self.assertIn('TestPoll', response) + self.assertNotIn('TestMinute', response) + self.assertNotIn('TestInformationDocument', response) + self.assertNotIn('TestInformationDocumentNever', 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('index'), user=self.user) + + form = response.forms["general_search"] + 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('index'), user=self.user) + + form = response.forms["general_search"] + form.set('search_phrase', search_string) + + response = form.submit() + + self.assertIn('<b><script>alert(Hello);</script></b> something else', response.body.decode('utf-8')) diff --git a/_1327/minutes/templates/minutes_list.html b/_1327/minutes/templates/minutes_list.html index 812e656b..4b305ba1 100644 --- a/_1327/minutes/templates/minutes_list.html +++ b/_1327/minutes/templates/minutes_list.html @@ -8,7 +8,7 @@ {% block sidebar %} <div class="toc hidden-print"> <ul> - <form class="pb-2" action="{% url 'minutes:search' group_id %}" method="post" id="text_search"> + <form class="pb-2" action="{% url 'minutes:search' group_id %}" method="post" id="minutes_search"> {% csrf_token %} {{ search_form }} </form> diff --git a/_1327/minutes/tests.py b/_1327/minutes/tests.py index bcbaabb0..765eff20 100644 --- a/_1327/minutes/tests.py +++ b/_1327/minutes/tests.py @@ -269,7 +269,7 @@ def test_two_minutes_results(self): response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user) - form = response.forms[0] + form = response.forms["minutes_search"] form.set('search_phrase', search_string) response = form.submit() @@ -287,7 +287,7 @@ def test_one_minute_results(self): response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user) - form = response.forms[0] + form = response.forms["minutes_search"] form.set('search_phrase', search_string) response = form.submit() @@ -304,7 +304,7 @@ def test_two_line_results(self): response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user) - form = response.forms[0] + form = response.forms["minutes_search"] form.set('search_phrase', search_string) response = form.submit() @@ -321,7 +321,7 @@ def test_case_insensitive_result(self): response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user) - form = response.forms[0] + form = response.forms["minutes_search"] form.set('search_phrase', search_string) response = form.submit() @@ -337,7 +337,7 @@ def test_substring_result(self): response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user) - form = response.forms[0] + form = response.forms["minutes_search"] form.set('search_phrase', search_string) response = form.submit() @@ -353,7 +353,7 @@ def test_nothing_found_message(self): response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user) - form = response.forms[0] + form = response.forms["minutes_search"] form.set('search_phrase', search_string) response = form.submit() @@ -367,7 +367,7 @@ def test_correct_escaping(self): response = self.app.get(reverse("minutes:list", args=[self.group.id]), user=self.user) - form = response.forms[0] + form = response.forms["minutes_search"] form.set('search_phrase', search_string) response = form.submit() diff --git a/_1327/polls/templates/polls_vote.html b/_1327/polls/templates/polls_vote.html index 24c5e29e..37dde692 100644 --- a/_1327/polls/templates/polls_vote.html +++ b/_1327/polls/templates/polls_vote.html @@ -15,7 +15,7 @@ </div> {% endif %} - <form action="{% url document.get_view_url_name document.url_title %}" method="post" class="form-horizontal" role="form"> + <form action="{% url document.get_view_url_name document.url_title %}" method="post" class="form-horizontal" role="form" id="polls_vote"> {% csrf_token %} <table class="table table-striped"> <tr> diff --git a/_1327/polls/tests.py b/_1327/polls/tests.py index 630a53aa..75ea8a4d 100644 --- a/_1327/polls/tests.py +++ b/_1327/polls/tests.py @@ -582,7 +582,7 @@ def test_vote_poll_with_results_that_can_not_be_seen_immediately(self): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'polls_vote.html') - form = response.form + form = response.forms['polls_vote'] form['choice'] = self.poll.choices.first().pk response = form.submit() diff --git a/_1327/templates/base.html b/_1327/templates/base.html index 88479853..1748a9a3 100644 --- a/_1327/templates/base.html +++ b/_1327/templates/base.html @@ -101,7 +101,7 @@ <span class="glyphicon glyphicon-search" aria-hidden="true"></span> </a> <div id="popover-content" class="hide"> - <form action='/search' method='post'> + <form action='/search' method='post' id='general_search'> {% csrf_token %} <input type='text' name='search_phrase'> </form> diff --git a/_1327/user_management/templates/login.html b/_1327/user_management/templates/login.html index 095170d2..e3ec4255 100644 --- a/_1327/user_management/templates/login.html +++ b/_1327/user_management/templates/login.html @@ -12,7 +12,7 @@ {% trans "Please login to see this page." %} </div> {% endif %} - <form action="{% url 'login' %}" method="post" class="form-horizontal" role="form"> + <form action="{% url 'login' %}" method="post" class="form-horizontal" role="form" id="login"> {% bootstrap_form form layout='horizontal' bound_css_class='has-error' %} <input type="hidden" name="next" value="{{ next }}" /> {% csrf_token %} diff --git a/_1327/user_management/tests.py b/_1327/user_management/tests.py index 6aafbe63..41178276 100644 --- a/_1327/user_management/tests.py +++ b/_1327/user_management/tests.py @@ -30,7 +30,7 @@ def setUpTestData(cls): def test_login(self): page = self.app.get("/login", user="") - login_form = page.forms[0] + login_form = page.forms[1] login_form['username'] = "user" login_form['password'] = "wrong_password" response = login_form.submit() @@ -38,7 +38,7 @@ def test_login(self): self.assertNotIn('has-success', response.body.decode('utf-8')) self.assertIn('has-error', response.body.decode('utf-8')) - login_form = page.forms[0] + login_form = page.forms[1] login_form['username'] = "user" login_form['password'] = "test" @@ -47,7 +47,7 @@ def test_login(self): def test_login_with_camel_case_name(self): page = self.app.get('/login') - login_form = page.forms[0] + login_form = page.forms[1] login_form['username'] = "test testman" login_form['password'] = "test" self.assertEqual(login_form.submit().status_code, 302) @@ -58,7 +58,7 @@ def test_login_with_camel_case_name(self): # now try to login again, but with camel case name page = self.app.get('/login') - login_form = page.forms[0] + login_form = page.forms[1] login_form['username'] = "Test Testman" login_form['password'] = "test" response = login_form.submit() @@ -73,7 +73,7 @@ def test_login_redirect_sufficient_permissions(self): self.assertRedirects(response, redirect_url) response = response.follow() - login_form = response.forms[0] + login_form = response.forms['login'] login_form['username'] = "user" login_form['password'] = "test" @@ -90,7 +90,7 @@ def test_login_insufficient_permissions(self): self.assertRedirects(response, redirect_url) response = response.follow() - login_form = response.forms[0] + login_form = response.forms['login'] login_form['username'] = "user" login_form['password'] = "test" From 6f33b8b5f76aab70d24d9fc46cccb5c2cd5e33be Mon Sep 17 00:00:00 2001 From: Juliane Waack <jkwaack@gmail.com> Date: Mon, 30 Jul 2018 21:39:26 +0200 Subject: [PATCH 3/4] fixes to the pr --- _1327/documents/models.py | 9 ------- _1327/documents/utils.py | 12 ++++----- _1327/main/templates/searched_list.html | 6 ++--- _1327/main/tests.py | 29 ++++++++++++++++++++++ _1327/main/views.py | 3 +-- _1327/polls/models.py | 3 +++ _1327/templates/base.html | 2 +- _1327/user_management/templates/login.html | 2 +- _1327/user_management/tests.py | 12 ++++----- 9 files changed, 50 insertions(+), 28 deletions(-) diff --git a/_1327/documents/models.py b/_1327/documents/models.py index 26d561c0..0522006c 100644 --- a/_1327/documents/models.py +++ b/_1327/documents/models.py @@ -35,11 +35,6 @@ def get_hash(): DOCUMENT_LINK_REGEX = r'\[(?P<title>[^\[]+)\]\(document:(?P<id>\d+)\)' VIEW_PERMISSION_NAME = DOCUMENT_VIEW_PERMISSION_NAME - PLURAL_CONTENT_TYPE_NAME = { - 'Information document': _('Information documents'), - 'Minutes': _('Minutes'), - 'poll': _('Polls') - } class Meta: verbose_name = _("Document") @@ -108,10 +103,6 @@ def authors(self): authors.add(version.revision.user) return authors - def plural_content_type(self): - content_type = ContentType.objects.get_for_model(self) - return Document.PLURAL_CONTENT_TYPE_NAME[str(content_type)] - @classmethod def generate_new_title(cls): return _("New Page from {}").format(str(datetime.now())) diff --git a/_1327/documents/utils.py b/_1327/documents/utils.py index 7d84520b..6dc6f589 100644 --- a/_1327/documents/utils.py +++ b/_1327/documents/utils.py @@ -197,21 +197,21 @@ def get_permitted_documents(documents, request, groupid): user_checker = ObjectPermissionChecker(request.user) user_checker.prefetch_perms(documents) - # Prefetch ip group permissions - ip_range_group_name = request.user._ip_range_group_name if hasattr(request.user, '_ip_range_group_name') else None + # Prefetch ip-range group permissions + ip_range_group_name = getattr(request.user, '_ip_range_group_name', None) if ip_range_group_name: ip_range_group = Group.objects.get(name=ip_range_group_name) ip_range_group_checker = ObjectPermissionChecker(ip_range_group) permitted_documents = [] - for d in documents: + for document in documents: # we show all documents for which the requested group has edit permissions # e.g. if you request FSR documents, all documents for which the FSR group has edit rights will be shown - if not group_checker.has_perm(d.edit_permission_name, d): + if not group_checker.has_perm(document.edit_permission_name, document): continue # we only show documents for which the user has view permissions - if not user_checker.has_perm(Document.get_view_permission(), d) and (not ip_range_group_name or not ip_range_group_checker.has_perm(Document.get_view_permission(), d)): + if not user_checker.has_perm(Document.get_view_permission(), document) and (not ip_range_group_name or not ip_range_group_checker.has_perm(Document.get_view_permission(), document)): continue - permitted_documents.append(d) + permitted_documents.append(document) return permitted_documents, own_group diff --git a/_1327/main/templates/searched_list.html b/_1327/main/templates/searched_list.html index 1bb4270c..50c04ff0 100644 --- a/_1327/main/templates/searched_list.html +++ b/_1327/main/templates/searched_list.html @@ -17,12 +17,12 @@ {% block content %} {% for type, documents in searched_documents %} - <h3 id="type{{ type }}">{{ type }}</h3> + <h3 name="type{{ type }}">{{ type }}</h3> <table class="table table-striped"> {% for document, lines in documents %} <tr> - <td style="width: 55%;"> - <a href="{{ document.get_view_url }}">{{ document.title}} {{ document.date| date:"d.m.Y" }}</a> + <td class="width: 95%;"> + <a href="{{ document.get_view_url }}">{{ document.title}} {% if document.date %}({{ document.date| date:"d.m.Y" }}){% endif %}</a> <ul class="minutes-lines"> {% for line in lines %} <li>{{ line }}</li> diff --git a/_1327/main/tests.py b/_1327/main/tests.py index f69ebf32..dc24b289 100644 --- a/_1327/main/tests.py +++ b/_1327/main/tests.py @@ -548,6 +548,35 @@ def setUpTestData(cls): cls.poll.set_all_permissions(cls.group) cls.information_document.set_all_permissions(cls.group) cls.minutes_document_w_script.set_all_permissions(cls.group) + cls.information_document_never.set_all_permissions(cls.group) + + def test_no_permission(self): + user = mommy.make(UserProfile) + search_string = "both" + + response = self.app.get(reverse('index'), user=user) + form = response.forms["general_search"] + form.set('search_phrase', search_string) + + response = form.submit() + + self.assertIn('No documents containing "both" found.', response) + self.assertNotIn('TestMinute', response) + self.assertNotIn('TestPoll', response) + + def test_some_permissions(self): + user = mommy.make(UserProfile) + self.minutes_document.set_all_permissions(user) + search_string = "both" + + response = self.app.get(reverse('index'), user=user) + form = response.forms["general_search"] + form.set('search_phrase', search_string) + + response = form.submit() + + self.assertIn('TestMinute', response) + self.assertNotIn('TestPoll', response) def test_all_types_of_documents(self): search_string = "all three" diff --git a/_1327/main/views.py b/_1327/main/views.py index 1e8289aa..67dea4d5 100644 --- a/_1327/main/views.py +++ b/_1327/main/views.py @@ -1,5 +1,4 @@ import json - import re from django.conf import settings @@ -208,7 +207,7 @@ def search(request): ) for line in lines if (line.casefold().find(search_text.casefold()) != -1) ] - content_type = d.plural_content_type() + content_type = ContentType.objects.get_for_model(d) if content_type not in result: result[content_type] = [] diff --git a/_1327/polls/models.py b/_1327/polls/models.py index 1ef6b304..1b2b0c69 100644 --- a/_1327/polls/models.py +++ b/_1327/polls/models.py @@ -40,6 +40,9 @@ def can_be_reverted(self): POLLS_LINK_REGEX = r'\[(?P<title>[^\[]+)\]\(poll:(?P<id>\d+)\)' class Meta: + + verbose_name = _("Poll") + verbose_name_plural = _("Polls") permissions = ( (POLL_VIEW_PERMISSION_NAME, 'User/Group is allowed to view that poll'), (POLL_VOTE_PERMISSION_NAME, 'User/Group is allowed to participate (vote) in that poll'), diff --git a/_1327/templates/base.html b/_1327/templates/base.html index 1748a9a3..795555a1 100644 --- a/_1327/templates/base.html +++ b/_1327/templates/base.html @@ -101,7 +101,7 @@ <span class="glyphicon glyphicon-search" aria-hidden="true"></span> </a> <div id="popover-content" class="hide"> - <form action='/search' method='post' id='general_search'> + <form action="{% url 'search' %}" method='post' id='general_search'> {% csrf_token %} <input type='text' name='search_phrase'> </form> diff --git a/_1327/user_management/templates/login.html b/_1327/user_management/templates/login.html index e3ec4255..fa084313 100644 --- a/_1327/user_management/templates/login.html +++ b/_1327/user_management/templates/login.html @@ -12,7 +12,7 @@ {% trans "Please login to see this page." %} </div> {% endif %} - <form action="{% url 'login' %}" method="post" class="form-horizontal" role="form" id="login"> + <form action="{% url 'login' %}" method="post" class="form-horizontal" role="form" id="login_form"> {% bootstrap_form form layout='horizontal' bound_css_class='has-error' %} <input type="hidden" name="next" value="{{ next }}" /> {% csrf_token %} diff --git a/_1327/user_management/tests.py b/_1327/user_management/tests.py index 41178276..a2877ba9 100644 --- a/_1327/user_management/tests.py +++ b/_1327/user_management/tests.py @@ -30,7 +30,7 @@ def setUpTestData(cls): def test_login(self): page = self.app.get("/login", user="") - login_form = page.forms[1] + login_form = page.forms['login_form'] login_form['username'] = "user" login_form['password'] = "wrong_password" response = login_form.submit() @@ -38,7 +38,7 @@ def test_login(self): self.assertNotIn('has-success', response.body.decode('utf-8')) self.assertIn('has-error', response.body.decode('utf-8')) - login_form = page.forms[1] + login_form = page.forms['login_form'] login_form['username'] = "user" login_form['password'] = "test" @@ -47,7 +47,7 @@ def test_login(self): def test_login_with_camel_case_name(self): page = self.app.get('/login') - login_form = page.forms[1] + login_form = page.forms['login_form'] login_form['username'] = "test testman" login_form['password'] = "test" self.assertEqual(login_form.submit().status_code, 302) @@ -58,7 +58,7 @@ def test_login_with_camel_case_name(self): # now try to login again, but with camel case name page = self.app.get('/login') - login_form = page.forms[1] + login_form = page.forms['login_form'] login_form['username'] = "Test Testman" login_form['password'] = "test" response = login_form.submit() @@ -73,7 +73,7 @@ def test_login_redirect_sufficient_permissions(self): self.assertRedirects(response, redirect_url) response = response.follow() - login_form = response.forms['login'] + login_form = response.forms['login_form'] login_form['username'] = "user" login_form['password'] = "test" @@ -90,7 +90,7 @@ def test_login_insufficient_permissions(self): self.assertRedirects(response, redirect_url) response = response.follow() - login_form = response.forms['login'] + login_form = response.forms['login_form'] login_form['username'] = "user" login_form['password'] = "test" From 68b686a875f81e3be46e897d77d1a89c60db9b94 Mon Sep 17 00:00:00 2001 From: Juliane Waack <jkwaack@gmail.com> Date: Mon, 13 Aug 2018 20:53:11 +0200 Subject: [PATCH 4/4] add missing migrations --- ...3_2050_squashed_0005_auto_20180813_2102.py | 21 +++++++++++++++++++ ...3_2050_squashed_0011_auto_20180813_2102.py | 21 +++++++++++++++++++ .../migrations/0007_auto_20180813_2050.py | 19 +++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 _1327/information_pages/migrations/0004_auto_20180813_2050_squashed_0005_auto_20180813_2102.py create mode 100644 _1327/minutes/migrations/0010_auto_20180813_2050_squashed_0011_auto_20180813_2102.py create mode 100644 _1327/polls/migrations/0007_auto_20180813_2050.py diff --git a/_1327/information_pages/migrations/0004_auto_20180813_2050_squashed_0005_auto_20180813_2102.py b/_1327/information_pages/migrations/0004_auto_20180813_2050_squashed_0005_auto_20180813_2102.py new file mode 100644 index 00000000..8e98c443 --- /dev/null +++ b/_1327/information_pages/migrations/0004_auto_20180813_2050_squashed_0005_auto_20180813_2102.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.15 on 2018-08-13 19:06 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + replaces = [('information_pages', '0004_auto_20180813_2050'), ('information_pages', '0005_auto_20180813_2102')] + + dependencies = [ + ('information_pages', '0003_auto_20180201_2301'), + ] + + operations = [ + migrations.AlterModelOptions( + name='informationdocument', + options={'base_manager_name': 'objects', 'permissions': (('view_informationdocument', 'User/Group is allowed to view that document'),), 'verbose_name': 'Information document', 'verbose_name_plural': 'Information documents'}, + ), + ] diff --git a/_1327/minutes/migrations/0010_auto_20180813_2050_squashed_0011_auto_20180813_2102.py b/_1327/minutes/migrations/0010_auto_20180813_2050_squashed_0011_auto_20180813_2102.py new file mode 100644 index 00000000..f50040a6 --- /dev/null +++ b/_1327/minutes/migrations/0010_auto_20180813_2050_squashed_0011_auto_20180813_2102.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.15 on 2018-08-13 19:04 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + replaces = [('minutes', '0010_auto_20180813_2050'), ('minutes', '0011_auto_20180813_2102')] + + dependencies = [ + ('minutes', '0009_auto_20180201_2301'), + ] + + operations = [ + migrations.AlterModelOptions( + name='minutesdocument', + options={'base_manager_name': 'objects', 'permissions': (('view_minutesdocument', 'User/Group is allowed to view those minutes'),), 'verbose_name': 'Minutes', 'verbose_name_plural': 'Minutes'}, + ), + ] diff --git a/_1327/polls/migrations/0007_auto_20180813_2050.py b/_1327/polls/migrations/0007_auto_20180813_2050.py new file mode 100644 index 00000000..427c1717 --- /dev/null +++ b/_1327/polls/migrations/0007_auto_20180813_2050.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-08-13 18:50 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('polls', '0006_poll_show_results_immediately'), + ] + + operations = [ + migrations.AlterModelOptions( + name='poll', + options={'permissions': (('view_poll', 'User/Group is allowed to view that poll'), ('vote_poll', 'User/Group is allowed to participate (vote) in that poll')), 'verbose_name': 'Poll', 'verbose_name_plural': 'Polls'}, + ), + ]