简体   繁体   中英

How to use TextInput widget with ModelChoiceField in Django forms?

In Django, the ChoiceField or ModelChoiceField represent the data in a Select widget. This is helpful when the list of objects is small. However it is extremely difficult to manage (for the end-user) if the number of objects are in thousands.

To eliminate the said problem I'd like the end-users to manually enter the field value in an input box of type text (ie, via TextInput widget).


So far, I have created the below code. As ModelChoiceField has Select widget by default; It behaves in similar manner as before even after changing the widget to TextInput . It expects a pk or id value of the model object and thus raising an error :

Select a valid choice. That choice is not one of the available choices.

However, I'd want the end-user to enter sku_number field in the input box rather than the pk or id of the object. What is the correct way to solve this problem?

models.py

class Product(models.Model):
    sku_number = models.CharField(null=False, unique=True)
    product_name = models.CharField(null=Flase)

    def __str__(self):
        return self.sku_number

forms.py

class SkuForm(forms.Form):
    sku_number = forms.ModelChoiceField(queryset=Product.objects.all(), 
                                        widget=forms.TextInput())
    extra_field = forms.CharField(required=True)

Note : I did try another approach to solve this problem. By displaying only last 10 objects by slicing the number of objects; this ensures that the select box is not flooded with thousands of items.

queryset=Product.objects.all().order_by('-id')[:10]

The latter methodology if correctly implemented would work with my particular use-case, however others might be interested in the former method. The above statement further raised errors because of Django's limitation with generating SQL statements.

Also note that even though slicing an unevaluated QuerySet returns another unevaluated QuerySet , modifying it further (eg, adding more filters, or modifying ordering) is not allowed, since that does not translate well into SQL and it would not have a clear meaning either.

Source : Django Docs

You can easily do that in the form clean() method.

Ie

from django.shortcuts import get_object_or_404

class SkuForm(forms.Form):
    sku = forms.CharField(required=True)
    extra_field = forms.CharField(required=True)

    def clean(self):
        # If you're on Python 2.x, change super() to super(SkuForm, self)
        cleaned_data = super().clean()
        sku = cleaned_data['sku']
        obj = get_object_or_404(Product, sku_number=sku)
        # do sth with the Product

In my case, I had to change the sku_number field to Product object's id . This had to be done before clean() . As seen in this answer , __init__() should be used to modify data before it reaches clean() .

class SkuForm(forms.Form):
    sku_number = forms.ModelChoiceField(queryset=Product.objects.all(), 
                                        widget=forms.TextInput())
    extra_field = forms.CharField(required=True)

    def __init__(self, data=None, *args, **kwargs):
        if data is not None:
            data = data.copy() # make it mutable
            if data['sku_number']:
                obj = get_object_or_404(Product, batch_name=data['sku_number'])
                data['sku_number'] = obj.id
        super(SkuForm, self).__init__(data=data, *args, **kwargs)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM