diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index ab2d6763..53a75f3a 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -136,7 +136,8 @@ TEMPLATES = [ os.path.join(PROJECT_DIR, 'hosting/templates/'), os.path.join(PROJECT_DIR, 'ungleich/templates/djangocms_blog/'), os.path.join(PROJECT_DIR, 'ungleich/templates/cms/ungleichch'), - os.path.join(PROJECT_DIR, 'ungleich/templates/ungleich') + os.path.join(PROJECT_DIR, 'ungleich/templates/ungleich'), + os.path.join(PROJECT_DIR, 'ungleich_page/templates/ungleich_page') ], 'APP_DIRS': True, diff --git a/hosting/forms.py b/hosting/forms.py index 8007b2f4..2a4d67e3 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -21,6 +21,9 @@ class HostingOrderAdminForm(forms.ModelForm): raise forms.ValidationError("""You can't make a charge over a canceled virtual machine plan""") + if not customer: + raise forms.ValidationError("""You need select a costumer""") + # Make a charge to the customer stripe_utils = StripeUtils() charge_response = stripe_utils.make_charge(customer=customer.stripe_id, diff --git a/hosting/models.py b/hosting/models.py index 887f0777..099b7894 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.functional import cached_property + from Crypto.PublicKey import RSA from stored_messages.settings import stored_messages_settings @@ -160,7 +161,7 @@ class VirtualMachinePlan(models.Model): private_key, public_key = self.generate_RSA() self.public_key = public_key self.save(update_fields=['public_key']) - return private_key + return private_key, public_key def cancel_plan(self): self.status = self.CANCELED_STATUS diff --git a/hosting/test_forms.py b/hosting/test_forms.py new file mode 100644 index 00000000..e0f5df30 --- /dev/null +++ b/hosting/test_forms.py @@ -0,0 +1,116 @@ +from django.test import TestCase + +from unittest import mock +from model_mommy import mommy + +from .forms import HostingOrderAdminForm, HostingUserLoginForm, HostingUserSignupForm +from .models import VirtualMachinePlan + + +class HostingUserLoginFormTest(TestCase): + + def setUp(self): + password = 'user_password' + self.user = mommy.make('CustomUser') + + self.user.set_password(password) + self.user.save() + self.completed_data = { + 'email': self.user.email, + 'password': password + } + + self.incorrect_data = { + 'email': 'test', + } + + def test_valid_form(self): + form = HostingUserLoginForm(data=self.completed_data) + self.assertTrue(form.is_valid()) + + def test_invalid_form(self): + form = HostingUserLoginForm(data=self.incorrect_data) + self.assertFalse(form.is_valid()) + + +class HostingUserSignupFormTest(TestCase): + + def setUp(self): + + self.completed_data = { + 'name': 'test name', + 'email': 'test@ungleich.com', + 'password': 'test_password', + 'confirm_password': 'test_password' + } + + self.incorrect_data = { + 'email': 'test', + } + + def test_valid_form(self): + form = HostingUserSignupForm(data=self.completed_data) + self.assertTrue(form.is_valid()) + + def test_invalid_form(self): + form = HostingUserSignupForm(data=self.incorrect_data) + self.assertFalse(form.is_valid()) + + +class HostingOrderAdminFormTest(TestCase): + + def setUp(self): + + self.customer = mommy.make('StripeCustomer') + self.vm_plan = mommy.make('VirtualMachinePlan') + self.vm_canceled_plan = mommy.make('VirtualMachinePlan', + status=VirtualMachinePlan.CANCELED_STATUS) + + self.mocked_charge = { + 'amount': 5100, + 'amount_refunded': 0, + 'balance_transaction': 'txn_18U99zGjsLAXdRPzUJKkBx3Q', + 'captured': True, + 'created': 1467785123, + 'currency': 'chf', + 'customer': 'cus_8V61MvJvMd0PhM', + 'status': 'succeeded' + } + + self.completed_data = { + 'customer': self.customer.id, + 'vm_plan': self.vm_plan.id, + } + + self.incompleted_data = { + 'vm_plan': self.vm_plan.id, + 'customer': None + } + + @mock.patch('utils.stripe_utils.StripeUtils.make_charge') + def test_valid_form(self, stripe_mocked_call): + stripe_mocked_call.return_value = { + 'paid': True, + 'response_object': self.mocked_charge, + 'error': None + } + form = HostingOrderAdminForm(data=self.completed_data) + self.assertTrue(form.is_valid()) + + @mock.patch('utils.stripe_utils.StripeUtils.make_charge') + def test_invalid_form_canceled_vm(self, stripe_mocked_call): + + self.completed_data.update({ + 'vm_plan': self.vm_canceled_plan.id + }) + stripe_mocked_call.return_value = { + 'paid': True, + 'response_object': self.mocked_charge, + 'error': None + } + form = HostingOrderAdminForm(data=self.completed_data) + self.assertFalse(form.is_valid()) + + def test_invalid_form(self): + form = HostingOrderAdminForm(data=self.incompleted_data) + self.assertFalse(form.is_valid()) diff --git a/hosting/test_views.py b/hosting/test_views.py index b2deb6d7..944cabcb 100644 --- a/hosting/test_views.py +++ b/hosting/test_views.py @@ -3,15 +3,20 @@ from django.conf import settings from django.test import TestCase, RequestFactory from django.core.urlresolvers import reverse from django.core.urlresolvers import resolve +from django.contrib.auth.tokens import default_token_generator +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode +from django.utils.encoding import force_bytes + from model_mommy import mommy +from stored_messages.models import Inbox from membership.models import CustomUser, StripeCustomer from .models import VirtualMachineType, HostingOrder, VirtualMachinePlan from .views import DjangoHostingView, RailsHostingView, NodeJSHostingView, LoginView, SignupView, \ PaymentVMView, OrdersHostingDetailView, OrdersHostingListView, VirtualMachineView, \ - VirtualMachinesPlanListView + VirtualMachinesPlanListView, PasswordResetView, PasswordResetConfirmView from utils.tests import BaseTestCase @@ -40,9 +45,12 @@ class DjangoHostingViewTest(TestCase, ProcessVMSelectionTestMixin): self.url = reverse('django.hosting') self.view = DjangoHostingView() self.expected_template = 'hosting/django.html' + HOSTING = 'django' + configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING) self.expected_context = { - 'hosting': "django", + 'hosting': HOSTING, 'hosting_long': "Django", + 'configuration_detail': configuration_detail, 'domain': "django-hosting.ch", 'google_analytics': "UA-62285904-6", 'email': "info@django-hosting.ch", @@ -56,9 +64,12 @@ class RailsHostingViewTest(TestCase, ProcessVMSelectionTestMixin): self.url = reverse('rails.hosting') self.view = RailsHostingView() self.expected_template = 'hosting/rails.html' + HOSTING = 'rails' + configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING) self.expected_context = { - 'hosting': "rails", + 'hosting': HOSTING, 'hosting_long': "Ruby On Rails", + 'configuration_detail': configuration_detail, 'domain': "rails-hosting.ch", 'google_analytics': "UA-62285904-5", 'email': "info@rails-hosting.ch", @@ -72,9 +83,12 @@ class NodeJSHostingViewTest(TestCase, ProcessVMSelectionTestMixin): self.url = reverse('node.hosting') self.view = NodeJSHostingView() self.expected_template = 'hosting/nodejs.html' + HOSTING = 'nodejs' + configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING) self.expected_context = { - 'hosting': "nodejs", + 'hosting': HOSTING, 'hosting_long': "NodeJS", + 'configuration_detail': configuration_detail, 'domain': "node-hosting.ch", 'google_analytics': "UA-62285904-7", 'email': "info@node-hosting.ch", @@ -115,6 +129,7 @@ class PaymentVMViewTest(BaseTestCase): 'memory': 10, 'disk_size': 10000, 'price': 22000, + 'configuration': dict(VirtualMachinePlan.VM_CONFIGURATION).get('django') } } @@ -153,7 +168,8 @@ class PaymentVMViewTest(BaseTestCase): 'memory': hosting_order.vm_plan.memory, 'disk_size': hosting_order.vm_plan.disk_size, 'price': hosting_order.vm_plan.price, - 'hosting_company': hosting_order.vm_plan.vm_type.hosting_company + 'hosting_company': hosting_order.vm_plan.vm_type.hosting_company, + 'configuration': hosting_order.vm_plan.configuration } self.assertEqual(vm_plan, self.session_data.get('vm_specs')) @@ -172,6 +188,92 @@ class PaymentVMViewTest(BaseTestCase): settings.STRIPE_API_PUBLIC_KEY) +class NotificationsViewTest(BaseTestCase): + + def setUp(self): + super(NotificationsViewTest, self).setUp() + + self.url = reverse('hosting:notifications') + self.expected_template = 'hosting/notifications.html' + + self.inboxes = mommy.make(Inbox, user=self.customer, _quantity=2) + self.messages = list(map(lambda x: x.message, self.inboxes)) + + def test_get(self): + + # Anonymous user should get redirect to login + response = self.client.get(self.url) + expected_url = "%s?next=%s" % (reverse('hosting:login'), reverse('hosting:notifications')) + self.assertRedirects(response, expected_url=expected_url, + status_code=302, target_status_code=200) + + # Logged user should get the page + response = self.customer_client.get(self.url, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['all_notifications'], self.messages) + self.assertTemplateUsed(response, self.expected_template) + + +class MarkAsReadNotificationViewTest(BaseTestCase): + + def setUp(self): + super(MarkAsReadNotificationViewTest, self).setUp() + + self.url = reverse('hosting:notifications') + self.expected_template = 'hosting/notifications.html' + + self.inbox = mommy.make(Inbox, user=self.customer) + self.message = self.inbox.message + + self.url = reverse('hosting:read_notification', kwargs={'pk': self.message.id}) + + def test_post(self): + + # Anonymous user should get redirect to login + response = self.client.get(self.url) + expected_url = "%s?next=%s" % (reverse('hosting:login'), + reverse('hosting:read_notification', + kwargs={'pk': self.message.id})) + self.assertRedirects(response, expected_url=expected_url, + status_code=302, target_status_code=200) + + # Logged user should mark a message as read + response = self.customer_client.post(self.url, follow=True) + self.assertEqual(response.status_code, 200) + self.assertFalse(Inbox.objects.filter(user=self.customer).exists()) + self.assertTemplateUsed(response, self.expected_template) + + +class GenerateVMSSHKeysViewTest(BaseTestCase): + + def setUp(self): + super(GenerateVMSSHKeysViewTest, self).setUp() + + self.vm = mommy.make(VirtualMachinePlan) + self.expected_template = 'hosting/virtual_machine_key.html' + self.url = reverse('hosting:virtual_machine_key', kwargs={'pk': self.vm.id}) + + def test_get(self): + + # Anonymous user should get redirect to login + response = self.client.get(self.url) + expected_url = "%s?next=%s" % (reverse('hosting:login'), + reverse('hosting:virtual_machine_key', + kwargs={'pk': self.vm.id})) + self.assertRedirects(response, expected_url=expected_url, + status_code=302, target_status_code=200) + + # Logged user should get the page + response = self.customer_client.get(self.url, follow=True) + self.assertEqual(response.status_code, 200) + updated_vm = VirtualMachinePlan.objects.get(id=self.vm.id) + self.assertEqual(response.context['public_key'].decode("utf-8"), updated_vm.public_key) + self.assertTrue(response.context['private_key'] is not None) + self.assertEqual(len(response.context['public_key']), 380) + self.assertTrue(len(response.context['private_key']) is 1678 or 1674) + self.assertTemplateUsed(response, self.expected_template) + + class VirtualMachineViewTest(BaseTestCase): def setUp(self): @@ -357,3 +459,78 @@ class SignupViewTest(TestCase): self.user = CustomUser.objects.get(email=self.signup_data.get('email')) self.assertEqual(response.context['user'], self.user) self.assertEqual(response.status_code, 200) + + +class PasswordResetViewTest(BaseTestCase): + + def setUp(self): + super(PasswordResetViewTest, self).setUp() + + self.url = reverse('hosting:reset_password') + self.view = PasswordResetView + self.expected_template = 'hosting/reset_password.html' + self.user = mommy.make('membership.CustomUser') + self.password = 'fake_password' + self.user.set_password(self.password) + self.user.save() + + self.post_data = { + 'email': self.user.email + } + + def test_url_resolve_to_view_correctly(self): + found = resolve(self.url) + self.assertEqual(found.func.__name__, self.view.__name__) + + def test_get(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, self.expected_template) + + def test_post(self): + response = self.client.post(self.url, data=self.post_data, follow=True) + self.assertEqual(response.status_code, 200) + + def test_test_generate_email_context(self): + context = self.setup_view(self.view()).\ + test_generate_email_context(self.user) + self.assertEqual(context.get('user'), self.user) + self.assertEqual(context.get('site_name'), 'ungleich') + self.assertEqual(len(context.get('token')), 24) + + +class PasswordResetConfirmViewTest(BaseTestCase): + + def setUp(self): + super(PasswordResetConfirmViewTest, self).setUp() + + self.view = PasswordResetConfirmView + self.expected_template = 'hosting/confirm_reset_password.html' + self.user = mommy.make('membership.CustomUser') + self.password = 'fake_password' + self.user.set_password(self.password) + self.user.save() + + self.token = default_token_generator.make_token(self.user) + self.uid = urlsafe_base64_encode(force_bytes(self.user.pk)) + self.url = reverse('hosting:reset_password_confirm', + kwargs={'token': self.token, 'uidb64': self.uid}) + + self.post_data = { + 'new_password1': 'new_password', + 'new_password2': 'new_password' + } + + def test_url_resolve_to_view_correctly(self): + found = resolve(self.url) + self.assertEqual(found.func.__name__, self.view.__name__) + + def test_get(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, self.expected_template) + + def test_post(self): + response = self.client.post(self.url, data=self.post_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertTrue(not response.context['form'].errors) diff --git a/hosting/views.py b/hosting/views.py index c66068d1..18d8819e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -195,13 +195,7 @@ class PasswordResetView(FormView): 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) - + def test_generate_email_context(self, user): context = { 'user': user, 'token': default_token_generator.make_token(user), @@ -210,6 +204,16 @@ class PasswordResetView(FormView): 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()) } + return context + + 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 = self.test_generate_email_context(user) email_data = { 'subject': 'Password Reset', 'to': email, @@ -246,15 +250,18 @@ class PasswordResetConfirmView(FormView): return self.form_valid(form) else: messages.error(request, 'Password reset has not been unsuccessful.') + form.add_error(None, 'Password reset has not been unsuccessful.') return self.form_invalid(form) else: messages.error(request, 'The reset password link is no longer valid.') + form.add_error(None, 'Password reset has not been unsuccessful.') return self.form_invalid(form) -class NotificationsView(TemplateView): +class NotificationsView(LoginRequiredMixin, TemplateView): template_name = 'hosting/notifications.html' + login_url = reverse_lazy('hosting:login') def get_context_data(self, **kwargs): context = super(NotificationsView, self).get_context_data(**kwargs) @@ -271,6 +278,7 @@ class NotificationsView(TemplateView): class MarkAsReadNotificationView(LoginRequiredMixin, UpdateView): model = Message success_url = reverse_lazy('hosting:notifications') + login_url = reverse_lazy('hosting:login') fields = '__all__' def post(self, *args, **kwargs): @@ -285,6 +293,7 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView): model = VirtualMachinePlan template_name = 'hosting/virtual_machine_key.html' success_url = reverse_lazy('hosting:orders') + login_url = reverse_lazy('hosting:login') context_object_name = "virtual_machine" def get_context_data(self, **kwargs): @@ -292,9 +301,10 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView): context = super(GenerateVMSSHKeysView, self).get_context_data(**kwargs) vm = self.get_object() if not vm.public_key: - private_key = vm.generate_keys() + private_key, public_key = vm.generate_keys() context.update({ - 'private_key': private_key + 'private_key': private_key, + 'public_key': public_key }) return context return context diff --git a/ungleich_page/static/ungleich_page/css/404.css b/ungleich_page/static/ungleich_page/css/404.css new file mode 100644 index 00000000..2528973d --- /dev/null +++ b/ungleich_page/static/ungleich_page/css/404.css @@ -0,0 +1,28 @@ +.error { + margin: 0 auto; + text-align: center; +} + +.error-code { + bottom: 60%; + color: #2d353c; + font-size: 96px; + line-height: 100px; +} + +.error-desc { + font-size: 12px; + color: #647788; +} + +.m-b-10 { + margin-bottom: 10px!important; +} + +.m-b-20 { + margin-bottom: 20px!important; +} + +.m-t-20 { + margin-top: 20px!important; +} diff --git a/ungleich_page/templates/ungleich_page/404.html b/ungleich_page/templates/ungleich_page/404.html new file mode 100644 index 00000000..6b6caf70 --- /dev/null +++ b/ungleich_page/templates/ungleich_page/404.html @@ -0,0 +1,28 @@ +{% load staticfiles bootstrap3%} + + + + + + 404 | ungleich + + + +
+
404
+

We couldn't find the page..

+ +
+ Sorry, but the page you are looking for was either not found or does not exist.
+ Try refreshing the page or click the button below to go back to the Homepage. +
+ +
+
+
+ + + diff --git a/utils/forms.py b/utils/forms.py index 7af6f99a..e87d3579 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -48,7 +48,6 @@ class SetPasswordForm(forms.Form): return password2 - class BillingAddressForm(forms.ModelForm): token = forms.CharField(widget=forms.HiddenInput()) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index fb0a328e..eb17ea09 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -82,7 +82,6 @@ class StripeUtils(object): ) return customer - @handleStripeError def make_charge(self, amount=None, customer=None): amount = int(amount * 100) # stripe amount unit, in cents diff --git a/utils/test_forms.py b/utils/test_forms.py index dc6d9fcc..46285fc5 100644 --- a/utils/test_forms.py +++ b/utils/test_forms.py @@ -1,5 +1,51 @@ from django.test import TestCase -from .forms import ContactUsForm, BillingAddressForm +from .forms import ContactUsForm, BillingAddressForm, PasswordResetRequestForm,\ + SetPasswordForm + +from model_mommy import mommy + + +class PasswordResetRequestFormTest(TestCase): + + def setUp(self): + self.user = mommy.make('CustomUser') + self.completed_data = { + 'email': self.user.email, + } + + self.incorrect_data = { + 'email': 'test', + } + + def test_valid_form(self): + form = PasswordResetRequestForm(data=self.completed_data) + self.assertTrue(form.is_valid()) + + def test_invalid_form(self): + form = PasswordResetRequestForm(data=self.incorrect_data) + self.assertFalse(form.is_valid()) + + +class SetPasswordFormTest(TestCase): + + def setUp(self): + # self.user = mommy.make('CustomUser') + self.completed_data = { + 'new_password1': 'new_password', + 'new_password2': 'new_password', + } + + self.incorrect_data = { + 'email': 'test', + } + + def test_valid_form(self): + form = SetPasswordForm(data=self.completed_data) + self.assertTrue(form.is_valid()) + + def test_invalid_form(self): + form = SetPasswordForm(data=self.incorrect_data) + self.assertFalse(form.is_valid()) class ContactUsFormTest(TestCase): diff --git a/utils/tests.py b/utils/tests.py index 83b87a85..42831050 100644 --- a/utils/tests.py +++ b/utils/tests.py @@ -1,5 +1,7 @@ from django.test import TestCase from django.test import Client +from django.http.request import HttpRequest + from model_mommy import mommy @@ -28,6 +30,11 @@ class BaseTestCase(TestCase): self.customer_client = self.get_client(self.customer) self.another_customer_client = self.get_client(self.another_customer) + # Request Object + self.request = HttpRequest() + self.request.META['SERVER_NAME'] = 'ungleich.com' + self.request.META['SERVER_PORT'] = '80' + def get_client(self, user): """ Authenticate a user and return the client @@ -64,3 +71,14 @@ class BaseTestCase(TestCase): }] } } + + def setup_view(self, view, *args, **kwargs): + """Mimic as_view() returned callable, but returns view instance. + + args and kwargs are the same you would pass to ``reverse()`` + + """ + view.request = self.request + view.args = args + view.kwargs = kwargs + return view