'Django - How to populate manytomany field in forms by previously selected options by users

How can I populate manytomany form field with previous user selected subs.

In this code forms render choices with empty checkboxes. I want checkboxes to show which subscriptions user subscribed to.

models.py

class Subscription(models.Model):
    SUBSCRIPTION_TYPES = (
        ('SUB1', _('sub 1')),
        ('SUB2', _('sub 2')),
    )

    subscription_type = models.CharField(choices=SUBSCRIPTION_TYPES, max_length=30, unique=True)
    description = models.CharField(max_length=255, blank=True)

class UserSubscription(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    subscriptions = models.ManyToManyField(Subscription, related_name='subscriptions',
                                           related_query_name='subscriptions')

forms.py

class SubscriptionForm(forms.ModelForm):
    class Meta:
        model = UserSubscription
        fields = ('subscriptions',)
        widgets = {
            'subscriptions': forms.CheckboxSelectMultiple(),
        }

views.py

class SubscriptionFormView(FormView):
    template_name = 'profile/subscription.html'
    form_class = SubscriptionForm


Solution 1:[1]

Please do not create a UserSubscription, now you defined two junction tables. This will result in duplicate data, and will make queries less efficient, and more error-prone logic.

What you need is a ManyToManyField from the Subscription to the User, so:

class Subscription(models.Model):
    # …
    subscribers = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='subscriptions'
    )

Then we can define a form to select the Subscriptions:

from django import forms

class SubscribingForm(forms.Form):
    subscriptions = forms.ModelMultipleChoiceField(
        queryset=Subscription.objects.all(),
        widget=forms.CheckboxSelectMultiple()
    )

Then in the view we can handle the form and subscribe the logged in user to all the subscriptions that have been selected:

from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect

class SubscriptionFormView(LoginRequiredMixin, FormView):
    template_name = 'profile/subscription.html'
    form_class = SubscribingForm
    
    def get_initial(self):
        initial = super().get_initial()
        initial['subscriptions'] = self.request.user.subscriptions.all()
        return initial
    
    def form_valid(self, form):
        subs = form.cleaned_data['subscriptions']
        self.request.user.subscriptions.add(*subs)
        return redirect('name-of-some-view')

Note: You can limit views to a class-based view to authenticated users with the LoginRequiredMixin mixin [Django-doc].


Note: In case of a successful POST request, you should make a redirect [Django-doc] to implement the Post/Redirect/Get pattern [wiki]. This avoids that you make the same POST request when the user refreshes the browser.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1