Package: django-tsvector-field
django-tsvector-fieldis a drop-in replacement for Django’sdjango.contrib.postgres.search.SearchVectorFieldfield that manages the
database triggers to keep your search field updated automatically in
the background.InstallationPython 3+,Django 1.11+andpsycopg2are the only requirements.Installdjango-tsvector-fieldwith your favorite python tool, e.g.pip installdjango-tsvector-field.You have two options to integrate it into your project:Simply addtsvector_fieldto yourINSTALLED_APPSand start using it. This method
uses Django’spre_migratesignal to inject the database operations into
your migrations. This will work fine for many use cases.However, you’ll run into issues with this method if you have unmigrated apps
or you have disabled migrations for your unit tests. The problem is related
to the fact that Django does not sendpre_migratesignal for apps that
do not have explicit migrations.Less simple but more reliable method is to create your own database engine module
referencingtsvector_field.DatabaseSchemaEditor. This will ensure that the
database triggers are reliably created and dropped for all methods of migration.Create a ‘db’ directory in your Django project with an__init__.pyand abase.pywith the following contents:fromdjango.db.backends.postgresqlimportbaseimporttsvector_fieldclassDatabaseWrapper(base.DatabaseWrapper):SchemaEditorClass=tsvector_field.DatabaseSchemaEditorThen update the'ENGINE'configuration in yourDATABASESsetting. For example,
if your project is calledmy_projectand it has thedbmodule as described
above, then change yourDATABASEsetting to have the following'ENGINE'configuration:DATABASES={'default':{'ENGINE':'my_project.db',}}Usagetsvector_field.SearchVectorFieldworks like any other Django field: add it to your model,
runmakemigrations, runmigrateandtsvector_fieldwill take care to create the
postgres trigger and stored procedure.To illustrate how this works we’ll create aTextDocumentmodel with atsvector_field.SearchVectorFieldfield and two textual fields to be used as
inputs for the full text search.fromdjango.dbimportmodelsimporttsvector_fieldclassTextDocument(models.Model):title=models.CharField(max_length=128)body=models.TextField()search=tsvector_field.SearchVectorField([tsvector_field.WeightedColumn('title','A'),tsvector_field.WeightedColumn('body','D'),],'english')After you’ve migrated you can create someTextDocumentrecords and see that
postgres keeps it synchronized in the background. Specifically, because thesearchfield is updated at the database level, you’ll need to callrefresh_from_db()to see the new value after a.save()or.create().>>>doc=TextDocument.objects.create(...title="My hovercraft is full of spam.",...body="It's what eels love!"...)>>>doc.search>>>doc.refresh_from_db()>>>doc.search"'eel':10 'full':4A 'hovercraft':2A 'love':11 'spam':6A"Note thatspamis recorded with6A, this will be important later. Let’s
continue with the previous session and create another document.>>>doc=TextDocument.objects.create(...title="What do eels eat?",...body="Spam, spam, spam, they love spam!"...)>>>doc.refresh_from_db()>>>doc.search"'eat':4A 'eel':3A 'love':9 'spam':5,6,7,10"Now we have two documents: first document has just onespamwith weightAand
the second document has 4spamwith lower weight. If we search forspamand apply
a search rank then theAweight on the first document will cause that document to
appear higher in the results.>>>fromdjango.contrib.postgres.searchimportSearchQuery,SearchRank>>>fromdjango.db.models.expressionsimportF>>>matches=TextDocument.objects\....annotate(rank=SearchRank(F('search'),SearchQuery('spam')))\....order_by('-rank')\....values_list('rank','title','body')>>>formatchinmatches:...print(match)...(0.607927,'My hovercraft is full of spam.',"It's what eels love!")(0.0865452,'What do eels eat?','Spam, spam, spam, they love spam!')If you are only interested in getting a list of possible matches without ranking
you can filter directly on the search column like so:>>>TextDocument.objects.filter(search='spam')<QuerySet[<TextDocument:TextDocumentobject>,<TextDocument:TextDocumentobject>]>Final note about thetsvector_field.SearchVectorFieldfield is that it takes alanguage_columnargument instead of or in addition to thelanguageargument. When
both arguments are provided then the database trigger will first look up the value in thelanguage_columnand if that is null it will use the language inlanguage.MigratingWhen adding atsvector_field.SearchVectorFieldfield to an existing model you likely
want to update the search vector for all existing records.django-tsvector-fieldincludes
thetsvector_field.IndexSearchVectoroperation that takes the model name and search vector
column as arguments. If we had previously created theTextDocumentwithout asearchcolumn
then to add search capability we would use the following migration:fromdjango.dbimportmigrations,modelsimporttsvector_fieldclassMigration(migrations.Migration):dependencies=[]operations=[migrations.AddField(model_name='textdocument',name='search',field=tsvector_field.SearchVectorField(columns=[tsvector_field.WeightedColumn('title','A'),tsvector_field.WeightedColumn('body','D')],language='english'),),tsvector_field.IndexSearchVector('textdocument','search'),]For more information on querying, see the Django documentation on Full Text Search:https://docs.djangoproject.com/en/dev/ref/contrib/postgres/search/For more information on configuring how the searches work, see PostgreSQL docs:https://www.postgresql.org/docs/devel/static/textsearch.html0.9.5Use IS DISTINCT FROM instead of <> as comparing anything to NULL returns NULL.0.9.4Initial support django 2.0 alpha0.9.3Automatically create GIN index on tsvector column0.9.2IndexSearchVector migration operation addeddocumentation fixesAdded support for both pre_migrate signal based integration and extending DatabaseSchemaEditor0.9.1Fixed bug with AlterField migrations.0.9.0Initial release.