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 Subscription
s:
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
Why Doesn't a Python Dict.Update() Return the Object
What's the Fastest Way in Python to Calculate Cosine Similarity Given Sparse Matrix Data
How to Override the [] Operator in Python
Get Lat/Long Given Current Point, Distance and Bearing
Find First Element in a Sequence That Matches a Predicate
Python Nested Functions Variable Scoping
Google Colab: How to Read Data from My Google Drive
How to Profile Python Code Line-By-Line
How to Change Default Anaconda Python Environment
Timeout for Python Requests.Get Entire Response
Multiple Variables in a 'With' Statement
Accessing Mp3 Metadata with Python
How to Dynamically Compose an or Query Filter in Django
How to Activate a Virtualenv Inside Pycharm's Terminal
How to Read and Write Ini File with Python3