Django: Accessing request in forms.py clean function - django

Hi Stackoverflow people,
In my clean function in forms.py, I would like to save automatically some information in a session variable. However, I do not seem to get access to the request variable.
All examples for handing over the request variable are based on function based views, but here I am using a class based view.
My forms.py:
from django import forms
from item.models import Item
class CreateItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ('name', 'description')
def __init__(self, request, *args, **kwargs):
self.request = request
super(CreateItemForm, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(CreateItemForm, self).clean()
if cleaned_data.get("address"):
self.request.session['name'] = cleaned_data.get("name")
else:
raise forms.ValidationError(_('Oops, can\'t find location.'))
return self.cleaned_data
My views.py:
from django.views.generic.edit import FormView
from item.forms import CreateItemForm
class ItemCreate(FormView):
form_class = CreateItemForm
template_name = 'item/item_create.html'
success_url = 'http://www.google.com'
What is the best way to hand over the request variable from the views.py to forms.py?
Thank you for your answer.

You can overwrite the FormMixin's get_form_kwargs method to add the request for to the form's init parameters:
class ItemCreate(FormView):
def get_form_kwargs(self):
kwargs = super(ItemCreate, self).get_form_kwargs()
kwargs.update({
'request' : self.request
})
return kwargs

Overriding the form.get_initial() works for me
class ItemCreate(FormView):
def get_initial(self):
init = super(ItemCreate, self).get_initial()
init.update({'request':self.request})
return init
And in the clean we can access it with form.initial dict
class sampleForm(forms.Form):
...
...
def clean(self):
user_request = self.initial['request']
By the way, we don't need to pop the extra args like suggested above.

Related

Django 1.11 pass kwarg to modelform field

I would like to pass a kwarg to set a modelform field but im struggling to figure out how to do it.
My URL is as follows:
url(r'^tent/create/(?P<munc>\d+)',views.TentCreate.as_view(),name='tent_create'),
My view is simply:
class TentCreate(CreateView):
model = Tent
form_class = TentForm
And my form:
class TentForm(ModelForm):
class Meta:
model = Tent
exclude =('asfo','niho')
def __init__(self, *args, **kwargs):
super(TentForm, self).__init__(*args, **kwargs)
self.fields['primary'].queryset = Mark.objects.filter(munc=self.kwargs['munc'])
from the model:
class Tent(models.Model):
primary = models.ForeignKey(Mark,on_delete=models.CASCADE)
I can render the form fine without overriding def __init, with no filtering applied to the 'primary' field.
However attempting to use the def __init code I've described above to pass the munc kwarg to the form field is resulting in the following error:
"'TentForm' object has no attribute 'kwargs'"
I've been going around in circles trying to work through this so I would be really appreciative if anyone is able to provide me some guidance to solve this. This is my first Django project so I'm learning how I go so I assume I have made some fundamental error somewhere here!
Try overriding get_form_kwargs method:
views.py
class TentCreate(CreateView):
model = Tent
form_class = TentForm
def get_form_kwargs(self):
kwargs = super(TentCreate, self).get_form_kwargs()
kwargs.update({'munc': self.kwargs['munc']})
return kwargs
forms.py
class TentForm(ModelForm):
class Meta:
model = Tent
exclude =('asfo','niho')
def __init__(self, *args, **kwargs):
munc = kwargs.pop('munc')
super(TentForm, self).__init__(*args, **kwargs)
self.fields['primary'].queryset = Mark.objects.filter(munc=munc)
class TentCreate(CreateView):
form_class = TentForm
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
kwargs = self.get_form_kwargs()
print(kwargs, self.kwargs)
kwargs.update(self.kwargs)
return form_class(**kwargs)
forms.py
class TentForm(ModelForm):
class Meta:
model = Tent
exclude =('asfo','niho')
def __init__(self, *args, **kwargs):
munc=self.kwargs['munc']
super(TentForm, self).__init__(*args, **kwargs)
self.fields['primary'].queryset = Mark.objects.filter(munc=munc)
you must pop munc before call super(TentForm, self).__init__(*args, **kwargs)

django Initialising Choices in Form from Class Based View

I've seen a lot of answers about how this is solved when not using Class Based Views. Is there some really obvious thing I'm missing about doing it with CBVs?
Basically I want to have a MultipleChoiceField in my form which has choices determined by what is happening in the view. e.g. I use the PK from the URL to do some backend requests and then those should be used to populate the choices.
# forms.py
from django.forms import Form, MultipleChoiceField, CharField
class EmailForm(Form):
users = MultipleChoiceField(required=False)
subject = CharField(max_length=100)
message = CharField()
def __init__(self, users=None, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
if users:
self.fields['users'].choices = users
#urls.py
from django.conf.urls import url, patterns
from .views import EmailView
# url patterns
urlpatterns = patterns('',
url( r'^(?P<pk>\d+)$', EmailView.as_view(), name="maindex" ),
)
#views.py
from django.views.generic import FormView, TemplateView
from .forms import EmailForm
class EmailView(FormView):
template_name = 'myapp/email.html'
form_class = EmailForm
success_ulr = '/thanks/'
def form_valid(self, form):
# Do stuff here
return super(EmailView, self).form_valid(form)
Basically it boils down to how/where to call the init function from the view. How do I do that? Or is there another way I've missed? I thought of overriding get_form_kwargs in the view, but couldn't make that do anything.
Thanks
The view:
from django.views.generic import FormView
class EmailView(FormView):
# ...
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
# get users, note: you can access request using: self.request
kwargs['users'] = users
return kwargs
The form:
from django import forms import Form
class EmailForm(Form):
users = MultipleChoiceField(required=False)
# ...
def __init__(self, *args, **kwargs):
self.users = kwargs.pop('users', None)
super(EmailForm, self).__init__(*args, **kwargs)
self.fields['users'].choices = self.users
Basically, what I've done in a similar case is the following (Python 3.5, Django 1.8):
def get_form(self, *args, **kwargs):
form= super().get_form(*args, **kwargs)
form.fields['rank'].choices= <sequence of 2-tuples>
return form
where obviously rank is the field name. This way I use the default form.
Alright, the FormMixin calls get_form to get the form-class which looks like
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return form_class(**self.get_form_kwargs())
So you can either override get_form to instance your Form yourself
def get_form(self, form_class):
return EmailForm(files=self.request.FILES or None,
data=self.request.POST or None,
users=some_user_queryset)
or stay a bit more generic and override get_form_kwargs to something like
def get_form_kwargs(self):
form_kws = super(EmailView, self).get_form_kwargs()
form_kws["users"] = some_user_queryset
return form_kws
One way to do it is:
class EmailView(FormView):
# ...
def get(self, request, *args, **kwargs):
self.users = ...
return super(EmailView, self).get(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
kwargs['users'] = self.users
return kwargs
This allows you to set the user choices in the view and to pass them to the form.
You can override get_form.
I needed to update the choices for ChoiceField based on logged in user.
Form:
class InteractionCreateForm(forms.Form):
device = forms.ChoiceField(choices=[(None, '----------')])
...
View:
class InteractionCreateView(FormView):
form_class = InteractionCreateForm
...
def get_form(self, form_class=None):
form_class = super().get_form(form_class=None)
form_class.fields['device'].choices = \
form_class.fields['device'].choices \
+ [(device.pk, device) for device in Device.objects.filter(owner=self.request.user.id)]
return form_class

FormSetView save with Django Extra Views

as shown in the following code i am trying to save the formset once it is validated. but i always get object has no attribute 'save'error. its great if someone can help me to use FormSetView to save and update data using forms.
from extra_views import FormSetView
from foo.forms import MyForm
class MyFormSetView(FormSetView):
template_name = 'myformset.html'
form_class = MyForm
success_url = 'success/'
def formset_valid(self, formset):
formset.save()
return super(MyFormSetView, self).formset_valid(formset)
You should use ModelFormSetView and then override your widget:
class MyForm(forms.ModelForm):
model = customer
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['birthday'].widget = birthdaySelector()
class MyFormSetView(ModelFormSetView):
template_name = 'myformset.html'
model = customer
success_url = 'success/'
form_class = MyForm
No need to define formset_valid, ModelFormSetView saves formset automatically.

How do I use an UpdateView to update a Django Model?

I'm trying to update a model in Django using the class-based generic view UpdateView.
I read the page Updating User model in Django with class based UpdateView to try and get me started, but I'm getting an error 'WSGIRequest' object has no attribute 'id'
I'm a fresh face to Django, so please be forgiving if I'm doing something stupid.
//urls.py
url(r'^portfolios/update/(?P<id>\d+)/$',PortfoliosUpdateView.as_view()),
//views.py
class PortfoliosUpdateView(UpdateView):
form_class = PortfoliosCreateForm
model = Portfolios
template_name = 'portfolios/create.html'
def get(self, request, **kwargs):
self.object = Portfolios.objects.get(id=self.request.id)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(object=self.object, form=form)
return self.render_to_response(context)
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.request.id)
return obj
It's mostly just a modified version of the code originally posted, but I thought it'd work. I know that I'm trying to retrieve the id passed as a GET parameter, but that doesn't seem to come through in the request variable. Am I going about this the wrong way?
Thanks
Edit: I think I fixed it, but this may be wrong:
I changed the lines
self.object = Portfolios.objects.get(id=self.request.id)
obj = Portfolios.objects.get(id=self.request.id)
to
self.object = Portfolios.objects.get(id=self.kwargs['id'])
obj = Portfolios.objects.get(id=self.kwargs['id'])
I could be wrong.
It should be:
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.kwargs['id'])
return obj
Look at class based generic view dispatch explains that keyword arguments are assigned to self.kwargs.:
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
self.request = request
self.args = args
self.kwargs = kwargs
return handler(request, *args, **kwargs)
id = self.request.GET.get('id',None) is what you needed when trying to access the GET query string.
However, your view can be simplified:
from django.conf.urls import *
from django.views.generic import UpdateView
from yourapp.models import Portfolios
from yourapp.forms import PortfoliosCreateForm
urlpatterns = patterns('',
url('^portfolios/update/(?P<pk>[\w-]+)$', UpdateView.as_view(
model=Portfolios,
form_class=PortfoliosCreateForm,
template_name='portfolios/create.html',
success_url='/portfolios'
), name='portfolio_update'),
)
views.py
class MyUpdateView(UpdateView):
model = ModelName # required
template_name = 'x/h1.html'
form_class = ModelNameForm
success_url = reverse_lazy('app:page1')
def get_queryset(self):
"""
Optional condition to restrict what users can see
"""
queryset = super().get_queryset()
return queryset.filter(id__lt=20)
def get_success_url(self):
return reverse_lazy(
'app1:abc',
kwargs={'pk': self.object.id}
)
urls.py
In urlpatterns=[]
path('xyz/<pk>/', MyUpdateView.as_view(),name='xyz')
my_model_view.html
{{form}}
You will be able to edit ModelName at url /xyz/<pk>/ where <pk> can be anything from 1 to 20 based on our condition in get_queryset(). Take that condition out to allow users to edit any object.
self.object is only available after post request to the UpdateView.

Django: Populate user ID when saving a model

I have a model with a created_by field that is linked to the standard Django User model. I need to automatically populate this with the ID of the current User when the model is saved. I can't do this at the Admin layer, as most parts of the site will not use the built-in Admin. Can anyone advise on how I should go about this?
UPDATE 2020-01-02
⚠ The following answer was never updated to the latest Python and Django versions. Since writing this a few years ago packages have been released to solve this problem. Nowadays I highly recommend using django-crum which implements the same technique but has tests and is updated regularly: https://pypi.org/project/django-crum/
The least obstrusive way is to use a CurrentUserMiddleware to store the current user in a thread local object:
current_user.py
from threading import local
_user = local()
class CurrentUserMiddleware(object):
def process_request(self, request):
_user.value = request.user
def get_current_user():
return _user.value
Now you only need to add this middleware to your MIDDLEWARE_CLASSES after the authentication middleware.
settings.py
MIDDLEWARE_CLASSES = (
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
'current_user.CurrentUserMiddleware',
...
)
Your model can now use the get_current_user function to access the user without having to pass the request object around.
models.py
from django.db import models
from current_user import get_current_user
class MyModel(models.Model):
created_by = models.ForeignKey('auth.User', default=get_current_user)
Hint:
If you are using Django CMS you do not even need to define your own CurrentUserMiddleware but can use cms.middleware.user.CurrentUserMiddleware and the cms.utils.permissions.get_current_user function to retrieve the current user.
If you want something that will work both in the admin and elsewhere, you should use a custom modelform. The basic idea is to override the __init__ method to take an extra parameter - request - and store it as an attribute of the form, then also override the save method to set the user id before saving to the database.
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(MyModelForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
Daniel's answer won't work directly for the admin because you need to pass in the request object. You might be able to do this by overriding the get_form method in your ModelAdmin class but it's probably easier to stay away from the form customisation and just override save_model in your ModelAdmin.
def save_model(self, request, obj, form, change):
"""When creating a new object, set the creator field.
"""
if not change:
obj.creator = request.user
obj.save()
This whole approach bugged the heck out of me. I wanted to say it exactly once, so I implemented it in middleware. Just add WhodidMiddleware after your authentication middleware.
If your created_by & modified_by fields are set to editable = False then you will not have to change any of your forms at all.
"""Add user created_by and modified_by foreign key refs to any model automatically.
Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
from django.db.models import signals
from django.utils.functional import curry
class WhodidMiddleware(object):
def process_request(self, request):
if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if hasattr(request, 'user') and request.user.is_authenticated():
user = request.user
else:
user = None
mark_whodid = curry(self.mark_whodid, user)
signals.pre_save.connect(mark_whodid, dispatch_uid = (self.__class__, request,), weak = False)
def process_response(self, request, response):
signals.pre_save.disconnect(dispatch_uid = (self.__class__, request,))
return response
def mark_whodid(self, user, sender, instance, **kwargs):
if 'created_by' in instance._meta.fields and not instance.created_by:
instance.created_by = user
if 'modified_by' in instance._meta.fields:
instance.modified_by = user
here's how I do it with generic views:
class MyView(CreateView):
model = MyModel
def form_valid(self, form):
object = form.save(commit=False)
object.owner = self.request.user
object.save()
return super(MyView, self).form_valid(form)
If you are using class based views Daniel's answer needs more. Add the following to ensure that the request object is available for us in your ModelForm object
class BaseCreateView(CreateView):
def get_form_kwargs(self):
"""
Returns the keyword arguments for instanciating the form.
"""
kwargs = {'initial': self.get_initial()}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
'request': self.request})
return kwargs
Also, as already mentioned, you need to return the obj at the end of ModelForm.save()
what is the problem with using something like:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ['created_by']
def save(self, user):
obj = super().save(commit = False)
obj.created_by = user
obj.save()
return obj
Now call it like myform.save(request.user) in the views.
here is ModelForm's save function, which has only a commit parameter.
For future references, best solution I found about this subject:
https://pypi.python.org/pypi/django-crum/0.6.1
This library consist of some middleware.
After setting up this libary, simply override the save method of model and do the following,
from crum import get_current_user
def save(self, *args, **kwargs):
user = get_current_user()
if not self.pk:
self.created_by = user
else:
self.changed_by = user
super(Foomodel, self).save(*args, **kwargs)
if you create and abstract model and inherit from it for all your model, you get your auto populated created_by and changed_by fields.
Based on bikeshedder's answer, I found a solution since his did not actually work for me.
app/middleware/current_user.py
from threading import local
_user = local()
class CurrentUserMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
_user.value = request.user
return self.get_response(request)
def get_current_user():
return _user.value
settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'common.middleware.current_user.CurrentUserMiddleware',
]
model.py
from common.middleware import current_user
created_by = models.ForeignKey(User, blank=False, related_name='created_by', editable=False, default=current_user.get_current_user)
I'm using python 3.5 and django 1.11.3
From the Django documentation Models and request.user:
" To track the user that created an object using a CreateView, you can
use a custom ModelForm. In the view, ensure that you
don’t include [the user field] in the list of fields to edit, and override
form_valid() to add the user:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(LoginRequiredMixin, CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
The 'save' method from forms.ModelForm returns the saved instanced.
You should add one last line to MyModelForm:
...
return obj
This change is necessary if you are using create_object or update_object generic views.
They use the saved object to do the redirect.
I don't believe Daniel's answer is the best there is since it changes the default behaviour of a model form by always saving the object.
The code I would use:
forms.py
from django import forms
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(MyModelForm, self).save(commit=False)
if obj.created_by_id is None:
obj.created_by = self.user
if commit:
obj.save()
return obj
Note sure if you were looking for this, but adding the following
user = models.ForeignKey('auth.User')
to a model will work to add the user id to the model.
In the following, each hierarchy belongs to a user.
class Hierarchy(models.Model):
user = models.ForeignKey('auth.User')
name = models.CharField(max_length=200)
desc = models.CharField(max_length=1500)