35

I'm making an election information app, and I want to allow the currently logged-in user to be able to declare himself and only himself as a candidate in an election.

I'm using Django's built-in ModelForm and CreateView. My problem is that the Run for Office form (in other words, the 'create candidate' form) allows the user to select any user in the database to make a candidate.

I want the user field in the Run for Office to be automatically set to the currently logged-in user, and for this value to be hidden, so the logged-in user cannot change the value of the field to someone else.

views.py

class CandidateCreateView(CreateView):
    model = Candidate
    form_class = CandidateForm
    template_name = 'candidate_create.html'

    def form_valid(self, form):
        f = form.save(commit=False)
        f.save()
        return super(CandidateCreateView, self).form_valid(form)

forms.py

class CandidateForm(forms.ModelForm):
    class Meta:
        model = Candidate

models.py

class Candidate(models.Model):
    user = models.ForeignKey(UserProfile)
    office = models.ForeignKey(Office)
    election = models.ForeignKey(Election)
    description = models.TextField()

    def __unicode__(self):
        return unicode(self.user)

    def get_absolute_url(self):
        return reverse('candidate_detail', kwargs={'pk': str(self.id)})
glls
  • 2,325
  • 1
  • 22
  • 39
Mitch Downey
  • 887
  • 1
  • 11
  • 15

2 Answers2

59
  1. Remove user field from rendered form (using exclude or fields, https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#selecting-the-fields-to-use )

    class CandidateForm(forms.ModelForm):
        class Meta:
            model = Candidate
            exclude = ["user"]
    
  2. Find user profile and set user field in the create view.

    class CandidateCreateView(CreateView):
        ...
        def form_valid(self, form):
            candidate = form.save(commit=False)
            candidate.user = UserProfile.objects.get(user=self.request.user)  # use your own profile here
            candidate.save()
            return HttpResponseRedirect(self.get_success_url())
    
nmb.ten
  • 2,158
  • 1
  • 20
  • 18
  • Dang, that was easy. I was trying things like "candidate.user = self.request.user" before, but obviously that doesn't work. This will come in handy for other forms I have to make, thanks a lot! – Mitch Downey Aug 15 '13 at 07:04
  • Why is candidate.user = self.request.user not a working ? Isn't self.request.user a user object too ? – hlkstuv_23900 Dec 20 '15 at 09:22
  • Because self.request.user is User instance, but Candidate.user is UserProfile instance. – nmb.ten Dec 21 '15 at 12:14
  • 1
    This approach will integrate user into form itself http://stackoverflow.com/questions/4141408/django-form-set-current-login-user – Igor Yalovoy Mar 08 '17 at 19:11
  • 1
    I am using crispy form. I have to changed the last line to 'return super(CanndidateCreateView, self).form_valid(form)' in order for the redirect to work. – Jason Ching May 14 '17 at 04:09
  • Is it possible to make a Mixin from it ? I would like to reuse the form_valid you just made in several create views. – Bravo2bad Jul 31 '20 at 19:30
8

Assumptions

  1. We don't want to set null=True becaues we don't want to allow null users at the model and/or database level

  2. We don't want to set blank=True to mess with the readability of model because the user actually will not be blank

@nbm.ten solution is a good one. It has an advantages over other 'solutions'to this problem that utilized model to set the user (like this one) in nbm.ten's doesn't undermine the assumptions above. We don't want to mess with the model to fix a problem in view!

But here I add two other solutions based on django documentation (Models and request.user):

Two other solutions

1. Using the generic CreateView

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Candidate

class CandidateCreate(LoginRequiredMixin, CreateView):
    model = Candidate
    exclude = ['user']

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super().form_valid(form)

2. Using class-based views

class CandidateForm(ModelForm):
    class Meta:
        model = Candidate
        exclude = [ 'user',]

class CandidateAddView(LoginRequiredMixin, View):
    def get(self, request, *args, **kwargs):
        form = CandidateForm()
        context = {'form':form}
        return render(request, 'myapp/addcandidateview.html', context)
    
    def post(self, request, *args, **kwargs):
           
        form = CandidateForm(request.POST)
        form.instance.user = request.user
        if form.is_valid():
            form.save()
            return redirect(reverse('myapp:index'))

NOTES

  • Note that LoginRequiredMixin prevents users who aren’t logged in from accessing the form. If we omit that, we'll need to handle unauthorized users in form_valid() or post().

  • Also exclude = ['user'] prevents the user field to be shown on the form.

  • We used form.instance.user to set the user not form.data or form.cleaned_data they don't work

Ehsan88
  • 3,569
  • 5
  • 29
  • 52
  • You should probably mention that the "generic create view" is a class based view that does all the work you're doing in what you call a "class based view", only with more abstractions and sane defaults. –  Jul 04 '20 at 09:22