Django Forms clean() method - need IP address of client - django

I am overriding the clean() method on a Django form. I want to have access to the IP address of the client (assuming this is a bound form). If I had a reference to the request object, I could get it easily from META("REMOTE_ADDR"). However, I do not have a reference to the request.
Any ideas on how this could be done?

So give yourself a reference to it.
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(MyModelForm, self).__init__(*args, **kwargs)
def clean(self):
ip_address = self.request['META']['REMOTE_ADDR']
and in your view:
myform = MyModelForm(request.POST, request=request)

Related

How to load a form with options from a queryset in Django

I am trying to load a form with user payment options, so this is needing a query set from the users profile.
I have tried initializing the form (below code) with user being required. The issue is if I make self.options when I am initializing. I have also tried creating the choice_field
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=options)
def __init__(self, user, *args, **kwargs):
self.options = list(UserPaymentOption.objects
.values_list('last_four', 'last_four')
.filter(user=user, active=True))
super(ListPaymentOptionsForm, self).__init__(self, *args, **kwargs)
The above code gives this error:
choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=options)
NameError: name 'options' is not defined
Then I have tried adding the options on the view instead like this
form = ListPaymentOptionsForm(user=request.user)
form.fields['choice_field'].choices = list(UserPaymentOption.objects
.values_list('id', 'last_four')
.filter(user=request.user, active=True))
This causes an error with the form being used on post, it seems like because it is trying to validate the value provided is a choice but in the actual form the choice is not set. The reason I believe this is the problem is this is what the form returns as
form=ListPaymentOptionsForm(request.POST)
print(form)
This returns: Choice field:Select a valid choice. 54 is not one of the available choices.
Any input on this would be very appreciated. Thanks.
Nearly there!
Try doing the fields['choice_field'].choices in the constructor.
class ListPaymentOptionsForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs) # assuming python 3 constructor
self.options = list(UserPaymentOption.objects.values_list('last_four', 'last_four').filter(user=user, active=True))
self.fields['choice_field'] = forms.ChoiceField(widget=forms.RadioSelect, choices=self.options)
Maybe consider having a look at ModelChoiceField instead however, that way you can specify a queryset instead of having to worry about creating a list:
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ModelChoiceField(widget=forms.RadioSelect, queryset=UserPaymentOption.objects.none())
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['choice_field'].queryset = UserPaymentOption.objects.filter(user=user, active=True)
EDIT based on comments we can use the kwargs to pass the user which may be better:
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ModelChoiceField(widget=forms.RadioSelect, queryset=UserPaymentOption.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user') # this must be done before super()
super().__init__(*args, **kwargs)
self.fields['choice_field'].queryset = UserPaymentOption.objects.filter(user=user, active=True)
Then instantiate the form to handle POST data:
form = ListPaymentOptionsForm(request.POST, user=user)

Inline formset factory - pass request to child form

I'm facing a quite challenging taks:
I need an inlineformset_factory connecting my ParentEntity to my foreign key-bound ChildEntities.
My ChildEntity contains a foreign key relation I need to filter per logged-in user - so I need the request in the ChildForm.
What I've tried so far:
I tried to use the form= kwarg but I can't pass an instance - just a class. So no way for me to add the request here.
I tried to use the formset= kwarg but when I try to pass the request=request as a kwarg of the inlineformset_factory I get an error (Unexpected kwarg)
Any idea what I can do?
Sometimes asking a colleague is even faster than StackOverflow :)
Here's my solution:
forms.py
class BaseFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super(BaseFormSet, self).__init__(*args, **kwargs)
views.py
MyFormSet = inlineformset_factory(ParentEntity, ChildEntity, formset=BaseFormSet, form=ChildForm, extra=2, max_num=max_num, can_delete=False)
...
formset = MyFormSet(request.POST, instance=obj, request=request)
you can pass it this way:
MyFormSet = inlineformset_factory(ParentEntity, ChildEntity, formset=BaseFormSet, form=ChildForm, extra=1)
formset = MyFormSet(form_kwargs={'request': request})
Then on your ChildForm:
def __init__(self, *args, **kwargs):
request = kwargs.pop('request', None)
super(ChildForm, self).__init__(*args, **kwargs)

django object is not iterable with custom instance

I am trying to leave my object itself out of the queryset of possible options. Problem is i get the error: 'Country' object is not iterable
Not sure where i am going wrong.
My view:
def edit_country(request, country_id):
country = get_object_or_404(Country, pk=country_id)
country_form = CountryForm(instance=country)
return render(request, 'create_country.html', {'country_form': country_form})
My form init:
def __init__(self, *args, **kwargs):
super(CountryForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
self.fields['likes'].queryset = Country.objects.exclude(kwargs['instance'])
self.fields['hates'].queryset = Country.objects.exclude(kwargs['instance'])
Where do i go wrong?
Change the order of the method, so you pop the kwarg first. You are sending the kwarg to super.
def __init__(self, *args, **kwargs):
instance = kwargs.pop('instance', None)
#all other stuff

Django modelform and passing extra parameter

I want to populate my modelform to include the IP address, however I'm not able to set the ip as I want, it simply returns "This field is required" for IP address.
models.py
class SignUp(models.Model):
....
ip = models.IPAddressField()
views.py
def my_view(request):
form = SignUpForm(request.POST, ip_addr=get_client_ip(request))
forms.py
class SignUpForm(ModelForm):
class Meta:
model = SignUp
def __init__(self, *args, **kwargs):
self.ip_addr = kwargs.pop('ip_addr', None)
super(SignUpForm, self).__init__(*args, **kwargs)
def clean_ip(self):
return self.ip_addr
You haven't actually set an initial value for the ip form field meaning it's empty. Try:
def __init__(self, *args, **kwargs):
super(SignUpForm, self).__init__(*args, **kwargs)
self.fields['ip'].initial = kwargs.pop('ip_addr', None)
The clean_ip method is for validation upon submission of the form and is only called after form.is_valid() is called. It should check that the value of the field is a valid ip
I don't think you should use clean_ip here. You are not cleaning it anyhow. Either use initial in your view or override save method.

Django: How to get current user in admin forms?

In Django's ModelAdmin, I need to display forms customized according to the permissions a user has. Is there a way of getting the current user object into the form class, so that i can customize the form in its __init__ method?
I think saving the current request in a thread local would be a possibility but this would be my last resort because I'm thinking it is a bad design approach.
Here is what i did recently for a Blog:
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostForm
def get_form(self, request, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, **kwargs)
form.current_user = request.user
return form
I can now access the current user in my forms.ModelForm by accessing self.current_user
EDIT: This is an old answer, and looking at it recently I realized the get_form method should be amended to be:
def get_form(self, request, *args, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, *args, **kwargs)
form.current_user = request.user
return form
(Note the addition of *args)
Joshmaker's answer doesn't work for me on Django 1.7. Here is what I had to do for Django 1.7:
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostForm
def get_form(self, request, obj=None, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, obj, **kwargs)
form.current_user = request.user
return form
For more details on this method, please see this relevant Django documentation
This use case is documented at ModelAdmin.get_form
[...] if you wanted to offer additional fields to superusers, you could swap in a different base form like so:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if request.user.is_superuser:
kwargs['form'] = MySuperuserForm
return super().get_form(request, obj, **kwargs)
If you just need to save a field, then you could just override ModelAdmin.save_model
from django.contrib import admin
class ArticleAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.user = request.user
super().save_model(request, obj, form, change)
I think I found a solution that works for me: To create a ModelForm Django uses the admin's formfield_for_db_field-method as a callback.
So I have overwritten this method in my admin and pass the current user object as an attribute with every field (which is probably not the most efficient but appears cleaner to me than using threadlocals:
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(MyAdmin, self).formfield_for_dbfield(db_field, **kwargs)
field.user = kwargs.get('request', None).user
return field
Now I can access the current user object in the forms __init__ with something like:
current_user=self.fields['fieldname'].user
stumbled upon same thing and this was first google result on my page.Dint helped, bit more googling and worked!!
Here is how it works for me (django 1.7+) :
class SomeAdmin(admin.ModelAdmin):
# This is important to have because this provides the
# "request" object to "clean" method
def get_form(self, request, obj=None, **kwargs):
form = super(SomeAdmin, self).get_form(request, obj=obj, **kwargs)
form.request = request
return form
class SomeAdminForm(forms.ModelForm):
class Meta(object):
model = SomeModel
fields = ["A", "B"]
def clean(self):
cleaned_data = super(SomeAdminForm, self).clean()
logged_in_email = self.request.user.email #voila
if logged_in_email in ['abc#abc.com']:
raise ValidationError("Please behave, you are not authorised.....Thank you!!")
return cleaned_data
Another way you can solve this issue is by using Django currying which is a bit cleaner than just attaching the request object to the form model.
from django.utils.functional import curry
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostForm
def get_form(self, request, **kwargs):
form = super(BlogPostAdmin, self).get_form(request, **kwargs)
return curry(form, current_user=request.user)
This has the added benefit making your init method on your form a bit more clear as others will understand that it's being passed as a kwarg and not just randomly attached attribute to the class object before initialization.
class BlogPostForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop('current_user')
super(BlogPostForm, self).__init__(*args, **kwargs)