pythondjangoformsdjango-formsorm

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

  • 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.