Commit dc685f1923ffcee8414dc416a53fb0cc7c2862d0

Authored by Andrew Buss
1 parent c1f4d3dea4
Exists in master

Section search works properly now

Showing 4 changed files with 56 additions and 5 deletions Side-by-side Diff

flashcards/migrations/0010_auto_20150513_1546.py View file @ dc685f1
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +import django.utils.timezone
  6 +
  7 +
  8 +class Migration(migrations.Migration):
  9 +
  10 + dependencies = [
  11 + ('flashcards', '0009_auto_20150512_0318'),
  12 + ]
  13 +
  14 + operations = [
  15 + migrations.AlterField(
  16 + model_name='flashcard',
  17 + name='material_date',
  18 + field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'The date with which the card is associated'),
  19 + ),
  20 + migrations.AlterField(
  21 + model_name='userflashcardquiz',
  22 + name='when',
  23 + field=models.DateTimeField(auto_now=True),
  24 + ),
  25 + ]
flashcards/models.py View file @ dc685f1
... ... @@ -144,6 +144,7 @@
144 144 """
145 145 A UCSD course taught by an instructor during a quarter.
146 146 We use the term "section" to avoid collision with the builtin keyword "class"
  147 + We index gratuitously to support autofill and because this is primarily read-only
147 148 """
148 149 department = CharField(db_index=True, max_length=50)
149 150 department_abbreviation = CharField(db_index=True, max_length=10)
... ... @@ -151,6 +152,29 @@
151 152 course_title = CharField(db_index=True, max_length=50)
152 153 instructor = CharField(db_index=True, max_length=100)
153 154 quarter = CharField(db_index=True, max_length=4)
  155 +
  156 + @classmethod
  157 + def search(cls, terms):
  158 + """
  159 + Search all fields of all sections for a particular set of terms
  160 + A matching section must match at least one field on each term
  161 + :param terms:iterable
  162 + :return: Matching QuerySet ordered by department and course number
  163 + """
  164 + final_q = Q()
  165 + for term in terms:
  166 + q = Q(department__icontains=term)
  167 + q |= Q(department_abbreviation__icontains=term)
  168 + q |= Q(course_title__icontains=term)
  169 + q |= Q(course_num__icontains=term)
  170 + q |= Q(instructor__icontains=term)
  171 + final_q &= q
  172 + qs = cls.objects.filter(final_q)
  173 + # Have the database cast the course number to an integer so it will sort properly
  174 + # ECE 35 should rank before ECE 135 even though '135' < '35' lexicographically
  175 + qs = qs.extra(select={'course_num_int': 'CAST(course_num AS INTEGER)'})
  176 + qs = qs.order_by('department_abbreviation', 'course_num_int')
  177 + return qs
154 178  
155 179 def is_whitelisted(self):
156 180 """
flashcards/tests/test_api.py View file @ dc685f1
... ... @@ -249,4 +249,8 @@
249 249 def test_section_flashcards(self):
250 250 response = self.client.get('/api/sections/1/flashcards/')
251 251 self.assertEqual(response.status_code, HTTP_200_OK)
  252 +
  253 + def test_section_search(self):
  254 + response = self.client.get('/api/sections/search/?q=Kramer')
  255 + self.assertEqual(response.status_code, HTTP_200_OK)
flashcards/views.py View file @ dc685f1
... ... @@ -75,11 +75,9 @@
75 75  
76 76 @list_route(methods=['get'], permission_classes=[IsAuthenticated])
77 77 def search(self, request):
78   - query = request.GET.get('q', '').split(' ')
79   - q = Q()
80   - for word in query:
81   - q |= Q(course_title__icontains=word)
82   - qs = Section.objects.filter(q).distinct()
  78 + query = request.GET.get('q',None)
  79 + if not query: return Response('[]')
  80 + qs = Section.search(query.split(' '))[:5]
83 81 serializer = SectionSerializer(qs, many=True)
84 82 return Response(serializer.data)
85 83