Package: django-typesense
django typesenseWhat is it?Faster Django Admin powered byTypesenseQuick Start GuideInstallationpip install django-typesenseor install directly from github to test the most recent versionpip install git+https://github.com/Siege-Software/django-typesense.gitAdddjango_typesenseto the list of installed apps.Follow thisguideto install and run typesenseCreate CollectionsThroughout this guide, we’ll refer to the following models, which comprise a song catalogue application:from django.db import models


class Genre(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Artist(models.Model):
    name = models.CharField(max_length=200)

    def __str__(self):
        return self.name


class Song(models.Model):
    title = models.CharField(max_length=100)
    genre = models.ForeignKey(Genre, on_delete=models.CASCADE)
    release_date = models.DateField(blank=True, null=True)
    artists = models.ManyToManyField(Artist)
    number_of_comments = models.IntegerField(default=0)
    number_of_views = models.IntegerField(default=0)
    duration = models.DurationField()
    description = models.TextField()

    def __str__(self):
        return self.title
     
    @property
    def release_date_timestamp(self):
        # read https://typesense.org/docs/0.25.0/api/collections.html#indexing-dates
        return self.release_date.timestamp() if self.release_date else self.release_date
      
    def artist_names(self):
        return list(self.artists.all().values_list('name', flat=True))For such an application, you might be interested in improving the search and load times on the song records list view.from django_typesense.collections import TypesenseCollection
from django_typesense import fields


class SongCollection(TypesenseCollection):
    # At least one of the indexed fields has to be provided as one of the `query_by_fields`. Must be a CharField
    query_by_fields = 'title,artist_names,genre_name'
    
    title = fields.TypesenseCharField()
    genre_name = fields.TypesenseCharField(value='genre.name')
    genre_id = fields.TypesenseSmallIntegerField()
    release_date = fields.TypesenseDateField(value='release_date_timestamp', optional=True)
    artist_names = fields.TypesenseArrayField(base_field=fields.TypesenseCharField(), value='artist_names')
    number_of_comments = fields.SmallIntegerField(index=False, optional=True)
    number_of_views = fields.SmallIntegerField(index=False, optional=True)
    duration = fields.DurationField()It's okay to store fields that you don't intend to search but to display on the admin. Such fields should be marked as un-indexed e.g:number_of_views = fields.SmallIntegerField(index=False, optional=True)Update the song model as follows:from django_typesense.mixins import TypesenseModelMixin

class Song(TypesenseModelMixin):
    ...
    collection_class = SongCollection
    ...TheTypesenseModelMixinprovides a Manager that overrides theupdateanddeletemethods of the Queryset.
If your model has a custom manager, make sure the custom manager inheritsdjango_typesense.mixin.TypesenseManagerHow the value of a field is retrieved from a model instance:The collection field name is called as a property of the model instanceIfvalueis provided, it will be called as a property or method of the model instanceWhere the collections live is totally dependent on you but we recommend having acollections.pyfile
in the django app where the model you are creating a collection for is.[!NOTE]We recommend displaying data from ForeignKey or OneToOne fields as string attributes using the display decorator to
avoid triggering database queries that will negatively affect performanceIssue #16.Instead of this in the admin:@admin.display('Genre')
def genre_name(self, obj):
    return obj.genre.nameDo this:@admin.display('Genre')
def genre_name(self, obj):
    # genre_name is field in the Collection. You can also store the object url as html
    return obj.genre_nameUpdate Collection SchemaTo add or remove fields to a collection's schema in place, update your collection then run:python manage.py updatecollections. Consider adding this to your CI/CD pipeline.This also updates thesynonymsHow updates are made to TypesenseSignals -django-typesenselistens to signal events (post_save,pre_delete,m2m_changed) to update typesense records.
Ifupdate_fieldswere provided in the save method, only these fields will be updated in typesense.Update query -django-typesenseoverrides Django'sQuerySet.updateto make updates to typesense on the specified fieldsManual -
You can also update typesense records manually e.g after doing abulk_createobjs = Song.objects.bulk_create(
    [
      Song(title="Watch What I Do"),
      Song(title="Midnight City"),
   ]
)
collection = SongCollection(objs, many=True)
collection.update()Admin IntegrationTo make a model admin display and search from the model's Typesense collection, the admin class should
inheritTypesenseSearchAdminMixin. This also adds Live Search to your admin changelist view.from django_typesense.admin import TypesenseSearchAdminMixin

@admin.register(Song)
class SongAdmin(TypesenseSearchAdminMixin):
    ...
    list_display = ['title', 'genre_name', 'release_date', 'number_of_views', 'duration']
    
    @admin.display(description='Genre')
    def genre_name(self, obj):
        return obj.genre.name
    ...IndexingFor the initial setup, you will need to index in bulk. Bulk updating is multi-threaded. Depending on your system specs, you should set thebatch_sizekeyword argument.from django_typesense.utils import bulk_delete_typsense_records, bulk_update_typsense_records

model_qs = Song.objects.all().order_by('id')  # querysets should be ordered
bulk_update_typesense_records(model_qs, batch_size=1024)Custom Admin FiltersTo make use of custom admin filters, define afilter_byproperty in the filter definition.
Define boolean typesense fieldhas_viewsthat gets it's value from a model property. This is example is not necessarily practical but for demo purposes.# models.py
class Song(models.Model):
    ...
    @property
    def has_views(self):
        return self.number_of_views > 0
    ...

# collections.py
class SongCollection(TypesenseCollection):
    ...
    has_views = fields.TypesenseBooleanField()
    ...class HasViewsFilter(admin.SimpleListFilter):
    title = _('Has Views')
    parameter_name = 'has_views'

    def lookups(self, request, model_admin):
        return (
            ('all', 'All'),
            ('True', 'Yes'),
            ('False', 'No')
        )

    def queryset(self, request, queryset):
        # This is used by the default django admin
        if self.value() == 'True':
            return queryset.filter(number_of_views__gt=0)
        elif self.value() == 'False':
            return queryset.filter(number_of_views=0)
            
        return queryset

    @property
    def filter_by(self):
        # This is used by typesense
        if self.value() == 'True':
            return {"has_views": "=true"}
        elif self.value() == 'False':
            return {"has_views": "!=false"}

        return {}Note that simple lookups like the one above are done by default (hence no need to definefilter_by) if
theparameter_nameis a field in the collectionSynonymsThesynonymsfeature allows you to define search terms that
should be considered equivalent. Synonyms should be defined with classes that inherit fromSynonymfrom django_typesense.collections import Synonym

# say you need users searching the genre hip-hop to get results if they use the search term rap

class HipHopSynonym(Synonym):
    name = 'hip-hop-synonyms'
    synonyms = ['hip-hop', 'rap']
 
# Update the collection to include the synonym
class SongCollection(TypesenseCollection):
    ...
    synonyms = [HipHopSynonym]
    ...To update the collection with any changes made to synonyms runpython manage.py updatecollections