applegrew / django-select2

This is a Django integration for Select2

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

field_id not found error

Mastermind-U opened this issue · comments

Hello, Ive found out that the same widgets on different pages can use the same cache key. I configured cache timeout to 5h, and I got user on production that went to sleep and after that he continued to use this widget, so it returned 404 (field id not found), its not trouble, trouble started when other user used the same widget on the other page (url) at the same time, he got 404 too (the results could not be loaded). He used this widget for only 2 minutes so cache should be okay (but not). In other cases it works fine.

I checked issue #103 and source code found out that older versions had GENERATE_RANDOM_SELECT2_ID, but not in latest.

packages:

Django==2.1
django-select2==6.3.1

cache config:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
        'TIMEOUT': 60 * 60 * 5,  # 5 hours
        'OPTIONS': {
            'MAX_ENTRIES': 2000
        }
    }
}

P.S.
I started gunicon with 2 workers.

Hi @Mastermind-U,

Thanks for reaching out, I hope we can resolve the problem together.
When you say "2 workers", do you mean gunicorn workers, or two separate machines (virtual or real).

Do you by any chance know the field_ids in those cases? Are they actually the same? See, the field_id is only the same if you would reuse the widget's instance. They are not unique per widget class. Maybe you could also share a bit of code with me, showing how you implemented select2.

Best
-Joe

@codingjoe yeah, I mean gunicorn workers.
Heres the form and widget code (and also I have two different FormViews, that just saves these forms):
(model has just one field)

class CreateValueTagWidgetMixin(ModelSelect2TagWidget):
    obj = None

    def value_from_datadict(self, data, files, name):
        values = super(CreateValueTagWidgetMixin, self).value_from_datadict(data, files, name)
        cleaned_values = [pk for pk in values if pk.isnumeric()]
        for val in [item for item in values if not item.isnumeric()]:
            val, _ = self.obj.objects.get_or_create(name=val)
            cleaned_values.append(val.pk)
        return cleaned_values


class SkillWidget(CreateValueTagWidgetMixin):
    obj = Skills


class SkillsForm(forms.ModelForm):
    class Meta:
        model = Student
        fields = 'skills',
        widgets = {
            'skills': SkillWidget(
                queryset=Skills.objects.all(),
                max_results=5,
                search_fields=['name__icontains'],
                attrs={'data-placeholder': 'Выберите навыки', 'data-language': 'ru'}
            ),
        }

and others:

from users.forms import SkillWidget

class VacancyForm(forms.ModelForm):
    class Meta:
        model = Vacancy
        exclude = (
            'created_by', 'created_at', 'edited_at',
            'archive_time', 'company', 'is_archived',
        )
        widgets = {
            'skills': SkillWidget(
                queryset=Skills.objects.all(),
                search_fields=['name__icontains'],
                max_results=5,
                attrs={'data-language': 'ru'}
            ),
            # other widgets
        }

Hi @Mastermind-U,

I took some time and went through your code example and cleaned it up a little. It should work the same way, but uses a couple of builtin shortcuts. Things like the placeholder can be defined via the empty_label. You can access the Model's queryset via get_queryset.

I don't think you need to define data-language. By default, it should use the language defined in your html-tag. If you don't have it set, make sure to change your template, since it's required, see also: https://www.w3.org/International/questions/qa-html-language-declarations
A good way to ensure that it's always set to the correct language is:

<html lang="{{ LANGUAGE_CODE }}">
class CreateValueTagWidgetMixin(ModelSelect2TagWidget):

    def value_from_datadict(self, data, files, name):
        values = super().value_from_datadict(data, files, name)
        cleaned_values = (pk for pk in values if pk.isnumeric())
        for val in (item for item in values if not item.isnumeric()):
            val, _ = self.get_queryset().get_or_create(name=val)
            cleaned_values.append(val.pk)
        return cleaned_values


class SkillWidget(CreateValueTagWidgetMixin):
    model = Skills
    max_results = 5
    search_fields = 'name__icontains',
    empty_label = 'Выберите навыки'


class SkillsForm(forms.ModelForm):
    class Meta:
        model = Student
        fields = 'skills',
        widgets = {
            'skills': SkillWidget
        }
from users.forms import SkillWidget

class VacancyForm(forms.ModelForm):
    class Meta:
        model = Vacancy
        exclude = (
            'created_by', 'created_at', 'edited_at',
            'archive_time', 'company', 'is_archived',
        )
        widgets = {
            'skills': SkillWidget,
            # other widgets
        }

Now back to your initial problem. You actually don't reuse the same widget instance in both forms, so they will not have the same ID and will not share the same cache key. So if you are getting the same error across multiple pages, it seems like you cash doesn't keep your entries as long as you'd expect. I would recommend switching to a more sophisticated caching solution, like Redis. It's super easy to set up and outperforms the file backend in all aspects. If you don't want to go through the trouble of setting up Redis, simply use the database cache backend.

I hope this helps you a bit. If you have further questions or need help, please don't be afraid to reach out at any time :)

Best
-Joe