Created reset password email, Added forgot password view, Added forgot password form, Added set new password form, Added set new password view, fixed signup response issue, fixed main menu ungleich button redirect to django-hosting
This commit is contained in:
parent
d7fc64258a
commit
d8150b6593
13 changed files with 235 additions and 20 deletions
|
@ -1,3 +0,0 @@
|
||||||
* * * * * (cd /home/app/app/; /usr/bin/python3 manage.py send_mail)
|
|
||||||
0,20,40 * * * * (cd /home/app/app/; /usr/bin/python3 manage.py retry_deferred)
|
|
||||||
0 0 * * * (cd /home/app/app/; /usr/bin/python3 manage.py purge_mail_log 7)
|
|
|
@ -4,7 +4,7 @@ from django.contrib.auth import authenticate
|
||||||
|
|
||||||
from utils.stripe_utils import StripeUtils
|
from utils.stripe_utils import StripeUtils
|
||||||
|
|
||||||
from .models import HostingOrder
|
from .models import HostingOrder, VirtualMachinePlan
|
||||||
|
|
||||||
|
|
||||||
class HostingOrderAdminForm(forms.ModelForm):
|
class HostingOrderAdminForm(forms.ModelForm):
|
||||||
|
@ -17,6 +17,10 @@ class HostingOrderAdminForm(forms.ModelForm):
|
||||||
customer = self.cleaned_data.get('customer')
|
customer = self.cleaned_data.get('customer')
|
||||||
vm_plan = self.cleaned_data.get('vm_plan')
|
vm_plan = self.cleaned_data.get('vm_plan')
|
||||||
|
|
||||||
|
if vm_plan.status == VirtualMachinePlan.CANCELED_STATUS:
|
||||||
|
raise forms.ValidationError("""You can't make a charge over
|
||||||
|
a canceled virtual machine plan""")
|
||||||
|
|
||||||
# Make a charge to the customer
|
# Make a charge to the customer
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
charge_response = stripe_utils.make_charge(customer=customer.stripe_id,
|
charge_response = stripe_utils.make_charge(customer=customer.stripe_id,
|
||||||
|
@ -53,7 +57,7 @@ class HostingUserLoginForm(forms.Form):
|
||||||
CustomUser.objects.get(email=email)
|
CustomUser.objects.get(email=email)
|
||||||
return email
|
return email
|
||||||
except CustomUser.DoesNotExist:
|
except CustomUser.DoesNotExist:
|
||||||
raise forms.ValidationError("User does not exists")
|
raise forms.ValidationError("User does not exist")
|
||||||
else:
|
else:
|
||||||
return email
|
return email
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,12 @@ h6 {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.intro-reset-password {
|
||||||
|
background: url(../img/signup-bg.png) no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.intro-message > h1 {
|
.intro-message > h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
13
hosting/templates/emails/password_reset_email.html
Normal file
13
hosting/templates/emails/password_reset_email.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% load i18n %}{% autoescape off %}
|
||||||
|
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||||
|
|
||||||
|
{% trans "Please go to the following page and choose a new password:" %}
|
||||||
|
{% block reset_link %}
|
||||||
|
{{ base_url }}{% url 'hosting:reset_password_confirm' uidb64=uid token=token %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% trans "Thanks for using our site!" %}
|
||||||
|
|
||||||
|
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||||
|
|
||||||
|
{% endautoescape %}
|
13
hosting/templates/emails/password_reset_email.txt
Normal file
13
hosting/templates/emails/password_reset_email.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% load i18n %}{% autoescape off %}
|
||||||
|
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||||
|
|
||||||
|
{% trans "Please go to the following page and choose a new password:" %}
|
||||||
|
{% block reset_link %}
|
||||||
|
{{ base_url }}{% url 'hosting:reset_password_confirm' uidb64=uid token=token %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% trans "Thanks for using our site!" %}
|
||||||
|
|
||||||
|
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||||
|
|
||||||
|
{% endautoescape %}
|
|
@ -53,7 +53,7 @@
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand topnav" href="{% url 'ungleich_page:landing' %}"><img src="{% static 'hosting/img/logo_black.svg' %}"></a>
|
<a class="navbar-brand topnav" href="{{ request.session.hosting_url}}"><img src="{% static 'hosting/img/logo_black.svg' %}"></a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
|
|
38
hosting/templates/hosting/confirm_reset_password.html
Normal file
38
hosting/templates/hosting/confirm_reset_password.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends "hosting/base_short.html" %}
|
||||||
|
{% load staticfiles bootstrap3%}
|
||||||
|
{% block content %}
|
||||||
|
<div class="intro-auth intro-reset-password">
|
||||||
|
<div class="container">
|
||||||
|
<div class="col-md-4"> </div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="intro-message">
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
{% for message in messages %}
|
||||||
|
<li>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h2 class="section-heading">Set your new password</h2>
|
||||||
|
|
||||||
|
<form action="" method="post" class="form" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
{% bootstrap_field field show_label=False %}
|
||||||
|
{% endfor %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-default">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
<span>Already have an account ? <a class="unlink" href="{% url 'hosting:login' %}">Log in</a></span>
|
||||||
|
<ul class="list-inline intro-social-buttons">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -6,6 +6,14 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="col-md-4 col-md-offset-4">
|
<div class="col-md-4 col-md-offset-4">
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
{% for message in messages %}
|
||||||
|
<li>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% block messages %}
|
{% block messages %}
|
||||||
{% if request.GET.logged_out %}
|
{% if request.GET.logged_out %}
|
||||||
<div class="alert"> <!-- singular -->
|
<div class="alert"> <!-- singular -->
|
||||||
|
@ -31,6 +39,8 @@
|
||||||
{% endbuttons %}
|
{% endbuttons %}
|
||||||
</form>
|
</form>
|
||||||
<span>Don't have an account yet ? <a class="unlink" href="{% url 'hosting:signup' %}">Sign up</a></span>
|
<span>Don't have an account yet ? <a class="unlink" href="{% url 'hosting:signup' %}">Sign up</a></span>
|
||||||
|
<br/>
|
||||||
|
<span> <a class="unlink" href="{% url 'hosting:reset_password' %}">Forgot your password ?</a></span>
|
||||||
|
|
||||||
<ul class="list-inline intro-social-buttons">
|
<ul class="list-inline intro-social-buttons">
|
||||||
|
|
||||||
|
|
29
hosting/templates/hosting/reset_password.html
Normal file
29
hosting/templates/hosting/reset_password.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "hosting/base_short.html" %}
|
||||||
|
{% load staticfiles bootstrap3%}
|
||||||
|
{% block content %}
|
||||||
|
<div class="intro-auth intro-reset-password">
|
||||||
|
<div class="container">
|
||||||
|
<div class="col-md-4"> </div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="intro-message">
|
||||||
|
<h2 class="section-heading">Reset your password</h2>
|
||||||
|
|
||||||
|
<form action="{% url 'hosting:reset_password' %}" method="post" class="form" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
{% bootstrap_field field show_label=False %}
|
||||||
|
{% endfor %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-default">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
<span>Already have an account ? <a class="unlink" href="{% url 'hosting:login' %}">Log in</a></span>
|
||||||
|
<ul class="list-inline intro-social-buttons">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -3,8 +3,8 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="intro-auth intro-signup">
|
<div class="intro-auth intro-signup">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="col-md-4"> </div>
|
<div class="col-md-4 col-sm-4 col-xs-4"> </div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4 col-sm-6 col-xs-6">
|
||||||
<div class="intro-message">
|
<div class="intro-message">
|
||||||
<h2 class="section-heading">Sign up</h2>
|
<h2 class="section-heading">Sign up</h2>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from .views import DjangoHostingView, RailsHostingView, PaymentVMView,\
|
||||||
NodeJSHostingView, LoginView, SignupView, IndexView, \
|
NodeJSHostingView, LoginView, SignupView, IndexView, \
|
||||||
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
|
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
|
||||||
VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
|
VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
|
||||||
MarkAsReadNotificationView
|
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'index/?$', IndexView.as_view(), name='index'),
|
url(r'index/?$', IndexView.as_view(), name='index'),
|
||||||
|
@ -27,6 +27,9 @@ urlpatterns = [
|
||||||
name='read_notification'),
|
name='read_notification'),
|
||||||
url(r'login/?$', LoginView.as_view(), name='login'),
|
url(r'login/?$', LoginView.as_view(), name='login'),
|
||||||
url(r'signup/?$', SignupView.as_view(), name='signup'),
|
url(r'signup/?$', SignupView.as_view(), name='signup'),
|
||||||
|
url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'),
|
||||||
|
url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
|
||||||
|
PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
|
||||||
url(r'^logout/?$', 'django.contrib.auth.views.logout',
|
url(r'^logout/?$', 'django.contrib.auth.views.logout',
|
||||||
{'next_page': '/hosting/login?logged_out=true'}, name='logout')
|
{'next_page': '/hosting/login?logged_out=true'}, name='logout')
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,6 +4,10 @@ from django.core.urlresolvers import reverse_lazy, reverse
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic import View, CreateView, FormView, ListView, DetailView,\
|
from django.views.generic import View, CreateView, FormView, ListView, DetailView,\
|
||||||
DeleteView, TemplateView, UpdateView
|
DeleteView, TemplateView, UpdateView
|
||||||
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
|
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.utils.encoding import force_bytes
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -16,7 +20,7 @@ from stored_messages.api import mark_read
|
||||||
|
|
||||||
from membership.models import CustomUser, StripeCustomer
|
from membership.models import CustomUser, StripeCustomer
|
||||||
from utils.stripe_utils import StripeUtils
|
from utils.stripe_utils import StripeUtils
|
||||||
from utils.forms import BillingAddressForm
|
from utils.forms import BillingAddressForm, PasswordResetRequestForm, SetPasswordForm
|
||||||
from utils.mailer import BaseEmail
|
from utils.mailer import BaseEmail
|
||||||
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
|
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
|
||||||
from .forms import HostingUserSignupForm, HostingUserLoginForm
|
from .forms import HostingUserSignupForm, HostingUserLoginForm
|
||||||
|
@ -164,6 +168,71 @@ class SignupView(CreateView):
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetView(FormView):
|
||||||
|
template_name = 'hosting/reset_password.html'
|
||||||
|
form_class = PasswordResetRequestForm
|
||||||
|
success_message = "The link to reset your email has been sent to your email"
|
||||||
|
success_url = reverse_lazy('hosting:login')
|
||||||
|
# form_valid_message = 'Thank you for registering'
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
|
||||||
|
email = form.cleaned_data.get('email')
|
||||||
|
user = CustomUser.objects.get(email=email)
|
||||||
|
|
||||||
|
messages.add_message(self.request, messages.SUCCESS, self.success_message)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'user': user,
|
||||||
|
'token': default_token_generator.make_token(user),
|
||||||
|
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
|
||||||
|
'site_name': 'ungleich',
|
||||||
|
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host())
|
||||||
|
|
||||||
|
}
|
||||||
|
email_data = {
|
||||||
|
'subject': 'Password Reset',
|
||||||
|
'to': email,
|
||||||
|
'context': context,
|
||||||
|
'template_name': 'password_reset_email',
|
||||||
|
'template_path': 'emails/'
|
||||||
|
}
|
||||||
|
email = BaseEmail(**email_data)
|
||||||
|
email.send()
|
||||||
|
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetConfirmView(FormView):
|
||||||
|
template_name = 'hosting/confirm_reset_password.html'
|
||||||
|
form_class = SetPasswordForm
|
||||||
|
success_url = reverse_lazy('hosting:login')
|
||||||
|
|
||||||
|
def post(self, request, uidb64=None, token=None, *arg, **kwargs):
|
||||||
|
try:
|
||||||
|
uid = urlsafe_base64_decode(uidb64)
|
||||||
|
user = CustomUser.objects.get(pk=uid)
|
||||||
|
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
|
||||||
|
user = None
|
||||||
|
|
||||||
|
form = self.form_class(request.POST)
|
||||||
|
|
||||||
|
if user is not None and default_token_generator.check_token(user, token):
|
||||||
|
if form.is_valid():
|
||||||
|
new_password = form.cleaned_data['new_password2']
|
||||||
|
user.set_password(new_password)
|
||||||
|
user.save()
|
||||||
|
messages.success(request, 'Password has been reset.')
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
messages.error(request, 'Password reset has not been unsuccessful.')
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
else:
|
||||||
|
messages.error(request, 'The reset password link is no longer valid.')
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
class NotificationsView(TemplateView):
|
class NotificationsView(TemplateView):
|
||||||
template_name = 'hosting/notifications.html'
|
template_name = 'hosting/notifications.html'
|
||||||
|
|
||||||
|
@ -286,16 +355,6 @@ class PaymentVMView(LoginRequiredMixin, FormView):
|
||||||
# Send notification to ungleich as soon as VM has been booked
|
# Send notification to ungleich as soon as VM has been booked
|
||||||
# TODO send email using celery
|
# TODO send email using celery
|
||||||
|
|
||||||
from django.core.mail import send_mail
|
|
||||||
|
|
||||||
send_mail(
|
|
||||||
'Subject here',
|
|
||||||
'Here is the message.',
|
|
||||||
'levinoelvm@gmail.com',
|
|
||||||
['levinoelvm@gmail.com'],
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'vm': plan,
|
'vm': plan,
|
||||||
'order': order,
|
'order': order,
|
||||||
|
|
|
@ -3,9 +3,52 @@ from .models import ContactMessage, BillingAddress
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from membership.models import CustomUser
|
||||||
# from utils.fields import CountryField
|
# from utils.fields import CountryField
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetRequestForm(forms.Form):
|
||||||
|
email = forms.CharField(widget=forms.EmailInput())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['email']
|
||||||
|
|
||||||
|
def clean_email(self):
|
||||||
|
email = self.cleaned_data.get('email')
|
||||||
|
try:
|
||||||
|
CustomUser.objects.get(email=email)
|
||||||
|
return email
|
||||||
|
except CustomUser.DoesNotExist:
|
||||||
|
raise forms.ValidationError("User does not exist")
|
||||||
|
else:
|
||||||
|
return email
|
||||||
|
|
||||||
|
|
||||||
|
class SetPasswordForm(forms.Form):
|
||||||
|
"""
|
||||||
|
A form that lets a user change set their password without entering the old
|
||||||
|
password
|
||||||
|
"""
|
||||||
|
error_messages = {
|
||||||
|
'password_mismatch': ("The two password fields didn't match."),
|
||||||
|
}
|
||||||
|
new_password1 = forms.CharField(label=("New password"),
|
||||||
|
widget=forms.PasswordInput)
|
||||||
|
new_password2 = forms.CharField(label=("New password confirmation"),
|
||||||
|
widget=forms.PasswordInput)
|
||||||
|
|
||||||
|
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(
|
||||||
|
self.error_messages['password_mismatch'],
|
||||||
|
code='password_mismatch',)
|
||||||
|
return password2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BillingAddressForm(forms.ModelForm):
|
class BillingAddressForm(forms.ModelForm):
|
||||||
token = forms.CharField(widget=forms.HiddenInput())
|
token = forms.CharField(widget=forms.HiddenInput())
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue