Merge branch 'develop' into feature/hosting_permission

This commit is contained in:
Levi 2016-07-09 17:43:28 -05:00
commit f024841c4e
24 changed files with 638 additions and 61 deletions

View file

@ -80,9 +80,8 @@ def blog(request):
def blog_detail(request, slug): def blog_detail(request, slug):
# post = Post.objects.filter_by_language(get_language()).filter(slug=slug).first() # post = Post.objects.filter_by_language(get_language()).filter(slug=slug).first()
language = 'en-us' # currently nothing is translated to german so we give then en
post = Post.objects.translated(language, slug=slug).first() post = Post.objects.translated(get_language(), slug=slug).first()
context = { context = {
'post': post, 'post': post,
} }

View file

@ -137,7 +137,8 @@ TEMPLATES = [
os.path.join(PROJECT_DIR, 'hosting/templates/'), os.path.join(PROJECT_DIR, 'hosting/templates/'),
os.path.join(PROJECT_DIR, 'ungleich/templates/djangocms_blog/'), 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/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, 'APP_DIRS': True,

View file

@ -7,9 +7,10 @@ from django.conf.urls.static import static
from django.conf import settings from django.conf import settings
from hosting.views import RailsHostingView, DjangoHostingView, NodeJSHostingView from hosting.views import RailsHostingView, DjangoHostingView, NodeJSHostingView
from membership import urls as membership_urls from membership import urls as membership_urls
from ungleich_page.views import LandingView
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [ url(r'^index.html$', LandingView.as_view()),
url(r'^hosting/', include('hosting.urls', namespace="hosting")), url(r'^hosting/', include('hosting.urls', namespace="hosting")),
url(r'^railshosting/', RailsHostingView.as_view(), name="rails.hosting"), url(r'^railshosting/', RailsHostingView.as_view(), name="rails.hosting"),
url(r'^nodehosting/', NodeJSHostingView.as_view(), name="node.hosting"), url(r'^nodehosting/', NodeJSHostingView.as_view(), name="node.hosting"),
@ -21,12 +22,13 @@ urlpatterns = [
# note the django CMS URLs included via i18n_patterns # note the django CMS URLs included via i18n_patterns
urlpatterns += i18n_patterns('', urlpatterns += i18n_patterns('',
url(r'^/?$', LandingView.as_view()),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
url(r'^digitalglarus/login/', include(membership_urls)), url(r'^digitalglarus/login/', include(membership_urls)),
url(r'^digitalglarus/', include('digitalglarus.urls', url(r'^digitalglarus/', include('digitalglarus.urls',
namespace="digitalglarus")), namespace="digitalglarus")),
# url(r'^blog/', include('ungleich.urls', namespace='ungleich')), #url(r'^blog/', include('ungleich.urls', namespace='ungleich')),
url(r'^ungleich_page/', url(r'^',
include('ungleich_page.urls', namespace='ungleich_page'), include('ungleich_page.urls', namespace='ungleich_page'),
name='ungleich_page'), name='ungleich_page'),
url(r'^blog/', include('ungleich.urls', namespace='ungleich')), url(r'^blog/', include('ungleich.urls', namespace='ungleich')),

View file

@ -21,6 +21,9 @@ class HostingOrderAdminForm(forms.ModelForm):
raise forms.ValidationError("""You can't make a charge over raise forms.ValidationError("""You can't make a charge over
a canceled virtual machine plan""") a canceled virtual machine plan""")
if not customer:
raise forms.ValidationError("""You need select a costumer""")
# 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,

View file

@ -1,18 +1,21 @@
from django.shortcuts import redirect from django.shortcuts import redirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from .models import VirtualMachinePlan
class ProcessVMSelectionMixin(object): class ProcessVMSelectionMixin(object):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
hosting = request.POST.get('configuration')
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(hosting)
vm_specs = { vm_specs = {
'cores': request.POST.get('cores'), 'cores': request.POST.get('cores'),
'memory': request.POST.get('memory'), 'memory': request.POST.get('memory'),
'disk_size': request.POST.get('disk_space'), 'disk_size': request.POST.get('disk_space'),
'hosting_company': request.POST.get('hosting_company'), 'hosting_company': request.POST.get('hosting_company'),
'location_code': request.POST.get('location_code'), 'location_code': request.POST.get('location_code'),
'configuration': request.POST.get('configuration'), 'configuration': hosting,
'configuration_detail': request.POST.get('configuration_detail'), 'configuration_detail': configuration_detail,
'final_price': request.POST.get('final_price') 'final_price': request.POST.get('final_price')
} }
request.session['vm_specs'] = vm_specs request.session['vm_specs'] = vm_specs

View file

@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.functional import cached_property from django.utils.functional import cached_property
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
@ -172,7 +173,7 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
private_key, public_key = self.generate_RSA() private_key, public_key = self.generate_RSA()
self.public_key = public_key self.public_key = public_key
self.save(update_fields=['public_key']) self.save(update_fields=['public_key'])
return private_key return private_key, public_key
def cancel_plan(self): def cancel_plan(self):
self.status = self.CANCELED_STATUS self.status = self.CANCELED_STATUS

View file

@ -1,4 +1,3 @@
{% load static from staticfiles %} {% load static from staticfiles %}
<!-- Inliner Build Version 4380b7741bb759d6cb997545f3add21ad48f010b --> <!-- Inliner Build Version 4380b7741bb759d6cb997545f3add21ad48f010b -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">

View file

@ -1,4 +1,3 @@
{% load static from staticfiles %} {% load static from staticfiles %}
<!-- Inliner Build Version 4380b7741bb759d6cb997545f3add21ad48f010b --> <!-- Inliner Build Version 4380b7741bb759d6cb997545f3add21ad48f010b -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">

View file

@ -0,0 +1,44 @@
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<link href="{% static 'hosting/css/pricing.css' %}" rel="stylesheet" />
<title>Hosting</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'hosting/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'hosting/css/pricing.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'hosting/css/landing-page.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href='//fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<link href="{% static 'hosting/font-awesome/css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="{% static 'hosting/img/favicon.ico' %}" type="image/x-icon" />
</head>
<body>
{% include "hosting/includes/_pricing.html" with select_configuration=True%}
<!-- Pricing data -->
{% if vm_types %}
<script type="text/javascript">
(function () {window.VMTypesData = "{{vm_types|safe}}";})();
</script>
{%endif%}
<!-- Lodash -->
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.1/lodash.min.js"></script>
<!-- jQuery -->
<script src="{% static 'hosting/js/jquery.js' %}"></script>
<!-- Pricing -->
<script src="{% static 'hosting/js/pricing.js' %}"></script>
</body>
</html>

View file

@ -24,8 +24,7 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="hosting_company" value="{{vm.hosting_company}}"> <input type="hidden" name="hosting_company" value="{{vm.hosting_company}}">
<input type="hidden" name="location_code" value="{{vm.location_code}}"> <input type="hidden" name="location_code" value="{{vm.location_code}}">
<input type="hidden" name="configuration_detail" value="{{configuration_detail}}">
<input type="hidden" name="configuration" value="{{hosting}}">
<ul class="pricing {% cycle 'p-red' 'p-black' 'p-red' 'p-yel' %}"> <ul class="pricing {% cycle 'p-red' 'p-black' 'p-red' 'p-yel' %}">
@ -46,13 +45,24 @@
</div> </div>
</li> </li>
<li> <li>
<!-- Single button --> <label for="configuration">Configuration: </label>
<div class="btn-group"> {% if select_configuration %}
<div class="form-group"> <select class="form-control" name="configuration" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
<label for="cores">Configuration: </label> {% for key,value in configuration_options.items %}
{{configuration_detail}} <option value="{{key}}">{{ value }}</option>
{% endfor %}
</select>
{% else %}
<input type="hidden" name="configuration_detail" value="{{configuration_detail}}">
<input type="hidden" name="configuration" value="{{hosting}}">
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label>Configuration: </label>
{{configuration_detail}}
</div>
</div> </div>
</div> {% endif %}
</li> </li>
<li> <li>
<!-- Single button --> <!-- Single button -->

View file

@ -89,6 +89,11 @@
</div><!--/row--> </div><!--/row-->
</div><!--/col-12--> </div><!--/col-12-->
</div><!--/row--> </div><!--/row-->
<div class="row">
<div class="col-md-12">
Configuration: {{virtual_machine.get_configuration_display}}
</div>
</div>
</div> </div>

116
hosting/test_forms.py Normal file
View file

@ -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())

View file

@ -3,15 +3,21 @@ from django.conf import settings
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.urlresolvers import resolve 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 model_mommy import mommy
from stored_messages.models import Inbox
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from .models import VirtualMachineType, HostingOrder, VirtualMachinePlan from .models import VirtualMachineType, HostingOrder, VirtualMachinePlan
from .views import DjangoHostingView, RailsHostingView, NodeJSHostingView, LoginView, SignupView, \ from .views import DjangoHostingView, RailsHostingView, NodeJSHostingView, LoginView, SignupView, \
PaymentVMView, OrdersHostingDetailView, OrdersHostingListView, VirtualMachineView, \ PaymentVMView, OrdersHostingDetailView, OrdersHostingListView, VirtualMachineView, \
VirtualMachinesPlanListView VirtualMachinesPlanListView, PasswordResetView, PasswordResetConfirmView, HostingPricingView, \
NotificationsView, MarkAsReadNotificationView, GenerateVMSSHKeysView
from utils.tests import BaseTestCase from utils.tests import BaseTestCase
@ -40,9 +46,12 @@ class DjangoHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
self.url = reverse('django.hosting') self.url = reverse('django.hosting')
self.view = DjangoHostingView() self.view = DjangoHostingView()
self.expected_template = 'hosting/django.html' self.expected_template = 'hosting/django.html'
HOSTING = 'django'
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
self.expected_context = { self.expected_context = {
'hosting': "django", 'hosting': HOSTING,
'hosting_long': "Django", 'hosting_long': "Django",
'configuration_detail': configuration_detail,
'domain': "django-hosting.ch", 'domain': "django-hosting.ch",
'google_analytics': "UA-62285904-6", 'google_analytics': "UA-62285904-6",
'email': "info@django-hosting.ch", 'email': "info@django-hosting.ch",
@ -56,9 +65,12 @@ class RailsHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
self.url = reverse('rails.hosting') self.url = reverse('rails.hosting')
self.view = RailsHostingView() self.view = RailsHostingView()
self.expected_template = 'hosting/rails.html' self.expected_template = 'hosting/rails.html'
HOSTING = 'rails'
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
self.expected_context = { self.expected_context = {
'hosting': "rails", 'hosting': HOSTING,
'hosting_long': "Ruby On Rails", 'hosting_long': "Ruby On Rails",
'configuration_detail': configuration_detail,
'domain': "rails-hosting.ch", 'domain': "rails-hosting.ch",
'google_analytics': "UA-62285904-5", 'google_analytics': "UA-62285904-5",
'email': "info@rails-hosting.ch", 'email': "info@rails-hosting.ch",
@ -72,9 +84,12 @@ class NodeJSHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
self.url = reverse('node.hosting') self.url = reverse('node.hosting')
self.view = NodeJSHostingView() self.view = NodeJSHostingView()
self.expected_template = 'hosting/nodejs.html' self.expected_template = 'hosting/nodejs.html'
HOSTING = 'nodejs'
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(HOSTING)
self.expected_context = { self.expected_context = {
'hosting': "nodejs", 'hosting': HOSTING,
'hosting_long': "NodeJS", 'hosting_long': "NodeJS",
'configuration_detail': configuration_detail,
'domain': "node-hosting.ch", 'domain': "node-hosting.ch",
'google_analytics': "UA-62285904-7", 'google_analytics': "UA-62285904-7",
'email': "info@node-hosting.ch", 'email': "info@node-hosting.ch",
@ -82,6 +97,36 @@ class NodeJSHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
} }
class HostingPricingViewTest(TestCase):
def setUp(self):
self.url = reverse('hosting:pricing')
self.view = HostingPricingView()
self.expected_template = 'hosting/hosting_pricing.html'
configuration_options = dict(VirtualMachinePlan.VM_CONFIGURATION)
self.expected_context = {
'configuration_options': configuration_options,
'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
def url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def get(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(self.view.get_context_data(), self.expected_context)
self.assertTemplateUsed(response, self.expected_template)
def test_anonymous_post(self):
response = self.client.post(self.url)
self.assertRedirects(response, expected_url=reverse('hosting:login'),
status_code=302, target_status_code=200)
class PaymentVMViewTest(BaseTestCase): class PaymentVMViewTest(BaseTestCase):
def setUp(self): def setUp(self):
@ -115,6 +160,7 @@ class PaymentVMViewTest(BaseTestCase):
'memory': 10, 'memory': 10,
'disk_size': 10000, 'disk_size': 10000,
'price': 22000, 'price': 22000,
'configuration': dict(VirtualMachinePlan.VM_CONFIGURATION).get('django')
} }
} }
@ -153,7 +199,8 @@ class PaymentVMViewTest(BaseTestCase):
'memory': hosting_order.vm_plan.memory, 'memory': hosting_order.vm_plan.memory,
'disk_size': hosting_order.vm_plan.disk_size, 'disk_size': hosting_order.vm_plan.disk_size,
'price': hosting_order.vm_plan.price, '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')) self.assertEqual(vm_plan, self.session_data.get('vm_specs'))
@ -172,6 +219,107 @@ class PaymentVMViewTest(BaseTestCase):
settings.STRIPE_API_PUBLIC_KEY) settings.STRIPE_API_PUBLIC_KEY)
class NotificationsViewTest(BaseTestCase):
def setUp(self):
super(NotificationsViewTest, self).setUp()
self.view = NotificationsView
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_url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
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.view = MarkAsReadNotificationView
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_url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
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.view = GenerateVMSSHKeysView
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_url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
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): class VirtualMachineViewTest(BaseTestCase):
def setUp(self): def setUp(self):
@ -357,3 +505,78 @@ class SignupViewTest(TestCase):
self.user = CustomUser.objects.get(email=self.signup_data.get('email')) self.user = CustomUser.objects.get(email=self.signup_data.get('email'))
self.assertEqual(response.context['user'], self.user) self.assertEqual(response.context['user'], self.user)
self.assertEqual(response.status_code, 200) 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)

View file

@ -4,13 +4,14 @@ 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, PasswordResetView, PasswordResetConfirmView MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'), url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'),
url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'), url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'),
url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'), url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'),
url(r'pricing/?$', HostingPricingView.as_view(), name='pricing'),
url(r'payment/?$', PaymentVMView.as_view(), name='payment'), url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'), url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'), url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),

View file

@ -99,6 +99,26 @@ class NodeJSHostingView(ProcessVMSelectionMixin, View):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class HostingPricingView(ProcessVMSelectionMixin, View):
template_name = "hosting/hosting_pricing.html"
def get_context_data(self, **kwargs):
configuration_options = dict(VirtualMachinePlan.VM_CONFIGURATION)
context = {
'configuration_options': configuration_options,
'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
return context
def get(self, request, *args, **kwargs):
request.session['hosting_url'] = reverse('hosting:djangohosting')
context = self.get_context_data()
return render(request, self.template_name, context)
class IndexView(View): class IndexView(View):
template_name = "hosting/index.html" template_name = "hosting/index.html"
@ -175,13 +195,7 @@ class PasswordResetView(FormView):
success_url = reverse_lazy('hosting:login') success_url = reverse_lazy('hosting:login')
# form_valid_message = 'Thank you for registering' # form_valid_message = 'Thank you for registering'
def form_valid(self, form): def test_generate_email_context(self, user):
email = form.cleaned_data.get('email')
user = CustomUser.objects.get(email=email)
messages.add_message(self.request, messages.SUCCESS, self.success_message)
context = { context = {
'user': user, 'user': user,
'token': default_token_generator.make_token(user), 'token': default_token_generator.make_token(user),
@ -190,6 +204,16 @@ class PasswordResetView(FormView):
'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()) '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 = { email_data = {
'subject': 'Password Reset', 'subject': 'Password Reset',
'to': email, 'to': email,
@ -226,15 +250,18 @@ class PasswordResetConfirmView(FormView):
return self.form_valid(form) return self.form_valid(form)
else: else:
messages.error(request, 'Password reset has not been unsuccessful.') messages.error(request, 'Password reset has not been unsuccessful.')
form.add_error(None, 'Password reset has not been unsuccessful.')
return self.form_invalid(form) return self.form_invalid(form)
else: else:
messages.error(request, 'The reset password link is no longer valid.') 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) return self.form_invalid(form)
class NotificationsView(TemplateView): class NotificationsView(LoginRequiredMixin, TemplateView):
template_name = 'hosting/notifications.html' template_name = 'hosting/notifications.html'
login_url = reverse_lazy('hosting:login')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(NotificationsView, self).get_context_data(**kwargs) context = super(NotificationsView, self).get_context_data(**kwargs)
@ -251,6 +278,7 @@ class NotificationsView(TemplateView):
class MarkAsReadNotificationView(LoginRequiredMixin, UpdateView): class MarkAsReadNotificationView(LoginRequiredMixin, UpdateView):
model = Message model = Message
success_url = reverse_lazy('hosting:notifications') success_url = reverse_lazy('hosting:notifications')
login_url = reverse_lazy('hosting:login')
fields = '__all__' fields = '__all__'
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
@ -265,6 +293,7 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView):
model = VirtualMachinePlan model = VirtualMachinePlan
template_name = 'hosting/virtual_machine_key.html' template_name = 'hosting/virtual_machine_key.html'
success_url = reverse_lazy('hosting:orders') success_url = reverse_lazy('hosting:orders')
login_url = reverse_lazy('hosting:login')
context_object_name = "virtual_machine" context_object_name = "virtual_machine"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -272,9 +301,10 @@ class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView):
context = super(GenerateVMSSHKeysView, self).get_context_data(**kwargs) context = super(GenerateVMSSHKeysView, self).get_context_data(**kwargs)
vm = self.get_object() vm = self.get_object()
if not vm.public_key: if not vm.public_key:
private_key = vm.generate_keys() private_key, public_key = vm.generate_keys()
context.update({ context.update({
'private_key': private_key 'private_key': private_key,
'public_key': public_key
}) })
return context return context
return context return context

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-04-15 19:16-0500\n" "POT-Creation-Date: 2016-07-09 16:47-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -70,32 +70,32 @@ msgstr "und"
msgid "the story continues!" msgid "the story continues!"
msgstr "Die Geschichte geht weiter!" msgstr "Die Geschichte geht weiter!"
#: templates/ungleich_page/includes/_contact_us.html:14 #: templates/ungleich_page/includes/_contact_us.html:15
#: templates/ungleich_page/includes/_contact_us.html:21 views.py:35 #: templates/ungleich_page/includes/_contact_us.html:28 views.py:36
msgid "Contact Us" msgid "Contact Us"
msgstr "Kontaktieren Sie uns" msgstr "Kontaktieren Sie uns"
#: templates/ungleich_page/includes/_contact_us.html:16 #: templates/ungleich_page/includes/_contact_us.html:18
msgid "Join us at" msgid "Join us at"
msgstr "Schliessen Sie sich uns an" msgstr "Schliessen Sie sich uns an"
#: templates/ungleich_page/includes/_contact_us.html:16 #: templates/ungleich_page/includes/_contact_us.html:19
msgid "Digital Glarus" msgid "Digital Glarus"
msgstr "Digital Glarus" msgstr "Digital Glarus"
#: templates/ungleich_page/includes/_contact_us.html:17 #: templates/ungleich_page/includes/_contact_us.html:20
msgid "a great co-working space in the middle of Alps!" msgid "a great co-working space in the middle of Alps!"
msgstr "ein wunderschöner Co-Working Space mitten in den Alpen" msgstr "ein wunderschöner Co-Working Space mitten in den Alpen"
#: templates/ungleich_page/includes/_contact_us.html:17 #: templates/ungleich_page/includes/_contact_us.html:21
msgid "You can contact us at" msgid "You can contact us at"
msgstr "Sie können uns kontaktieren unter" msgstr "Sie können uns kontaktieren unter"
#: templates/ungleich_page/includes/_contact_us.html:20 #: templates/ungleich_page/includes/_contact_us.html:26
msgid "or" msgid "or"
msgstr "oder" msgstr "oder"
#: templates/ungleich_page/includes/_contact_us.html:50 #: templates/ungleich_page/includes/_contact_us.html:60
msgid "Submit" msgid "Submit"
msgstr "Absenden" msgstr "Absenden"
@ -126,21 +126,24 @@ msgid ""
"infrastructure is powered by Free and Open Source Software like OpenNebula, " "infrastructure is powered by Free and Open Source Software like OpenNebula, "
"Qemu and GlusterFS." "Qemu and GlusterFS."
msgstr "" msgstr ""
"Wir offerieren hohe Verfügbarkeit für das Hosting in Deutschland und in der "
"Schweiz. Unsere Infrastruktur ist unterstützt durch Free and Open Source "
"Software wie OpenNebula."
#: templates/ungleich_page/includes/_portfolio.html:24 #: templates/ungleich_page/includes/_portfolio.html:24
msgid "Rails Hosting" msgid "Rails Hosting"
msgstr "" msgstr "Rails Hosting"
#: templates/ungleich_page/includes/_portfolio.html:26 #: templates/ungleich_page/includes/_portfolio.html:26
msgid "" msgid ""
"Ready to go live with your Ruby on Rails application? We offer you ready-to-" "Ready to go live with your Ruby on Rails application? We offer you ready-to-"
"deploy virtual machines or configure your existing infrastructure for Ruby " "deploy virtual machines or configure your existing infrastructure for Ruby "
"on Rails." "on Rails."
msgstr "" msgstr "Sind bereit mit ihrem Ruby on Rails Applikation live zu gehen?"
#: templates/ungleich_page/includes/_portfolio.html:32 #: templates/ungleich_page/includes/_portfolio.html:32
msgid " Configuration as a Service" msgid " Configuration as a Service"
msgstr "" msgstr "Konfiguration als Service"
#: templates/ungleich_page/includes/_portfolio.html:34 #: templates/ungleich_page/includes/_portfolio.html:34
msgid "" msgid ""
@ -148,11 +151,23 @@ msgid ""
"experienced team that configure your systems to provide service like DNS, E-" "experienced team that configure your systems to provide service like DNS, E-"
"Mail, Databases or Webservers." "Mail, Databases or Webservers."
msgstr "" msgstr ""
"Sie brauchen eine Konfiguration? Mit ungleich haben sie ein erfahrenes Team "
"gefunden, dass ihnen die Konfiguration von DNS, E-Mail, Datenbanken oder "
"Webservern für ihr System anbietet"
#: templates/ungleich_page/includes/_services.html:8 #: templates/ungleich_page/includes/_services.html:8
msgid "our services" msgid "our services"
msgstr "Unsere Dienstleistungen" msgstr "Unsere Dienstleistungen"
#: templates/ungleich_page/includes/_services.html:9
msgid "We support our clients in all areas of Unix infrastructure."
msgstr ""
#: templates/ungleich_page/includes/_services.html:10
msgid ""
"Our top notch configuration management is refreshingly simple and reliable."
msgstr ""
#: templates/ungleich_page/includes/_services.html:18 #: templates/ungleich_page/includes/_services.html:18
msgid "Hosting" msgid "Hosting"
msgstr "Hosting" msgstr "Hosting"
@ -162,6 +177,8 @@ msgid ""
"Ruby on Rails. Java hosting, Django hosting, we make it everything run " "Ruby on Rails. Java hosting, Django hosting, we make it everything run "
"smooth and safe." "smooth and safe."
msgstr "" msgstr ""
"Ruby on Rails. Java hosting, Django hosting, wir garantieren einen "
"reibungslosen Ablauf"
#: templates/ungleich_page/includes/_services.html:28 #: templates/ungleich_page/includes/_services.html:28
msgid "Configuration as a Service" msgid "Configuration as a Service"
@ -173,16 +190,21 @@ msgid ""
"needs to configured, we provide comprehensive solutions. Amazon, rackspace " "needs to configured, we provide comprehensive solutions. Amazon, rackspace "
"or bare metal servers, we configure for you." "or bare metal servers, we configure for you."
msgstr "" msgstr ""
"Ruby on Rails, Django, Java, Webserver, Mailserver, jegliche Infrastruktur "
"welche eine Konfiguration braucht, wir offerieren umfassende Lösungen, "
"Amazon, Rackspace oder Bare Metal Servers, wir konfigurieren alles."
#: templates/ungleich_page/includes/_services.html:38 #: templates/ungleich_page/includes/_services.html:38
msgid "Linux System Engineering" msgid "Linux System Engineering"
msgstr "" msgstr "Linux System Engineering"
#: templates/ungleich_page/includes/_services.html:41 #: templates/ungleich_page/includes/_services.html:41
msgid "" msgid ""
"Let your developers develop! We take care of your system administration. " "Let your developers develop! We take care of your system administration. "
"Gentoo, Archlinux, Debian, Ubuntu, and many more." "Gentoo, Archlinux, Debian, Ubuntu, and many more."
msgstr "" msgstr ""
"Lassen sie ihre Entwickler entwickeln! Wir kümmern uns um ihre "
"Systemadministration. Gentoo, Archlinux, Debian, Ubuntu und viele mehr."
#: templates/ungleich_page/includes/_team.html:8 #: templates/ungleich_page/includes/_team.html:8
msgid "Why ungleich?*" msgid "Why ungleich?*"
@ -190,7 +212,7 @@ msgstr "Warum ungleich?"
#: templates/ungleich_page/includes/_team.html:9 #: templates/ungleich_page/includes/_team.html:9
msgid "What our customers say" msgid "What our customers say"
msgstr "" msgstr "Was unsere Kunden sagen"
#: templates/ungleich_page/includes/_team.html:29 #: templates/ungleich_page/includes/_team.html:29
msgid "" msgid ""
@ -249,16 +271,19 @@ msgstr ""
#: templates/ungleich_page/includes/_team.html:95 #: templates/ungleich_page/includes/_team.html:95
msgid "*ungleich means not equal to (≠) U+2260." msgid "*ungleich means not equal to (≠) U+2260."
msgstr "" msgstr "*ungleich bedeutet nicht gleich wie (≠) U+2260."
#: urls.py:7 #: urls.py:8
msgid "contact/?$" #, fuzzy
msgstr "" #| msgid "Contact Us"
msgid "contact/$"
msgstr "Kontaktieren Sie uns"
#: views.py:25 #: views.py:26
msgid "Message Successfully Sent" msgid "Message Successfully Sent"
msgstr "Nachricht erfolgreich versendet" msgstr "Nachricht erfolgreich versendet"
#: views.py:36 #: views.py:37
msgid "If you have any question, just send us an email." msgid "If you have any question, just send us an email."
msgstr "Wenn Sie irgendwelche Fragen haben, schicken Sie uns einfach eine E-Mail." msgstr ""
"Wenn Sie irgendwelche Fragen haben, schicken Sie uns einfach eine E-Mail."

View file

@ -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;
}

View file

@ -0,0 +1,27 @@
{% load staticfiles%}
<!DOCTYPE html>
<html>
<head>
<link href="{% static 'ungleich_page/css/404.css' %}" rel="stylesheet">
<title>404 | ungleich</title>
</head>
<body>
<div class="error">
<div class="error-code m-b-10 m-t-20">404 <i class="fa fa-warning"></i></div>
<h3 class="font-bold">We couldn't find the page..</h3>
<div class="error-desc">
Sorry, but the page you are looking for was either not found or does not exist. <br/>
Try refreshing the page or click the button below to go back to the Homepage.
<div>
<a class=" login-detail-panel-button btn" href="{% url 'ungleich_page:landing' %}">
<i class="fa fa-arrow-left"></i>
Go back to Homepage
</a>
</div>
</div>
</div>
</body>
</html>

View file

@ -6,8 +6,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12 text-center wow fadeInDown"> <div class="col-lg-12 text-center wow fadeInDown">
<h2 class="section-heading">{% trans "our services" %}</h2> <h2 class="section-heading">{% trans "our services" %}</h2>
<h3 class="section-subheading text-muted">We support our clients in all areas of Unix infrastructure.<p></p> <h3 class="section-subheading text-muted">{% trans "" %}</h3>
Our top notch configuration management is refreshingly simple and reliable."</h3>
</div> </div>
</div> </div>
<div class="row text-center"> <div class="row text-center">

View file

@ -48,7 +48,6 @@ class SetPasswordForm(forms.Form):
return password2 return password2
class BillingAddressForm(forms.ModelForm): class BillingAddressForm(forms.ModelForm):
token = forms.CharField(widget=forms.HiddenInput()) token = forms.CharField(widget=forms.HiddenInput())

View file

@ -82,7 +82,6 @@ class StripeUtils(object):
) )
return customer return customer
@handleStripeError @handleStripeError
def make_charge(self, amount=None, customer=None): def make_charge(self, amount=None, customer=None):
amount = int(amount * 100) # stripe amount unit, in cents amount = int(amount * 100) # stripe amount unit, in cents

View file

@ -1,5 +1,51 @@
from django.test import TestCase 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): class ContactUsFormTest(TestCase):

View file

@ -1,5 +1,7 @@
from django.test import TestCase from django.test import TestCase
from django.test import Client from django.test import Client
from django.http.request import HttpRequest
from model_mommy import mommy from model_mommy import mommy
@ -28,6 +30,11 @@ class BaseTestCase(TestCase):
self.customer_client = self.get_client(self.customer) self.customer_client = self.get_client(self.customer)
self.another_customer_client = self.get_client(self.another_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): def get_client(self, user):
""" """
Authenticate a user and return the client 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