Django Modelform for Many-To-Many Fields

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

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.

Django ModelForm for Many-to-Many fields

I guess you would have here to add a new ModelMultipleChoiceField to your PizzaForm, and manually link that form field with the model field, as Django won't do that automatically for you.

The following snippet might be helpful :

class PizzaForm(forms.ModelForm):
class Meta:
model = Pizza

# Representing the many to many related field in Pizza
toppings = forms.ModelMultipleChoiceField(queryset=Topping.objects.all())

# Overriding __init__ here allows us to provide initial
# data for 'toppings' field
def __init__(self, *args, **kwargs):
# Only in case we build the form from an instance
# (otherwise, 'toppings' list should be empty)
if kwargs.get('instance'):
# We get the 'initial' keyword argument or initialize it
# as a dict if it didn't exist.
initial = kwargs.setdefault('initial', {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data.
initial['toppings'] = [t.pk for t in kwargs['instance'].topping_set.all()]

forms.ModelForm.__init__(self, *args, **kwargs)

# Overriding save allows us to process the value of 'toppings' field
def save(self, commit=True):
# Get the unsave Pizza instance
instance = forms.ModelForm.save(self, False)

# Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
# This is where we actually link the pizza with toppings
instance.topping_set.clear()
instance.topping_set.add(*self.cleaned_data['toppings'])
self.save_m2m = save_m2m

# Do we need to save all changes now?
if commit:
instance.save()
self.save_m2m()

return instance

This PizzaForm can then be used everywhere, even in the admin :

# yourapp/admin.py
from django.contrib.admin import site, ModelAdmin
from yourapp.models import Pizza
from yourapp.forms import PizzaForm

class PizzaAdmin(ModelAdmin):
form = PizzaForm

site.register(Pizza, PizzaAdmin)

Note

The save() method might be a bit too verbose, but you can simplify it if you don't need to support the commit=False situation, it will then be like that :

def save(self):
instance = forms.ModelForm.save(self)
instance.topping_set.clear()
instance.topping_set.add(*self.cleaned_data['toppings'])
return instance

Django ModelForm: search filter many to many field widget

There are autocomplete_fields for that: https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields

So you can write:

class FormulariMostra(ModelForm):
class Meta:
model = Sample
autocomplete_fields = ["pools"]
fields = ("name", "sample_id_sex", "pools",)

or there is also raw_id_fields
https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields

filter choices for many to many field in form django

Just as an aside - are you sure you want Picking Person to be ManyToMany, is there more than one in a group?

Anyhow - as the list of pickable persons may be different for each form, you want to use the init function in your form to generate the options as it is called when the form instantiates. You can use the instance you are already passing in to help with the queryset. Obviously this will only work for an existing group.

class ChoosePersonPickingForm(ModelForm):

picking_person_choices= None
picking_person = forms.ModelMultipleChoiceField(label='Pick Person', queryset=picking_person_choices, required=True)
#could be a ModelChoiceField if picking_person not actually ManyToMany

class Meta:
model = Group
fields = ['picking_person']

def __init__(self, *args, **kwargs):
super(ChoosePersonPickingForm, self).__init__(*args, **kwargs)
self.picking_people_choices= Profile.objects.filter(members = self.instance)
self.fields['picking_person'].queryset = self.picking_people_choices


Django Include ManyToManyField on other model in ModelForm

@hedgie To change the field in the other model is not a good option for me because I use it already.

But the __init__() was a good hint. I come up with this solution and it seems to work.

class StoreForm(ModelForm):
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
brand_ids = [t.pk for t in kwargs['instance'].brands.all()]

kwargs['initial'] = {
'brands': brand_ids,
}
super().__init__(*args, **kwargs)

# https://stackoverflow.com/questions/49932426/save-many-to-many-field-django-forms
def save(self, commit=True):
# Get the unsaved Pizza instance
instance = forms.ModelForm.save(self, False)

# Prepare a 'save_m2m' method for the form,
old_save_m2m = self.save_m2m

def save_m2m():
old_save_m2m()
# This is where we actually link the pizza with toppings
instance.brands.clear()
for brand in self.cleaned_data['brands']:
instance.brands.add(brand)

self.save_m2m = save_m2m

# Do we need to save all changes now?
# Just like this
# if commit:
instance.save()
self.save_m2m()

return instance

brands = forms.ModelMultipleChoiceField(
queryset=Brand.objects.all(),
widget=forms.CheckboxSelectMultiple,
)

Though it seems to be not very elegant. I wonder why django does not support a better way.



Related Topics



Leave a reply



Submit