ModelForm User Mixin - django

I've got some models with user field.
For this purpose I'd like to create a form mixin that would add self.user instance (which is provided to the form in views). Is it possible ?
Here's the example
class UserFormMixin(object):
"""Removes user instance from kwargs and adding it to object"""
def __init__(self, *args, **kwargs):
super(UserFormMixin, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user')
def save(self, **kwargs):
obj = super(UserFormMixin, self).save(commit=False)
obj.user = self.user
if kwargs['commit']:
return obj.save()
else:
return obj
What I'd like to achieve:
class SomeFormWithUserField(UserFormMixin, ModelForm):
class Meta:
model = SomeModelWithUserField
fields = ['fields without user']
def save(self, **kwargs):
data = super(SomeFormWithUserField, sefl).save(commit=False)
#data already with user prepended
#do some other stuff with data
if kwargs['commit']:
return data.save()
else
return data
class SomeOtherFormWithUser(UserFormMixin, ModelForm):
class Meta:
model = SomeOtherModel
fields = ['some fields without user']
# no need to save here.. standard model form with user prepended on save()
The problem is that UserFormMixin doesn't know about model instance? Or am I wrong here?
I am getting some problems.. like 'commit' kwargs key error.. or object is not saved..

You're close, you just have some logic errors. First, in order to override ModelForm methods, your mixin needs to inherit from ModelForm.
class UserFormMixin(forms.ModelForm):
...
Then, any forms that inherit from it just inherit UserFormMixin, not ModelForm.
class SomeOtherFormWithUser(UserFormMixin):
...
Second, your __init__ method override is incorrect. You need to accept any and all args and kwargs that get passed into it.
def __init__(self, *args, **kwargs):
...
Finally, don't override the save method again, in the subclass. I guess it won't technically hurt anything, but what's the point of inheritance if you're going to repeat code, anyways? If user is not nullable, you can always add an if block to check if self.user is not None before adding it to the model. Of course, if user is not nullable, your model won't likely save without self.user anyways.

This one seems to work fine. Thanks Chris!
If this can be coded better please let me know.
class UserFormMixin(forms.ModelForm):
"""Removes user instance from kwargs and adding it to object"""
def __init__(self, *args, **kwargs):
super(UserFormMixin, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user')
def save(self, commit=True):
obj = super(UserFormMixin, self).save(commit=False)
obj.user = self.user
if commit:
return obj.save()
else:
return obj
class SomeFormWithUserField(UserFormMixin):
class Meta:
model = SomeModelWithUserField
fields = ['fields without user']
def save(self, **kwargs):
data = super(SomeFormWithUserField, sefl).save(commit=False)
#data already with user prepended
#do some other stuff with data
# self.send_mail() f.e.
return data.save()
class SomeOtherFormWithUser(UserFormMixin):
class Meta:
model = SomeOtherModel
fields = ['some fields without user']
# this will work too

Related

How to override queryset used by Django admin/form for a Foreign Key

I've tried everything I can find on the internet here, and nothing seems to work, so wondering if lots of the previous answers are for old versions. I'm on Django 2.2.9.
#models.py
class ParentModel(models.Model):
title = models.CharField()
class ChildModel(models.Model):
parent = models.ForeignKey(
ParentModel,
on_delete=models.CASCADE,
related_name='parent'
)
# admin.py
#admin.register(ParentModel)
class ParentModelAdmin(admin.ModelAdmin):
model = ParentModel
def get_queryset(self, request):
return ParentModel.objects.get_complete_queryset()
class ChildModelForm(forms.Form):
def __init__(self, u, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
self.fields['parent'].queryset = ParentModel.objects.get_complete_queryset()
class Meta:
model = ChildModel
fields = '__all__'
#admin.register(ChildModel)
class ChildModelAdmin(admin.ModelAdmin):
model = ChildModel
form = ChildModelForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "parent":
kwargs["queryset"] = ParentModel.objects.get_complete_queryset()
return super().formfield_for_foreignkey(db_field, request, **kwargs)
I have a manager query called get_complete_queryset on ParentModel that returns a broader set of Parents than the default queryset.
The setup above allows me to go to my ChildModelAdmin and select the 'hidden' Parents from the dropdown, but when I try and save it gives me this error:
parent instance with id 2 does not exist.
There must be some queryset the form is using to save the model that isn't overridden, but I can't find what it is.
You can override get_form method like this:
def get_form(self, request, obj, **kwargs):
form = super(<YourModelAdmin>,self).get_form(request, obj, **kwargs)
form.base_fields['<you_field>'] = forms.ModelChoiceField(queryset=<your_queryset>)
return form

Add a field value outside form in Django

Whenever I have to add a value to the instance of a form obtained from the context or from the URL I do it in the following way, using form.instance.
class PreguntaForm(forms.ModelForm):
class Meta:
model = Pregunta
fields = ('etiqueta', 'grupo', 'tipo_pregunta', 'opciones', 'mostrar_tabla', 'activo')
def __init__(self, *args, **kwargs):
cuestionario = kwargs.pop('cuestionario', False)
super(PreguntaForm, self).__init__(*args, **kwargs)
self.fields['grupo'].queryset = Grupo.objects.filter(cuestionario=cuestionario)
class PreguntaNueva(InfoPregunta, CreateView):
form_class = PreguntaForm
encabezado = 'Nueva Pregunta'
model = Pregunta
def get_form_kwargs(self):
kwargs = super(PreguntaNueva, self).get_form_kwargs()
kwargs['cuestionario'] = self.dame_cuestionario()
return kwargs
def form_valid(self, form):
form.instance.cuestionario = self.dame_cuestionario()
return super(PreguntaNueva, self).form_valid(form)
The problem that arises now is that I want to perform a check CreateView and EditView. To DRY, I want to do it in the clean method of the model, but the value that I assign to form.instance.cuestionario, is not available within the clean method. How could I do it? This value must not be edited by the user in any case.
Yes it is, you pass it in via get_form_kwargs; you just need to assign it to an instance variable in the form's __init__.
def __init__(self, *args, **kwargs):
self.cuestionario = kwargs.pop('cuestionario', False)
super(PreguntaForm, self).__init__(*args, **kwargs)
self.fields['grupo'].queryset = Grupo.objects.filter(cuestionario=self.cuestionario)
def clean(self):
# do something with self.cuestionario

Django: Current User Id for ModelForm Admin

I want for filter a ModelChoiceField with the current user. I found a solution very close that I want to do, but I dont understand
Django: How to get current user in admin forms
The answer accepted says
"I can now access the current user in my forms.ModelForm by accessing self.current_user"
--admin.py
class Customer(BaseAdmin):
form = CustomerForm
def get_form(self, request,obj=None,**kwargs):
form = super(Customer, self).get_form(request, **kwargs)
form.current_user = request.user
return form
--forms.py
class CustomerForm(forms.ModelForm):
default_tax = forms.ModelChoiceField(queryset=fa_tax_rates.objects.filter(tenant=????))
class Meta:
model = fa_customers
How do I get the current user on modelchoice queryset(tenant=????)
How do I call the self.current_user in the modelform(forms.py)
Override __init__ constructor of the CustomerForm:
class CustomerForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.fields['default_tax'].queryset =
fa_tax_rates.objects.filter(tenant=self.current_user))
Queryset in the form field definition can be safely set to all() or none():
class CustomerForm(forms.ModelForm):
default_tax = forms.ModelChoiceField(queryset=fa_tax_rates.objects.none())
Just to sum up the solution because it was very hard for me to make this work and understand the accepted answer
In admin.py
class MyModelForm (forms.ModelForm):
def __init__(self, *args,**kwargs):
super (MyModelForm ,self).__init__(*args,**kwargs)
#retrieve current_user from MyModelAdmin
self.fields['my_model_field'].queryset = Staff.objects.all().filter(person_name = self.current_user)
#The person name in the database must be the same as in Django User, otherwise use something like person_name__contains
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
def get_form(self, request, *args, **kwargs):
form = super(MyModelAdmin, self).get_form(request, *args, **kwargs)
form.current_user = request.user #get current user only accessible in MyModelAdminand pass it to MyModelForm
return form

Behaviour of Django's forms.ModelForm.save(): Why does models.save_instance not work via parent?

I am using class based views, but the forms have two underlying models, instead of one. So the "main" form (the Employee, the view knows about) has another form object.
The model of the Shift has a pkey, referencing one element of the Employees.
I would like to know, why this is not working:
class ShiftSubForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super( ShiftSubForm, self).__init__(*args, **kwargs)
... some adjustment of widgets, deleting and adding form fields
class Meta:
model=Shift
class EmployeeForm(forms.ModelForm):
second_form = None
def __init___(self, *args, **kwargs):
kwargs.update({'instance': kwargs['shift']})
del kwargs['shift']
self.second_form = ShiftSubForm(*args, **kwargs)
def save(self):
employee = super(ShiftSubForm, self).save()
self.second_form.cleaned_data.update({'employee': employee})
self.second_form.save()
class Meta:
model = Employee
I'd expect the save() in the Parents of ShiftSubForm to call models.save_instance(..) and to save the data. But it fails, with an integrity error because employee_id is Null. So the employee object didn't make it into the save.
But, if i call it directly, it works:
class ShiftSubForm(forms.ModelForm):
... as above ...
def save(self):
return models.save_instance(self, self.instance, self._meta.fields,
fail_message, True, exclude=self._meta.exclude)
What am i missing?
EDIT: Can't answer myself, so here ...
Think that this might be the best way?
class ShiftSubForm(forms.ModelForm):
... as above ...
def createInstanceWith(self,employee):
self.save(commit=False) # prepares self.instance
self.instance.employee = employee
class EmployeeForm(forms.ModelForm):
...
def save(self):
employee = super(ShiftSubForm, self).save()
self.second_form.full_clean() # populates its self.cleaned_data
self.second_form.createInstanceWith(employee)
self.second_form.save()
PS: Ignore typos - this is not the real code. But it has everything, that fumbles around with the forms
Don't update the cleaned_data. Call save() with commit=False, set the employee then save it to the db.
class EmployeeForm(forms.ModelForm):
second_form = None
def save(self):
employee = super(ShiftSubForm, self).save()
shift = self.second_form.save(commit=False)
shift.employee = employee
shift.save()
return employee
class Meta:
model = Employee

Django admin - remove field if editing an object

I have a model which is accessible through the Django admin area, something like the following:
# model
class Foo(models.Model):
field_a = models.CharField(max_length=100)
field_b = models.CharField(max_length=100)
# admin.py
class FooAdmin(admin.ModelAdmin):
pass
Let's say that I want to show field_a and field_b if the user is adding an object, but only field_a if the user is editing an object. Is there a simple way to do this, perhaps using the fields attribute?
If if comes to it, I could hack a JavaScript solution, but it doesn't feel right to do that at all!
You can create a custom ModelForm for the admin to drop the field in the __init__
class FooForm(forms.ModelForm):
class Meta(object):
model = Foo
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Since the pk is set this is not a new instance
del self.fields['field_b']
class FooAdmin(admin.ModelAdmin):
form = FooForm
EDIT: Taking a hint from John's comment about making the field read-only, you could make this a hidden field and override the clean to ensure the value doesn't change.
class FooForm(forms.ModelForm):
class Meta(object):
model = Foo
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Since the pk is set this is not a new instance
self.fields['field_b'].widget = forms.HiddenInput()
def clean_field_b(self):
if self.instance and self.instance.pk:
return self.instance.field_b
else:
return self.cleaned_data['field_b']
You can also do the following
class FooAdmin(admin.ModelAdmin)
def change_view(self, request, object_id, extra_context=None):
self.exclude = ('field_b', )
return super(SubSectionAdmin, self).change_view(request, object_id, extra_context)
Taken from here Django admin: exclude field on change form only