Skip to main content

Django field implementation for PostgreSQL tsvector.

Project description

django-tsvector-field is a drop-in replacement for Django’s django.contrib.postgres.search.SearchVectorField field that manages the database triggers to keep your search field updated automatically in the background.

Installation

Python 3+, Django 1.11+ and psycopg2 are the only requirements.

Install django-tsvector-field with your favorite python tool, e.g. pip install django-tsvector-field.

You have two options to integrate it into your project:

  1. Simply add tsvector_field to your INSTALLED_APPS and start using it. This method uses Django’s pre_migrate signal 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 send pre_migrate signal for apps that do not have explicit migrations.

  2. Less simple but more reliable method is to create your own database engine module referencing tsvector_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__.py and a base.py with the following contents:

    from django.db.backends.postgresql import base
    import tsvector_field
    
    class DatabaseWrapper(base.DatabaseWrapper):
        SchemaEditorClass = tsvector_field.DatabaseSchemaEditor

    Then update the 'ENGINE' configuration in your DATABASES setting. For example, if your project is called my_project and it has the db module as described above, then change your DATABASE setting to have the following 'ENGINE' configuration:

    DATABASES = {
        'default': {
            'ENGINE': 'my_project.db',
        }
    }

Usage

tsvector_field.SearchVectorField works like any other Django field: add it to your model, run makemigrations, run migrate and tsvector_field will take care to create the postgres trigger and stored procedure.

To illustrate how this works we’ll create a TextDocument model with a tsvector_field.SearchVectorField field and two textual fields to be used as inputs for the full text search.

from django.db import models
import tsvector_field

class TextDocument(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 some TextDocument records and see that postgres keeps it synchronized in the background. Specifically, because the search field is updated at the database level, you’ll need to call refresh_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 that spam is recorded with 6A, 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 one spam with weight A and the second document has 4 spam with lower weight. If we search for spam and apply a search rank then the A weight on the first document will cause that document to appear higher in the results.

>>> from django.contrib.postgres.search import SearchQuery, SearchRank
>>> from django.db.models.expressions import F
>>> matches = TextDocument.objects\
...     .annotate(rank=SearchRank(F('search'), SearchQuery('spam')))\
...     .order_by('-rank')\
...     .values_list('rank', 'title', 'body')
>>> for match in matches:
...   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: TextDocument object>, <TextDocument: TextDocument object>]>

Final note about the tsvector_field.SearchVectorField field is that it takes a language_column argument instead of or in addition to the language argument. When both arguments are provided then the database trigger will first look up the value in the language_column and if that is null it will use the language in language.

Migrating

When adding a tsvector_field.SearchVectorField field to an existing model you likely want to update the search vector for all existing records. django-tsvector-field includes the tsvector_field.IndexSearchVector operation that takes the model name and search vector column as arguments. If we had previously created the TextDocument without a search column then to add search capability we would use the following migration:

from django.db import migrations, models
import tsvector_field

class Migration(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.html

0.9.5

  • Use IS DISTINCT FROM instead of <> as comparing anything to NULL returns NULL.

0.9.4

  • Initial support django 2.0 alpha

0.9.3

  • Automatically create GIN index on tsvector column

0.9.2

  • IndexSearchVector migration operation added

  • documentation fixes

  • Added support for both pre_migrate signal based integration and extending DatabaseSchemaEditor

0.9.1

  • Fixed bug with AlterField migrations.

0.9.0

  • Initial release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-tsvector-field-0.9.5.tar.gz (16.8 kB view details)

Uploaded Source

Built Distribution

django_tsvector_field-0.9.5-py3-none-any.whl (11.7 kB view details)

Uploaded Python 3

File details

Details for the file django-tsvector-field-0.9.5.tar.gz.

File metadata

  • Download URL: django-tsvector-field-0.9.5.tar.gz
  • Upload date:
  • Size: 16.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.15.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.5.2

File hashes

Hashes for django-tsvector-field-0.9.5.tar.gz
Algorithm Hash digest
SHA256 d81465019e24a4afea5f84d70f9c6fc0df084052adeb3f5c8959f17b1b0d6ec9
MD5 151c826b1691dd68893226aa565f47ba
BLAKE2b-256 abc4c85d100f803d22d0d38454ab2e157a300f62861ec142263e2817521d9d78

See more details on using hashes here.

File details

Details for the file django_tsvector_field-0.9.5-py3-none-any.whl.

File metadata

  • Download URL: django_tsvector_field-0.9.5-py3-none-any.whl
  • Upload date:
  • Size: 11.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.15.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.5.2

File hashes

Hashes for django_tsvector_field-0.9.5-py3-none-any.whl
Algorithm Hash digest
SHA256 a92cc54609421dd5b9e22623871ca93622bbe9213260447c1b4700d4d651996e
MD5 e9db52f64541d43ed6baa71c5e039167
BLAKE2b-256 27132d1321d8a049eb3ee0d278a78aa8837281efca336dcdaa9c9c77e1c65f3a

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page