1

I have a ModelForm in which I use the FilteredSelectMultiple widget. It works perfectly fine when I'm logged in as the superuser I have created. However, when not logged in, I cannot see the widget in the form, I only see the list of all items (like a multiple select). So my question is : why is FilteredSelectMultiple working perfectly fine when logged in, but is not there when logged out ?

I haven't set any permission or anything like that anywhere that I can think of.

Here are parts of my code :

forms.py

from django.contrib.admin.widgets import FilteredSelectMultiple

class MyModelForm(forms.ModelForm):
    my_field = forms.ModelMultipleChoiceField(queryset=Something.objects.all(), widget=FilteredSelectMultiple("Somethings", is_stacked=False), required=False)

    class Media:
        css = {
            'all': (os.path.join(settings.BASE_DIR, '/static/admin/css/widgets.css'),),
        }
        js = ('/admin/jsi18n'),

    class Meta:
        model = MyModel
        fields = ('some_field', 'some_other_field')

form.html

{% extends base.html %}
{% block head %}
{% load staticfiles %}
    some stuff
{% endblock head %}
{% block content %}
<script type="text/javascript" src="{% url 'jsi18n' %}" > </script>

{{ form.media }}

  <form enctype="multipart/form-data" method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="save btn btn-default">Submit</button>
  </form>

{% endblock content %}

urls.py

url(r'^admin/jsi18n/$',
    'django.views.i18n.javascript_catalog',
    name='jsi18n'
),

Tell me if you need any other code. (I use Django 1.8 and Python 2.7)

EDIT

When loading the page when logged out, the consoles displays the following :

jsi18n : SyntaxError: expected expression, got '<'

jsi18n : SyntaxError: expected expression, got '<'

SelectFilter2.js : ReferenceError: interpolate is not defined

None of these messages appear when I am logged in as the superuser.

EDIT 2

As suggested in the answers, I tried changing my media class to :

class Media:
    # Nécessaire pour l'affichage de FilteredSelectMultiple
    css = {
        'all': (os.path.join(settings.BASE_DIR, '/static/admin/css/widgets.css'),),
    }
    extra = '' if settings.DEBUG else '.min'
    js = ('/admin/jsi18n', 'jquery%s.js' % extra, 'jquery.init.js', 'core.js', 'SelectBox.js', 'SelectFilter2.js'),

Which results in an AttributeError:

AttributeError at /my/url/form

'tuple' object has no attribute 'startswith'

[...]

Exception Location: /path/to/virtualenv/local/lib/python2.7/site-packages/django/forms/widgets.py in absolute_path, line 74

I also try changing my template:

<script type="text/javascript" src="{% url 'jsi18n' %}" > </script>
<script type="text/javascript" src="{% static 'admin/js/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/jquery.init.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/core.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/SelectBox.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/SelectFilter2.js' %}"></script>
{{ form.media }}

which didn't produce any error, but also didn't change anything to my problem :(

Any other idea ?

5 Answers5

3

I am not fully sure, but it might be because of the url is under admin auth?

url(r'^admin/jsi18n/$',
    'django.views.i18n.javascript_catalog',
    name='jsi18n'
)

So when not logged in as admin, the script jsi18n cannot be accessed because you need to be authenticated as admin to access the script.

Write the url to not be under admin:

url(r'^jsi18n/$',
    'django.views.i18n.javascript_catalog',
    name='jsi18n'
)
sergiuz
  • 5,353
  • 1
  • 35
  • 51
2

Old question but I did it recently and wanted to clear things out. You will need few things to make it work.

  1. As stated earlier so I will not repeat code You will need FilteredSelectMultiple widget assigned to your form field.
  2. CSS widget.css from admin in template.
  3. Jquery for the widget. Here I had some problems because I'am using bootstrap 4 and bootstrap date widget where jquery was declared like this:

    <script src="{% static 'admin/js/vendor/jquery/jquery.min.js' %}"></script>
    <script>window.jQuery || document.write("<script src=\"{% static 'admin/js/vendor/jquery/jquery.min.js' %}\"><\/script>")</script>
    

You have to add declaration for window.jquery:

    <script>window.jquery || document.write("<script src=\"{% static 'admin/js/vendor/jquery/jquery.min.js' %}\"><\/script>")</script>

Also You have to include jquery.init.js:

    <script src="{% static 'admin/js/jquery.init.js' %}"></script>
  1. jsi18n is generated JS in django admin and here are few things to say too.

    • You can declare it directly but You will have to be logged in as admin otherwise it won't work:

      <script type="text/javascript" src="/admin/jsi18n"></script>
      
    • Solution for above is mentioned above to create url for this BUT if You are using other language than english it won't localize your widget:

      url(r'^jsi18n/$',
          'django.views.i18n.javascript_catalog',
          name='jsi18n')
      
    • My solution is to generate manually localizeds file going to /admin/jsi18n url, saving it in static and then importing generated file from static to template - go in browser to "http://localhost/admin/jsi18n/" this will generate javascript file localized with your language in project settings. Save content like regular js file and import it in your template like regular js script/file.
  2. JS for widget. You can use django {{ form.media }} in template but if You are using this widget in modal window loaded by ajax like me it will delay things so I just import them manually in my base template.

  3. From django 10 up (if I am not mistaken) You have to manually initiate widget in template:

    {{ form.building }}
    <script>SelectFilter.init("id_building", "Builidngs", 0, "/static/");</script>
    
  4. Last thing is if You want to look exactly like in admin panel then You will have to override few css cause if you are using bootstrap it is using common css in few places. I won't paste code because I have modified these little more but You should deal with it just fine :)

rifle2000
  • 371
  • 2
  • 8
  • Thank you for your detailed answer. It won't be of use for me, because sergiuz's answer solved my problem perfectly and in a very simple way ; however, it might be useful for other people. For the record, when I posted the question, I was using Django 1.8, Bootstrap 3 and whetever version of jQuery that was available at that time. The code still worked when I upgraded to Bootstrap 4 & the latest jQuery. – iHaveNoIdeaWhatsoever Jul 14 '18 at 08:55
  • I know that it is old post, but maybe you could give example and explain in more details - what do you mean by: `My solution is to generate manually localizeds file going to /admin/jsi18n url, saving it in static and then importing generated file from static to template.` How should I do it? – Gexas Sep 09 '19 at 06:50
  • I made small edit for solution You asked. Hope it explains more. It is just simple process of saving generated file by django and importing it like regular js file – rifle2000 Sep 18 '19 at 14:37
1

Here is what I did

from django.contrib.admin.widgets import FilteredSelectMultiple

class MyModelForm(forms.ModelForm):
    my_field = forms.ModelMultipleChoiceField(queryset=Something.objects.all(), widget=FilteredSelectMultiple("Somethings", is_stacked=False), required=False)

    class Media:
        extend = False
        css = {
            'all': [
                'admin/css/widgets.css'
            ]
        }
        js = (
            'js/django_global.js',
            'admin/js/jquery.init.js',
            'admin/js/core.js',
            'admin/js/prepopulate_init.js',
            'admin/js/prepopulate.js',
            'admin/js/SelectBox.js',
            'admin/js/SelectFilter2.js',
            'admin/js/admin/RelatedObjectLookups.js',
        )  

    class Meta:
        model = MyModel
        fields = ('some_field', 'some_other_field')

Where 'js/django_global.js' is saved from Admin module like explained by 'rifle2000' @ point 4.3. Navigate in admin where 'filter_horizontal' is implemented, inspect sources and save (index) file stored in folder 'localhost/admin/jsi18n' as django_global.js in your 'app/static' folder.

And in your form template

{% extends 'base.html' %}
{% load static bootstrap4 %}

{% block extrastyle %}
    {% bootstrap_javascript jquery='full' %}  {# Embed Bootstrap JS+jQuery #}
    {{ form.media }}
    <style>
    .selector h2 {
        margin: 0;
        padding: 8px;
        font-weight: 400;
        font-size: 15px;
        text-align: left;
        background: #005236;
        color: white;
    }
    </style>
{% endblock extrastyle %}
openHBP
  • 627
  • 4
  • 11
0

It's probably due to the fact that when logged in and viewing in the admin it's loading extra javascript via the widget's media property.

Look at the widget's source here: https://github.com/django/django/blob/master/django/contrib/admin/widgets.py

See if those javascript files from the widget's media property are loaded when you're not logged in. If they're not, try adding them to your form's media.

Gareth
  • 1,430
  • 11
  • 15
0

I was having the same issue, but for Django 3.2. All other answers I found pointed to old solutions that were incompatible with how urls.py currently works in the latest version of Django.

If you find that the FilteredSelectMultiple widget only works when you are signed into the Admin portal, then you need to add the following to your urls.py in your main project folder:

from django.views.i18n import JavaScriptCatalog

urlpatterns = [
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),]

You should also ensure that your form's Media class looks something like this:

from django.contrib.admin.widgets import FilteredSelectMultiple
class DepartmentForm(ModelForm):
    employees = forms.ModelMultipleChoiceField(label="", queryset=Employees.objects.all(), widget=FilteredSelectMultiple("Employees", is_stacked=False))
    
    class Media:
            css = {'all': ('/static/path/to/widgets.css',),}
            js = ('/jsi18n',)

And after that, mine works as expected. If you are using a recent version of Django and find that the FilteredSelectMultiple widget only works when you are signed into the Admin portal, try the above. Hope this helps. Drove me nuts for a few hours.