Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin form widget for multiple countries #273

Open
browniebroke opened this issue Sep 3, 2019 · 4 comments
Open

Admin form widget for multiple countries #273

browniebroke opened this issue Sep 3, 2019 · 4 comments

Comments

@browniebroke
Copy link

We are using this field with multiple=True on a couple of models, and the Admin widget is a simple select multiple. Would be nice to enable a nicer widget by default, ideally reusing the one from Django's filter_horizontal option.

It looks like adding the FilteredSelectMultiple widget to the field on the admin form works, but could it be used by default? If not, would be nice to mention it in the readme maybe?

@dylan-tkt
Copy link

dylan-tkt commented Feb 14, 2020

example code in case anyone just wants to copy-paste workaround.

from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple


class MovieForm(forms.ModelForm):
    """ Use FilteredSelectMultiple instead for easier country selection. """

    class Meta:
        model = models.Movie
        fields = '__all__'
        widgets = {'countries': FilteredSelectMultiple('pays', is_stacked=False)}

@k0nG
Copy link

k0nG commented Mar 24, 2021

Thanks for the example @dylan-tkt .

Just a note for anyone else doing this, I had an issue with FilteredSelectMultiple and COUNTRIES_FIRST_REPEAT=True. It seems the widget JS then adds the countries in COUNTRIES_FIRST twice to the chosen countries and it looks odd. I had to manually override first_repeat = False in a new Countries instance and set that on my model field:

class NoFirstRepeatCountries(Countries):
    # Repeating first causes an issue with FilteredSelectMultiple used in admin
    first_repeat = False

....

countries = CountryField(multiple=True, countries=NoFirstRepeatCountries, blank=True)

@shawnngtq
Copy link

@browniebroke @dylan-tkt @k0nG , thanks for code snippet, it works for admin.

I use FilteredSelectMultiple on my html form and it breaks my form layout 😅

Was wondering if you have any library recommendation for a multiple select field such as manytomany and multiple countries

image

@philgyford
Copy link
Contributor

I think there's been a change in how Django renders these fields and so the filter fields must be wrapped in RelatedFieldWidgetWrapper. However, while I've got this to work with similar fields, I hit an error when trying to do so with django-countries' CountryField.

As an example of how it works for a simple ManyToManyField, let's say I have these models:

from django.db  import models

class Publication(models.Model):
    title = models.CharField(max_length=30)

class Article(models.Model):
    headline = models.CharField(max_length=100)
    publications = models.ManyToManyField(Publication)

And then in my admin.py:

from django import forms
from django.contrib import admin
from django.contrib.admin.widgets import FilteredSelectMultiple, RelatedFieldWidgetWrapper
from .models import Article, Publication

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        exclude = ()

    publications = forms.ModelMultipleChoiceField(
        queryset=Publication.objects.all(),
        widget=RelatedFieldWidgetWrapper(
            FilteredSelectMultiple("publications", is_stacked=False),
            rel=Article._meta.get_field("publications").remote_field,
            admin_site=admin.site,
            can_add_related=True,
        )
    )

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    filter_horizontal = ("publications",)
    form = ArticleForm

This works fine, replicating how the form would look by default, if I didn't declare the publications field manually:

Screenshot 2024-04-06 at 11 50 55

But if I replace that declaration of publications with this simpler version, omitting RelatedFieldWidgetWrapper:

    publications = forms.ModelMultipleChoiceField(
        queryset=Publication.objects.all(),
        widget=FilteredSelectMultiple("publications", is_stacked=False)
    )

then we get a messed-up layout with the field's label on the right:

Screenshot 2024-04-06 at 11 51 11

So, that's how it should work. Back to django-countries. In my project I have been using this, for a Website model that has a countries field using CountryField:

from django_countries import countries as countries_object

class WebsiteForm(forms.ModelForm):
    class Meta:
        model = Website
        exclude = ()

    countries = forms.MultipleChoiceField(
        choices=list(countries_object),
        widget=FilteredSelectMultiple("countries", is_stacked=False),
        required=False,
    )

That used to work but at some point started generating the second layout above, with the field's label on the right. If I try to wrap the widget in RelatedFieldWidgetWrapper like this:

    countries = forms.MultipleChoiceField(
        choices=list(countries_object),
        widget=RelatedFieldWidgetWrapper(
            FilteredSelectMultiple("countries", is_stacked=False),
            rel=Website._meta.get_field("countries").remote_field,
            admin_site=admin.site,
        ),
        required=False,
    )

Then I get this error:

web_1       |   File "/usr/local/lib/python3.10/site-packages/django/forms/boundfield.py", line 108, in as_widget
web_1       |     return widget.render(
web_1       |   File "/usr/local/lib/python3.10/site-packages/django/forms/widgets.py", line 278, in render
web_1       |     context = self.get_context(name, value, attrs)
web_1       |   File "/usr/local/lib/python3.10/site-packages/django/contrib/admin/widgets.py", line 316, in get_context
web_1       |     rel_opts = self.rel.model._meta
web_1       | AttributeError: 'NoneType' object has no attribute 'model'

So I guess something is unexpected about CountryField that means it's not working like a standard field?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants