I'm coming from Laravel and new to Django. I'm trying to add a username to the url after login. This has been asked before a few times, but I have yet to make the solutions work (they involve having a model attached to the generic FormView class). Here is what I have:
urls.py
path('login/', views.Login.as_view(), name='login'),
# Logged in user
path('home/<str:username>', views.UserIndex.as_view(), name='user_index'),
views.py
class Login(views.AnonymousRequiredMixin, views.FormValidMessageMixin, generic.FormView):
authenticated_redirect_url = '/'
form_class = LoginForm
form_valid_message = "You have successfully logged in"
template_name = 'pythonmodels/registration/login.html'
success_url = reverse_lazy('pythonmodels:user_index', args=("Bill",))
def form_valid(self, form):
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = authenticate(username=username, password=password)
if user is not None and user.is_active:
login(self.request, user)
return super(Login, self).form_valid(form)
else:
return self.form_invalid(form)
forms.py
class LoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
'username',
'password',
ButtonHolder(
Submit('login', 'Login', css_class='btn-primary')
)
)
In the views.py file, I would like the args for success_url to be the username of the user that was just authenticated. Should this be done in the LoginForm class? I have also seen that you can go to an intermediate url and then get the User data, but this seems like a terrible extra step. I would like to keep this as close to the base FormView and AuthenticationForm as I don't understand more in-depth customization yet. Thanks so much!
You can't set success_url in the view, because you don't know the argument until after the user has been logged in.
Override get_success_url instead:
def get_success_url(self):
return reverse('pythonmodels:user_index', args=[self.request.user.username])
Related
How would one go about creating a user-profile page that other users can view without being able to edit the profile unless they are the user?
The thing I'm trying to work out is how the url routing would work, is it best practice to store a user's profile on a profile/ or <user_id> page and then load in the individual user's data like recent posts using the username or id passed through the url?
Also would this be handled by the one view and template and just use {% if request.user == profile.user %} to display things like edit profile etc?
my problem is any user can edit for others there profiles when he edit url
for example my id is www.test.com/profile/44/ and other user have this id www.test.com/profile/40/
okay ,, now when i edit the link to be 40 not 44 i can access and edit the second user ! how to fix that
models.py :
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
email_confirmed = models.BooleanField(default=False)
#receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
instance.profile.save()
def __str__(self):
return self.user
urls.py :
from django.urls import path
from blog_app.views import ProfileView
urlpatterns = [
path('profile/<int:pk>/', ProfileView.as_view(), name='profile'),
]
forms.py :
# Profile Form
class ProfileForm(forms.ModelForm):
# constructor of the UserForm, not Meta
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
self.fields['username'].widget.attrs.update({'class':'form-control','placeholder':' Enter your username in English ','style': 'font-size:19px;text-align: center;'})
class Meta:
model = User
fields = [
'username',
'first_name',
'last_name',
'email',
]
views.py:
# Edit Profile View
class ProfileView(UpdateView):
model = User
form_class = ProfileForm
success_url = reverse_lazy('home')
template_name = 'user/commons/profile.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False # Deactivate account till it is confirmed
user.save()
current_site = get_current_site(request)
subject = 'Activate Your MySite Account'
message = render_to_string('user/emails/account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
user.email_user(subject, message)
messages.success(request, ('Please Confirm your new email to change email.'))
return redirect('login')
return render(request, self.template_name, {'form': form})
html page :
<button type="button" id="submit"> <a href="{% url 'profile' user.id %}" > edit profile info </a></button>
You can override the get_object() method to always return the currently logged on user from request.user, then you will not need to provide "pk" variable in your path.
Implement get_object() in your view
class ProfileView(UpdateView):
model = User
form_class = ProfileForm
success_url = reverse_lazy('home')
template_name = 'user/commons/profile.html'
def get_object(self, queryset=None):
return self.request.user
Then configure the path without pk
urlpatterns = [
path('profile/me/', ProfileView.as_view(), name='profile'),
]
Note that you should use login_required() decorator or LoginRequiredMixin on that view to avoid anonymous users accessing this view.
Django
How to prevent users from directly accessing URLS in main_app/urls.py if the user is logged out and user.is_authenticated = FALSE
Please note that I used Class Based Views in views.py. The condition if request.user.is_authenticated(): is not working. See below:
class EmployeeCreate(CreateView):
model = Employee
fields = ['first_name', 'last_name', 'role']
def post(self, request, *args, **kwargs):
if request.user.is_authenticated():
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('main_app:index'))
elif "another" in request.POST:
return HttpResponseRedirect(reverse('main_app:employee-add'))
else:
return super(EmployeeCreate, self).post(request, *args, **kwargs)
Class LoginRequiredMixin solved the problem https://docs.djangoproject.com/en/2.0/topics/auth/default/#the-loginrequired-mixin
I have a custom user model. After doing successful login, I am getting the anonymous user in HttpResponseRedirect and templates as well. How do I get the logged in user?
Login View:
class LoginFormView(View):
form_class = UserLoginForm
user_model = get_user_model()
template_name = 'account/login.html'
def get(self, request, *args, **kwargs):
form = self.form_class
return render(request, self.template_name, {'form':form})
def post(self, request, *args, **kwargs):
email = request.POST['email']
password = request.POST['password']
user = authenticate(email=email, password=password)
if user is not None:
if user.is_active:
login(request, user)
return HttpResponseRedirect(reverse('home'))
else:
messages.error(request, 'Please enter correct email and password!')
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
If you have the request template context processor enabled, you'll be able to access the user in the template with {{ request.user}}.
Secondly, make sure you are importing the login function and not the login view. It should be:
from django.contrib.auth import login
I have form:
class FindAdvert(forms.ModelForm):
class Meta:
model = Advert
fields = ('id', 'code')
But in this way I can't 'login' because validation return error: id already exist.
How modify this form to allow 'login' (instead of registration)?
You don't need ModelForm for login, because it's semantical goal - create|edit database data (anyway you can hardcode id in your view - but it is wrong way).
So create simple Form with custom validation rules (in clean method or in your views), something like that below:
class LoginForm(forms.Form):
username = forms.CharField(
label=u'Username',
required=True,
)
password = forms.CharField(
label=u'Password',
required=True,
widget=forms.PasswordInput
)
def clean(self):
cleaned_data = super(LoginForm, self).clean()
username = cleaned_data.get('username')
password = cleaned_data.get('password')
if (username and password and User.objects.filter(username=username).count() == 0)\
or (username and password and User.objects.filter(username=username).count() == 1 and
User.objects.get(username=username).password != password):
raise forms.ValidationError(u'Wrong username or password')
return cleaned_data
views:
from django.contrib.auth import logout, authenticate, login
from django.views.generic import FormView, RedirectView
# ...
class LoginFormView(FormView):
template_name = 'common/login.html'
form_class = LoginForm
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
logout(request)
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None and user.is_superuser:
login(request, user)
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self):
return self.request.GET.get('next') or reverse('human:add')
class LogoutRedirectView(RedirectView):
permanent = False
def get_redirect_url(self, *args, **kwargs):
logout(self.request)
return reverse('common:login')
Right now, this is how the password is changed within a user profile. What is the best way of converting this to a class based view knowing that there is no model involved?
This is the view for changing the password
#login_required
def profile_change_password(request):
"""
Change password of user.
"""
user = get_object_or_404(User, username__iexact=request.user.username)
if request.method == 'POST':
form = PasswordChangeFormPrivate(user=user, data=request.POST)
if form.is_valid():
form.save()
messages.add_message (request, messages.INFO,
_('password changed'))
return HttpResponseRedirect(reverse('profile_view_details'))
else:
form = PasswordChangeFormPrivate(user=request.user)
return render_to_response('profiles/profile_change_password.html',
{ 'form': form,},
context_instance=RequestContext(request)
)
This is the form for changing the password
class PasswordChangeFormPrivate(PasswordChangeForm):
def __init__(self, *args, **kwargs):
super(PasswordChangeForm, self).__init__(*args, **kwargs)
def clean_new_password2(self):
password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2')
if password1 and password2:
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
min_len = getattr(settings, "PASSWORD_MINIMUM_LENGHT", 6)
if len(password1) < min_len:
raise forms.ValidationError(_("Password too short! minimum length is ")+" [%d]" % min_len)
return password2
This is the URL
url(r'^password/change/$',
profile_change_password,
name='profile_change_password'
),
As you see no model is involved as the password will replace "User" password field up on validation. Any simple way of converting this to a class-based view? Does it matter?
There doesn't need to be a model involved -- you can use a FormView. It would look something like this:
from django.core.urlresolvers import reverse
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.views.generic.edit import FormView
from myapp.forms import PasswordChangeFormPrivate
class ProfileChangePassword(FormView):
form_class = PasswordChangeFormPrivate
success_url = reverse('profile_view_details')
template_name = 'profiles/profile_change_password.html'
def get_form_kwargs(self):
kwargs = super(ProfileChangePassword, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.save()
messages.add_message(self.request, messages.INFO, _('profile changed'))
return super(ProfileChangePassword, self).form_valid(form)
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProfileChangePassword, self).dispatch(*args, **kwargs)
I'm not sure why you have
user = get_object_or_404(User, username__iexact=request.user.username)
You require login for the form anyway, so request.user is guaranteed to be a valid user.