Urnitemare Urnitemare - 6 months ago 30
Python Question

Restrict form field in CreateView to not show current-user as a choice in template

I am using simple forms and the generic CBV UpdateView to all a user to use "tokens" to give other users extra-credit. A user should not be allowed to give themselves extra-credit, so they should not show-up in the select field as a choice. I am not using a model form here and would like to know how to do this without doing so. Here is my code thus far:

models.py

class ExtraCredit(models.Model):
recipient = models.ForeignKey(Developer, related_name='extracredit_recipient')
sender = models.ForeignKey(Developer, related_name='extracredit_sender')
skill = models.ForeignKey(Skill, related_name='extracredit_skill')
description = models.TextField(blank=True, null=True)
date_credited = models.DateField(auto_now_add=True)

def __str__(self):
return '%s %s -> %s %s - %s' % (self.sender.user.first_name, self.recipient.user.last_name, self.recipient.user.first_name, self.recipient.user.last_name, self.skill.name)


views.py

class ExtraCreditCreateView(CreateView):
model = ExtraCredit
template_name = 'extracredit_create.html'

def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(ExtraCreditCreateView, self).__init__(*args, **kwargs)

self.fields['recipient'].queryset = ExtraCredit.objects.exclude(recipient=user)
self.fields['description'].help_text = "Why does this user deserve extra credit?"

def get_form_kwargs(self):
kwargs = super(ExtraCreditCreateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs

def get_success_url(self):
return reverse('my_developer_details')

def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form))

def form_valid(self, form):
form.save(commit=False)
sender = get_object_or_404(Developer, user_id=self.request.user.id)
extra_credit_tokens = Developer.objects.get(user_id=sender.user_id).extra_credit_tokens

if extra_credit_tokens:
Developer.objects.filter(user_id=sender.user_id).update(extra_credit_tokens=extra_credit_tokens-1)

form.instance.sender = sender
form.save()

return super(ExtraCreditCreateView, self).form_valid(form)
else:
raise forms.ValidationError("The user does not have enough tokens")

fields = ['recipient', 'skill', 'description']


The only reason I have the
get_form_kwargs(self)
function is to try to use
self.request.user
in the
def __init__
function. If I use self.request.user in the
__init__
function, I get an error that request is not an attribute of
ExtraCreditCreateView
. If I do add request to the options, like
__init__(self, request, *args, **kwargs):
then I get an error about needing two arguments and only supplying one. The way it is set up now, I get a TypeError:
list indices must be integers, not str
.

What would be the correct way to make sure that the current logged-in user is not in the 'recipient' field when the form is created in the template?

UPDATED SOLUTION (Based on @Alasdair's Answer):

class ExtraCreditForm(forms.ModelForm):
class Meta:
model = ExtraCredit
exclude = ['id', 'sender', 'date_credited']

def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(ExtraCreditForm, self).__init__(*args, **kwargs)

self.fields['recipient'].queryset = self.fields['recipient'].queryset.exclude(user_id=user.id)
self.fields['description'].help_text = "Why does this user deserve extra credit?"

class ExtraCreditCreateView(CreateView):
template_name = 'extracredit_create.html'
form_class = ExtraCreditForm

def get_form_kwargs(self):
kwargs = super(ExtraCreditCreateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs

def get_success_url(self):
return reverse('my_developer_details')

def form_valid(self, form):
form.save(commit=False)
sender = get_object_or_404(Developer, user_id=self.request.user.id)
extra_credit_tokens = Developer.objects.get(user_id=sender.user_id).extra_credit_tokens

if extra_credit_tokens:
Developer.objects.filter(user_id=sender.user_id).update(extra_credit_tokens=extra_credit_tokens-1)

form.instance.sender = sender
form.save()

return super(ExtraCreditCreateView, self).form_valid(form)
else:
raise forms.ValidationError("The user does not have enough tokens")

Answer

You say that you want to avoid defining a model form, but that's the best approach here. It might be possible to hack the form's fields in the get_form method, but you shouldn't do this.

Setting the recipient's queryset belongs in the form, not the view's __init__ method.

class ExtraCreditForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        super(ExtraCreditForm, self).__init__(*args, **kwargs)

        self.fields['recipient'].queryset = ExtraCredit.objects.exclude(recipient=user)
        self.fields['description'].help_text = "Why does this user deserve extra credit?"

Then remove the __init__ method from your view, and set form_class so that it uses your model form. You have already updated get_form_kwargs to pass the user to the form's __init__ method, so you shouldn't have to make any other changes. Your form_invalid method isn't doing anything special, so you can remove it.

class ExtraCreditCreateView(CreateView):
    form_class = ExtraCreditForm
    ...
Comments