I have a Django form where I'm trying to save user profile details. My UserProfile has a many to many field, which I'm having trouble saving. Here is my attempted view code:
#login_required
def updateProfile(request, uid):
import pdb; pdb.set_trace()
"""
First, grab the existing user data out of the db.
If it's not there, we'll create it, then fill in the blanks from user input on post.
"""
requested_user = get_object_or_404(User, pk=uid)
user_profile = None
try:
user_profile = UserProfile.objects.get(user = requested_user)
except UserProfile.DoesNotExist:
default_skill_level = SkillLevel.objects.all()[0] # default value.
user_profile = UserProfile(user = requested_user, skill_level = default_skill_level)
user_profile.save()
if request.method == 'POST':
form = UserProfileForm(request.POST, instance = user_profile)
if form.is_valid() and (request.user.id == uid or request.user.is_superuser):
obj = form.save(commit=False) # get just the object but don't commit it yet.
obj.save() # finally save it.
obj.save_m2m() # this is failing. UserProfile has no attribute save_m2m
return index(request)
else:
print "Not authorized to do that! Implement real authorization someday."
return index(request)
else:
profile_form = UserProfileForm(instance=user_profile)
context = {
'user' : request.user,
'form' : profile_form
}
return render(request, 'booker/profile.html', context)
On a POST, once the form is validated I'm able to save the basic object but afterwards saving the many to many fields fails with the given exception. What is the right way to go about this?
Example:
...
if formset.is_valid():
items = formset.save(commit=False)
for item in items:
item.save()
formset.save_m2m()
E:
Try this:
if form.is_valid() and (request.user.id == uid or request.user.is_superuser):
obj = form.save(commit=False) # get just the object but don't commit it yet.
obj.save() # finally save it.
form.save_m2m()
The save_m2m() is only required if you have previously saved with commit=False. In your example the commit=False save does not appear to be necessary.
E.g. you could replace
obj = form.save(commit=False) # get just the object but don't commit it yet.
obj.save() # finally save it.
obj.save_m2m() # this is failing. UserProfile has no attribute save_m2m
With:
form.save()
Related
I already have seen this bug in other post, but still in trouble.
I'm trying to create a social network like instagram where users will be able to publish posts (photos).
I have User class which herit from AbstractUser, and got a OneToMany field of posts: each user can publish many posts.
After successfully pulling my photo from: PostForm(request.POST, request.FILES) and saving it correctly, I cannot add this photo to the current user's publications/posts and got error:
'NoneType' object has no attribute 'add'
def blog_and_photo_upload(request):
form = PostForm()
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
user = get_user(request) # user instance is correct with good pk
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.save()
user.posts.add(post) # row doesnt work
redirect('home')
return render(request, 'base/upload_post.html', {'form': form})
models.py
class Post(models.Model):
...
image = ResizedImageField(size=[300, 300], blank=True, upload_to='posts')
class User(AbstractUser):
...
posts = models.ForeignKey(Post, on_delete=models.Cascade, null=True)
You can simply update the form like this:
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.posts = post
user.save()
return redirect('home')
But, I think the design of the model is wrong, User to Post relation should be like this:
Class User(...):
posts = models.ManyToManyField(Post)
In that way, your original implementation should work. (Probably you don't need user.save() call in your view).
At first there should be return redirect(...) not only redirect() and secondly try to use the following view:
def blog_and_photo_upload(request):
form = PostForm()
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
user = get_user(request) # user instance is correct with good pk
post = Post.objects.create(image=form.cleaned_data['image']) # post instance looks correct also
post.save()
user.posts.add(post) # add post to user's posts field
user.save()
return redirect('home')
return render(request, 'base/upload_post.html', {'form': form})
You need to bind first Post with User model like add a ForeignKey or a ManyToManyFields to relate them
posts = models.ForeignKey(User)
then you will be able to call it like you did
user.posts # this won't return None
Check this many to many field docs: https://docs.djangoproject.com/en/4.1/topics/db/examples/many_to_many/
What I'm trying to do?
I want to display 2 registration forms separately of each other on the same page. The forms are: built-in User model and my self created UserProfile. To track, on what form user is now, I use sessions. It some sort of flags for me at the moment.
Why I don't want to use sessions?
I discovered a 'bug', at least for me, that I don't know how to fix. Bug appears if user passed first registration form, and close browser/tab. Next time user opens registration page, it will show second registration form, instead of first, as expected.
Where bug happens, but now with code.
When user opens register page first time, built-in UserCreationForm will be show, because there is no session called username yet.
def get(self, request):
if request.session.get("username", None):
self.context["form"] = RegisterProfile()
else:
self.context["form"] = UserCreationForm()
I'm using CBV, so it's OK that function called get and first argument is self. Also I created context dictionary as instance variable, so I can just add new field form to it.
Next, if user fill in given form (note, that first form is built-in User's form) built-in User instance will be created and it's username will be stored in username session.
If you confused at the moment don't worry much, I leave full view code at the bottom.
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
request.session["username"] = request.POST["username"]
return redirect("register")
Now, when session username exsists, and user redirected to same view, my self-created form will be shown. As in first code example.
Now, bug happens. User can freely leave page, and when he come back, second registration form will be shown again. That's not what I want.
Full view code:
class Register(View):
context = {"title": "Register new account"}
def get(self, request):
if request.session.get("username", None):
self.context["form"] = RegisterProfile()
else:
self.context["form"] = UserCreationForm()
return render(request, "users/register.html", context=self.context)
def post(self, request):
if request.session.get("username", None):
form = RegisterProfile(request.POST, request.FILES)
if form.is_valid():
username = request.session.get("username", None)
if not username:
messages.error(request, "We are sorry, but error happend. Try again!")
return redirect("index")
user = User.objects.filter(username=username).first()
profile = UserProfile(
user=user,
nickname=request.POST["nickname"],
sex=request.POST["sex"],
age=request.POST["age"],
profile_picture=form.files["profile_picture"],
)
profile.save()
del request.session["username"]
messages.success(request, "Profile created successfully!")
return redirect("index")
else:
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
request.session["username"] = request.POST["username"]
return redirect("register")
self.context["form"] = form
return render(request, "users/register.html", context=self.context)
UPD 1:
I changed register logic a little, now full code looks like this:
class Register(View):
context = {"title": "Register user page"}
def get(self, request):
if request.session.get("user_data", None):
form = ProfileRegisterForm()
else:
form = UserCreationForm()
self.context["form"] = form
return render(request, "users/register.html", context=self.context)
def post(self, request):
if request.session.get("user_data", None):
form = ProfileRegisterForm(request.POST, request.FILES)
if form.is_valid():
user = User.objects.create_user(*request.session["user_data"])
user.save()
UserProfile.objects.create(
user=user,
nickname=request.POST["nickname"],
sex=request.POST["sex"],
age=request.POST["age"],
profile_picture=form.files["profile_picture"],
)
del request.session["user_data"]
messages.success(request, "Profile created successfully!")
return redirect("index")
else:
form = UserCreationForm(request.POST)
if form.is_valid():
request.session["user_data"] = [
request.POST["username"],
request.POST["password1"],
request.POST["password2"]
]
return redirect("register")
self.context["form"] = form
return redirect("register")
But anyway, I need place to store temporary data like username, password1 and password2. If someone knows, where I can store data like in sessions, please, answer bellow.
I have a simple user registration form (in forms.py):
from django.contrib.auth.models import User
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput
validators=[MinLengthValidator(6)])
password_repeat = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = ['username', 'password','password_repeat']
If someone tries to enter something and the validation fails I want the same form to be rendered again but all fields should be cleared. At the moment my view looks like this (in views.py):
def signup(request):
form = UserForm(request.POST or None)
if form.is_valid():
user = form.save(commit=False)
username = form.cleaned_data['username']
password = form.cleaned_data['password']
password_repeat = form.cleaned_data['password-repeat']
user.set_password(password)
user.save()
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
auth.login(request, user)
return redirect('/')
return render(request, 'signup.html', {'form': form})
The problem is that the form.fields['username'] field still contains the username that was entered and is thus passed to render.
I've been searching for a solution a while now but can't find it. My guess is that the solution has something to do with the clean() method that I don't seem to get.
This is an odd thing to want to do - it is the opposite of the question people normally ask, as most people want to preserve the fields and show the errors.
However, if you really want to clear the form, you should just instantiate a new one.
if form.is_valid():
...
else:
form = UserForm()
return render(request, 'signup.html', {'form': form})
To always clear a particular form field while preserving all form validation errors, you can create a custom input widget that always "forgets" its old value. For example:
from django import forms
class NonstickyTextInput(forms.TextInput):
'''Custom text input widget that's "non-sticky"
(i.e. does not remember submitted values).
'''
def get_context(self, name, value, attrs):
value = None # Clear the submitted value.
return super().get_context(name, value, attrs)
class MyForm(forms.Form):
username = forms.CharField(widget=NonstickyTextInput())
# ...
Reference: django.forms.Widget.get_context
Behavior
Suppose we are using MyForm in such a view:
from django.shortcuts import render, redirect
from myapp.forms import MyForm
def myview(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
# Do something with the submitted values...
return redirect('home_page')
else:
form = MyForm()
return render(request, 'myapp/myview.html', {'form': form})
When the form encounters any validation error and the form is re-displayed, all the usual validation error messages will be shown on the form, but the displayed username form field will be blank.
I'm new to Django and have a Q&A project. For each question you may have multiple tags which already exist or put new ones. The new tags should be created before the question is saved. How do I solve this properly? So far I have:
def question_add(request):
# redirect user to login page if not authenticated
if not request.user.is_authenticated():
return render(request, 'account/login.html')
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = QuestionForm(request.POST)
if form.is_valid():
# process the data in form.cleaned_data as required
instance = form.save(commit=False)
instance.created_by = request.user
instance.save()
messages.success(request, 'Question added with success.')
# redirect to the main page:
return HttpResponseRedirect('/')
else:
messages.warning(request, 'Please correct the errors.')
# if a GET (or any other method) we'll create a blank form
else:
form = QuestionForm()
return render(request, 'question/add.html', {'form': form})
This should be done prior form.is_valid() or does it exist a magic method for doing this?
I am assuming tag is ManyToMany field for Question model.
Inside form.is_valid() to add manyToMany field data.
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
instance.save()
# returns the list of tag names
tags = request.POST.get('tags')
for tag in tags:
# create if tag not found with given name or return existing tag
obj, created = Tag.objects.get_or_create(name=tag)
instance.tags.add(obj)
If the Tag is ForiegnKey:
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
tag_name = request.POST.get('tag')
obj, created = Tag.objects.get_or_create(name=tag_name)
instance.tag = obj.id
instance.save()
What I basically want to accomplish is that when a user edits his profile, a blank field causes everything to be overwritten.
For example:
if a first name is added and the user wants t change it he wont have to refill the whole form, the only field he will have to change is the first name field.When the form gets submitted the other blank fields cause their model field to be empty.
here is what i tried:
def editUserprofile(request):
rc = context_instance=RequestContext(request)
u = request.user
if request.method=='POST':
form = UserProfileEdit(request.POST, request.FILES)
if form.is_valid():
u = UserProfile.objects.get(user=u)
if 'avatar' in request.FILES :
u.avatar = request.FILES['avatar']
if 'first_name' in request.POST:
u.first_name = form.cleaned_data['first_name']
if 'last_name' in request.POST:
u.last_name = form.cleaned_data['last_name']
if 'email' in request.POST:
u.email = form.cleaned_data['email']
if 'country' in request.POST:
u.country = form.cleaned_data['country']
if 'date_of_birth' in request.POST:
u.date_of_birth = form.cleaned_data['date_of_birth']
u.save()
return HttpResponseRedirect(reverse('photocomp.apps.users.views.editUserprofile'))
else:
u = UserProfile.objects.get(user=u)
form = UserProfileEdit()
return render_to_response('users/editprofile.html',
{'form':form, 'u':u},
rc)
Can I someway make the empty field pass without editing my models content?
Hope it doesn't confuse you so much!
If you want more detail just tell me :) thanks in advance!
Since this is edit form, you should consider pre-filling the fields with the current values. This is easy by submitting the constructor an instance argument (if you are using ModelForm). This would pre-fill the values with the correct values and the user can change whichever he/she wants.
form = UserProfileEdit(instance=u)
Django doc link.