I'm trying to pass some variables into a model formset. I've looked at the official django documentation and they talk about doing this with a regular, non-model formset. I can't get this to work for a modelformset though.
views.py
EmployeeTeamFormset = modelformset_factory(EmployeeTeamMembership, form=EmployeeTeamForm(), extra=0, max_num=10, can_delete=True)
formset = EmployeeTeamFormset(request.POST or None, queryset=EmployeeTeamMembership.objects.filter(employee__user=user, team=team), kwargs={'user': user, 'team': team})
forms.py
class EmployeeTeamForm(forms.ModelForm):
class Meta:
model = EmployeeTeamMembership
fields = ('employee', 'team_lead',)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
self.team = kwargs.pop('team', None)
I get the following error:
__init__() got an unexpected keyword argument 'kwargs'
Thanks for your help!
Couple of typos in my code. This works:
EmployeeTeamFormset = modelformset_factory(EmployeeTeamMembership, form=EmployeeTeamForm, extra=0, max_num=10, can_delete=True)
formset = EmployeeTeamFormset(request.POST or None, queryset=EmployeeTeamMembership.objects.filter(employee__user=user, team=team), form_kwargs={'user': user, 'team': team})
Related
How can i pass extra kwargs to django modelform in modelformset factory
I want for example, disable all form fields in detail view.
forms.py:
class ConceptForm(forms.ModelForm):
class Meta:
model = app_models.Concept
fields = '__all__'
def __init__(self, *args, **kwargs):
self.action = kwargs.pop('action', None)
super(ConceptForm, self).__init__(*args, **kwargs)
if self.action is not None:
if self.action == 'detail':
for field in self.fields:
self.fields[field].widget.attrs['readonly'] = True
views.py
modelformset = modelformset_factory(
model = model,
fields = show_fields,
form = ConceptForm(kwargs={'action': 'detail'), <-- I would like something like this
)
The error is:
'ConceptForm' object is not callable
Without call init not errors showed but the form fields not are disabled
modelformset = modelformset_factory(
model = model,
fields = show_fields,
form = ConceptForm
)
Thanks in advance
As is specified in the Passing custom parameters to formset forms section of the documentation, you can make use of the form_kwargs=… parameter of the FormSet:
ConceptFormSet = modelformset_factory(
model = Concept,
form = ConceptForm,
fields = show_fields
)
formset = ConceptFormSet(form_kwargs={'action': 'detail'})
When saving the formset, it is not validating and respecting the model fields that I have made unique and therefore renders an Integrity Error should I duplicate fields deliberately to test it.
My guess is that the formsets themselves validate correctly, but as the unique field is something I have to assign during the save(commit=False) process it never gets validated. Does that make sense?
Is there something I am missing please?
My code:
class ClientCreate(LoginRequiredMixin, FormView):
def dispatch(self, *args, **kwargs):
self.case = Case.objects.get(pk=kwargs['case_pk'])
self.num_clients = self.case.number_clients
return super().dispatch(*args, **kwargs)
template_name = 'clients/client_form.html'
form_class = modelformset_factory(Client, ClientForm,
min_num=2, max_num=2, extra=0,
validate_max=True, validate_min=True,
can_delete=False)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["queryset"] = Client.objects.none()
return kwargs
def form_valid(self, form_class):
form_class.save()
return super().form_valid(form)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if self.request.POST:
ctx['inlines'] = self.form_class(self.request.POST)
else:
ctx['inlines'] = self.form_class()
return ctx
def get_success_url(self):
return reverse('client-list',
kwargs={'case_pk': self.kwargs['case_pk']})
I appreciate that formview is really supposed to be used for single form saving, but this does actually work correctly when not having duplicate unique items.
Many thanks
EDIT:::
This is my function based version that also suffers from the same issue:
#login_required
def client(request, case_pk):
template_name = 'clients/client_form.html'
case = get_object_or_404(Case,
pk=case_pk, adviser__company__account=request.user
)
formset_class = modelformset_factory(Client, ClientForm,
min_num=case.number_clients,
max_num=case.number_clients, extra=0,
validate_max=True, validate_min=True,
can_delete=False)
formset = formset_class(request.POST or None)
if request.method == 'POST':
# check all formsets valid
if all(form.is_valid() for form in formset):
for f in formset:
if f.is_valid():
form = f.save(commit=False)
form.case = case
f.save()
return HttpResponseRedirect(reverse('client-create',
kwargs={'case_pk': case_pk}))
return render(request, template_name, {
'inlines': formset,
'case': case,
'breadcrumbs': 'Family & Dependants'
})
Client Form:
class ClientForm(ModelForm):
class Meta:
model = Client
fields = ['prefix', 'first_name', 'middle_names', 'last_name',
'gender', 'date_of_birth', 'residence', 'address_1',
'address_2', 'address_3', 'city', 'postcode', 'telephone',
'marital_status', 'widowed_date_of_death',
'have_will', 'why_changing', 'existing_poa', 'dependant', ]
Client Model is large so this is the unique clause:
class Meta:
unique_together = ('case', 'first_name', 'last_name',
'date_of_birth', )
modelformset_factory appears to not respect database level constraints. Therefore, the check has to be done before saving the form.
They way to do this is by overriding the BaseModelFormSet class.
Solution here: Save multiple objects with a unique attribute using Django formset
I'm trying to get the request.user into a ModelForm. I feel like I've tried all permutations of this from overloading the
__init__
argument (per Django form, request.post and initial) to trying to pass it as a kwargs (per Django form __init__() got multiple values for keyword argument). I
It does seem like the kwargs is the best approach but I'm totally stymied by it.
Here's the ModelForm:
class DistListForm(ModelForm):
members = ModelMultipleChoiceField(queryset=Company.objects.none())
class Meta:
model = DistList
fields = ['name', 'description', 'members', 'is_private']
def __init__(self, *args, **kwargs):
super(DistListForm, self).__init__(*args, **kwargs)
user = kwargs.pop('user', None)
up = UserProfile.objects.get(user=user)
/.. etc ../
Here's how the create function currently works:
def distlistcreate(request):
user = {'user': request.user}
form = DistListForm(**user)
if request.method == 'POST':
form = DistListForm(request.POST)
if form.is_valid():
distlist = form.save(commit=False)
distlist.creator = request.user
distlist.save()
form.save_m2m()
return HttpResponseRedirect(reverse('distlistsmy'))
return render(request, 'distlistcreate.html',{'form':form})
which throws a TypeError: init() got an unexpected keyword argument 'user'. The update method is equally unhelpful:
def distlistupdate(request, object_id):
distlist = get_object_or_404(DistList, id=object_id)
form = DistListForm(user=request.user, instance=distlist)
if request.method == 'POST':
form = DistListForm(request.POST, user=request.user)
It also throws the same error.
I've been banging my head against this wall for two hours now. What is the correct way to pass a keyword argument into a ModelForm?
This is Django 1.6.1 if that makes a difference.
You have to pop the user argument before call super() so it will no conflict wit the default arguments of ModelForm
class DistListForm(ModelForm):
members = ModelMultipleChoiceField(queryset=Company.objects.none())
class Meta:
model = DistList
fields = ['name', 'description', 'members', 'is_private']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(DistListForm, self).__init__(*args, **kwargs)
user_profile = UserProfile.objects.get(user=user)
Just did exactly this yesterday, on Django 1.5, and I am able to do:
def __init__(self, user, *args, **kwargs):
on my ModelForm. Then I just use user without having to pop it from the kwargs.
I'm needing to filter out a significant amount of objects from my query. Currently, it is grabbing all objects in the class, and I want to filter it to the relevant ones which are in a querystring. How can I do this? When I try, I get an Attribute Error stating
''QuerySet' object has no attribute '__name__'.'
The code that works, but very slowly is:
formset = modelformset_factory(Transaction, form=PaidDateForm, extra=0, can_delete=False)
Also, the formset:
formset = formset(request.POST, Transaction.objects.filter(pk__in=qs))
The QueryString that I am wanting to filter by is called 'qs.'
class PaidDateForm(forms.ModelForm):
formfield_callback = jquery_datefield
Amount",max_digits=14,decimal_places=2,required=False)
date_cleared = forms.DateField(label="Cleared Date",widget=JQueryDateWidget(), input_formats=settings.DATE_INPUT_FORMATS, required=False)
class Meta:
model = Transaction
include = ('date_time_created')
def __init__(self, *args, **kwargs):
self.queryset = Transaction.objects.filter(pk__in=qs)
super(PaidDateForm, self).__init__(*args, **kwargs)
for field in self.fields:
if field != 'date_cleared':
self.fields[field].widget = forms.HiddenInput()
self.fields['paid_amount'].widget.attrs['size'] = 12
self.initial['paid_amount'] = '%.2f' % (self.instance.usd_amount)
Look at the example in Django documentation:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#changing-the-queryset
If I understand your question correctly there is two approach to your problem:
First:
TransactionFormset = modelformset_factory(Transaction,form=PaidDateForm, extra=0, can_delete=False)
formset = TransactionFormset(queryset=Transaction.objects.filter(pk__in=qs))
Second options is to create BaseTransactionFormset
class BaseTransactionFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseTransactionFormSet, self).__init__(*args, **kwargs)
#create filtering here whatever that suits you needs
self.queryset = Transaction.objects.filter()
formset = modelformset_factory(Transaction, formset=BaseTransactionFormSet,form=PaidDateForm, extra=0, can_delete=False)
Does this code help you?
I hesitate you still need it, and yet this code works for me
FormSet = modelformset_factory(YourModel, fields=(...))
form_set = FormSet(queryset = YourModel.objects.filter(...))
#login_required
def post_review(request):
if request.method == 'POST':
formset = ReviewForm(request.POST)
if formset.is_valid():
formset.save(commit=False)
#formset.author = User.objects.get(pk=int(request.user.id))
formset.pub_date = datetime.datetime.now
formset.save()
return HttpResponseRedirect(reverse(review_index))
else:
formset = ReviewForm()
return render_to_response("review/post_review.html",
{"formset": formset}, context_instance=RequestContext(request),
)
I have this view, I want to auto set the current logged-in user in my review form author field. But I dont know how. Any ideas/hint pls?
Below is my form:
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = ('title','category', 'body', )
widgets = {
'body': Textarea(attrs={'cols': 60, 'rows': 20}),
}
I've always done this by accepting a new kwarg in my form's __init__, and saving the value until save-time.
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = ('title','category', 'body', )
widgets = {
'body': Textarea(attrs={'cols': 60, 'rows': 20}),
}
def __init__(self, *args, **kwargs):
self._user = kwargs.pop('user')
super(ReviewForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
inst = super(ReviewForm, self).save(commit=False)
inst.author = self._user
if commit:
inst.save()
self.save_m2m()
return inst
And then in my view:
def post_review(request):
# ... snip ...
if request.method == 'POST'
form = ReviewForm(request.POST, user=request.user)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thanks/') #or whatever the url
else:
# Don't forget to add user argument
form = ReviewForm(user=request.user)
# ... snip ...
If Review.author isn't a required field, you can add a second value to the kwargs.pop call to set a default, like None. Otherwise, if the user kwarg isn't provided, it'll raise an error, effectively making it a required argument.
As an alternative solution, in Django 2+ using a form view - such as a CreateView or FormView, I can simply pass the self.request.user to my pre-saved form model:
class AppCreateView(CreateView):
model = models.App
fields = ['name']
success_url = '/thanks/'
def form_valid(self, form):
app_model = form.save(commit=False)
app_model.author = self.request.user
# app_model.user = User.objects.get(user=self.request.user) # Or explicit model
app_model.save()
return super().form_valid(form)
I agree the class based view is not important here. The important line is app_model.author = self.request.user.
The model is not special:
from django.db import models
from django.contrib.auth.models import User
class App(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255, help_text="Arbitrary name")
created = models.DateTimeField(auto_now_add=True, max_length=255)
I have a formset mixin which lets you pass extra arguments to the generated forms.
Just add the mixin as the first base class, set a dictionary named "form_kwargs" as a class attribute to describe the
arguments to pass.
from django.forms.formsets import BaseFormSet
class BaseKwargsFormSet(BaseFormSet):
"""
A formset mix-in to allow keyword arguments to be passed to constructed forms
For model_formsets, derive from this model *first* because django's formsets
can't grok the extra arguments.
To use, specify a dictionary with the kwargs & default values as an attribute
named "form_kwargs" on the formset base class.
example:
class BaseUserModelFormset (BaseKwargsFormSet, BaseModelFormSet):
form_kwargs = { 'user': None }
UserFormset = modelformset_factory (usermodel, form=userform,
formset=BaseUserModelFormset)
formset = UserFormset (request.POST or None, user=request.user)
"""
def __init__(self, *args, **kwargs):
form_kwargs = getattr(self, 'form_kwargs', {})
self.form_kwargs = dict((k, kwargs.pop(k, v)) for k, v in form_kwargs.items())
super(BaseKwargsFormSet, self).__init__(*args, **kwargs)
def _construct_form(self, index, **kwargs):
kwargs.update(**self.form_kwargs)
return super(BaseKwargsFormSet, self)._construct_form(index, **kwargs)