Jim
Jim

Reputation: 14290

How to combine Django FormSet and QuerySet in view and template?

I have Django 1.6 application that will contain a form that displays a list of customers with a pair of "Approve" and "Reject" radio buttons next to each name to indicate if we will approve or reject their method of payment. I want each radio button to be set by default to "Reject" when the form is first rendered. I also want to include a hidden "uid" field on each line that contains the customer's user ID. When the admin clicks the Approve button next to each username s/he wants to approve and then submits the form, the view should read each hidden id value for each user, inspect the radio button, and update the the model if the user is approved. Here's what the form will look like:

customer1 (hidden id) [ ] approve  [x] reject
customer2 (hidden id) [ ] approve  [x] reject
...
customerN (hidden id) [ ] approve  [x] reject

I have three problems that I don't quite understand how to solve:

  1. How do you combine the Queryset that contains my customer usernames and IDs with the FormSet that will contain the radio button pair for each queryset object? I'm pretty sure I need to use a FormSet to hold the radio buttons and I think I need to set the formset's "initial" value to the queryset but I can't get them to "connect" so that the form looks like what I've shown above. I don't see my account queryset objects when I do a "view source" in my browser.
  2. How do you connect the customer's ID that comes from the Account model's user column via the new_accounts query set to the uid field in the form?
  3. How do you iterate through the submitted formset and pull out the user IDs and the radio button objects for inspection?

I'm really having a hard time wrapping my head around these tasks. Thanks very much for your help.

# views.py
def review_payment_methods(request, template):
if request.method == "POST":
    payment_method_form = ReviewPaymentMethodForm(request.POST)
    if payment_method_form.is_valid():
        # How to iterate through form and pull out ids and radio button values??
        # Update Account table here
        return HttpResponseRedirect('/admin/')
else:
    new_accounts = Account.objects.filter(method_approved=False).values()
    PaymentMethodFormset = formset_factory(ReviewPaymentMethodForm, extra=new_accounts.count())
    formset = PaymentMethodFormset(initial=new_accounts)  # This doesn't seem to work
return render_to_response(template, locals(), context_instance=RequestContext(request))

# models.py
class Account(models.Model):
"""A user's account."""
user = models.OneToOneField(User, primary_key=True, unique=True)
method_approved = models.BooleanField(default=False)  # This contains Approve/Reject

# forms.py
from django import forms
from django.utils.safestring import mark_safe

class ReviewPaymentMethodForm(forms.Form):
    class HorizontalRadioRenderer(forms.RadioSelect.renderer):
        def render(self):
            return mark_safe(u'\n'.join([u'%s\n' % w for w in self]))

    DECISION_CHOICES = (('1', 'Approve'), ('2', 'Reject'))
    uid = forms.IntegerField(widget=forms.HiddenInput)
    decision = forms.ChoiceField(
        choices = DECISION_CHOICES,
        widget = forms.RadioSelect(renderer=HorizontalRadioRenderer),
        initial = '2',  # 1 => Approve, 2 => Reject
    )

# review_payment_methods.html
<div class="custom-content">
    <h1>Review Payment Methods</h1>
    <form action="." method="post">{% csrf_token %}
        {% for form in formset %}
            {{ form.as_p }}
        {% endfor %}
        <input type="submit" value="Submit" />
    </form>
</div>

Upvotes: 0

Views: 1344

Answers (1)

Daniel Roseman
Daniel Roseman

Reputation: 599956

This isn't really a job for formsets. You want a single form with a dynamic set of fields; each field's name is the customer UID and its value is accept or reject. To do that, you can create the fields programmatically when instantiating the form:

class ReviewPaymentMethodForm(forms.Form):
    def __init__(self, *args, **kwargs):
        accounts = kwargs.pop('accounts')
        super(ReviewPaymentMethodForm, self).__init__(*args, **kwargs)
        for account in accounts:
            self.fields[str(account.id)] = forms.ChoiceField(
                label=account.user.username,
                choices=DECISION_CHOICES,
                widget=forms.RadioSelect(renderer=HorizontalRadioRenderer),
                initial='2',  # 1 => Approve, 2 => Reject
            )

And the view becomes:

def review_payment_methods(request, template):
    new_accounts = Account.objects.filter(method_approved=False)
    if request.method == "POST":
        payment_method_form = ReviewPaymentMethodForm(request.POST, accounts=new_accounts)
        if payment_method_form.is_valid():
            for acc_id, value in payment_method_form.cleaned_data.items():
                approved = (value == '1')
                Account.objects.filter(pk=acc_id).update(method_approved=approved)
            return HttpResponseRedirect('/admin/')
    else:

        form = ReviewPaymentMethodForm(accounts=new_accounts)
    return render(request, template, {'form': form})

Upvotes: 2

Related Questions