├── README.md
├── basic_search
├── Pipfile
├── Pipfile.lock
├── cities
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── citysearch_project
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── manage.py
└── templates
│ ├── home.html
│ └── search_results.html
└── full_text_search
├── Dockerfile
├── Pipfile
├── Pipfile.lock
├── cities
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── tests.py
├── urls.py
└── views.py
├── citysearch_project
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
├── db.sqlite3
├── docker-compose.yml
├── manage.py
└── templates
├── home.html
└── search_results.html
/README.md:
--------------------------------------------------------------------------------
1 | # Search: From the Ground Up
2 |
3 | _Talk given at [DjangoCon US 2019](https://2018.djangocon.us/talk/finally-understand-authentication-in/)_
4 |
5 | [Slides available here](https://tinyurl.com/djangocon2019-search)
6 |
7 | Search is a fundamental part of most websites but not built into Django itself. This talk explains how search works, how to configure it from scratch in a new project, and looks at built-in full text search options available via PostgreSQL.
8 |
9 | ---
10 | ## Basic Search Set Up
11 | Navigate to the `basic_search` directory and make sure [Pipenv](https://docs.pipenv.org/en/latest/) is installed. Then on the command line:
12 |
13 | ```
14 | $ pipenv install
15 | $ pipenv shell
16 | (basic_search) $ python manage.py runserver
17 | ```
18 |
19 | Navigate to [127.0.0.1:8000](127.0.0.1:8000).
20 |
21 | When done run `exit` to leave the virtual environment.
22 |
23 | ## Full Text Search Set Up
24 | Navigate to the `full_text_search` directory and make sure [Docker desktop](https://www.docker.com/products/docker-desktop) is installed and running. Then on the command line:
25 |
26 | ```
27 | $ docker-compose up -d
28 | ```
29 | Navigate to [127.0.0.1:8000](127.0.0.1:8000). When done run `docker-compose down` to stop the running Docker containers.
30 |
31 | ---
32 | Superuser admin for both is `djangocon` and `testpass123`.
33 |
--------------------------------------------------------------------------------
/basic_search/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "==2.2.5"
10 |
11 | [requires]
12 | python_version = "3.7"
13 |
--------------------------------------------------------------------------------
/basic_search/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "356091b1a109ccf810b9c3672b6f020eb68426927a2dc8d99c02caa6b9a208e2"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.7"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "django": {
20 | "hashes": [
21 | "sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa",
22 | "sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56"
23 | ],
24 | "index": "pypi",
25 | "version": "==2.2.5"
26 | },
27 | "pytz": {
28 | "hashes": [
29 | "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
30 | "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
31 | ],
32 | "version": "==2019.2"
33 | },
34 | "sqlparse": {
35 | "hashes": [
36 | "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
37 | "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"
38 | ],
39 | "version": "==0.3.0"
40 | }
41 | },
42 | "develop": {}
43 | }
44 |
--------------------------------------------------------------------------------
/basic_search/cities/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/basic_search/cities/__init__.py
--------------------------------------------------------------------------------
/basic_search/cities/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import City
3 |
4 | class CityAdmin(admin.ModelAdmin):
5 | list_display = ('name', 'state')
6 |
7 | admin.site.register(City, CityAdmin)
8 |
--------------------------------------------------------------------------------
/basic_search/cities/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class CitiesConfig(AppConfig):
5 | name = 'cities'
6 |
--------------------------------------------------------------------------------
/basic_search/cities/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | class SearchForm(forms.Form):
4 | q = forms.CharField(label='Search', max_length=50)
5 |
--------------------------------------------------------------------------------
/basic_search/cities/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.5 on 2019-09-19 17:52
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='City',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=100)),
19 | ('state', models.CharField(max_length=100)),
20 | ],
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/basic_search/cities/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/basic_search/cities/migrations/__init__.py
--------------------------------------------------------------------------------
/basic_search/cities/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | class City(models.Model):
4 | name = models.CharField(max_length=100)
5 | state = models.CharField(max_length=100)
6 |
7 | class Meta:
8 | verbose_name_plural = 'cities'
9 |
--------------------------------------------------------------------------------
/basic_search/cities/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/basic_search/cities/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import SearchResultsView, HomepageView
3 |
4 | urlpatterns = [
5 | path('search/', SearchResultsView.as_view(), name='search_results'),
6 | path('', HomepageView.as_view(), name='home'),
7 | ]
8 |
--------------------------------------------------------------------------------
/basic_search/cities/views.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Q # new
2 | from django.views.generic import TemplateView, ListView, FormView
3 | from .models import City
4 | from .forms import SearchForm
5 |
6 | class HomepageView(FormView): # new
7 | template_name = 'home.html'
8 | form_class = SearchForm # new
9 |
10 | class SearchResultsView(ListView):
11 | model = City
12 | template_name = 'search_results.html'
13 |
14 | # def get_queryset(self): # new
15 | # query = self.request.GET.get('q')
16 | # object_list = City.objects.filter(
17 | # Q(name__icontains=query) | Q(state__icontains=query)
18 | # )
19 | # return object_list
20 | def get_queryset(self): # new
21 | return City.objects.filter(
22 | Q(name__icontains='Boston') | Q(state__icontains='NY')
23 | )
24 |
--------------------------------------------------------------------------------
/basic_search/citysearch_project/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/basic_search/citysearch_project/__init__.py
--------------------------------------------------------------------------------
/basic_search/citysearch_project/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for citysearch_project project.
3 |
4 | Generated by 'django-admin startproject' using Django 2.2.5.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/2.2/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'w_p1iq!yyhqji-ubbfa$hk=)2!ll4g1=t5l4l!afc951zjewmw'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'cities.apps.CitiesConfig', # new
41 | ]
42 |
43 | MIDDLEWARE = [
44 | 'django.middleware.security.SecurityMiddleware',
45 | 'django.contrib.sessions.middleware.SessionMiddleware',
46 | 'django.middleware.common.CommonMiddleware',
47 | 'django.middleware.csrf.CsrfViewMiddleware',
48 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
49 | 'django.contrib.messages.middleware.MessageMiddleware',
50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
51 | ]
52 |
53 | ROOT_URLCONF = 'citysearch_project.urls'
54 |
55 | TEMPLATES = [
56 | {
57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
58 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], # new
59 | 'APP_DIRS': True,
60 | 'OPTIONS': {
61 | 'context_processors': [
62 | 'django.template.context_processors.debug',
63 | 'django.template.context_processors.request',
64 | 'django.contrib.auth.context_processors.auth',
65 | 'django.contrib.messages.context_processors.messages',
66 | ],
67 | },
68 | },
69 | ]
70 |
71 | WSGI_APPLICATION = 'citysearch_project.wsgi.application'
72 |
73 |
74 | # Database
75 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
76 |
77 | DATABASES = {
78 | 'default': {
79 | 'ENGINE': 'django.db.backends.sqlite3',
80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
81 | }
82 | }
83 |
84 |
85 | # Password validation
86 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
87 |
88 | AUTH_PASSWORD_VALIDATORS = [
89 | {
90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
91 | },
92 | {
93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
94 | },
95 | {
96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
97 | },
98 | {
99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
100 | },
101 | ]
102 |
103 |
104 | # Internationalization
105 | # https://docs.djangoproject.com/en/2.2/topics/i18n/
106 |
107 | LANGUAGE_CODE = 'en-us'
108 |
109 | TIME_ZONE = 'UTC'
110 |
111 | USE_I18N = True
112 |
113 | USE_L10N = True
114 |
115 | USE_TZ = True
116 |
117 |
118 | # Static files (CSS, JavaScript, Images)
119 | # https://docs.djangoproject.com/en/2.2/howto/static-files/
120 |
121 | STATIC_URL = '/static/'
122 |
--------------------------------------------------------------------------------
/basic_search/citysearch_project/urls.py:
--------------------------------------------------------------------------------
1 | """citysearch_project URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import path, include # new
18 |
19 | urlpatterns = [
20 | path('admin/', admin.site.urls),
21 | path('', include('cities.urls')), # new
22 | ]
23 |
--------------------------------------------------------------------------------
/basic_search/citysearch_project/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for citysearch_project project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'citysearch_project.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/basic_search/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/basic_search/db.sqlite3
--------------------------------------------------------------------------------
/basic_search/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'citysearch_project.settings')
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError as exc:
12 | raise ImportError(
13 | "Couldn't import Django. Are you sure it's installed and "
14 | "available on your PYTHONPATH environment variable? Did you "
15 | "forget to activate a virtual environment?"
16 | ) from exc
17 | execute_from_command_line(sys.argv)
18 |
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
--------------------------------------------------------------------------------
/basic_search/templates/home.html:
--------------------------------------------------------------------------------
1 |
Homepage
2 |
3 |
6 |
--------------------------------------------------------------------------------
/basic_search/templates/search_results.html:
--------------------------------------------------------------------------------
1 | Search Results
2 |
3 | {% for city in object_list %}
4 | -
5 | {{ city.name }}, {{ city.state }}
6 |
7 | {% endfor %}
8 |
9 |
--------------------------------------------------------------------------------
/full_text_search/Dockerfile:
--------------------------------------------------------------------------------
1 | # Pull base image
2 | FROM python:3.7-slim
3 |
4 | # Set environment varibles
5 | ENV PYTHONDONTWRITEBYTECODE 1
6 | ENV PYTHONUNBUFFERED 1
7 |
8 | # Set work directory
9 | WORKDIR /code
10 |
11 | # Install dependencies
12 | COPY Pipfile Pipfile.lock /code/
13 | RUN pip install pipenv && pipenv install --system
14 |
15 | # Copy project
16 | COPY . /code/
17 |
--------------------------------------------------------------------------------
/full_text_search/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | django = "==2.2.5"
10 | psycopg2-binary = "==2.8.3"
11 |
12 | [requires]
13 | python_version = "3.7"
14 |
--------------------------------------------------------------------------------
/full_text_search/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "b4d0536b3ac659b0959d724e0fd0423a52db5e1d1c78e3705322b3243454b36c"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.7"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "django": {
20 | "hashes": [
21 | "sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa",
22 | "sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56"
23 | ],
24 | "index": "pypi",
25 | "version": "==2.2.5"
26 | },
27 | "psycopg2-binary": {
28 | "hashes": [
29 | "sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809",
30 | "sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598",
31 | "sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5",
32 | "sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1",
33 | "sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d",
34 | "sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e",
35 | "sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00",
36 | "sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf",
37 | "sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43",
38 | "sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5",
39 | "sha256:51a2fc7e94b98bd1bb5d4570936f24fc2b0541b63eccadf8fdea266db8ad2f70",
40 | "sha256:52f1bdafdc764b7447e393ed39bb263eccb12bfda25a4ac06d82e3a9056251f6",
41 | "sha256:5b3581319a3951f1e866f4f6c5e42023db0fae0284273b82e97dfd32c51985cd",
42 | "sha256:63c1b66e3b2a3a336288e4bcec499e0dc310cd1dceaed1c46fa7419764c68877",
43 | "sha256:8123a99f24ecee469e5c1339427bcdb2a33920a18bb5c0d58b7c13f3b0298ba3",
44 | "sha256:85e699fcabe7f817c0f0a412d4e7c6627e00c412b418da7666ff353f38e30f67",
45 | "sha256:8dbff4557bbef963697583366400822387cccf794ccb001f1f2307ed21854c68",
46 | "sha256:908d21d08d6b81f1b7e056bbf40b2f77f8c499ab29e64ec5113052819ef1c89b",
47 | "sha256:af39d0237b17d0a5a5f638e9dffb34013ce2b1d41441fd30283e42b22d16858a",
48 | "sha256:af51bb9f055a3f4af0187149a8f60c9d516cf7d5565b3dac53358796a8fb2a5b",
49 | "sha256:b2ecac57eb49e461e86c092761e6b8e1fd9654dbaaddf71a076dcc869f7014e2",
50 | "sha256:cd37cc170678a4609becb26b53a2bc1edea65177be70c48dd7b39a1149cabd6e",
51 | "sha256:d17e3054b17e1a6cb8c1140f76310f6ede811e75b7a9d461922d2c72973f583e",
52 | "sha256:d305313c5a9695f40c46294d4315ed3a07c7d2b55e48a9010dad7db7a66c8b7f",
53 | "sha256:dd0ef0eb1f7dd18a3f4187226e226a7284bda6af5671937a221766e6ef1ee88f",
54 | "sha256:e1adff53b56db9905db48a972fb89370ad5736e0450b96f91bcf99cadd96cfd7",
55 | "sha256:f0d43828003c82dbc9269de87aa449e9896077a71954fbbb10a614c017e65737",
56 | "sha256:f78e8b487de4d92640105c1389e5b90be3496b1d75c90a666edd8737cc2dbab7"
57 | ],
58 | "index": "pypi",
59 | "version": "==2.8.3"
60 | },
61 | "pytz": {
62 | "hashes": [
63 | "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
64 | "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
65 | ],
66 | "version": "==2019.2"
67 | },
68 | "sqlparse": {
69 | "hashes": [
70 | "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
71 | "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"
72 | ],
73 | "version": "==0.3.0"
74 | }
75 | },
76 | "develop": {}
77 | }
78 |
--------------------------------------------------------------------------------
/full_text_search/cities/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/full_text_search/cities/__init__.py
--------------------------------------------------------------------------------
/full_text_search/cities/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import City
3 |
4 | class CityAdmin(admin.ModelAdmin):
5 | list_display = ('name', 'state')
6 |
7 | admin.site.register(City, CityAdmin)
8 |
--------------------------------------------------------------------------------
/full_text_search/cities/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class CitiesConfig(AppConfig):
5 | name = 'cities'
6 |
--------------------------------------------------------------------------------
/full_text_search/cities/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | class SearchForm(forms.Form):
4 | q = forms.CharField(label='Search', max_length=50)
5 |
--------------------------------------------------------------------------------
/full_text_search/cities/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.5 on 2019-09-21 17:42
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='City',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=100)),
19 | ('state', models.CharField(max_length=100)),
20 | ],
21 | options={
22 | 'verbose_name_plural': 'cities',
23 | },
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/full_text_search/cities/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/full_text_search/cities/migrations/__init__.py
--------------------------------------------------------------------------------
/full_text_search/cities/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | class City(models.Model):
4 | name = models.CharField(max_length=100)
5 | state = models.CharField(max_length=100)
6 |
7 | class Meta:
8 | verbose_name_plural = 'cities'
9 |
--------------------------------------------------------------------------------
/full_text_search/cities/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/full_text_search/cities/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from .views import SearchResultsView, HomepageView
3 |
4 | urlpatterns = [
5 | path('search/', SearchResultsView.as_view(), name='search_results'),
6 | path('', HomepageView.as_view(), name='home'),
7 | ]
8 |
--------------------------------------------------------------------------------
/full_text_search/cities/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.postgres.search import (
2 | SearchVector, SearchQuery, SearchRank
3 | )
4 | from django.views.generic import FormView, ListView
5 | from .forms import SearchForm
6 | from .models import City
7 |
8 | class HomepageView(FormView):
9 | template_name = 'home.html'
10 | form_class = SearchForm
11 |
12 | class SearchResultsView(ListView):
13 | model = City
14 | template_name = 'search_results.html'
15 |
16 | ## SearchRank ##
17 | def get_queryset(self):
18 | query = self.request.GET.get('q')
19 | vector = SearchVector('state')
20 | search_query = SearchQuery(query)
21 | object_list = City.objects.annotate(
22 | rank=SearchRank(vector, search_query)
23 | ).order_by('-rank')
24 | return object_list
25 |
26 | ## SearchQuery ##
27 | # def get_queryset(self):
28 | # query = self.request.GET.get('q')
29 | # object_list = City.objects.annotate(
30 | # search=SearchVector('name', 'state'),
31 | # ).filter(search=SearchQuery(query))
32 | # return object_list
33 |
34 | ## SearchVector ##
35 | # def get_queryset(self):
36 | # query = self.request.GET.get('q')
37 | # object_list = City.objects.annotate(
38 | # search=SearchVector('name', 'state'),
39 | # ).filter(search=query)
40 | # return object_list
41 |
--------------------------------------------------------------------------------
/full_text_search/citysearch_project/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/full_text_search/citysearch_project/__init__.py
--------------------------------------------------------------------------------
/full_text_search/citysearch_project/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for citysearch_project project.
3 |
4 | Generated by 'django-admin startproject' using Django 2.2.5.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/2.2/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'y^*@m9(+prs8=!csba&qzbz*@3r%mzqc%8gzq43#3f-bqnx%@9'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'cities.apps.CitiesConfig', # new
41 | ]
42 |
43 | MIDDLEWARE = [
44 | 'django.middleware.security.SecurityMiddleware',
45 | 'django.contrib.sessions.middleware.SessionMiddleware',
46 | 'django.middleware.common.CommonMiddleware',
47 | 'django.middleware.csrf.CsrfViewMiddleware',
48 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
49 | 'django.contrib.messages.middleware.MessageMiddleware',
50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
51 | ]
52 |
53 | ROOT_URLCONF = 'citysearch_project.urls'
54 |
55 | TEMPLATES = [
56 | {
57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
58 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], # new
59 | 'APP_DIRS': True,
60 | 'OPTIONS': {
61 | 'context_processors': [
62 | 'django.template.context_processors.debug',
63 | 'django.template.context_processors.request',
64 | 'django.contrib.auth.context_processors.auth',
65 | 'django.contrib.messages.context_processors.messages',
66 | ],
67 | },
68 | },
69 | ]
70 |
71 | WSGI_APPLICATION = 'citysearch_project.wsgi.application'
72 |
73 |
74 | # Database
75 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
76 |
77 | DATABASES = {
78 | 'default': {
79 | 'ENGINE': 'django.db.backends.postgresql',
80 | 'NAME': 'postgres',
81 | 'USER': 'postgres',
82 | 'PASSWORD': 'postgres',
83 | 'HOST': 'db',
84 | 'PORT': 5432
85 | }
86 | }
87 |
88 |
89 | # Password validation
90 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
91 |
92 | AUTH_PASSWORD_VALIDATORS = [
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
95 | },
96 | {
97 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
98 | },
99 | {
100 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
101 | },
102 | {
103 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
104 | },
105 | ]
106 |
107 |
108 | # Internationalization
109 | # https://docs.djangoproject.com/en/2.2/topics/i18n/
110 |
111 | LANGUAGE_CODE = 'en-us'
112 |
113 | TIME_ZONE = 'UTC'
114 |
115 | USE_I18N = True
116 |
117 | USE_L10N = True
118 |
119 | USE_TZ = True
120 |
121 |
122 | # Static files (CSS, JavaScript, Images)
123 | # https://docs.djangoproject.com/en/2.2/howto/static-files/
124 |
125 | STATIC_URL = '/static/'
126 |
--------------------------------------------------------------------------------
/full_text_search/citysearch_project/urls.py:
--------------------------------------------------------------------------------
1 | """citysearch_project URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import path, include
18 |
19 | urlpatterns = [
20 | path('admin/', admin.site.urls),
21 | path('', include('cities.urls')),
22 | ]
23 |
--------------------------------------------------------------------------------
/full_text_search/citysearch_project/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for citysearch_project project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'citysearch_project.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/full_text_search/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wsvincent/djangocon2019-search/cf9af84b5831286cd60acb9ddb250c9062b508c6/full_text_search/db.sqlite3
--------------------------------------------------------------------------------
/full_text_search/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | web:
5 | build: .
6 | command: python /code/manage.py runserver 0.0.0.0:8000
7 | volumes:
8 | - .:/code
9 | ports:
10 | - 8000:8000
11 | depends_on:
12 | - db
13 | db:
14 | image: postgres:11
15 | volumes:
16 | - postgres_data:/var/lib/postgresql/data/
17 |
18 | volumes:
19 | postgres_data:
20 |
--------------------------------------------------------------------------------
/full_text_search/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'citysearch_project.settings')
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError as exc:
12 | raise ImportError(
13 | "Couldn't import Django. Are you sure it's installed and "
14 | "available on your PYTHONPATH environment variable? Did you "
15 | "forget to activate a virtual environment?"
16 | ) from exc
17 | execute_from_command_line(sys.argv)
18 |
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
--------------------------------------------------------------------------------
/full_text_search/templates/home.html:
--------------------------------------------------------------------------------
1 | Homepage
2 |
3 |
6 |
--------------------------------------------------------------------------------
/full_text_search/templates/search_results.html:
--------------------------------------------------------------------------------
1 | Search Results
2 |
3 | {% for city in object_list %}
4 | -
5 | {{ city.name }}, {{ city.state }}
6 |
7 | {% endfor %}
8 |
9 |
--------------------------------------------------------------------------------