Django Design Pattern - request.user in Model - django

I run into this pattern all the time and I'm wondering if there's a better way to handle it. Lots of my models contemplate the idea of 'creator' - in other words, I want to make sure the user who created the object is saved as the creator. As such, my models almost always include
creator = models.ForeignKey(User)
There doesn't seem to be a way of defaulting this user to the user who created it (request.user). As such I find myself building model forms and adding creator as a HiddenInput()
class MyModelForm(ModelForm):
class Meta:
model = MyModelForm
fields = ['name', 'creator']
widgets = {
'creator': HiddenInput()
}
and then binding the form in the view with initial
form = MyModelForm(initial={'creator': request.user})
and checking on POST to make sure no one dickered with the form (full view):
def create(request):
form = MyModelForm(initial={'creator': request.user})
if request.method == 'POST':
if int(request.POST['creator']) != int(request.user.id):
return render(request,'500.html', {'message':'form tampering detected'})
form = FeedGroupForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('index'))
return render(request, 'mymodelformpage.html',{'form':form})
I'm fine with this but it seems like an anti-pattern. It strikes me that there ought to be a way to default the creator in the model.
creator = models.ForeignKey(User, default=request.user)
Or do it in a post_save perhaps?

No, this is not the right way. The correct way is to exclude the creator field from the form, then set it on save:
if form.is_valid:
obj = form.save(commit=False)
obj.creator = request.user
obj.save()
... redirect ...

Related

Different ways to save form values to the database

I have started learning Django recently using a Udemy course. While going through the course instructor asked to save values from a Form to database.
After searching on the internet I figured out how to put form values into database and everything is working fine. Below is my views.py and forms.py files.
forms.py
class FormName(forms.Form):
fname = forms.CharField( label="First Name")
lname = forms.CharField(label="Last name:")
email = forms.EmailField()
verify_email = forms.EmailField(label='Verify Email:')
def clean(self):
all_clean_data = super().clean()
email = all_clean_data['email']
vmail = all_clean_data['verify_email']
if email != vmail:
raise forms.ValidationError("Check the emails")
views.py
def signup(request):
form = forms.FormName()
if request.method == 'POST':
form = forms.FormName(request.POST)
if form.is_valid():
post = User()
post.fname=request.POST.get('fname')
post.lname=request.POST.get('lname')
post.email=request.POST.get('email')
post.save()
return render(request,'third_app/greet.html')
else:
return render(request,'third_app/oops.html',{'form':form})
return render(request, 'third_app/signup.html',{'form':form})
Now coming to question, the instructor is using Meta class to store the form values to the database. Below are his forms.py and views.py files. I am curious about what the difference is between my method and the instructor's.
forms.py
class FormName(forms.ModelForm):
class Meta():
model = User
fields = 'all'
views.py
def signup(request):
form = forms.FormName()
if request.method == 'POST':
form = forms.FormName(request.POST)
if form.is_valid():
form.save(commit=True)
return render(request,'third_app/greet.html')
else:
return render(request,'third_app/oops.html',{'form':form})
return render(request, 'third_app/signup.html',{'form':form})
Thanks.
The Django docs explain this very well. It's what is known as a ModelForm:
If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. For instance, you might have a BlogComment model, and you want to create a form that lets people submit comments. In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model.
For this reason, Django provides a helper class that lets you create a Form class from a Django model.
So, to answer your question, your method uses a regular form (forms.Form) where you define the form fields, perform validation and then save each field individually in your view. When using form.ModelForm, field validation and saving is taken care of for you. Seeing as you have already defined what your fields are, the ModelForm uses this to perform the validation. The save() method conveniently saves each field to the database.

Django Form getting invalid

I am newbie to Django. I have some troubles with forms after moving into new verison. Following,
1, The model
class UserProfileDetails(models.Model):
user = models.OneToOneField(User)
profilePicture = models.ImageField('Profile Picture',upload_to='static/ProfilePic/', null=True,blank=True)
def __str__(self):
return self.user.username
2, The form
class imageUploadForm(forms.ModelForm):
class Meta:
model= UserProfileDetails
fields = ['user','profilePicture']
3, And finally the view function
def upload_pic(request):
current_user = request.user
if request.method == 'POST':
form = imageUploadForm(request.POST, request.FILES)
if form.is_valid():
pic = form.cleaned_data['profilePicture']
m = UserProfileDetails(user= current_user.id,profilePicture=pic)
m.save()
else:
raise NotImplemented("What if the user doesn't have an associated profile?")
return HttpResponseRedirect(reverse('polls:profile'))
This code worked with Django 1.8. But after porting to Django 1.10.4, the form is getting invalid. I believe, the problem is with OneToOneField.
IMP: Also, i am using pinax account app for account management.
Why this form is getting invalid?
When you submit the form, it doesn't seem as though a correct input has been given for both fields (user and profile picture). My guess is that you aren't sending through the user in the form which means it is invalid. So you are only uploading the image.
You do not need to have 'user' in the form fields attribute as you already access that in the view with 'request.user'. So remove the 'user' field from the form.
Also, to make sure it is correct, change 'user=current_user.id' to 'user=current_user' so you are match instance with instance rather than instance with id.

where to hash form data before saving the model in createview in Django?

I am a little confused with where validation of form/model fields can happen in generic CreateView/UpdateView. Consider my hypothetical model below. I want the field secret to be hashed using my custom hashfunction and saved and assume some validation for secret field is done(NOT shown in the example below). My options to do this are:
1) in the model save method (I have not shown this below)
2) in the form's save method (I have shown below)
3) in the form_valid method of AccountCreateView (I have shown below)
4) how can I access the cleaned_data in the generic views (cleaned_data is available
only after form_valid is called)
Which is the right way to do it, both pros and cons. I will use the same form for updateView, in which case I will unhash the secret before displaying its data on the form. where this should happen?
My model:
class Account(models.Model):
user = models.ForeignKey(User)
created = models.DateField(auto_now=True)
secret = models.IntegerField()
My form:
AccountCreateForm(forms.ModelForm):
secret = forms.CharField(max_length=100)
class Meta:
model = MediaContent
exclude = (secret,user,created)
def save(self, user, debate):
obj = super(AccountCreateView, self).save(commit=False)
obj.user = self.cleaned_data['user']
obj.secret = myHashfunction(self.cleaned_data['secret'])
obj.save()
My view:
class AccountCreateView(CreateView):
"""
displays form to create a new search
"""
model = Account
form_class = AccountCreateForm
success_url = reverse_lazy('home')
template_name = 'app/account_form.html'
def form_valid(self, form):
f = form.save(commit=False)
secret=myHashfunction(self.request.POST['secret'])
f.user = self.request.user
f.save()
return super(AccountCreateView,self).form_valid(form)
EDIT:
please see the edit to my model and form. the field I use in form is not the field in model.
It is a new Field, that takes CharField but the model saves as IntegerField. my hashfunciton will convert the charField to IntegerField.
I think in this case Form is the better than ModelForm, as excluding every field on your model makes it redundant. You should then do any additional validation for the un-hashed secret here with clean_secret.
AccountCreateForm(forms.Form):
secret=forms.CharField(max_length=100)
Now, if you are not using the ModelForm anymore, I would suggest using FormView over CreateView as the generic CreateView has become less of a good fit.
class AccountCreateView(FormView):
"""
displays form to create a new search
"""
form_class = AccountCreateForm
success_url = reverse_lazy('home')
template_name = 'app/account_form.html'
def form_valid(self, form):
unhashed_secret = form.cleaned_data['secret']
hashed_secret = myHashfunction(unhashed_secret)
user = self.request.user
# Probably put some extra logic here to check if the user already exists
Account.objects.create(
user=user,
secret=hashed_secret,
)
return super(AccountCreateView,self).form_valid(form)
None of the above. The correct place to do this is in the clean_secret method of the form. That is the place for any field-related validation and conversion. Simply return the hashed value from that method.

django form use excluded field

with django 1.5.1 I try to use the django form for one of my models.
I dont want to add the "user" field (Foreignkey) somewhere in the code instead of letting the user deceide whoes new character it is.
My Code:
Model:
class Character(models.Model):
user = models.ForeignKey(User)
creation = models.DateTimeField(auto_now_add=True, verbose_name='Creation Date')
name = models.CharField(max_length=32)
portrait = models.ForeignKey(Portrait)
faction = models.ForeignKey(Faction)
origin = models.ForeignKey(Origin)
The form:
class CreateCharacterForm(forms.ModelForm):
class Meta:
model = Character
fields = ['name', 'portrait', 'faction', 'origin']
The view:
def create_character(request, user_id):
user = User.objects.get(id=user_id)
if request.POST:
new_char_form = CreateCharacterForm(request.POST)
if new_char_form.is_valid():
new_char_form.save()
return HttpResponseRedirect('%s/characters/' % user_id)
else:
return render_to_response('create.html',
{'user': user, 'create_char':new_char_form},
context_instance=RequestContext(request))
else:
create_char = CreateCharacterForm
return render_to_response('create.html',
{'user': user, 'create_char': create_char},
context_instance=RequestContext(request))
I have tried to use a instance to incluse the userid already. i've tried to save the userid to the form before saving it, or changing the save() from my form.
I keep getting the error that character.user cant be null
I have to tell that im pretty new to django and im sure one way or another it should be possible
Can someone please help me out?
Its explained well in document model form selecting fields to use
You have to do something like this in your view
...
if request.POST:
new_char_form = CreateCharacterForm(request.POST)
if new_char_form.is_valid():
#save form with commit=False
new_char_obj = new_char_form.save(commit=False)
#set user and save
new_char_obj.user = user
new_char_obj.save()
return HttpResponseRedirect('%s/characters/' % user_id)
else:
...

Validate Django model form with excluded fields

I have a form ReviewForm that allows one user to review another user (source of the review and target of the review). I would like to check that the user does not review herself and I think the place to do that should be in the ReviewForm itself. However, the two users (source and target) are not part of the form since they are selected through the UI before. What is the cleanest way of doing this? Right now I am doing the validation in the view but I don't find that nice.
class ReviewForm(forms.ModelForm):
class Meta:
model = models.Review
fields=["title", "content"]
class Review(models.Model):
title=models.CharField(max_length=50,blank=True)
content=models.TextField(blank=True)
source=models.ForeignKey(Profile,related_name="reviews_by")
target=models.ForeignKey(Profile,related_name="reviews_of")
def new_review(request,profile_id):
form=ReviewForm()
if request.method=='POST':
review=models.Review()
review.source=request.user.profile
review.target=models.Profile.objects.get(id=profile_id)
form = ReviewForm(request.POST, instance = review)
if request.user.profile.id == int(profile_id):
form.errors["__all__"]= "Don't review yourself!"
if form.is_valid():
form.save()
return redirect(reverse("profile"))
else:
return render(request,"reviews/new.html",{"form":form})
return render(request,"reviews/new.html",{"form":form})
Thank you.
Try this:
class ReviewForm(forms.ModelForm):
def clean(self):
if self.instance.source == self.instance.target:
raise forms.ValidationError("You cannot review your entity")
return super(ReviewForm, self).clean()
class Meta:
model = models.Review
fields=["title", "content"]