Đăng ký SPRINGMEBLOG để tiếp thu kiến thức hiệu quả hơn!

Forgot Password

Đăng ký SPRINGMEBLOG để tiếp thu kiến thức hiệu quả hơn!

Cảm ơn bạn đã đăng ký tài khoản Springmeweb!

Vui lòng kiểm tra Email, click vào Link để kích hoạt tài khoản.

How To Dynamically Filter Modelchoice's Queryset In A Modelform?

BY: Nguyen-Ngoan  |   July 5, 2018
Python
  • 0

Click vào từ bạn chưa biết để dịch

Ask Vitor #2: How to dynamically filter ModelChoice's queryset in a ModelForm?

Michał Strumecki asks: I just want to filter select field in a form, regarding a currently logged user. Every user has own categories and budgets. I want to display only a models related with a currently logged user. I’ve tried stuff with filtering before is_valid field, but with no result.

Answer

This is a very common use case when dealing with ModelForms. The problem is that in the form fields ModelChoice and ModelMultipleChoiceField, which are used respectively for the model fields ForeignKeyand ManyToManyField, it defaults the queryset to the Model.objects.all().

If the filtering was static, you could simply pass a filtered queryset in the form definition, likeModel.objects.filter(status='pending').

When the filtering parameter is dynamic, we need to do a few tweaks in the form to get the right queryset.

Let’s simplify the scenario a little bit. We have the Django User model, a Category model and Product model. Now let’s say it’s a multi-user application. And each user can only see the products they create, and naturally only use the categories they own.

models.py

from django.contrib.auth.models import User
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=30)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

class Product(models.Model):
    name = models.CharField(max_length=30)
    price = models.DecimalField(decimal_places=2, max_digits=10)
    category = models.ForeignKey(Category)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

Here is how we can create a ModelForm for the Product model, using only the currently logged-in user:

forms.py

from django import forms
from .models import Category, Product

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ('name', 'price', 'category', )

    def __init__(self, user, *args, **kwargs):
        super(ProductForm, self).__init__(*args, **kwargs)
        self.fields['category'].queryset = Category.objects.filter(user=user)

That means now the ProductForm has a mandatory parameter in its constructor. So, instead of initializing the form as form = ProductForm(), you need to pass a user instance: form = ProductForm(user).

Here is a working example of view handling this form:

views.py

from django.shortcuts import render, redirect
from .forms import ProductForm

@login_required
def new_product(request):
    if request.method == 'POST':
        form = ProductForm(request.user, request.POST)
        if form.is_valid():
            product = form.save(commit=False)
            product.user = request.user
            product.save()
            return redirect('products_list')
    else:
        form = ProductForm(request.user)
    return render(request, 'products/product_form.html', {'form': form})

Using ModelFormSet

The machinery behind the modelformset_factory is not very flexible, so we can’t add extra parameters in the form constructor. But we certainly can play with the available resources.

The difference here is that we will need to change the queryset on the fly.

Here is what we can do:

models.py

@login_required
def edit_all_products(request):
    ProductFormSet = modelformset_factory(Product, fields=('name', 'price', 'category'), extra=0)
    data = request.POST or None
    formset = ProductFormSet(data=data, queryset=Product.objects.filter(user=request.user))
    for form in formset:
        form.fields['category'].queryset = Category.objects.filter(user=request.user)

    if request.method == 'POST' and formset.is_valid():
        formset.save()
        return redirect('products_list')

    return render(request, 'products/products_formset.html', {'formset': formset})

The idea here is to provide a screen where the user can edit all his products at once. The product form involves handling a list of categories. So for each form in the formset, we need to override the queryset with the proper list of values.

ProductFormSet

The big difference here is that each of the categories list is filtered by the categories of the logged in user.

Get the Code

I prepared a very detailed example you can explore to get more insights.

The code is available on GitHub: github.com/sibtc/askvitor.

Please Share This With

Dictionaries : 104 words

Click vào từ bạn chưa biết để dịch

  • Click đoạn văn để hiển thị từ điển
  • Hiển thị toàn bộ từ điển của bài viết
  • Ẩn các từ quen thuộc

ĐĂNG KÝ ĐỂ TIẾP THU KIẾN THỨC HIỆU QUẢ HƠN

ABOUT SPRINGMEBLOG

Springmeblog.com giới thiệu các bài viết về chủ đề KINH DOANH, PHÁT TRIỂN SỰ NGHIỆP, QUẢN LÝ CON TRẺ.

Các bài viết của Springmeblog được chọn lọc kỹ từ các bài viết English của các chuyên gia giỏi trên thế giới để mang lại nguồn kiến thức HỮU ÍCH nhất và THIẾT THỰC nhất có thể áp dụng ngay vào công việc và cuộc sống.

Springmeblog.com được thiết kế nhằm mang đến sự tiếp thu kiến thức một cách HIỆU QUẢ nhất. Mỗi bài viết English được giới thiệu có công cụ TỪ ĐIỂN TÍCH HỢP hỗ trợ đọc hiểu. Cùng với các công cụ LƯU BÀI VIẾT, TẠO GHI CHÚ cho mỗi bài viết, springmeblog hỗ trợ cho việc HỌC TẬP SUỐT ĐỜI qua tiêu chí ĐỌC ĐỂ LÀM và HỌC ĐỂ LÀM ĐƯỢC.

© 2018 SPRINGMEWEB. DESIGIN BY SPRINGMEWEB