Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #34 from guaq/topic/search-improvements

Improve searching.

Support searching by alias and exact word match
  • Loading branch information...
commit fecbc19e22ae5b9d25e0abb1319010ad44cb3a31 2 parents 22df186 + 99d42c7
@joneskoo joneskoo authored
Showing with 96 additions and 38 deletions.
  1. +54 −2 membership/models.py
  2. +33 −5 membership/tests.py
  3. +9 −31 membership/views.py
View
56 membership/models.py
@@ -284,11 +284,11 @@ def delete_membership(self, user):
log_change(self, user, change_message="Deleted")
def duplicates(self):
- '''
+ """
Finds duplicates of memberships, looks for similar names, emails, phone
numbers and contact details. Returns a QuerySet object that doesn't
include the membership of which duplicates are search for itself.
- '''
+ """
qs = Membership.objects
if self.person and not self.organization:
@@ -306,6 +306,58 @@ def duplicates(self):
return qs.exclude(id__exact=self.id)
+ @classmethod
+ def search(cls, query):
+ person_contacts = Contact.objects
+ org_contacts = Contact.objects
+
+ # Split into words and remove duplicates
+ words = set(query.split(" "))
+
+ # Each word narrows the search further
+ for word in words:
+ # Exact word match when word is "word"
+ if word.startswith('"') and word.endswith('"'):
+ word = word[1:-1]
+ # Search query for people
+ f_q = Q(first_name__iexact=word)
+ l_q = Q(last_name__iexact=word)
+ g_q = Q(given_names__iexact=word)
+ person_contacts = person_contacts.filter(f_q | l_q | g_q)
+
+ # Search for organizations
+ o_q = Q(organization_name__iexact=word)
+ org_contacts = org_contacts.filter(o_q)
+ else:
+ # Common search parameters
+ email_q = Q(email__icontains=word)
+ phone_q = Q(phone__icontains=word)
+ sms_q = Q(sms__icontains=word)
+ common_q = email_q | phone_q | sms_q
+
+ # Search query for people
+ f_q = Q(first_name__icontains=word)
+ l_q = Q(last_name__icontains=word)
+ g_q = Q(given_names__icontains=word)
+ person_contacts = person_contacts.filter(f_q | l_q | g_q | common_q)
+
+ # Search for organizations
+ o_q = Q(organization_name__icontains=word)
+ org_contacts = org_contacts.filter(o_q | common_q)
+
+ # Finally combine matches; all membership for which there are matching
+ # contacts or aliases
+ person_q = Q(person__in=person_contacts)
+ org_q = Q(organization__in=org_contacts)
+ alias_q = Q(alias__name__in=words)
+ qs = Membership.objects.filter(person_q | org_q | alias_q).distinct()
+
+ qs = qs.order_by("organization__organization_name",
+ "person__last_name",
+ "person__first_name")
+
+ return qs
+
def __repr__(self):
return "<Membership(%s): %s (%i)>" % (self.type, str(self), self.id)
View
38 membership/tests.py
@@ -174,21 +174,25 @@ def create_dummy_member(status, type='P', mid=None):
'given_names' : '%s %s' % (fname, "Kapsi"),
'last_name' : random_last_name(),
}
- person = Contact(**d)
- person.save()
+ contact = Contact(**d)
+ contact.save()
if type == 'O':
+ contact.organization_name = contact.name()
+ contact.first_name = u''
+ contact.last_name = u''
+ contact.save()
membership = Membership(id=mid, type=type, status=status,
- organization=person,
+ organization=contact,
nationality='Finnish',
municipality='Paska kaupunni',
extra_info='Hintsunlaisesti semmoisia tietoja.')
else:
membership = Membership(id=mid, type=type, status=status,
- person=person,
+ person=contact,
nationality='Finnish',
municipality='Paska kaupunni',
extra_info='Hintsunlaisesti semmoisia tietoja.')
- logger.info("New application %s from %s:." % (str(person), '::1'))
+ logger.info("New application %s from %s:." % (str(contact), '::1'))
membership.save()
return membership
@@ -1051,3 +1055,27 @@ def test_duplicate_phone(self):
m2.person.save()
self.assertEquals(len(m1.duplicates()), 1)
+
+
+class MembershipSearchTest(TestCase):
+ def setUp(self):
+ self.m = create_dummy_member('N')
+ self.m.save()
+
+ self.o = create_dummy_member('N', type='O')
+ self.o.save()
+
+ def test_find_by_first_name(self):
+ self.assertEquals(len(Membership.search(self.m.person.first_name)), 1)
+
+ def test_find_by_last_name(self):
+ self.assertEquals(len(Membership.search(self.m.person.last_name)), 1)
+
+ def test_find_by_organization_name(self):
+ self.assertEquals(len(Membership.search(self.o.organization.organization_name)), 1)
+
+ def test_find_by_alias(self):
+ alias = Alias(owner=self.m,
+ name=u"this.alias.should.be.unique")
+ alias.save()
+ self.assertEquals(len(Membership.search(alias.name)), 1)
View
40 membership/views.py
@@ -897,39 +897,17 @@ def search(request, **kwargs):
extra['search_query'] = query
kwargs['extra_context'] = extra
+ # Shorthand for viewing a membership by giving # and the id
if query.startswith("#"):
- return redirect('membership_edit', query.lstrip("#"))
-
- person_contacts = Contact.objects
- org_contacts = Contact.objects
- # Split into words and remove duplicates
- d = {}.fromkeys(query.split(" "))
- for word in d.keys():
- # Common search parameters
- email_q = Q(email__icontains=word)
- phone_q = Q(phone__icontains=word)
- sms_q = Q(sms__icontains=word)
- common_q = email_q | phone_q | sms_q
-
- # Search query for people
- f_q = Q(first_name__icontains=word)
- l_q = Q(last_name__icontains=word)
- g_q = Q(given_names__icontains=word)
- person_contacts = person_contacts.filter(f_q | l_q | g_q | common_q)
-
- # Search for organizations
- o_q = Q(organization_name__icontains=word)
- org_contacts = org_contacts.filter(o_q | common_q)
-
- # Combined single query
- person_q = Q(person__in=person_contacts)
- org_q = Q(organization__in=org_contacts)
- qs = Membership.objects.filter(person_q | org_q)
+ try:
+ return redirect('membership_edit', int(query.lstrip("#")))
+ except ValueError, ve:
+ pass
+
+ qs = Membership.search(query)
+
if qs.count() == 1:
return redirect('membership_edit', qs[0].id)
- qs = qs.order_by("organization__organization_name",
- "person__last_name",
- "person__first_name")
+ return django.views.generic.list_detail.object_list(request, qs, **kwargs)
- return django.views.generic.list_detail.object_list(request, qs, **kwargs)
Please sign in to comment.
Something went wrong with that request. Please try again.