Saving Django user forms without changing the password - django

I'm maintaining a Django application and have encountered a bug where if a user edits their profile then their password is corrupted; it seems that set_password is not being used and so the password is set to an unencrypted value which is hardcoded in the form. It's not clear how I could change the existing setup to get around this nuisance, and would welcome any suggestions. The update code looks like this:
#login_required
def profile(request):
user = request.user
profile, created = UserProfile.objects.get_or_create(user=user)
if request.method == 'POST':
userform = UserForm(request.POST, instance=user)
profileform = ProfileForm(request.POST, instance=profile)
if profileform.is_valid():
profileform.save()
if userform.is_valid():
userform.save()
return redirect('user_profile_page')
else:
profileform = ProfileForm(instance=profile)
userform = UserForm(instance=user)
render(request, 'profiles/edit_profile.html', {'profileform': profileform, 'userform': userform})
return render(request, 'profiles/edit_profile.html', {'profileform': profileform, 'userform': userform})
Then, the userform which is causing the problem contains this odd-looking code:
class UserForm(forms.ModelForm):
class Meta:
model = User
exclude = ('last_login', 'date_joined', 'is_active', 'is_superuser', 'is_staff')
username = forms.CharField(widget=forms.TextInput(attrs={'readonly': 'readonly'}))
password = forms.CharField(widget=forms.PasswordInput(attrs={'readonly': 'readonly', 'value': '00000000000000000'}))
I'm not really sure what the value attr in password is meant to be doing. Anyway, I tried to modify this by adding the following to the UserForm:
def save(self, commit=True):
user = super(UserForm, self).save(commit=False)
password = self.cleaned_data["password"]
if len(password) > 0 and password != '00000000000000000':
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
I had no luck with this either, and if I simply omit the password field from the userform or the relevant html then the form does not validate.
<form method="post" action="" class="wide">
{% csrf_token %}
.....
<label for="id_password">Password:</label>
{{ userform.password }}
Can anyone suggest how this might be cleaned up?

Use a separate form for editing the password and remove it from this one. Add password to the exclude list and remove the field declaration.

Related

Django forms: Update a user's attributes without changing username

I'm trying to create a form that allows a user to update their username or avatar. The problem that I am running into is that if I update the profile picture without changing the username, the django form validation if form.is_valid() will recognize the form as invalid, since the username already exists in the database. I'm using an update view, though I'm not sure I've implemented it correctly.
When I try to update the avatar without changing the username, I get a page that says "Form is invalid" from the line: HttpResponse("Form is invalid"). Is there a workaround to remove the form validation? I have tried removing if form.is_valid(): but received the error 'User_form' object has no attribute 'cleaned_data'.
I feel like there has to be an easy way around this that I have not been able to find, as so many sites allow you to update only one attribute at a time.
Views.py
model = User
form = User_form
fields = ['username', 'avatar']
template_name_suffix = '_update_form'
def update_profile(request, user_id):
if request.method == "POST":
form = User_form(request.POST, request.FILES)
if form.is_valid():
user = User.objects.get(pk=user_id)
username = form.cleaned_data['username']
avatar = form.cleaned_data['avatar']
if username != user.username:
user.username = username
if avatar != user.avatar:
if avatar:
user.avatar = avatar
user.save()
return redirect('index')
else:
return HttpResponse("Form is invalid")
models.py
class User(AbstractUser):
followed_by = models.ManyToManyField("User", blank=True, related_name="following")
avatar = models.ImageField(upload_to='profilepics/', verbose_name='Avatar', null=True, blank=True)
class User_form(ModelForm):
class Meta:
model = User
fields = ['username', 'avatar']
user_update_form.html
<form action="/profile/{{user.id}}/update" method="post" enctype='multipart/form-data'>
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update">
</form>
You are doing too much work yourself. Django's ModelForms can not only create data, but update data as well. You pass the instance through the instance=… parameter:
from django.shortcuts import get_object_or_404
def update_profile(request, user_id):
user = get_object_or_404(User, pk=user_id)
if request.method == 'POST':
form = User_form(request.POST, request.FILES, instance=user)
if form.is_valid():
form.save()
return redirect('index')
else:
return HttpResponse('Form is invalid')
else:
form = User_form(instance=user)
return render(request, 'some_template.html', {'form': form})
For uniqness checks, it will exclude the instance when checking if the username already exists. So one can change the username, given the new username of course does not yet exists.

Django: Creating a function-based view Login & Signup form in one single page

I want to create a landing page similar to linkedin's where it has the signup form and the login form at the navbar. I'm currently using a single form for both the signup and login in forms.py:
forms.py
class UserRegisterForm(UserCreationForm):
firstname = forms.CharField(max_length=200, label='')
lastname = forms.CharField(max_length=200, label='')
email = forms.EmailField(label='')
class Meta:
model = User
fields = ['username', 'firstname', 'lastname', 'email', 'password1']
My template includes 2 {{ form }}s and each has a submit button.
Button for the sign up {{ form }}:
name='signupbtn' type='submit'
Button for the login {{ form }}:
name='loginbtn' type='submit'
Now I have trouble trying to login or authenticate the user that I created (which is the super user). My signup/login view goes as follows:
def index(request):
if request.method == 'POST':
if 'signupbtn' in request.POST:
form = UserRegisterForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Your account has been created! You may now login.')
return redirect('index')
elif 'loginbtn' in request.POST:
username = request.POST['username']
password = request.POST['password1']
form = UserRegisterForm(request.POST)
if form.is_valid():
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
return redirect('home')
else:
form = UserRegisterForm()
return render(request, 'users/index.html', {'form': form})
Is it possible that I need to create 2 forms in my forms.py? When I try to login, it doesn't go past the form.is_valid() under elif 'loginbtn' in request.POST:. Also both forms have {% csrf_token %}.
I got everything to work by just simply REMOVING the form.is_valid() and adjusting the view after that.

Django Forms: Validation message not showing

Can anyone spot what I'm doing wrong in the following example. Validating messages are not appearing in my template when incorrect details are entered such as a invalid email address. The template is loading and there are no errors.
I'm excepting validation messages to be printed on page, however for some reason this has suddenly stop working. As you can see from the code example below I'm passing the form in the context back to the template. this used to work and today just stopped.
view.py
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
# If form has passed all validation checks then continue to save member.
user = User.objects.create_user(
username=form.cleaned_data['username'],
email=form.cleaned_data['email'],
password=form.cleaned_data['password']
)
user.save()
#member = User.get_profile()
#member.name = form.cleaned_data['name']
#member.save()
member = Member(
user=user,
name=form.cleaned_data['name']
)
member.save()
# Save is done redirect member to logged in page.
return HttpResponseRedirect('/profile')
else:
# If form is NOT valid then show the registration page again.
form = RegistrationForm()
context = {'form':form}
return render_to_response('pageRegistration.html', context,context_instance=RequestContext(request))
form.py
class RegistrationForm(ModelForm):
username = forms.CharField(label=(u'User Name'))
email = forms.EmailField(label=(u'Email'))
password = forms.CharField(label=(u'Password'), widget=forms.PasswordInput(render_value=False))
passwordConfirm = forms.CharField(label=(u'Confirm Password'), widget=forms.PasswordInput(render_value=False))
class Meta:
model = Member
# Don't show user drop down.
exclude = ('user',)
def clean_username(self):
username = self.cleaned_data['username']
try:
User.objects.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError("Username already taken.")
def clean(self):
try:
cleaned_data = super(RegistrationForm, self).clean()
password = cleaned_data.get("password")
passwordConfirm = cleaned_data.get('passwordConfirm')
if password != passwordConfirm:
raise forms.ValidationError("Password does not match, try again.")
return cleaned_data
except:
raise forms.ValidationError("Error")
pageRegistration.html
<form action="" method="POST">
{% csrf_token %}
{% if forms.errors %}
<p>
correct some stuff
</p>
{% endif %}
{{form}}
<br>
<input type="submit" value="submit">
</form>
Since, the form is not validate in the else clause your form variable is overriden with a new form where it looses all of the errors
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
# If form has passed all validation checks then continue to save member.
user = User.objects.create_user(
username=form.cleaned_data['username'],
email=form.cleaned_data['email'],
password=form.cleaned_data['password']
)
user.save()
#member = User.get_profile()
#member.name = form.cleaned_data['name']
#member.save()
member = Member(
user=user,
name=form.cleaned_data['name']
)
member.save()
# Save is done redirect member to logged in page.
return HttpResponseRedirect('/profile')
return render_to_response('pageRegistration.html', context,context_instance=RequestContext(request))
Updated for the CBV world.
This is what was causing the equivalent error for me:
class CreateView(generic.CreateView): # or generic.UpdateView
def get_context_data(self, **kwargs):
context_data = super(CreateView, self).get_context_data(**kwargs)
# context_data['form'] = self.form_class # << this was the problematic override
Perhaps the page is returning a new form every time it reloads. Check if the
context variable in views.py contains 'form' : form

How to update User object without creating new one?

Following works fine in shell:
>>> from django.contrib.auth.models import User
>>> user=User.objects.get(pk=1)
>>> user.first_name = u'Some'
>>> user.last_name = u'Name'
>>> user.save()
>>> user.first_name
u'Some'
>>> user.last_name
u'Name'
Then I try to do the same with forms:
# forms.py
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name']
# views.py
def edit_names(request, template_name="registration/edit_names.html"):
if request.method == "POST":
form = UserForm(data=request.POST)
if form.is_valid():
user = form.save(commit=False)
user.save()
url = urlresolvers.reverse('my_account')
return HttpResponseRedirect(url)
else:
form = UserForm(instance=request.user)
page_title = _('Edit user names')
return render_to_response(template_name, locals(),
context_instance=RequestContext(request))
# edit_names.html
<form action="." method="post">{% csrf_token %}
<table>
{{ form.as_table }}
<tr><td colspan="2">
<input type="submit" />
</td></tr>
</table>
</form>
I open page in browser and see two fields First name and Last name. When I fill in the fields and submit the form I get the error:
Exception Type: IntegrityError
Exception Value: column username is not unique
I also tried to add ['username'] to fields list in UserForm. If I submit the form with my username (as request.user), the form displays errormessage:
User with this Username already exists.
If I change the username to some unique name, the new user with that username is being created.
The question is:
How can I update User object, not create new one?
Sorry for being so verbose, but I had hard search here and could not find the answer to my question.
BTW, these cases don't work for me:
Extending UserCreationForm to include email, first name, and last name
add field first_name and last_name in django-profile
How to create a UserProfile form in Django with first_name, last_name modifications?
EDIT:
As suggested #fceruti I just added on request.method == 'post' branch this:
form = UserForm(data=request.POST, instance=request.user)
Just add on request.method == 'post' branch this:
form = UserForm(data=request.POST, instance=request.user)
if request.method == "POST":
kwargs = { 'data' : request.POST }
try:
kwargs['instance'] = User.objects.get(username=request.POST['username'])
except:
pass
form = UserForm(kwargs**)
if form.is_valid():
user = form.save(commit=False)
...

Django username edit issue

I want to allow users to change their usernames, so I have a form:
class UserForm(models.ModelForm):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name')
In template, I have a greeting block, something like Hello, {{ request.user.username }}
When I submit a form with a username that already exists, it throws an error "User with this Userame already exists.", but in the greeting block I see Hello, username (where username is this one I have submitted).
What am I doing wrong? Anyone faced something similar?
Here is a view code:
#login_required
def persona_edit(request):
form = PersonaForm(request.POST or None, instance=request.user.persona)
user = User.objects.get(pk=request.user.pk)
user_form = UserForm(request.POST or None, instance=user)
if form.is_valid() and user_form.is_valid():
form.save()
user_form.save()
messages.success(request, _(u'Profile updated'))
return redirect('identity:dashboard')
payload = {'form': form, 'user_form': user_form}
return render(request, 'identity/persona_edit.html', payload)