I have a form:
class CreateConferenceForm(forms.ModelForm):
class Meta:
model = Conference
fields = ['name', 'participants']
def clean(self):
cleaned_data = super(CreateConferenceForm, self).clean()
if not request.user.id in cleaned_data.get('participants'):
raise forms.ValidationError('Error')
But I don't know how to import a request object from view, because method is_valid hasn't additional arguments. How I can do it?
Give your form an __init__ method that allows you to pass a user:
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
Now you can use self.user in clean. Wherever you create the form remember to pass the user, e.g. form = CreateConferenceForm(request.user, request.POST) in the view.
Related
I got a strange bug. I am failing validation if I add an email field. If validation for only 1 username field, then everything works fine. Tell me please, what am I doing wrong?
file forms.py:
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField(required=False)
def __init__(self, user, *args, **kwargs):
self.user = user
super(UserUpdateForm, self).__init__(*args, **kwargs)
if 'label_suffix' not in kwargs:
kwargs['label_suffix'] = '*'
self.fields['username'].widget = forms.TextInput(attrs={'class':'input-text'})
self.fields['email'].widget = forms.EmailInput(attrs={'class':'input-text'})
class Meta:
model = User
fields = ("username","email",)
file views.py:
def get_context_data(self, request):
self.object = get_object_or_404(Profile,slug=self.kwargs['slug'])
ctx={}
ctx['UserUpdateForm']=UserUpdateForm(request.POST if "UserUpdateForm" in request.POST else None,instance=request.user)
сtx['comments']=Comments.objects.filter(profile=self.object)
return ctx
def post(self, request, *args, **kwargs):
if request.method=='POST':
if "UserUpdateForm" in request.POST:
form=UserUpdateForm(request.POST)
if form.is_valid():
user=User.objects.get(username=self.request.user)
user.username=form.cleaned_data.get('username')
user.email=form.cleaned_data.get('email')
user.save()
obj=Profile.objects.get(user__username=user.username)
return HttpResponseRedirect(reverse_lazy('profile',kwargs={'slug': obj.slug},))
return render(request,self.template_name,self.get_context_data(request))
You construct your form with an extra parameter user:
class UserUpdateForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
# …
so that means that the first parameter when you construct the form is the user. You thus should pass a user:
form=UserUpdateForm(request.user, request.POST)
or if you want to edit the user object:
form=UserUpdateForm(request.user, request.POST, instance=request.user)
it however does not make much sense to pass the user, since as far as one can see, you never use the .user attribute in your form.
I have a ModelForm with a custom save method to populate a model field with a kwarg from the url params (that was passed to the form):
from app.models import MyModel
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fk_customer = kwargs.pop('customer')
super(MyModelForm, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
fields = '__all__'
def clean(self):
cleaned_data = super(MyModelForm, self).clean()
cleaned_data['fk_customer'] = self.fk_customer
return cleaned_data
When I inspect cleaned_data in my view, fk_customer is present and valid. However is_valid() is false, and the ModelForm won't save(). If I override a few things and force save, the field fk_customer is saved as None.
What's going on and how can I alter cleaned_data and still validate?
If you are not displaying the customer field from your form, then you should exclude it from your form class instead of using __all__.
Then, I would try to set the customer in the form's save method instead of the clean method. The following is untested:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.customer = kwargs.pop('customer')
super(MyModelForm, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
exclude = ('customer',)
def save(self, commit=True)
instance = super(MyModelForm, self).save(commit=False)
instance.customer = self.customer
if commit:
instance.save()
self.save_m2m()
return instance
I can't find the answer to this problem which I guessed was very easy for Django.
I simply want to define an author field in a model, like this:
class Article(models.Model):
author = models.ForeignKey(User)
It seems there is no easy way like author = models.ForeignKey(User, default=current_user) so what is the easiest way to store the currently logged in user in the database? (if not logged in, a pre-defined default user called "anonymous" can be used)
Thanks for any help!
Currently logged user is available in the view as the request.user attribute:
def create_article(request):
...
if request.user.is_active():
article.author = request.user
article.save()
...
Alternatively, you can pass request.user to your form class and override the save:
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
def __init__(self, *args, **kwargs):
# Allows you to pass the user in from the request, or just set the property
if not hasattr(self, 'user'):
self.user = kwargs.pop('user')
super(ArticleForm, self).__init__(*args, **kwargs)
def save(self, commit=True)
article = super(ArticleForm, self).save(commit=False)
article.user = self.user
if commit:
article.save()
return article
Slightly more code, but it's encapsulated in the form class and not in the view, so you can use it in more than one place.
Example usage:
# in a view
#login_required
def your_view(request):
form = ArticleForm(request.POST or None, user=request.user)
. . .
# in Django admin
class ArticleAdmin(admin.ModelAdmin):
form = ArticleForm
def get_form(self, request, obj=None, **kwargs):
form = super(ArticleAdmin, self).get_form(request, obj, **kwargs)
form.user = request.user
return form
In either use case, you can be assured you have an authenticated user.
Two scoops of Django advises to make a custom validator for a form as follows:
class MyModel(models.Model):
body = models.TextField()
repo_name = models.CharField(max_length=50)
def validate_repo_existance(value):
# Validate repo existance.
# This needs the Github account which is bound
# to the user account though.
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields["repo_name"].validators.append(validate_repo_existance)
class Meta:
model = MyModel
Is there any way to pass the user that is on the form page to the custom validator?
This is what I was looking for:
views.py
form = MyModelForm(request.user) # when unbound
form = MyModelForm(request.user, data=request.POST) # when bound
validators.py
class RepoValidator(object):
def __init__(self, user):
self.user = user
def __call__(self, value):
#self.user and value are accessible from here
forms.py
class MyModelForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields["repo_name"].validators.append(RepoValidator(user))
class Meta:
model = MyModel
The current user is stored in the request object, and can be accessed with request.user. You can pass this user as an argument into the form.
class MyModelForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields["repo_name"].validator.append(validate_repo_existance)
And pass the user from your view where you instantiate the form:
form = MyModelForm(request.user) # when unbound
form = MyModelForm(request.user, data=request.POST) # when bound
This pattern is used for any other data that you need to pass to a Django form.
In my implementation of ModelForm, I would like to perform different types of validation checks based on whether current user is superuser. How can I access the current request user?
If you're using Class Based Views (CBVs) then passing an extra argument in the form constructor (e.g. in get_forms_class) or in form_class will not work, as <form> object is not callable will be shown.
The solution for CBVs is to use get_form_kwargs(), e.g.:
views.py:
class MyUpdateView(UpdateView):
model = MyModel
form_class = MyForm
# Sending user object to the form, to verify which fields to display/remove (depending on group)
def get_form_kwargs(self):
kwargs = super(MyUpdateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
forms.py:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
super(MyForm, self).__init__(*args, **kwargs)
# If the user does not belong to a certain group, remove the field
if not self.user.groups.filter(name__iexact='mygroup').exists():
del self.fields['confidential']
you can pass the user object as an extra argument in the form constructor.
e.g.
f = MyForm(user=request.user)
and the constructor will look like:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user',None)
super(MyForm, self).__init__(*args, **kwargs)
and then use user in the clean_XX forms as you wish
My small addition,
I had a requirement where one of the model choice fields of the form is dependent on the request.user, and it took a while to take my head around.
The idea is that
you need to have a __init__ method in the model form class,
and you access the request or other parameters from the arguments of the __init__ method,
then you need to call the super constructor to new up the form class
and then you set the queryset of the required field
code sample
class CsvUploadForm(forms.Form):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(CsvUploadForm, self).__init__(*args, **kwargs)
self.fields['lists'].queryset = List.objects.filter(user=user)
lists = forms.ModelChoiceField(queryset=None, widget=forms.Select, required=True)
as you can see, the lists variable is dependent on the current user, which is available via request object, so we set the queryset of the field as null, and its assigned dynamically from the constructor later.
Take a look into the order of the statements in the above code
you can pass the user variable like this from the view file
form = CsvUploadForm(user=request.user)
or with other POST, FILE data like below
form = CsvUploadForm(request.POST, request.FILES, user=request.user)
You may reference the user object using the instance attribute within the instance it self.
Ex; self.instance.user
class StatusForm(ModelForm):
# def __init__(self, *args, **kwargs):
# self.user = kwargs.pop('user', None)
# super(StatusForm, self).__init__(*args, **kwargs)
class Meta:
model = Status
fields = [
'user',
'content',
'image'
]
def clean_content(self):
content = self.cleaned_data.get("content", None)
if len(content) > 240:
raise ValidationError(f"Hey {self.instance.user.username}, the content is too long")
return content
This worked for me, when I am not sending form in context explicitly in get_context_data:
views.py
class MyView(FormView):
model = MyModel
form_class = MyForm
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
form.py
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(MyForm, self).__init__(*args, **kwargs)
if not self.user.groups.filter(name__iexact='t1_group').exists():
del self.fields['test_obj']
When sending form explicitly in get_context_data we can use and this is forms.Form :
views.py
class MyView(FormView):
model = MyModel
form_class = MyForm
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['form'] = self.form_class(self.request.user)
return context
forms.py
class MyForm(forms.Form):
def __init__(self, user,*args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
if not user.groups.filter(name__iexact='t1_group').exists():
del self.fields['test_obj']