I have been trying to implement the django-otp with qrcode using custom Forms and Views. The problem is I am a little caught up on whether my implementation is correct or not. As the documentation states that a request.user.is_verified() attribute is added to users who have been OTP verified, I am actually not able to get it right. Have created confirmed TOTP device for user with the QR code setup using Microsoft Authenticator app.
I was able to successfully implement the default Admin Site OTP verification without any issues. Below is the files for the custom implementation.
urls.py
from django.conf.urls import url
from account.views import AccountLoginView, AccountHomeView, AccountLogoutView
urlpatterns = [
url(r'^login/$', AccountLoginView.as_view(), name='account-login',),
url(r'^home/$', AccountHomeView.as_view(), name='account-home',),
url(r'^logout/$', AccountLogoutView.as_view(), name='account-logout',)
]
views.py
from django.contrib.auth import authenticate, login as auth_login
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
from django_otp.forms import OTPAuthenticationForm
class AccountLoginView(FormView):
template_name = 'login.html'
form_class = OTPAuthenticationForm
success_url = '/account/home/'
def form_invalid(self, form):
return super().form_invalid(form)
def form_valid(self, form):
# self.request.user returns AnonymousUser
# self.request.user.is_authenticated returns False
# self.request.user.is_verified() returns False
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
otp_token = form.cleaned_data.get('otp_token')
otp_device = form.cleaned_data.get('otp_device')
user = authenticate(request=self.request, username=username, password=password)
if user is not None:
device_match = match_token(user=user, token=otp_token)
# device_match returns None
auth_login(self.request, user)
# self.request.user returns admin#mywebsite.com
# self.request.user.authenticated returns True
# self.request.user.is_verified returns AttributeError 'User' object has no attribute 'is_verified'
# user.is_verified returns AttributeError 'User' object has no attribute 'is_verified'
return super().form_valid(form)
class AccountHomeView(TemplateView):
template_name = 'account.html'
def get(self, request, *args, **kwargs):
# request.user.is_authenticated returns True
# request.user.is_verified() returns False
return super(AccountHomeView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['data'] = 'This is secured text'
return context
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="." method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.username.errors }}
<label for="{{ form.username.id_for_label }}">{{ form.username.label_tag }}</label>
{{ form.username }}
</div>
<div class="fieldWrapper">
{{ form.password.errors }}
<label for="{{ form.password.id_for_label }}">{{ form.password.label_tag }}</label>
{{ form.password }}
</div>
{% if form.get_user %}
<div class="fieldWrapper">
{{ form.otp_device.errors }}
<label for="{{ form.otp_device.id_for_label }}">{{ form.otp_device.label_tag }}</label>
{{ form.otp_device }}
</div>
{% endif %}
<div class="fieldWrapper">
{{ form.otp_token.errors }}
<label for="{{ form.otp_token.id_for_label }}">{{ form.otp_token.label_tag }}</label>
{{ form.otp_token }}
</div>
<input type="submit" value="Log In" />
{% if form.get_user %}
<input type="submit" name="otp_challenge" value="Get Challenge" />
{% endif %}
</form>
</body>
</html>
Could anyone please let me know what is that I am missing. I want to be able to authenticate and verify them using my own views by using the existing OTP form classes.
Please advice.
What exactly is match_token? You don't need a device field, rather you try and a device for a user.
Here's my implementation for that.
from django_otp import devices_for_user
from django_otp.plugins.otp_totp.models import TOTPDevice
def get_user_totp_device(user, confirmed=None):
devices = devices_for_user(user, confirmed=confirmed)
for device in devices:
if isinstance(device, TOTPDevice):
return device
def create_device_topt_for_user(user):
device = get_user_totp_device(user)
if not device:
device = user.totpdevice_set.create(confirmed=False)
return device.config_url
def validate_user_otp(user, data):
device = get_user_totp_device(user)
serializer = otp_serializers.TokenSerializer(data=data)
if not serializer.is_valid():
return dict(data='Invalid data', status=status.HTTP_400_BAD_REQUEST)
elif device is None:
return dict(data='No device registered.', status=status.HTTP_400_BAD_REQUEST)
elif device.verify_token(serializer.data.get('token')):
if not device.confirmed:
device.confirmed = True
device.save()
return dict(data='Successfully confirmed and saved device..', status=status.HTTP_201_CREATED)
else:
return dict(data="OTP code has been verified.", status=status.HTTP_200_OK)
else:
return dict(
data=
dict(
statusText='The code you entered is invalid',
status=status.HTTP_400_BAD_REQUEST
),
status=status.HTTP_400_BAD_REQUEST
)
And then in a view, you can just do something like
create_device_topt_for_user(user=request.user)
validate_user_otp(request.user, request.data)
Related
I'm using django parler to translate my models. now i'm creating a custom admin Panel and i have a view for create and update of Contents. I'm using a class based view inherit from "View" for both create and update views so i can't use the TranslatableCreateView and TranslatableUpdateView. I saw in the codes of Django parler that using TranslatableModelFormMixin you can add translation support to the class-based views. I used this mixin but still i don't have access to the language tabs.
Here is Views.py:
class ContentCreateUpdateView(TranslatableModelFormMixin, TemplateResponseMixin, View):
module = None
model = None
obj = None
template_name = 'content-form.html'
def get_model(self, model_name):
if model_name in ['text', 'video', 'image', 'file']:
return apps.get_model(app_label='courses', model_name=model_name)
return None
def get_form(self, model, *args, **kwargs):
Form = modelform_factory(model, exclude=['owner',
'order',
'created',
'updated'])
return Form(*args, **kwargs)
def dispatch(self, request, module_id, model_name, id=None):
self.module = get_object_or_404(Module, id=module_id, course__owner=request.user)
self.model = self.get_model(model_name)
if id:
self.obj = get_object_or_404(self.model,
id=id,
owner=request.user)
return super().dispatch(request, module_id, model_name, id)
def get(self, request, module_id, model_name, id=None):
form = self.get_form(self.model, instance=self.obj,)
return self.render_to_response({'form': form, 'object': self.obj})
def post(self, request, module_id, model_name, id=None):
form = self.get_form(self.model, instance=self.obj, data=request.POST, files=request.FILES)
if form.is_valid():
obj = form.save(commit=False)
obj.owner = request.user
obj.save()
if not id:
# new content
Content.objects.create(module=self.module, item=obj)
return redirect('module_content_list', self.module.id)
return self.render_to_response({'form': form, 'object': self.obj})
Template:
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% load crispy_forms_filters %}
{% block content %}
<div class="col-md-12">
<!-- Horizontal Form -->
<div class="card card-primary">
<div class="card-header ">
{% if object %}
<h3 class="card-title mb-0 float-left"> Edit Content "{{ object.title }}"</h3>
{% else %}
<h3 class="card-title mb-0 float-left"> Add New Content</h3>
{% endif %}
</div>
<!-- /.card-header -->
<!-- form start -->
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<div class="row">
<div class="col-12">
{% if language_tabs %}
<ul class="nav nav-tabs">
{% for url,name,code,status in language_tabs %}
{% if status == 'current' %}
<input type="hidden" class="language_button selected" name="{{ code }}"/>
<li class="nav-item">
<a class="current nav-link active"
aria-selected="true">{{ name }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="{{ status }} nav-link"
href="{{ url }}"
aria-selected="false">{{ name }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
</div>
<div class="col-6">
{{ form }}
</div>
</div>
{% csrf_token %}
<div class="col-6">
<button class="btn btn-success">Save Content</button>
</div>
</form>
</div>
</div>
</div>
<!-- /.card -->
{% endblock %}
Here is the source code of django parler:
class TranslatableModelFormMixin(LanguageChoiceMixin):
"""
Mixin to add translation support to class based views.
For example, adding translation support to django-oscar::
from oscar.apps.dashboard.catalogue import views as oscar_views
from parler.views import TranslatableModelFormMixin
class ProductCreateUpdateView(TranslatableModelFormMixin, oscar_views.ProductCreateUpdateView):
pass
"""
def get_form_class(self):
"""
Return a ``TranslatableModelForm`` by default if no form_class is set.
"""
super_method = super().get_form_class
# no "__func__" on the class level function in python 3
default_method = getattr(
ModelFormMixin.get_form_class, "__func__", ModelFormMixin.get_form_class
)
if not (super_method.__func__ is default_method):
# Don't get in your way, if you've overwritten stuff.
return super_method()
else:
# Same logic as ModelFormMixin.get_form_class, but using the right form base class.
if self.form_class:
return self.form_class
else:
model = _get_view_model(self)
if self.fields:
fields = self.fields
return modelform_factory(model, form=TranslatableModelForm, fields=fields)
else:
return modelform_factory(model, form=TranslatableModelForm)
def get_form_kwargs(self):
"""
Pass the current language to the form.
"""
kwargs = super().get_form_kwargs()
# The TranslatableAdmin can set form.language_code, because the modeladmin always creates a fresh subclass.
# If that would be done here, the original globally defined form class would be updated.
kwargs["_current_language"] = self.get_form_language()
return kwargs
# Backwards compatibility
# Make sure overriding get_current_language() affects get_form_language() too.
def get_form_language(self):
return self.get_current_language()
The tabs should look like this:
But now it looks like this:
If someone have a similiar exprience, feel free to write your opinion
In the settings.py file find PARLER_LANGUAGES settings and change None to SITE_ID.
PARLER_LANGUAGES = {
1: (
{'code': 'en', }, # English
{'code': 'ru', }, # Russian
),
'default': {
'fallbacks': ['en'],
'hide_untranslated': False,
}
}
Good day SO.
How to add message if PASSWORD_RESET_TIMEOUT is expired? I am using the django.contrib.auth PasswordResetView to get url link with token for Password Reset. Based on docs, I can add PASSWORD_RESET_TIMEOUT like this:
PASSWORD_RESET_TIMEOUT = 10
10 seconds just to test.
After 10 seconds pass, I tried to to refresh the page and user can still use the URL to access the PasswordResetConfirmView but can no longer change the password. Even using mismatched password, no response. How should I proceed with this?
For reference, this is my URL:
path('reset_password/', views.MyPasswordResetView.as_view(
template_name="../templates/logauth/reset_password.html",
subject_template_name='../templates/logauth/password_reset_subject.txt',
email_template_name='../templates/logauth/password_reset_email.html',
html_email_template_name='../templates/logauth/password_reset_email.html',
from_email=settings.EMAIL_HOST_USER,),
name="reset_password"),
path('reset_password_sent/', auth_views.PasswordResetDoneView.as_view(template_name="../templates/logauth/reset_password_sent.html"), name="password_reset_done"),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name="../templates/logauth/reset_password_2.html"), name="password_reset_confirm"),
path('reset_password_complete/', auth_views.PasswordResetCompleteView.as_view(template_name="../templates/logauth/reset_password_complete.html"), name="password_reset_complete"),
Form to alter my PasswordResetView:
class MyPasswordResetForm(PasswordResetForm):
username = forms.CharField(max_length=254)
field_order = ['username', 'email']
def __init__(self, *args, **kwargs):
super(MyPasswordResetForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs = {'class': 'user-input-form'}
View:
class MyPasswordResetView(PasswordResetView):
form_class = MyPasswordResetForm
def form_valid(self, form):
username = form.cleaned_data.get('username')
email = form.cleaned_data.get('email', '').lower()
try:
user = get_user_model().objects.get(username=username, email=email)
except(get_user_model().DoesNotExist):
user = None
if user is None:
return redirect('password_reset_done')
return super().form_valid(form)
Django provides in PasswordResetConfirmView class, a template context: validlink --> It is a Boolean, which returns True if the link (combination of uidb64 and token) is valid or unused yet.
Docs: https://docs.djangoproject.com/en/3.2/topics/auth/default/#django.contrib.auth.views.PasswordResetConfirmView
You can use this to do the logic inside the password_reset_confirm.html template like this:
{% if validlink %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Change Password</legend>
{{ form }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Change password</button>
</div>
</form>
</div>
{% else %}
<div class="alert alert-info mt-2">
The link you follewd has expired! To login or reset password again click
here
</div>
{% endif %}
I'm creating an app in which I'd like to use my own custom login form with a captcha field. My intention is to do this without using an external library (except for requests) but I couldn't add captcha field to my custom form in forms.py, so I added it directly to login.html but for some reason when I do form.is_valid() it returns an error.
I've already seen the solutions in Django - adding google recaptcha v2 to login form and Adding a Recaptcha form to my Django login page but as I said, I'd like to do this without using an external library.
views.py
...
def login_view(request):
if request.method == 'POST':
form = CustomLoginForm(request.POST)
result = is_recaptcha_valid(request)
print(result) # prints True
if form.is_valid():
username = form.cleaned_data['username']
email = form.cleaned_data['email']
password = form.cleaned_data['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# Redirect to index
messages.success(request, "Logged in.")
return HttpResponseRedirect(reverse('orders:index'))
else:
messages.error(request, "Invalid credentials.")
else:
print("error")
return render(request, 'registration/login.html', {'form': CustomLoginForm()})
else:
form = CustomLoginForm()
return render(request, 'registration/login.html', {'form': form})
forms.py
class CustomLoginForm(AuthenticationForm):
email = forms.EmailField(
error_messages={
'required': 'Please enter your email.',
'invalid': 'Enter a valid email address.'
},
help_text='Email',
)
login.html
<form class="" action="{% url 'orders:login' %}" method="post">
{% csrf_token %}
{% for field in form %}
<p>
{{ field.label_tag }}<br>
{{ field }}
{% if field.help_text %}
<small style="color: grey">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<p style="color: red">{{ error }}</p>
{% endfor %}
</p>
{% endfor %}
<!-- ReCAPTCHAV3 -->
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey='key-here'></div>
<button class="btn btn-success" type="submit" name="">Login</button>
<!-- <input type="hidden" name="next" value="{{ next }}"> -->
</form>
is_recaptcha_valid() function already returns True so I didn't share that. I'm a beginner in Django, so if you can please explain in two words what I've done wrong instead of just posting the answer, I'd be grateful. Thank you for your time.
The AuthenticationForm is slightly different than the others..
If your check AuthenticationForm class, AuthenticationForm 's first arguments is not data like others form:
class AuthenticationForm(forms.Form):
...
def __init__(self, request=None, *args, **kwargs):
...
Thats why you need to pass request.POST to data.
So update your code like this:
def login_view(request):
if request.method == 'POST':
form = CustomLoginForm(data=request.POST)
...
When I try to test my form with an email which has already been entered into the database it doesn't give the error message like it should, it redirects back to the homepage.
My views.py file looks like this:
from django.shortcuts import render
from .forms import LotteryForm
from django.http import HttpResponseRedirect
# Create your views here.
def lottery_list(request):
return render(request, 'lottery/lottery.html', {})
def lottery_new(request):
if request.method == 'POST':
form = LotteryForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('lottery_submitted')
else:
return render(request, 'lottery/lottery.html' {'form': LotteryForm()})
else:
form = LotteryForm()
return render(request, 'lottery/lottery.html', {'form': LotteryForm()})
My form is:
from django import forms
from django.db import models
from django.forms import ModelForm
from .models import Lottery
from .models import Lottery_user
from django.core.exceptions import ValidationError
class LotteryForm(forms.ModelForm):
lottery_type = forms.ModelChoiceField(queryset = Lottery.objects.all(), empty_label=None)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
lottery_numbers = forms.CharField(max_length=12, required=True)
email = forms.EmailField(required=True)
telephone = forms.CharField(max_length=18,
error_messages={'invalid':'Enter a valid mobile number',
'required':'Enter a valid mobile number'})
def clean_email(self):
email = self.cleaned_data['email']
if Lottery_user.objects.filter(email=email).exists():
raise ValidationError("Email already exists")
return email
if Lottery_user.objects.filter(email=self.cleaned_data['email']).exists():
raise ValidationError("You've already entered")
return email
class Meta:
model = Lottery_user
fields = ['lottery_numbers', 'lottery_type', 'first_name', 'last_name', 'email', 'telephone',]
Template form:
<form action="{% url 'lottery_new' %}" method="post">
{% csrf_token %}
<div class="fieldWrapper">
<label for="lotterytype">Choose a lottery:</label>
{{ form.lottery_type }}
<p>See <a href=lottery_instructions>instructions and rules</a></p>
</div>
<div class="fieldWrapper">
<label for="lotterynumbers">Lottery Numbers:</label>
{{ form.lottery_numbers }}
</div>
<div class="fieldWrapper">
<label for="firstname">First name:</label>
{{ form.first_name }}
</div>
<div class="fieldWrapper">
<label for="lastname">Last name:</label>
{{ form.last_name }}
</div>
<div class="fieldWrapper">
{{ form.email.errors }}
{{ form.email.non_field_errors }}
<label for="email">Email:</label>
{{ form.email }}
</div>
<div class="fieldWrapper">
<label for="phonenumber">Telephone Number:</label>
{{ form.telephone }}
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
I want a user to not be able to enter twice with the same email, so it should error when the user clicks submit with a used email address.
Also, does anyone know how to get the error messages to show before the user submits rather than afterwards?
You return a newly instantiated form when the form is invalid, when you should instead return the invalid form (with the errors). Try:
def lottery_new(request):
if request.method == 'POST':
form = LotteryForm(request.POST)
if form.is_valid():
....
else:
return render(request, 'lottery/lottery.html' {'form': form})
Hi guys I am new to Django.
I wants that when I login to my account there is a edit button which shows me a form of some fields which I can edit.
I am confused how the data is saved to the same user profile.
So can anybody tell me how is that possible.Can show me it with one example
With my profile.html I can see my profile and on click on edit button I can edit my profile
{% extends 'base.html' %}
{% block content %}
<p>User_id: {{ drinker.user_id }}
<p>Name: {{ drinker.name }}</p>
<p>Birthday: {{ drinker.birthday }}</p>
<p>first_name: {{ user.first_name }}</p>
<p>Users: {{ user.username }}</p>
<p>Edit Profile
{% endblock %}
Edit function
def Edit(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/login/')
drinker = request.user.get_profile()
context = {'drinker':drinker}
return render_to_response('edit.html', context, context_instance=RequestContext(request))
**Edit.html**
{% extends "base.html" %}
{% block extrahead %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" type="text/javascript"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js" type="text/javascript"></script>
<script>
$(function() {
$( "#birth" ).datepicker();
});
</script>
{% endblock %}
{% block content %}
<form action="/edit1/" method="post">
{% csrf_token %}
<div class="register_div">
<p><label for="name">Name:</label></p>
<p><input type="text" value="{{ drinker.name }}"></p>
</div>
<div class="register_div">
<p><label for="birthday">Birthday: {{ drinker.birthday }} </label></p>
<p>Choose new date of birth</p>
<p><input type="text" value="" id="birth"></p>
</div>
<p><input type="submit" value="submit" /></p>
</form>
{% endblock %}
On edit1 edit request function works
def EditRequest(request):
#if request.method == 'POST':
#form = UserProfileForm(request.POST, instance=user)
#if request.user.is_authenticated():
#return render_to_response('hgdhg')
if request.method == 'POST':
form = EditForm(request.POST)
if form.is_valid():
user=User.objects.create_user(usere_id=form.cleaned_data['user_id'])
#user.save()
drinker=user.get_profile()
drinker.name=form.cleaned_data['name']
drinker.birthday=form.cleaned_data['birthday']
drinker.save()
return HttpResponseRedirect('/profile/')
else:
return HttpResponseRedirect('/f/')
else:
return render_to_response('f')#,{'form':form} , context_instance=RequestContext(request))
this editrequest doesn't work ?
Here are the steps you need to execute to edit a user's profile:
Find out which user is logged in (read up on user authentication)
Check if the user has a profile or not; use the normal django query mechanism for that.
If the user has a profile; populate a ModelForm with the instance of the profile (see this page in the manual)
Display the form to the end user just like any other form.
When the user submits changes, do the normal form validation and save the object to the database.
Here is some code that does steps 1-4:
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from myapp.models import UserProfile
from myapp.forms import UserProfileForm
#login_required
def edit_profile(request):
try:
user_profile = UserProfile.objects.get(user=request.user)
except UserProfile.DoesNotExist:
# this user has no profile
return redirect('/error')
user_profile_form = UserProfileForm(instance=user_profile)
return render(request,'profile_edit.html',{'form':user_profile_form})
The UserProfileForm class:
from django import forms
from myapp.models import UserProfile
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
pass the instance of user along your model form
user = User.objects.get(user_name = username)
form = Registrationform(instance=user)
and render this form to your template
Example i did before:
#login_required
def lexuseditform(request,userpk):
if Adult.objects.filter(user=request.user).exists():
adult = Adult.objects.get(user=request.user) # load existing Adult
else:
adult = Adult(user=request.user) # create new Adult
if request.method == 'POST': # If the form has been submitted...
form = AdultForm(request.POST,instance=adult) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
form.save()
redirect_url = reverse('lexusedited',kwargs={'userpk': request.user.pk})
return HttpResponseRedirect(redirect_url) # Redirect after POST
else:
form = AdultForm(instance=adult) # An unbound form
return render(request,'lexus/lexuseditform.html', {'form': form})
#login_required
def lexusedited(request,userpk):
return render(request,'lexus/lexusedited.html')
Hope this helps...