algolia / algoliasearch-django

Seamless integration of Algolia into your Django project.
https://www.algolia.com
MIT License
173 stars 65 forks source link

Proxy method for related (M2M) field giving previously saved values #264

Closed qcaron closed 3 years ago

qcaron commented 6 years ago

I followed the documentation to get proxy methods to work but when I save an object with related fields (M2M), the values list reflect what was in the related field before saving. This is most likely because an instance is saved (thus the post_save signal is triggered) before its related fields are saved (m2m_changed signal).

Following and slightly adapting the documentation, I have the following code:

models.py

class Account(models.Model):
    username = models.CharField(max_length=40)
    service = models.CharField(max_length=40)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = 'Account'
        verbose_name_plural = 'Accounts'

class Contact(models.Model):
    name = models.CharField(max_length=40)
    email = models.EmailField(max_length=60)
    accounts = models.ManyToManyField(Account)

    def __str__(self):
        return self.name + ' ' + self.email

    def account_names(self):
        return [str(account) for account in self.accounts.all()]

    def account_ids(self):
        return [account.id for account in self.accounts.all()]

    class Meta:
        verbose_name = 'Contact'
        verbose_name_plural = 'Contacts'

index.py

from algoliasearch_django import AlgoliaIndex
from algoliasearch_django.decorators import register

from .models import Contact

@register(Contact)
class ContactIndex(AlgoliaIndex):
    fields = (
        'name',
        'email',
        'account_names',
        'account_ids',
    )

    settings = {
        'searchableAttributes': [
            'name',
            'email',
            'account_names',
        ]
    }

I am using:

I believe you will find this interesting: https://stackoverflow.com/questions/23795811/django-accessing-manytomany-fields-from-post-save-signal

Any idea how to get that working?

P.S.: For a ready-to-go and correct test example, the documentation could be updated according to the above models.py and index.py 😉

qcaron commented 6 years ago

I found a solution for this as I finally figured out how I did the trick in an old project.

It would look like this for the above Account and Contact models:

apps.py The magic here comes from auto_indexing=False when registering the Algolia index.

from django.apps import AppConfig

import algoliasearch_django as algoliasearch

from .index import ContactIndex

class ContactAppConfig(AppConfig):
    name = 'contact'
    verbose_name = 'Contact Management'

    def ready(self):
        contact_model = self.get_model('Contact')
        algoliasearch.register(contact_model, ContactIndex, auto_indexing=False)

admin.py Here, I overrride django.contrib.admin.ModelAdmin.response_add and django.contrib.admin.ModelAdmin.response_change to call algoliasearch_django.save_record.

from django.contrib import admin

import algoliasearch_django

from .models import Contact

class ContactAdmin(admin.ModelAdmin):
    list_display = (
        'name',
        'email',
        'accounts',
    )
    fieldsets = (
        (None, {
            'fields': (
                'name', 
                'email',
                'accounts')
        }),
    )

    def response_add(self, request, obj, post_url_continue=None):
        algoliasearch_django.save_record(obj)

        return super(ContactAdmin, self).response_add(
            request=request,
            obj=obj,
            post_url_continue=post_url_continue
        )

    def response_change(self, request, obj):
        algoliasearch_django.save_record(obj)

        return super(ContactAdmin, self).response_change(request=request, obj=obj)

Of course this is only for the admin but if you use views and forms, you could also trigger algoliasearch_django.save_record after models.Model.save_m2m.

This solution prevents using the algoliasearch_django.decorators.register decorator as it does not accept an auto_indexing parameter. This could be a nice improvement 🙂

clemfromspace commented 6 years ago

Hi again @qcaron !

Thank you for your feedback, If I understand correctly there are two issues there:

Do I correctly get it?

The first issue will take some time to implement (I used it in the past and the m2m_changed is a tricky signal to listen to...).

I think the second one can be done really fast (the register decorator is just a wrapper around the register method).

EDIT: Actually, the first issue might be a duplicate of #202

qcaron commented 6 years ago

You definitely got it correctly @clemfromspace 😉

I also believe it will not be straight forward to find an elegant and simple solution for the m2m problem. I will be busy in the next 4 weeks but if I find time to investigate, I will put my findings in this thread and make my changes available in a fork. This m2m issue indeed seems to duplicate #202.

tkrugg commented 3 years ago

dup https://github.com/algolia/algoliasearch-django/issues/202