Search code examples
pythondjangomodelforeign-keys

Django: How to autopopulate foreign key with the corresponding model class instance


Working on my first Django project and could use some help. I have 2 models (Decisions, Votes) linked by the foreign key called 'decision'. The template, vote_list.html, shows the user a list of decisions (generated by other users) that are contained in Decisions. The user taps a particular decision and is re-directed to a second template to vote on options pertaining to that decision. How do I autopopulate the foreign key 'decision' in Votes with the corresponding instance of Decision so that the second template, vote_form.html, displays the options for the decision they tapped on? I assume it's coded in views.py (I commented an attempt below that doesn't work), but how might it be done? Thank you!

urls.py

path('vote-list/', views.VoterView.as_view(), name='vote_list'),
path('vote-list/<pk>/vote-form/', views.VoteForm.as_view(), name='vote_form'),

models.py

class Decisions(models.Model):
    custom_user = models.ForeignKey(CustomUser, 
                  default=None, null=True, 
                  on_delete=models.SET_NULL)
    description = models.CharField(default="", 
                  max_length=100, verbose_name="Decision 
                  Summary")

class Votes(models.Model):
    decision = models.ForeignKey(Decisions, 
               default=None, null=True, 
               on_delete=models.SET_NULL)
    vote = models.CharField(default="", max_length=100, 
            verbose_name="Your vote")

views.py

class VoteForm(LoginRequiredMixin, CreateView):
    model = Votes
    form_class = VotingForm
    template_name = 'users/vote_form.html'

    def post(self, request, *args, **kwargs):
        super()
        form = self.form_class(data=request.POST)
        if form.is_valid():
            instance = form.save(commit=False)
        
            # instance.decision = Decisions.description
        
            instance.save()
        return redirect('users:vote_list')    

forms.py

class VotingForm(forms.ModelForm):
    class Meta:
        model = Votes
        fields = ['vote']

vote_list.html

     {% for item in Decisions %}
         <tr>
            <td>{{ item.description }}</td>
            <td><a href="{% url 'users:vote_form' item.id 
                 %}">Vote</a></td>
         </tr>                   
     {% endfor %}    

vote_form.html

     {# trying to display the corresponding 
          decision description here from vote_list.html # }}

     {{ form.vote|as_crispy_field }}

Solution

  • I think this might solve your problem:

    1. Add decision field in the voting form. This will display an option to select for which decision you need to save this Vote for. If you don't want to allow users to change the Decision, you can mark the field as disabled. See this issue for more details on how to do that. Another alternative is to completely hide the field.
    class VotingForm(forms.ModelForm):
        class Meta:
            model = Votes
            fields = ['vote', 'decision']
    
    1. Add initial value of the decision when instantiating the VotingForm. This will automatically set which decision is selected when displaying the form.
    class VoteForm(LoginRequiredMixin, CreateView):
        model = Votes
        form_class = VotingForm
        template_name = 'users/vote_form.html'
    
        # Use this to pass 'pk' to your context in the template
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context.update({'pk': self.kwargs['pk'})
    
            return context
    
        def get_initial(self):
            initial = super().get_initial()
            initial.update({'decision': self.kwargs['pk']})
    
            return initial
    
        def get_success_url():
            # Import reverse from django.urls
            return reverse('users:vote_list')
    

    Also, your form should probably be displayed like this in the HTML template: {% crispy form %}. This way all defined fields from the VotingForm class are rendered automatically.

    <form method="post" action="{% url 'users:vote_form' pk %}">
         {% crispy form %}
    </form>