diff --git a/digitalglarus/admin.py b/digitalglarus/admin.py index 0c84adb1..a83ac4ad 100644 --- a/digitalglarus/admin.py +++ b/digitalglarus/admin.py @@ -1,5 +1,7 @@ from django.contrib import admin -from .models import Supporter, DGGallery, DGPicture +from .models import Supporter, DGGallery, DGPicture, Booking, BookingPrice,\ + MembershipOrder, Membership, MembershipType, BookingOrder + from utils.models import ContactMessage # class DGPictureInline(admin.StackedInline): @@ -10,4 +12,9 @@ class DGGalleryAdmin(admin.ModelAdmin): admin.site.register(DGGallery, DGGalleryAdmin) admin.site.register(ContactMessage) -admin.site.register(Supporter) \ No newline at end of file +admin.site.register(Booking) +admin.site.register(BookingPrice) +admin.site.register(MembershipOrder) +admin.site.register(Membership) +admin.site.register(MembershipType) +admin.site.register(BookingOrder) diff --git a/digitalglarus/forms.py b/digitalglarus/forms.py new file mode 100644 index 00000000..f5bb53aa --- /dev/null +++ b/digitalglarus/forms.py @@ -0,0 +1,117 @@ +from django import forms +from django.db.models import Q +from django.utils.translation import ugettext_lazy as _ +from datetime import datetime + + +from utils.models import BillingAddress +from utils.forms import LoginFormMixin, SignupFormMixin, BillingAddressForm + +from .models import MembershipType, MembershipOrder +from .models import Booking + + +class LoginForm(LoginFormMixin): + email = forms.CharField(widget=forms.EmailInput()) + password = forms.CharField(widget=forms.PasswordInput()) + + +class SignupForm(SignupFormMixin): + confirm_password = forms.CharField(widget=forms.PasswordInput()) + password = forms.CharField(widget=forms.PasswordInput()) + name = forms.CharField(label='name', + widget=forms.TextInput(attrs={'placeholder': 'Full name'})) + + +class MembershipBillingForm(BillingAddressForm): + token = forms.CharField(widget=forms.HiddenInput()) + membership_type = forms.ModelChoiceField(queryset=MembershipType.objects.all(), + widget=forms.HiddenInput()) + + class Meta: + model = BillingAddress + fields = ['membership_type', 'street_address', 'city', 'postal_code', 'country'] + labels = { + 'street_address': _('Street Address'), + 'city': _('City'), + 'postal_code': _('Postal Code'), + 'country': _('Country'), + } + + +class MembershipOrderForm(forms.ModelForm): + + class Meta: + model = MembershipOrder + fields = ['membership', 'customer', 'billing_address', + 'last4', 'cc_brand', 'stripe_charge_id', 'amount'] + + # def save(self, commit=True): + # instance = super(MembershipOrderForm, self).save(commit=False) + + # # if commit: + # # DonatorStatus.create(self.cleaned_data['donator'].user) + # # instance.save() + + # return instance + + +class BookingBillingForm(BillingAddressForm): + token = forms.CharField(widget=forms.HiddenInput(), required=False) + start_date = forms.DateField(widget=forms.HiddenInput()) + end_date = forms.DateField(widget=forms.HiddenInput()) + price = forms.FloatField(widget=forms.HiddenInput()) + + class Meta: + model = BillingAddress + fields = ['start_date', 'end_date', 'price', 'street_address', + 'city', 'postal_code', 'country'] + labels = { + 'street_address': _('Street Address'), + 'city': _('City'), + 'postal_code': _('Postal Code'), + 'country': _('Country'), + } + + +class BookingDateForm(forms.Form): + start_date = forms.DateField(required=False, + widget=forms.TextInput(attrs={'id': 'booking-date-1', + 'value': 'Select your date'})) + end_date = forms.DateField(required=False, + widget=forms.TextInput(attrs={'id': 'booking-date-2'})) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user') + super(BookingDateForm, self).__init__(*args, **kwargs) + + def clean_start_date(self): + start_date = self.cleaned_data.get('start_date') + if not start_date: + raise forms.ValidationError("This field is required.") + return start_date + + def clean_end_date(self): + end_date = self.cleaned_data.get('end_date') + if not end_date: + raise forms.ValidationError("This field is required.") + return end_date + + def clean(self): + start_date = self.cleaned_data.get('start_date') + end_date = self.cleaned_data.get('end_date') + + if not start_date or not end_date: + return self.cleaned_data + + if start_date > end_date: + raise forms.ValidationError("Your end date must be greather than your start date.") + + q1 = Q(bookingorder__customer__user=self.user, + start_date__lte=start_date, end_date__gte=start_date) + q2 = Q(bookingorder__customer__user=self.user, + start_date__gt=start_date, start_date__lte=end_date) + if Booking.objects.filter(q1 | q2).exists(): + raise forms.ValidationError("You already have a booking in these dates.") + + return self.cleaned_data diff --git a/digitalglarus/management/commands/make_membership_charge.py b/digitalglarus/management/commands/make_membership_charge.py new file mode 100644 index 00000000..41013b12 --- /dev/null +++ b/digitalglarus/management/commands/make_membership_charge.py @@ -0,0 +1,97 @@ +from django.core.management.base import BaseCommand + +from datetime import datetime +from django.utils import translation + +from utils.stripe_utils import StripeUtils + +from utils.mailer import BaseEmail +from digitalglarus.models import MembershipOrder +from digitalglarus.forms import MembershipOrderForm +# from membership.U +# from nosystemd.models import DonatorStatus, Donation +# from nosystemd.forms import DonationForm + + +class Command(BaseCommand): + help = 'Make the monthly stripe charge to all donators' + CURRENCY = 'usd' + + def handle(self, *args, **options): + translation.activate('en-us') + memberships_orders = MembershipOrder.objects.filter(membership__active=True) + current_month = datetime.now().month + current_year = datetime.now().year + + print("--------- STARTING MEMBERSHIP CHARGING SCRIPT ---------") + print("Memberhips date: %s-%s" % (current_month, current_year)) + + for membership_order in memberships_orders: + member = membership_order.customer + try: + MembershipOrder.objects.get(created_at__month=current_month, + created_at__year=current_year, + customer=member) + except MembershipOrder.DoesNotExist: + try: + current_membership_price = membership_order.membership.type.price + + last_membership_order = MembershipOrder.objects.filter(customer=member).last() + + # Make stripe charge to a customer + stripe_utils = StripeUtils() + stripe_utils.CURRENCY = self.CURRENCY + charge_response = stripe_utils.make_charge(amount=current_membership_price, + customer=member.stripe_id) + charge = charge_response.get('response_object') + # Check if the payment was approved + if not charge: + # There is an error trying to creating the stripe charge + context = { + 'paymentError': charge_response.get('error'), + } + print("--------- STRIPE PAYMENT ERROR ---------") + print(context) + print("-------------------------") + continue + + # Create a donation + charge = charge_response.get('response_object') + membership_order_data = { + 'cc_brand': charge.source.brand, + 'stripe_charge_id': charge.id, + 'last4': charge.source.last4, + 'membership': last_membership_order.membership.id, + 'billing_address': last_membership_order.billing_address.id, + 'customer': member.id, + 'amount': current_membership_price + } + membership_order_form = MembershipOrderForm(membership_order_data) + if membership_order_form.is_valid(): + membership_order = membership_order_form.save() + + context = { + 'order': membership_order, + 'base_url': "{0}://{1}".format('https', 'dynamicweb.ungleich.ch') + + } + email_data = { + 'subject': 'Your monthly membership has been charged', + 'to': member.user.email, + 'context': context, + 'template_name': 'membership_monthly_charge', + 'template_path': 'digitalglarus/emails/' + } + email = BaseEmail(**email_data) + email.send() + + print("--------- PAYMENT DONATION SUCCESSFULL ---------") + print("Member: %s" % member.user.email) + print("Amount: %s %s" % (current_membership_price, self.CURRENCY)) + print("-----------------------------------------------") + + except Exception as e: + print("--------- ERROR ---------") + print(e) + print("-------------------------") + continue diff --git a/digitalglarus/migrations/0007_auto_20160820_0408.py b/digitalglarus/migrations/0007_auto_20160820_0408.py new file mode 100644 index 00000000..0c719572 --- /dev/null +++ b/digitalglarus/migrations/0007_auto_20160820_0408.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-20 04:08 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0006_delete_message'), + ] + + operations = [ + migrations.CreateModel( + name='Membership', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.CreateModel( + name='MembershipOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('approved', models.BooleanField(default=False)), + ('last4', models.CharField(max_length=4)), + ('cc_brand', models.CharField(max_length=10)), + ('stripe_charge_id', models.CharField(max_length=100, null=True)), + ('membership', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='digitalglarus.Membership')), + ], + ), + migrations.CreateModel( + name='MembershipType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(choices=[('standard', 'Standard')], max_length=20)), + ('price', models.FloatField()), + ], + ), + migrations.AddField( + model_name='membership', + name='type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='digitalglarus.MembershipType'), + ), + ] diff --git a/digitalglarus/migrations/0008_auto_20160820_1959.py b/digitalglarus/migrations/0008_auto_20160820_1959.py new file mode 100644 index 00000000..d71350b5 --- /dev/null +++ b/digitalglarus/migrations/0008_auto_20160820_1959.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-20 19:59 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0002_billingaddress'), + ('membership', '0006_auto_20160526_0445'), + ('digitalglarus', '0007_auto_20160820_0408'), + ] + + operations = [ + migrations.AddField( + model_name='membershiporder', + name='billing_address', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress'), + preserve_default=False, + ), + migrations.AddField( + model_name='membershiporder', + name='customer', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer'), + preserve_default=False, + ), + ] diff --git a/digitalglarus/migrations/0009_booking_bookingorder_bookingprices.py b/digitalglarus/migrations/0009_booking_bookingorder_bookingprices.py new file mode 100644 index 00000000..784c1f54 --- /dev/null +++ b/digitalglarus/migrations/0009_booking_bookingorder_bookingprices.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-25 05:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0002_billingaddress'), + ('membership', '0006_auto_20160526_0445'), + ('digitalglarus', '0008_auto_20160820_1959'), + ] + + operations = [ + migrations.CreateModel( + name='Booking', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('price', models.FloatField()), + ('membership', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='digitalglarus.Membership')), + ], + ), + migrations.CreateModel( + name='BookingOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('approved', models.BooleanField(default=False)), + ('last4', models.CharField(max_length=4)), + ('cc_brand', models.CharField(max_length=10)), + ('stripe_charge_id', models.CharField(max_length=100, null=True)), + ('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress')), + ('booking', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='digitalglarus.Booking')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='BookingPrices', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('price_per_day', models.FloatField()), + ], + ), + ] diff --git a/digitalglarus/migrations/0010_auto_20160828_1745.py b/digitalglarus/migrations/0010_auto_20160828_1745.py new file mode 100644 index 00000000..b526562f --- /dev/null +++ b/digitalglarus/migrations/0010_auto_20160828_1745.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-28 17:45 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0009_booking_bookingorder_bookingprices'), + ] + + operations = [ + migrations.RenameModel( + old_name='BookingPrices', + new_name='BookingPrice', + ), + ] diff --git a/digitalglarus/migrations/0011_bookingprice_special_price_offer.py b/digitalglarus/migrations/0011_bookingprice_special_price_offer.py new file mode 100644 index 00000000..08a75e39 --- /dev/null +++ b/digitalglarus/migrations/0011_bookingprice_special_price_offer.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-28 21:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0010_auto_20160828_1745'), + ] + + operations = [ + migrations.AddField( + model_name='bookingprice', + name='special_price_offer', + field=models.FloatField(default=290.0), + preserve_default=False, + ), + ] diff --git a/digitalglarus/migrations/0012_booking_free_days.py b/digitalglarus/migrations/0012_booking_free_days.py new file mode 100644 index 00000000..17d95319 --- /dev/null +++ b/digitalglarus/migrations/0012_booking_free_days.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-30 03:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0011_bookingprice_special_price_offer'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='free_days', + field=models.IntegerField(default=0), + ), + ] diff --git a/digitalglarus/migrations/0013_remove_booking_membership.py b/digitalglarus/migrations/0013_remove_booking_membership.py new file mode 100644 index 00000000..7b2ffa24 --- /dev/null +++ b/digitalglarus/migrations/0013_remove_booking_membership.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-08-30 04:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0012_booking_free_days'), + ] + + operations = [ + migrations.RemoveField( + model_name='booking', + name='membership', + ), + ] diff --git a/digitalglarus/migrations/0014_booking_final_price.py b/digitalglarus/migrations/0014_booking_final_price.py new file mode 100644 index 00000000..f417b714 --- /dev/null +++ b/digitalglarus/migrations/0014_booking_final_price.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-09-02 03:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0013_remove_booking_membership'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='final_price', + field=models.FloatField(default=250), + preserve_default=False, + ), + ] diff --git a/digitalglarus/migrations/0015_auto_20160908_0149.py b/digitalglarus/migrations/0015_auto_20160908_0149.py new file mode 100644 index 00000000..cd41f920 --- /dev/null +++ b/digitalglarus/migrations/0015_auto_20160908_0149.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-09-08 01:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0014_booking_final_price'), + ] + + operations = [ + migrations.AddField( + model_name='bookingorder', + name='amount', + field=models.FloatField(default=35), + preserve_default=False, + ), + migrations.AddField( + model_name='membershiporder', + name='amount', + field=models.FloatField(default=35.0), + preserve_default=False, + ), + ] diff --git a/digitalglarus/migrations/0016_auto_20160909_0110.py b/digitalglarus/migrations/0016_auto_20160909_0110.py new file mode 100644 index 00000000..03d68203 --- /dev/null +++ b/digitalglarus/migrations/0016_auto_20160909_0110.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-09-09 01:10 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0015_auto_20160908_0149'), + ] + + operations = [ + migrations.RenameField( + model_name='bookingprice', + old_name='special_price_offer', + new_name='special_month_price', + ), + migrations.AddField( + model_name='bookingorder', + name='original_price', + field=models.FloatField(default=20), + preserve_default=False, + ), + migrations.AddField( + model_name='bookingorder', + name='special_month_price', + field=models.FloatField(default=290), + preserve_default=False, + ), + ] diff --git a/digitalglarus/migrations/0017_membership_active.py b/digitalglarus/migrations/0017_membership_active.py new file mode 100644 index 00000000..167fc18d --- /dev/null +++ b/digitalglarus/migrations/0017_membership_active.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-09-13 01:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0016_auto_20160909_0110'), + ] + + operations = [ + migrations.AddField( + model_name='membership', + name='active', + field=models.BooleanField(default=True), + ), + ] diff --git a/digitalglarus/migrations/0018_auto_20160928_0424.py b/digitalglarus/migrations/0018_auto_20160928_0424.py new file mode 100644 index 00000000..10a92271 --- /dev/null +++ b/digitalglarus/migrations/0018_auto_20160928_0424.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-09-28 04:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0017_membership_active'), + ] + + operations = [ + migrations.AddField( + model_name='bookingorder', + name='membership_required_months', + field=models.IntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name='bookingorder', + name='membership_required_months_price', + field=models.FloatField(default=0), + preserve_default=False, + ), + ] diff --git a/digitalglarus/migrations/0019_auto_20160929_0324.py b/digitalglarus/migrations/0019_auto_20160929_0324.py new file mode 100644 index 00000000..f78cf3e7 --- /dev/null +++ b/digitalglarus/migrations/0019_auto_20160929_0324.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-09-29 03:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0018_auto_20160928_0424'), + ] + + operations = [ + migrations.AlterField( + model_name='bookingorder', + name='membership_required_months', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='bookingorder', + name='membership_required_months_price', + field=models.FloatField(default=0), + ), + ] diff --git a/digitalglarus/migrations/0020_auto_20161013_0253.py b/digitalglarus/migrations/0020_auto_20161013_0253.py new file mode 100644 index 00000000..f9d49868 --- /dev/null +++ b/digitalglarus/migrations/0020_auto_20161013_0253.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-10-13 02:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0019_auto_20160929_0324'), + ] + + operations = [ + migrations.AlterField( + model_name='bookingorder', + name='cc_brand', + field=models.CharField(blank=True, max_length=10), + ), + migrations.AlterField( + model_name='bookingorder', + name='last4', + field=models.CharField(blank=True, max_length=4), + ), + migrations.AlterField( + model_name='membershiporder', + name='cc_brand', + field=models.CharField(blank=True, max_length=10), + ), + migrations.AlterField( + model_name='membershiporder', + name='last4', + field=models.CharField(blank=True, max_length=4), + ), + ] diff --git a/digitalglarus/migrations/0021_auto_20161017_1958.py b/digitalglarus/migrations/0021_auto_20161017_1958.py new file mode 100644 index 00000000..69b44dde --- /dev/null +++ b/digitalglarus/migrations/0021_auto_20161017_1958.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-10-17 19:58 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0020_auto_20161013_0253'), + ] + + operations = [ + migrations.AddField( + model_name='membership', + name='end_date', + field=models.DateField(default=datetime.datetime(2016, 10, 17, 19, 58, 0, 209303, tzinfo=utc)), + preserve_default=False, + ), + migrations.AddField( + model_name='membership', + name='start_date', + field=models.DateField(default=datetime.datetime(2016, 10, 17, 19, 58, 7, 361473, tzinfo=utc)), + preserve_default=False, + ), + migrations.AddField( + model_name='membershiporder', + name='end_date', + field=models.DateField(default=datetime.datetime(2016, 10, 17, 19, 58, 15, 657240, tzinfo=utc)), + preserve_default=False, + ), + migrations.AddField( + model_name='membershiporder', + name='start_date', + field=models.DateField(default=datetime.datetime(2016, 10, 17, 19, 58, 16, 897120, tzinfo=utc)), + preserve_default=False, + ), + ] diff --git a/digitalglarus/migrations/0022_auto_20161023_0218.py b/digitalglarus/migrations/0022_auto_20161023_0218.py new file mode 100644 index 00000000..cc576832 --- /dev/null +++ b/digitalglarus/migrations/0022_auto_20161023_0218.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-10-23 02:18 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('digitalglarus', '0021_auto_20161017_1958'), + ] + + operations = [ + migrations.RemoveField( + model_name='bookingorder', + name='membership_required_months', + ), + migrations.RemoveField( + model_name='bookingorder', + name='membership_required_months_price', + ), + ] diff --git a/digitalglarus/mixins.py b/digitalglarus/mixins.py new file mode 100644 index 00000000..5392ad37 --- /dev/null +++ b/digitalglarus/mixins.py @@ -0,0 +1,52 @@ +from django.db import models +from django.http import HttpResponseRedirect +from membership.models import StripeCustomer +from utils.models import BillingAddress + + +class MembershipRequiredMixin(object): + membership_redirect_url = None + + def dispatch(self, request, *args, **kwargs): + from .models import Membership + if not Membership.is_digitalglarus_active_member(request.user): + return HttpResponseRedirect(self.membership_redirect_url) + + return super(MembershipRequiredMixin, self).dispatch(request, *args, **kwargs) + + +class IsNotMemberMixin(object): + already_member_redirect_url = None + + def dispatch(self, request, *args, **kwargs): + from .models import Membership + if Membership.is_digitalglarus_active_member(request.user): + return HttpResponseRedirect(self.already_member_redirect_url) + + return super(IsNotMemberMixin, self).dispatch(request, *args, **kwargs) + + +class Ordereable(models.Model): + customer = models.ForeignKey(StripeCustomer) + amount = models.FloatField() + billing_address = models.ForeignKey(BillingAddress) + created_at = models.DateTimeField(auto_now_add=True) + approved = models.BooleanField(default=False) + last4 = models.CharField(max_length=4, blank=True) + cc_brand = models.CharField(max_length=10, blank=True) + stripe_charge_id = models.CharField(max_length=100, null=True) + + class Meta: + abstract = True + + @classmethod + def create(cls, data): + stripe_charge = data.pop('stripe_charge', None) + instance = cls.objects.create(**data) + if not stripe_charge: + return instance + instance.stripe_charge_id = stripe_charge.id + instance.last4 = stripe_charge.source.last4 + instance.cc_brand = stripe_charge.source.brand + instance.save() + return instance diff --git a/digitalglarus/models.py b/digitalglarus/models.py index 3939dac1..876df6d7 100644 --- a/digitalglarus/models.py +++ b/digitalglarus/models.py @@ -1,7 +1,234 @@ + +import calendar +from datetime import datetime, date, timedelta +from dateutil.relativedelta import relativedelta from django.db import models +from django.db.models import Q from cms.models import CMSPlugin from filer.fields.image import FilerImageField from django.core.urlresolvers import reverse +from django.utils.functional import cached_property +from .mixins import Ordereable + +# from membership.models import StripeCustomer +# from utils.models import BillingAddress + + +class MembershipType(models.Model): + + STANDARD = 'standard' + MEMBERSHIP_TYPES = ( + (STANDARD, 'Standard'), + + ) + name = models.CharField(choices=MEMBERSHIP_TYPES, max_length=20) + price = models.FloatField() + + @cached_property + def days_left(self): + current_date = date.today() + _, days_in_month = calendar.monthrange(current_date.year, current_date.month) + pass_days = current_date.day + days_left = days_in_month - pass_days + 1 + return days_left + + @cached_property + def first_month_price(self): + current_date = date.today() + _, days_in_month = calendar.monthrange(current_date.year, current_date.month) + pass_days = current_date.day + days_left = days_in_month - pass_days + 1 + percentage = days_left / days_in_month + membership_price = self.price + final_price = membership_price * percentage + return final_price + + @cached_property + def first_month_range(self): + current_date = date.today() + _, days_in_month = calendar.monthrange(current_date.year, current_date.month) + pass_days = current_date.day + days_left = days_in_month - pass_days + end_date = current_date + timedelta(days=days_left) + return current_date, end_date + + @cached_property + def first_month_formated_range(self): + start_date, end_date = self.first_month_range + return "{} - {}".format(datetime.strftime(start_date, "%b, %d %Y"), + datetime.strftime(end_date, "%b, %d %Y")) + + +class Membership(models.Model): + type = models.ForeignKey(MembershipType) + active = models.BooleanField(default=True) + start_date = models.DateField() + end_date = models.DateField() + + @classmethod + def get_by_user(cls, user): + return cls.objects.\ + filter(membershiporder__customer__user=user).last() + + @classmethod + def create(cls, data): + instance = cls.objects.create(**data) + return instance + + @classmethod + def is_digitalglarus_active_member(cls, user): + past_month = (datetime.today() - relativedelta(months=1)).month + has_order_current_month = Q(membershiporder__customer__user=user, + membershiporder__created_at__month=datetime.today().month) + has_order_past_month = Q(membershiporder__customer__user=user, + membershiporder__created_at__month=past_month) + active_membership = Q(active=True) + return cls.objects.filter(has_order_past_month | has_order_current_month).\ + filter(active_membership).exists() + + def update_dates(self, start_date, end_date): + self.start_date = start_date + self.end_date = end_date + self.save() + + def deactivate(self): + self.active = False + self.save() + + +class MembershipOrder(Ordereable, models.Model): + membership = models.ForeignKey(Membership) + start_date = models.DateField() + end_date = models.DateField() + + @classmethod + def current_membership_dates(cls, user): + last_membership_payment = cls.objects.\ + filter(customer__user=user).last() + if not last_membership_payment: + return [None, None] + + return last_membership_payment.start_date, last_membership_payment.end_date + + @classmethod + def next_membership_dates(cls, user): + current_start_date, current_end_date = cls.current_membership_dates(user) + if not current_start_date or not current_end_date: + return [None, None] + next_start_date = current_end_date + relativedelta(months=1) + _, days_in_month = calendar.monthrange(next_start_date.year, + next_start_date.month) + next_start_date = next_start_date.replace(day=1) + next_end_date = next_start_date + timedelta(days=days_in_month) + return next_start_date, next_end_date + + def first_membership_range_date(self): + start_date = self.created_at + _, days_in_month = calendar.monthrange(start_date.year, + start_date.month) + pass_days = start_date.day + days_left = days_in_month - pass_days + end_date = start_date + timedelta(days=days_left) + return start_date, end_date + + def get_membership_order_cc_data(self): + return { + 'last4': self.last4, + 'cc_brand': self.cc_brand, + } + + def get_membership_range_date(self): + return self.start_date, self.end_date + + @classmethod + def create(cls, data): + stripe_charge = data.pop('stripe_charge', None) + instance = cls.objects.create(**data) + instance.stripe_charge_id = stripe_charge.id + instance.last4 = stripe_charge.source.last4 + instance.cc_brand = stripe_charge.source.brand + instance.save() + return instance + + +class BookingPrice(models.Model): + price_per_day = models.FloatField() + special_month_price = models.FloatField() + + +class Booking(models.Model): + start_date = models.DateField() + end_date = models.DateField() + price = models.FloatField() + free_days = models.IntegerField(default=0) + final_price = models.FloatField() + + @classmethod + def create(cls, data): + instance = cls.objects.create(**data) + return instance + + @classmethod + def get_ramaining_free_days(cls, user, start_date, end_date): + + TWO_DAYS = 2 + ONE_DAY = 1 + start_month = start_date.month + end_month = end_date.month + months = abs(start_month - (end_month + 12) if end_month < start_month + else end_month - start_month) + current_date = datetime.today() + current_month_bookings = cls.objects.filter(bookingorder__customer__user=user, + start_date__month=current_date.month) + free_days_this_month = TWO_DAYS - sum(map(lambda x: x.free_days, current_month_bookings)) + + if start_date == end_date and free_days_this_month == TWO_DAYS: + free_days_this_month = ONE_DAY + + total_free_days = months * TWO_DAYS + free_days_this_month + return total_free_days + + @classmethod + def booking_price(cls, user, start_date, end_date): + + MAX_MONTH_PRICE = BookingPrice.objects.last().special_month_price + MAX_MONTH_DAYS_PROMOTION = 31 + MIN_MONTH_DAYS_PROMOTION = 19 + + booking_prices = BookingPrice.objects.last() + price_per_day = booking_prices.price_per_day + booking_days = (end_date - start_date).days + 1 + months = booking_days // MAX_MONTH_DAYS_PROMOTION + remanent_days = booking_days % MAX_MONTH_DAYS_PROMOTION + months_prices = months * MAX_MONTH_PRICE + remanent_days_price = remanent_days * price_per_day \ + if remanent_days <= MIN_MONTH_DAYS_PROMOTION else MAX_MONTH_PRICE + normal_price = months_prices + remanent_days_price + + free_days = cls.get_ramaining_free_days(user, start_date, end_date) + + final_booking_price = normal_price - (free_days * price_per_day) + + return normal_price, final_booking_price, free_days + + +class BookingOrder(Ordereable, models.Model): + booking = models.OneToOneField(Booking) + original_price = models.FloatField() + special_month_price = models.FloatField() + + @classmethod + def user_has_not_bookings(cls, user): + return cls.objects.filter(customer__user=user).exists() + + def get_booking_cc_data(self): + return { + 'last4': self.last4, + 'cc_brand': self.cc_brand, + } if self.last4 and self.cc_brand else None + + def booking_days(self): + return (self.booking.end_date - self.booking.start_date).days + 1 class Supporter(models.Model): @@ -15,7 +242,6 @@ class Supporter(models.Model): return reverse('dgSupporters_view', args=[self.pk]) - class DGGallery(models.Model): parent = models.ForeignKey('self', blank=True, null=True) name = models.CharField(max_length=30) diff --git a/digitalglarus/static/digitalglarus/css/agency.css b/digitalglarus/static/digitalglarus/css/agency.css index 7be17307..67154295 100755 --- a/digitalglarus/static/digitalglarus/css/agency.css +++ b/digitalglarus/static/digitalglarus/css/agency.css @@ -241,6 +241,8 @@ fieldset[disabled] .btn-xl.active { .navbar-default .navbar-collapse { border-color: rgba(255,255,255,.02); + padding-right: 100px; + text-align: right; } .navbar-default .navbar-toggle { @@ -259,7 +261,7 @@ fieldset[disabled] .btn-xl.active { .navbar-default .nav li a { text-transform: uppercase; - font-family: Montserrat,"Helvetica Neue",Helvetica,Arial,sans-serif; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; font-weight: 400; letter-spacing: 1px; color: #fff; @@ -1170,11 +1172,11 @@ footer { } .map-caption{ - text-transform: none; + text-transform:uppercase; + font-size: 13px; font-family:"Montserrat","Helvetica Neue",Helvetica, Arial,sans-serif; font-weight: 400; color: #ffffff; - font-size: 14px; letter-spacing: 1px; text-align:center; } diff --git a/digitalglarus/static/digitalglarus/css/price.css b/digitalglarus/static/digitalglarus/css/price.css new file mode 100644 index 00000000..359b47fe --- /dev/null +++ b/digitalglarus/static/digitalglarus/css/price.css @@ -0,0 +1,622 @@ +@charset "UTF-8"; +/* CSS Document */ +#membership-includes { + text-align: center; + color: #fff; + background-attachment: scroll; + background-image: url(../img/header_bg_5.png); + background-position: center center; + background-repeat: none; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; + -o-background-size: cover; +} + +.intro-headline-small{ + font-family: 'Raleway' , "Open Sans Bold", Helvetica, Arial, "Arial Bold", sans-serif; + font-size: 46px; + font-style: normal; + font-weight: 200; + text-transform: none; + text-transform: uppercase; + color: #FFF; +} + + +.intro-smaller { + font-family: 'Raleway' ,'Montserrat' ,"Open Sans Bold", Helvetica, Arial, "Arial Bold", sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 100; + text-transform: none; + color: #FFF; +} +.intro-price { + padding-top: 70px; + padding-bottom: 50px; +} + +.ul{ + columns: 2; + padding: 15px 0; + font-size: 18px; + font-weight: 300; + color: #fff; + margin: 40px 0; +} +.price-list{ + background-color: transparent; + font-size: 18px; + border: 0; + padding-top: 15px; + padding-bottom: 15px; +} + +.price{ + text-transform: none; +} + +#price { + background-image: url(../img/bg-price.png); + background-position: center center; + background-repeat: none; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; + -o-background-size: cover; +} +.price-box{ + padding-left: 15px; + padding-right: 15px; + padding-top: 15px; + padding-bottom: 30px; + margin-top: 80px; + margin-bottom: 30px; + margin-left: 10px; + margin-right: 10px; + color: inherit; + background-color: #fff; +} + +.graph{ + padding-top: 30px; +} + +.glyphicon-ok{ + margin-right: 0.5em; +} + +.glyphicon-plus{ + font-size: 42px; + display: block; + text-align: center; + margin: 40px auto 20px; + color: #88c7d7; +} + +.signup-container { + padding: 0; + margin: 0; + overflow-x: hidden; +} + +.greyline{ + border-color: #ddd; + border-width: 1px; + max-width: 600px; +} + +.greyline-long{ + border-color: #ddd; + border-width: 1px; + max-width: 95%; +} + +.signup-form { + padding: 2em; + padding-top: 1em; + padding-bottom: 0.1em; + margin-bottom: 0px; +} + +.signup-lead { + font-size: 16px; + line-height: 1.4em; + text-transform: none; +} + +.form-control { + margin-bottom: 1em; + border-image-source: initial; + border-image-slice: initial; + border-image-width: initial; + border-image-outset: initial; + border-image-repeat: initial; + min-height: 20px; + background: rgb(255, 255, 255); + border-width: 1px; + border-style: solid; + border-color: rgba(37, 39, 41, 0.498039); + padding: 10.5px 10px; + transition: all 0.15s ease-in-out; +} + +.notice-box { + padding-top: 1.5em; + padding-bottom: 2em; +} + +.signup-text { + color: #777; + margin: 0; + line-height: 1.5; + font-size: 13px; + text-transform: none; + font-family: "Helvetica Neue" ,"Helvetica Neue" ,"Open Sans" ,"Arial" , sans-serif; + color: #999; +} + +.signup-text a { + font-weight : 400; + margin-left: 0.5em; + color: #31708f; +} + +.signup-notice a { + font-weight : 400; + margin-left: 0.5em; + color: #31708f; +} + +.signup-box { + padding-left: 15px; + padding-right: 15px; + padding-top: 15px; + padding-bottom: 15px; + margin-top: 80px; + margin-bottom: 30px; + margin-left: 10px; + margin-right: 10px; + color: inherit; + background-color: #fff; +} + +.signup-notice { + padding-top: 0.5em; + font-size: 12px; + color: #777; +} + +.glyphicon-user{ + font-size: 42px; + display: block; + text-align: center; + margin: 40px auto 20px; + color: #88c7d7; +} + +.glyphicon-ok { + font-size: 28px; + display: block; + text-align: center; + margin-bottom: 20px; + color: #88c7d7; + margin-top: 0px; +} +} + + +.membership-amount{ + font-size: 18px; + text-align: right; + width: 100%; +} +.payment-box{ + padding-top: 3em; + padding-left: 30px; + padding-right: 30px; + padding-bottom: 15px; + margin-top: 80px; + margin-bottom: 30px; + margin-left: 10px; + margin-right: 10px; + color: inherit; + background-color: #fff; +} + +.payment-head{ + font-weight: 600; + padding-left: 15px; + text-align: left; +} + +.membership-lead { + font-size: 16px; + line-height: 1.4em; + text-transform: none; + text-align: left; + padding-left: 15px; + padding-right: 15px; + font-family: helvetica neue, helvitica, open-sans, sans-serif; + font-weight: 200; +} + +.order-name{ + font-size: 18px; + text-transform: uppercase; + margin: 0; + font-weight: 700; + padding-top: 15px; + padding-bottom: 15px; + letter-spacing: 1px; +} + +.order-person{ + text-align: right; + margin-top: 0; + margin-bottom: 15px; + font-size: 20px; + color: #494949; + text-transform: none; +} + +.order-item{ + font-size: 18px; + text-transform: none; + font-weight: 400; + text-align: left; + width: 100%; + letter-spacing: 0.1px; +} + +.order-duration { + padding-bottom: 1em; + color: #999; + font-size: 16px; + text-transform: none; + font-weight: 200; + text-align: left; + width: 100%; + margin-top: 5px; +} + +.header{ + padding: 12px 15px; + margin-bottom: 0; + border-bottom: 2px solid #f7f7f7; + background-color: #a1cfd7; +} + +.payment-total{ + font-size: 16px; + text-transform: none; + margin: 0; + font-weight: 200; + letter-spacing: 1px; +} + +.order-sum { + text-align: right; + margin-top: 0; + margin-bottom: 15px; + font-size: 20px; + color: #494949; +} +.order-result { + font-family: open-sans, montserrat, Helvetica Neue, Helvetica, sans-serif; + margin-top: 0; + margin-bottom: 15px; + font-size: 28px; + color: #494949; + text-align: right; +} + +.order-bottom-text { + padding-top: 0.5em; + color: #777; + padding-left: 15px; + font-size: 13px; +} + +.order-bottom-text a { + font-weight : 400; + margin-left: 0.5em; + color: #31708f; +} + +.order-summary { + background-color:#fff; + margin-top: 80px; +} + +.order-box{ + padding-top: 0; + padding-left: 20px; + padding-right: 20px; + padding-bottom: 15px; + margin-top: 0; + margin-bottom: 30px; + margin-left: 10px; + margin-right: 10px; + color: inherit; + background-color: #fff; +} + +.billing-head { + padding-left: 15px; + margin-top: 0px; + margin-bottom: 0px; + font-weight: 400; + font-size: 21px; + text-align: left; + text-transform: none; +} + +.reset-head { + padding-left: 15px; + margin-top: 0px; + margin-bottom: 0px; + font-weight: 400; + font-size: 24px; + text-align: left; + text-transform: none; +} + +.form-control { + color: #000; + border-radius: 0px; + box-shadow: none; +} + +.custom-control-description { + color: #999; + font-weight: 300; + font-family: "helvetica neue", "helvetica", "sans-serif" ; + letter-spacing: 0.5px; + font-size: 12px; + line-height: 1; +} + +.custom-control-description a { + color: #31708f; +} + +.custom-control { + text-align: left; +} + +.button-box { + margin-top: 20px; +} + +.date-box { + padding-top: 1.5em; + padding-bottom: 0.5em; +} + +.date-oneline { + font-size: 16px; + text-transform: none; + margin: 0; + font-weight: 400; + letter-spacing: 1px; + color: #555; +} + +.btn-grey { + background-color: #b2b7b9;; + border-color: #b2b7b9;; +} + +.btn-grey:hover, +.btn-grey:focus, +.btn-grey:active, +.btn-grey.active, +.open .dropdown-toggle.btn-grey { + text-transform: uppercase; + font-weight: 400; + border-color: #ddd; + color: #fff; + background-color: #ddd; +} + +.btn-blue:hover, +.btn-blue:focus, +.btn-blue:active, +.btn-blue.active, +.open .dropdown-toggle.btn-grey { + background-color: #5699b9; + border-color: #5699b9; +} + +.loggedin-head { + padding-left: 15px; + margin-top: 0px; + margin-bottom: 0px; + font-weight: 400; + font-size: 21px; + text-align: center; + text-transform: none; + padding-top: 10px; +} + +.loggedin-lead { + font-size: 16px; + line-height: 1.4em; + text-transform: none; + text-align: center; + padding-left: 15px; + padding-right: 15px; + font-family: helvetica neue, helvitica, open-sans, sans-serif; + font-weight: 200; +} + +.activation-lead { + font-size: 15px; + line-height: 1.4em; + text-transform: none; + text-align: center; + padding-left: 15px; + padding-right: 15px; + padding-bottom: 0; + font-family: helvetica neue, helvitica, open-sans, sans-serif; + font-weight: 200; + color: #777; +} + +.button-booking-box { + margin-top: 0; +} + +.member-name{ + font-size: 16px; + text-transform: none; + font-weight: 400; + text-align: left; + width: 100%; + letter-spacing: 0.1px; + text-align: left; + padding-left: 15px; + line-height: 0.35em; +} + +.history-name{ + font-size: 16px; + text-transform: none; + font-weight: 400; + text-align: left; + width: 100%; + letter-spacing: 0.1px; + text-align: left; + padding-left: 15px; + line-height: 1.4em; +} + +.button-center-box { + margin-top: 20px; + text-align: center; +} + +.table td, .table th { + padding: .75rem; + vertical-align: top; + border-top: 1px solid #eceeef; +} + +.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td { + padding: .75rem; + vertical-align: top; + border-top: 1px solid #eceeef; + border-bottom: 1px solid #eceeef; + color: #777; + +} + +.table{ + margin-top: 1em; +} + +.order-head { + font-size: 18px; + text-transform: uppercase; + margin: 0; + font-weight: 900; + padding-top: 15px; + letter-spacing: 1px; + text-align: left; + padding-left: 15px; + color: #88c7d7; +} + +th { + text-align: center; +} + +.table td { + color: #999; +} + +.btn-edit { + font-size: 10px; + padding-left: 8px; + padding-right: 8px; + padding-top: 2px; + padding-bottom: 2px; + background-color: #b2b7b9; + border-color: #b2b7b9; + color: #fff; + font-weight: 200; + margin-left: 1em; +} + +.edit-button { + text-align:left; + padding-left: 15px; + padding-top: 10px; +} + +.btn-deactivate { + font-size: 11px; + padding-left: 10px; + padding-right: 10px; + padding-top: 4px; + padding-bottom: 4px; + + color: #fff; + font-weight: 200; +} + +.btn-darkgrey { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: 400; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + background-color: #777; + border-color: #777; + text-transform: none; +} + +.btn-darkgrey:hover, +.btn-darkgrey:focus, +.btn-darkgrey:active, +.btn-darkgrey.active, +.open .dropdown-toggle.btn-darkgrey { + text-transform: none; + font-weight: 400; + border-color: #88c7d7; + color: #fff; + background-color: #88c7d7; +} + +.thankyou { + + font-size: 30px; + text-align: center; + padding-top: 45px; + +} + +.icon-up{ + margin-top: 30px; +} + +.price-exp-box { + padding-left: 15px; + padding-right: 15px; + padding-top: 15px; + padding-bottom: 0px; + margin-top: 0px; + margin-bottom: 30px; + margin-left: 10px; + margin-right: 10px; + color: inherit; + background-color: #fff; + text-align: left; +} \ No newline at end of file diff --git a/digitalglarus/static/digitalglarus/img/bg-price.png b/digitalglarus/static/digitalglarus/img/bg-price.png new file mode 100644 index 00000000..f4597723 Binary files /dev/null and b/digitalglarus/static/digitalglarus/img/bg-price.png differ diff --git a/digitalglarus/static/digitalglarus/img/graph.png b/digitalglarus/static/digitalglarus/img/graph.png new file mode 100644 index 00000000..74ab22a8 Binary files /dev/null and b/digitalglarus/static/digitalglarus/img/graph.png differ diff --git a/digitalglarus/static/digitalglarus/img/header_bg_5.png b/digitalglarus/static/digitalglarus/img/header_bg_5.png new file mode 100644 index 00000000..063b1642 Binary files /dev/null and b/digitalglarus/static/digitalglarus/img/header_bg_5.png differ diff --git a/digitalglarus/static/digitalglarus/js/booking.js b/digitalglarus/static/digitalglarus/js/booking.js new file mode 100644 index 00000000..814d1a3c --- /dev/null +++ b/digitalglarus/static/digitalglarus/js/booking.js @@ -0,0 +1,28 @@ +$( document ).ready(function() { + + + // $('#booking-date-range').daterangepicker(); + + + + + var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); + + $('#booking-date-1').datetimepicker({ + minDate: tomorrow, + format: 'MM/DD/YYYY', + // defaultDate: false + }); + $('#booking-date-1').val(''); + $('#booking-date-2').datetimepicker({ + useCurrent: false, //Important! See issue #1075 + format: 'MM/DD/YYYY', + }); + $("#booking-date-1").on("dp.change", function (e) { + $('#booking-date-2').data("DateTimePicker").minDate(e.date); + }); + $("#booking-date-2").on("dp.change", function (e) { + $('#booking-date-1').data("DateTimePicker").maxDate(e.date); + }); + +}); \ No newline at end of file diff --git a/digitalglarus/static/digitalglarus/js/cbpAnimatedHeader.js b/digitalglarus/static/digitalglarus/js/cbpAnimatedHeader.js index 4554e2e5..dc750576 100755 --- a/digitalglarus/static/digitalglarus/js/cbpAnimatedHeader.js +++ b/digitalglarus/static/digitalglarus/js/cbpAnimatedHeader.js @@ -13,7 +13,7 @@ var cbpAnimatedHeader = (function() { var docElem = document.documentElement, header = document.querySelector( '.navbar-default' ), didScroll = false, - changeHeaderOn = 300; + changeHeaderOn = 20; function init() { window.addEventListener( 'scroll', function( event ) { diff --git a/digitalglarus/static/digitalglarus/js/payment.js b/digitalglarus/static/digitalglarus/js/payment.js new file mode 100644 index 00000000..d9c6e0f7 --- /dev/null +++ b/digitalglarus/static/digitalglarus/js/payment.js @@ -0,0 +1,133 @@ +$( document ).ready(function() { + + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { + // Only send the token to relative URLs i.e. locally. + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + } + } + }); + + var submit_form_btn = $('#payment_button'); + submit_form_btn.on('click', submit_payment); + + function submit_payment(e){ + $('#billing-form').submit(); + // $form.submit(); + } + + + var $form = $('#payment-form'); + $form.submit(payWithStripe); + + /* If you're using Stripe for payments */ + function payWithStripe(e) { + console.log("submiting"); + e.preventDefault(); + + /* Visual feedback */ + $form.find('[type=submit]').html('Validating '); + + var PublishableKey = window.stripeKey; + Stripe.setPublishableKey(PublishableKey); + Stripe.card.createToken($form, function stripeResponseHandler(status, response) { + if (response.error) { + /* Visual feedback */ + $form.find('[type=submit]').html('Try again'); + /* Show Stripe errors on the form */ + $form.find('.payment-errors').text(response.error.message); + $form.find('.payment-errors').closest('.row').show(); + } else { + /* Visual feedback */ + $form.find('[type=submit]').html('Processing '); + /* Hide Stripe errors on the form */ + $form.find('.payment-errors').closest('.row').hide(); + $form.find('.payment-errors').text(""); + // response contains id and card, which contains additional card details + var token = response.id; + // AJAX + + //set token on a hidden input + $('#id_token').val(token); + $('#billing-form').submit(); + } + }); + } + + /* Form validation */ + $.validator.addMethod("month", function(value, element) { + return this.optional(element) || /^(01|02|03|04|05|06|07|08|09|10|11|12)$/.test(value); + }, "Please specify a valid 2-digit month."); + + $.validator.addMethod("year", function(value, element) { + return this.optional(element) || /^[0-9]{2}$/.test(value); + }, "Please specify a valid 2-digit year."); + + validator = $form.validate({ + rules: { + cardNumber: { + required: true, + creditcard: true, + digits: true + }, + expMonth: { + required: true, + month: true + }, + expYear: { + required: true, + year: true + }, + cvCode: { + required: true, + digits: true + } + }, + highlight: function(element) { + $(element).closest('.form-control').removeClass('success').addClass('error'); + }, + unhighlight: function(element) { + $(element).closest('.form-control').removeClass('error').addClass('success'); + }, + errorPlacement: function(error, element) { + $(element).closest('.form-group').append(error); + } + }); + + paymentFormReady = function() { + if ($form.find('[name=cardNumber]').hasClass("success") && + $form.find('[name=expMonth]').hasClass("success") && + $form.find('[name=expYear]').hasClass("success") && + $form.find('[name=cvCode]').val().length > 1) { + return true; + } else { + return false; + } + } + + $form.find('[type=submit]').prop('disabled', true); + var readyInterval = setInterval(function() { + if (paymentFormReady()) { + $form.find('[type=submit]').prop('disabled', false); + clearInterval(readyInterval); + } + }, 250); + +}); + diff --git a/digitalglarus/static/digitalglarus/js/utils.js b/digitalglarus/static/digitalglarus/js/utils.js new file mode 100644 index 00000000..e824dafc --- /dev/null +++ b/digitalglarus/static/digitalglarus/js/utils.js @@ -0,0 +1,23 @@ +$( document ).ready(function() { + + + function printDiv(divName) { + var printContents = document.getElementById(divName).innerHTML; + var originalContents = document.body.innerHTML; + + document.body.innerHTML = printContents; + + window.print(); + + document.body.innerHTML = originalContents; + } + + + $('.print').on('click', function(e){ + printDiv(e.target.getAttribute("data-print") ); + + }); + + + +}); \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking.html b/digitalglarus/templates/digitalglarus/booking.html new file mode 100644 index 00000000..85b15fd2 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking.html @@ -0,0 +1,61 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking_orders_detail.html b/digitalglarus/templates/digitalglarus/booking_orders_detail.html new file mode 100644 index 00000000..9dd8a25c --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking_orders_detail.html @@ -0,0 +1,94 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + + + +
+
+
+
+

Your Booking Detail

+
+

InvoiceGet PDF

+ +

Order Number

+

#{{order.id}}

+ +

Billed to :

+

{{user.name}}
+ {{order.billing_address.street_address}},{{order.billing_address.postal_code}}
+ {{order.billing_address.city}}, {{order.billing_address.country}}. +

+ +

Payment Method

+

+ {{order.cc_brand}} ending **** {{order.last4}}
+ {{user.email}} +

+ +
+

Order Summary

+

+ Dates: {{start_date}} - {{end_date}}
+

+

Total days {{booking_days}}

+

{{original_price|floatformat}}CHF

+ {% if free_days %} +

Free days {{free_days}}

+

-{{total_discount|floatformat}}CHF

+ {% endif %} +
+

Total

+

{{final_price|floatformat}}CHF

+
+ +

+ View my bookings
Go to my page +

+
+ +
+ +
+
+
+

Order Summary

+
+
+

Dates: {{start_date}} - {{end_date}}

+ +

Total days {{booking_days}}

+

{{original_price|floatformat}}CHF

+ {% if free_days %} +

Free days {{free_days}}

+

-{{total_discount|floatformat}}CHF

+ {% endif %} +
+

Total

+

{{final_price|floatformat}}CHF

+
+ +
+
+
+ + +{% if stripe_key %} + + +{%endif%} + +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking_orders_list.html b/digitalglarus/templates/digitalglarus/booking_orders_list.html new file mode 100644 index 00000000..9fa3d793 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking_orders_list.html @@ -0,0 +1,73 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + + +
+
+
+
+

Your Order History

+
+

Member Name

+

{{request.user.name}}

+
+

Active Membership

+

2016.11.13-2016.12.13

+
+

Booking history

+ + + + + + + + + + + {% for order in orders%} + + + + + + + {% endfor %} + +
#Booking datesDaysInvoice
{{order.id}}{{order.booking.start_date}}-{{order.booking.end_date}}{{order.booking_days}}View
+ +

Billing AdressEdit

+

+ {{request.user.name}} +

+

+ {{billing_address.street_address}},{{billing_address.postal_code}}
+ {{billing_address.city}}, {{billing_address.country}}. +

+ + +
+
+ + +
+
+

Thank You!

+
+ + + +
+ +

This box is here just to thank you

+
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/booking_payment.html b/digitalglarus/templates/digitalglarus/booking_payment.html new file mode 100644 index 00000000..1b465282 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking_payment.html @@ -0,0 +1,215 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + +
+
+
+
+

Booking

+ +
+ +

+ Your Digital Glarus Membership enables + you to use our coworking space and it includes + 2 working days for the month you signed up. + The membership fee is a monthly subscription. + Additional day costs + 15CHF per day. More than 17 days a month it + will charge only 290CHF/month. +

+ {% if is_free %} +

Billing Adress

+ + +
+
+

Your Booking is TOTALLY FREE

+

+ {% else %} +

Billing Adress

+ + {% if credit_card_data %} +
+

Credit Card

+

Last 4: *****{{credit_card_data.last4}}

+

Type: {{credit_card_data.cc_brand}}

+ +
+ {% else %} +

Credit Card (Last used)

+ + {% endif %} + {% endif %} +
+
+
+
+
+

Booking Summary

+
+
+

Dates {{start_date}} - {{end_date}}

+

+ Change dates +

+
+

Total days: {{booking_days}}

+

{{original_price|floatformat}}CHF

+
+ {% if free_days %} +

Free days: {{free_days}}

+

-{{total_discount|floatformat}}CHF

+ {% endif %} +
+

Total

+

{{final_price|floatformat}}CHF

+
+ +
+
+
+ +
+
+
+
+
+
+
+
+ + + +
+ + + +
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+ + +{% if stripe_key %} + + +{%endif%} + +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/confirm_reset_password.html b/digitalglarus/templates/digitalglarus/confirm_reset_password.html new file mode 100644 index 00000000..40a93730 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/confirm_reset_password.html @@ -0,0 +1,55 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} + +
+
+
+
+
+

Set your new password

+
+ + + +
+
+
+
+
+ +
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/emails/membership_charge.html b/digitalglarus/templates/digitalglarus/emails/membership_charge.html new file mode 100644 index 00000000..d2d3971f --- /dev/null +++ b/digitalglarus/templates/digitalglarus/emails/membership_charge.html @@ -0,0 +1,136 @@ +{% load static from staticfiles %} + + + + + + +Oxygen Invoice + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+ logo + +
+
+ +
+
+
+
+ + + + + + + + + + +
+ Thank you for your subscription. +
+ Your monthly membership for period {{membership_start_date|date}} - {{membership_start_date|date}} has been charged.
You can view your invoice clicking on the button below. +
+ +
+
+
+
+ + +
+ ungleich
+
+
+
+ + + diff --git a/digitalglarus/templates/digitalglarus/emails/membership_charge.txt b/digitalglarus/templates/digitalglarus/emails/membership_charge.txt new file mode 100644 index 00000000..d2d3971f --- /dev/null +++ b/digitalglarus/templates/digitalglarus/emails/membership_charge.txt @@ -0,0 +1,136 @@ +{% load static from staticfiles %} + + + + + + +Oxygen Invoice + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+ logo + +
+
+ +
+
+
+
+ + + + + + + + + + +
+ Thank you for your subscription. +
+ Your monthly membership for period {{membership_start_date|date}} - {{membership_start_date|date}} has been charged.
You can view your invoice clicking on the button below. +
+ +
+
+
+
+ + +
+ ungleich
+
+
+
+ + + diff --git a/digitalglarus/templates/digitalglarus/emails/membership_monthly_charge.html b/digitalglarus/templates/digitalglarus/emails/membership_monthly_charge.html new file mode 100644 index 00000000..68347768 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/emails/membership_monthly_charge.html @@ -0,0 +1,136 @@ +{% load static from staticfiles %} + + + + + + +Oxygen Invoice + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+ logo + +
+
+ +
+
+
+
+ + + + + + + + + + +
+ Thank you for your subscription. +
+ Your monthly membership for {{order.created_at|date:"F"}} has been charged.
You can view your invoice clicking on the button below. +
+ +
+
+
+
+ + +
+ ungleich
+
+
+
+ + + diff --git a/digitalglarus/templates/digitalglarus/emails/membership_monthly_charge.txt b/digitalglarus/templates/digitalglarus/emails/membership_monthly_charge.txt new file mode 100644 index 00000000..68347768 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/emails/membership_monthly_charge.txt @@ -0,0 +1,136 @@ +{% load static from staticfiles %} + + + + + + +Oxygen Invoice + + + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+ logo + +
+
+ +
+
+
+
+ + + + + + + + + + +
+ Thank you for your subscription. +
+ Your monthly membership for {{order.created_at|date:"F"}} has been charged.
You can view your invoice clicking on the button below. +
+ +
+
+
+
+ + +
+ ungleich
+
+
+
+ + + diff --git a/digitalglarus/templates/digitalglarus/emails/password_reset_email.html b/digitalglarus/templates/digitalglarus/emails/password_reset_email.html new file mode 100644 index 00000000..20244b11 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/emails/password_reset_email.html @@ -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 'digitalglarus:reset_password_confirm' uidb64=uid token=token %} + {% endblock %} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/emails/password_reset_email.txt b/digitalglarus/templates/digitalglarus/emails/password_reset_email.txt new file mode 100644 index 00000000..20244b11 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/emails/password_reset_email.txt @@ -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 'digitalglarus:reset_password_confirm' uidb64=uid token=token %} + {% endblock %} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/index.html b/digitalglarus/templates/digitalglarus/index.html index a2ad5827..152dd48e 100644 --- a/digitalglarus/templates/digitalglarus/index.html +++ b/digitalglarus/templates/digitalglarus/index.html @@ -20,7 +20,8 @@

Book a date today and dive in

-
+ Join now +
@@ -187,7 +188,7 @@ Join our community. Be our member now!

- Sign Up + Sign Up diff --git a/digitalglarus/templates/digitalglarus/login.html b/digitalglarus/templates/digitalglarus/login.html new file mode 100644 index 00000000..f61a3f9b --- /dev/null +++ b/digitalglarus/templates/digitalglarus/login.html @@ -0,0 +1,80 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_activated.html b/digitalglarus/templates/digitalglarus/membership_activated.html new file mode 100644 index 00000000..db641555 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_activated.html @@ -0,0 +1,57 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} +
+
+
+
+ + +
+

Membership Activated

+
+

Your Digital Glarus membership is successfully activated for the following date.

+
+

{{membership_dates}}

+

Now you can book your next coworking!

+
+ + + +
+
+
+
+ + + +
+
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_deactivated.html b/digitalglarus/templates/digitalglarus/membership_deactivated.html new file mode 100644 index 00000000..977c467c --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_deactivated.html @@ -0,0 +1,106 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} + + + +
+
+
+
+ + +
+

Membership Deactivation

+
+

Are you sure do you want to cancel your membership with us ?

+
+
+ + + + + + +
+
+
+
+ + + +
+
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_deactivated_success.html b/digitalglarus/templates/digitalglarus/membership_deactivated_success.html new file mode 100644 index 00000000..97f41a04 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_deactivated_success.html @@ -0,0 +1,54 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} +
+
+
+
+ + +
+

Membership Deactivated

+
+

Your Digital Glarus membership has been deactivated.

+
+
+ + + +
+
+
+
+ + + +
+
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_orders_detail.html b/digitalglarus/templates/digitalglarus/membership_orders_detail.html new file mode 100644 index 00000000..c8c5018c --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_orders_detail.html @@ -0,0 +1,81 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + + + +
+
+
+
+

Your membership invoice for {{order.created_at|date:"Y-m"}}

+
+

InvoiceGet PDF

+ +

Order Number

+

#{{order.id}}

+ +

Billed to :

+

{{user.name}}
+ {{order.billing_address.street_address}},{{order.billing_address.postal_code}}
+ {{order.billing_address.city}}, {{order.billing_address.country}}. +

+ +

Payment Method

+

+ {{order.cc_brand}} ending **** {{order.last4}}
+ {{user.email}} +

+ +
+

Order Summary

+

+ Dates: {{membership_start_date|date}} - {{membership_end_date|date}}
+

+

Membership month {{order.created_at|date:"F"}}

+

{{order.amount|floatformat}}CHF

+
+

Total

+

{{order.amount|floatformat}}CHF

+
+
+
+ +
+
+
+

Order Summary

+
+
+

Dates: {{membership_start_date|date}} - {{membership_end_date|date}}

+ +

Membership month {{order.created_at|date:"F"}}

+

{{order.amount|floatformat}}CHF

+
+
+

Total

+

{{order.amount|floatformat}}CHF

+
+ +
+
+
+ + +{% if stripe_key %} + + +{%endif%} + +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_orders_list.html b/digitalglarus/templates/digitalglarus/membership_orders_list.html new file mode 100644 index 00000000..57284121 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_orders_list.html @@ -0,0 +1,126 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + + +
+
+
+
+

Your Membership History

+
+

Member Name

+

{{request.user.name}}

+
+

Current Membership

+ {% if membership_start_date and membership_end_date%} +

{{membership_start_date|date}}-{{membership_end_date|date}}

+ {% else %} +

You don't have an active membership

+ {% endif %} +
+

Orders history

+ + + + + + + + + + + {% for order in orders%} + + + + + + + {% endfor %} + +
#Valid MonthDateInvoice
{{order.id}}{{order.created_at|date:"F"}}{{order.created_at|date}}View
+ +

Billing AdressEdit

+

+ {{request.user.name}} +

+ {% if billing_address %} +

+ {{billing_address.street_address}},{{billing_address.postal_code}}
+ {{billing_address.city}}, {{billing_address.country}}. +

+ {% else %} +

+ Edit your billing address +

+ {% endif %} +
+

Your Next Membership

+ {% if next_membership_start_date and next_membership_end_date%} + + + +

+ Dates: {{next_membership_start_date|date}} - {{next_membership_end_date|date}}
+

+ {% else %} +

+ You are not a member yet +

+ {% endif %} +
+ + Deactivate +
+
+
+
+

You will be charged on the first of the month until you + cancel your subscription. Previous charges won't be refunded.

+
+
+ +
+
+ + +
+ +
+ + {% if messages %} +
+

Message

+
+ {% for message in messages %} + + {% endfor %} +
+ {% else %} + +

Thank You!

+
+ + + + + + +
+

This box is here just to thank you

+ + {% endif %} + + + + +
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_payment.html b/digitalglarus/templates/digitalglarus/membership_payment.html new file mode 100644 index 00000000..0aa1c7ee --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_payment.html @@ -0,0 +1,186 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + +
+
+
+
+

Membership

+ +

Your Digital Glarus Membership enables + you to use our coworking space and it includes + 2 working days for the month you signed up. + The membership fee is a monthly subscription. + Additional day costs + 15CHF per day. More than 17 days a month it + will charge only 290CHF/month.

+

+ You will be charged on the first of the month until you + cancel your subscription. Previous charges won't be refunded. +

+
+

Member Name

+

{{request.user.name}}

+
+

Billing Adress

+ +

Credit Card

+ +
+
+
+
+
+

Order Summary

+
+
+

Digital Glarus Membership

+
+

valid {{membership_type.first_month_formated_range}}

+
+

1 person

+

Today's Total for {{membership_type.days_left}} days

+

{{membership_type.first_month_price|floatformat}}CHF

+
+

Total

+

{{membership_type.first_month_price|floatformat}}CHF

+
+
+
+ +
+
+
+
+
+
+
+ + + +
+ + + +
+
+
+
+
+ Digital Glarus
+ In der Au 7 Schwanden 8762 Switzerland +
info@digitalglarus.ch +
+ (044) 534-66-22 +

 

+
+
+

 

+
+
+
+
+ + +{% if stripe_key %} + + +{%endif%} + +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/membership_pricing.html b/digitalglarus/templates/digitalglarus/membership_pricing.html new file mode 100644 index 00000000..13349b22 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/membership_pricing.html @@ -0,0 +1,87 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} +
+
+
+
+
+ +

Digital Glarus Membership

+

{{membership_type.price}}CHF/month
(free working 2days included)

+
+
+
+ Get your Digital Glarus Membership for only + {{membership_type.price}}CHF + and get 2 working days for free! + The membership fee is a monthly subscription and + with every payment you help us to create a better + workspace. + You want to work more than 2 days per month in our + coworking space? Great! It's only 15CHF + per additional day. + You want more? That's really cool! + If you work 17 days a month or EVERY DAY of a month, + you can do so for 290CHF/month.. + And the best? The discount is + applied automatically! + Just become a member, book your + working day and we will take care of the rest. +

+ +
+ +
+
+
+
+
+ +
+
+
+
+ + Become our member + +
+

Our membership includes:

+
+
  • +
    Super-fast Internet
    +
    Any workspace in our common areas
    +
    Free 2 working day passes
    +
    Access to any of our great workshops
    +
    Special invitation to our fondue nights, cooking & hacking sessions, coworking & cohiking, and many more cool stuff.
    +
  • + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + Digital Glarus
    + In der Au 7 Schwanden 8762 Switzerland +
    info@digitalglarus.ch +
    + (044) 534-66-22 +

     

    +
    +
    +

     

    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/reset_password.html b/digitalglarus/templates/digitalglarus/reset_password.html new file mode 100644 index 00000000..265248fd --- /dev/null +++ b/digitalglarus/templates/digitalglarus/reset_password.html @@ -0,0 +1,57 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} + +
    +
    +
    +
    +
    +

    Reset Password

    +
    +

    To have your password reset, enter your email address below. + We will then send an email containing a link to reset your password.

    + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Digital Glarus
    + In der Au 7 Schwanden 8762 Switzerland +
    info@digitalglarus.ch +
    + (044) 534-66-22 +

     

    +
    +
    +

     

    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/signup.html b/digitalglarus/templates/digitalglarus/signup.html new file mode 100644 index 00000000..aea83f4e --- /dev/null +++ b/digitalglarus/templates/digitalglarus/signup.html @@ -0,0 +1,59 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles cms_tags bootstrap3%} +{% block title %}crowdfunding{% endblock %} + +{% block content %} + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Digital Glarus
    + In der Au 7 Schwanden 8762 Switzerland +
    info@digitalglarus.ch +
    + (044) 534-66-22 +

     

    +
    +
    +

     

    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/digitalglarus/user_billing_address.html b/digitalglarus/templates/digitalglarus/user_billing_address.html new file mode 100644 index 00000000..691ad57f --- /dev/null +++ b/digitalglarus/templates/digitalglarus/user_billing_address.html @@ -0,0 +1,91 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + +
    +
    +
    +
    +

    Billing Address

    + +
    +

    Billing Adress

    + + +
    +
    +
    + + + +
    + + + +
    +
    +
    +
    +
    + Digital Glarus
    + In der Au 7 Schwanden 8762 Switzerland +
    info@digitalglarus.ch +
    + (044) 534-66-22 +

     

    +
    +
    +

     

    +
    +
    +
    +
    + + +{% if stripe_key %} + + +{%endif%} + +{% endblock %} \ No newline at end of file diff --git a/digitalglarus/templates/new_base_glarus.html b/digitalglarus/templates/new_base_glarus.html index 48a65457..b156dd45 100644 --- a/digitalglarus/templates/new_base_glarus.html +++ b/digitalglarus/templates/new_base_glarus.html @@ -1,6 +1,5 @@ {% load static %} -{% load bootstrap3 %} -{% load staticfiles cms_tags menu_tags sekizai_tags menu_tags %} +{% load staticfiles cms_tags menu_tags sekizai_tags menu_tags bootstrap3 %} {% load i18n %} @@ -25,6 +24,11 @@ + + + + + @@ -53,7 +57,32 @@ - + + + + + @@ -78,7 +107,7 @@
  • - booking & price + booking & price
  • history @@ -89,13 +118,39 @@
  • Contact
  • -
  • - Log In + + {% if request.user.is_authenticated %} +
  • + {% else %} +
  • + Login +
  • + {% endif %} + @@ -135,16 +190,25 @@ - + + + + - + + + + + + + @@ -156,11 +220,30 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/digitalglarus/test_views.py b/digitalglarus/test_views.py index 5658a73c..63b2cc06 100644 --- a/digitalglarus/test_views.py +++ b/digitalglarus/test_views.py @@ -1,11 +1,25 @@ import json +from model_mommy import mommy +from unittest import mock from django.test import TestCase +from django.conf import settings from django.core.urlresolvers import reverse from django.core.urlresolvers import resolve from cms.test_utils.testcases import CMSTestCase +from django.contrib.auth.tokens import default_token_generator +from django.utils.http import urlsafe_base64_encode +from django.utils.encoding import force_bytes from cms.api import create_page +from membership.models import CustomUser, StripeCustomer +from utils.tests import BaseTestCase + + +from .views import LoginView, SignupView, PasswordResetView, PasswordResetConfirmView,\ + MembershipPricingView, MembershipPaymentView +from .models import MembershipType, MembershipOrder + class ContactViewTest(TestCase): def setUp(self): @@ -38,14 +52,243 @@ class ViewsTest(CMSTestCase): self.assertEqual(res2.status_code, 200) -class CalendarApiTestCase(TestCase): - def test_api_response(self): - calendar_api_url_1 = reverse('digitalglarus:calendar_api_1', kwargs={'month': '3', 'year': '2016'}) - res1 = self.client.get(calendar_api_url_1) - pd = json.loads(res1.content.decode('utf-8')) - self.assertEqual(pd['month'], '3') - self.assertEqual(pd['year'], '2016') +class MembershipPricingViewTest(BaseTestCase): - # TODO:check post - # calendar_api_url = reverse('digitalglarus:calendar_api') - # res = self.client.get(calendar_api_url) + def setUp(self): + super(MembershipPricingViewTest, self).setUp() + + self.membership_type = mommy.make(MembershipType) + self.url = reverse('digitalglarus:membership_pricing') + self.view = MembershipPricingView + self.expected_template = 'digitalglarus/membership_pricing.html' + + 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 data + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['membership_type'], self.membership_type) + self.assertTemplateUsed(response, self.expected_template) + + +class MembershipPaymentViewTest(BaseTestCase): + + def setUp(self): + super(MembershipPaymentViewTest, self).setUp() + + self.membership_type = mommy.make(MembershipType) + self.url = reverse('digitalglarus:membership_payment') + self.view = MembershipPaymentView + self.expected_template = 'digitalglarus/membership_payment.html' + + # post data + self.billing_address = { + 'street_address': 'street name', + 'city': 'MyCity', + 'postal_code': '32123123123123', + 'country': 'VE', + 'token': 'a23kfmslwxhkwis', + 'membership_type': self.membership_type.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('digitalglarus:signup'), + reverse('digitalglarus:membership_payment')) + 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['stripe_key'], + settings.STRIPE_API_PUBLIC_KEY) + self.assertEqual(response.context['membership_type'], + self.membership_type) + + @mock.patch('utils.stripe_utils.StripeUtils.create_customer') + def test_post(self, stripe_mocked_call): + + # Anonymous user should get redirect to login + # response = self.client.post(self.url) + # expected_url = "%s?next=%s" % (reverse('digitalglarus:signup'), + # reverse('digitalglarus:membership_payment')) + # self.assertRedirects(response, expected_url=expected_url, + # status_code=302, target_status_code=200) + + # Customer user should be able to pay + stripe_mocked_call.return_value = { + 'paid': True, + 'response_object': self.stripe_mocked_customer, + 'error': None + } + response = self.customer_client.post(self.url, self.billing_address) + self.assertEqual(response.status_code, 200) + self.assertTrue(StripeCustomer.objects.filter(user__email=self.customer.email).exists()) + stripe_customer = StripeCustomer.objects.get(user__email=self.customer.email) + self.assertEqual(stripe_customer.user, self.customer) + self.assertTrue(MembershipOrder.objects.filter(customer=stripe_customer).exists()) + membership_order = MembershipOrder.objects.filter(customer=stripe_customer).first() + session_data = { + 'membership_price': membership_order.membership.type.first_month_price, + 'membership_dates': membership_order.membership.type.first_month_formated_range + } + self.assertEqual(session_data.get('membership_price'), + self.session_data.get('membership_price')) + self.assertEqual(session_data.get('membership_dates'), + self.session_data.get('membership_dates')) + + # self.assertTrue(HostingOrder.objects.filter(customer=stripe_customer).exists()) + # hosting_order = HostingOrder.objects.filter(customer=stripe_customer)[0] + # vm_plan = { + # 'cores': hosting_order.vm_plan.cores, + # '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, + # 'configuration': hosting_order.vm_plan.configuration + # } + # self.assertEqual(vm_plan, self.session_data.get('vm_specs')) + + +class LoginViewTest(TestCase): + + def setUp(self): + self.url = reverse('digitalglarus:login') + self.view = LoginView + self.expected_template = 'digitalglarus/login.html' + self.user = mommy.make('membership.CustomUser') + self.password = 'fake_password' + self.user.set_password(self.password) + self.user.save() + + 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_anonymous_user_can_login(self): + data = { + 'email': self.user.email, + 'password': self.password + } + response = self.client.post(self.url, data=data, follow=True) + self.assertEqual(response.context['user'], self.user) + self.assertEqual(response.status_code, 200) + + +class SignupViewTest(TestCase): + + def setUp(self): + self.url = reverse('digitalglarus:signup') + self.expected_template = 'digitalglarus/signup.html' + self.view = SignupView + self.signup_data = { + 'name': 'ungleich', + 'email': 'test@ungleich.com', + 'password': 'fake_password', + 'confirm_password': 'fake_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_anonymous_user_can_signup(self): + response = self.client.post(self.url, data=self.signup_data, follow=True) + 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('digitalglarus:reset_password') + self.view = PasswordResetView + self.expected_template = 'digitalglarus/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 = 'digitalglarus/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('digitalglarus: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/digitalglarus/urls.py b/digitalglarus/urls.py index 26f606a8..6ed5271a 100644 --- a/digitalglarus/urls.py +++ b/digitalglarus/urls.py @@ -2,14 +2,44 @@ from django.conf.urls import url from django.utils.translation import ugettext_lazy as _ from . import views -from .views import ContactView, IndexView, AboutView, HistoryView -from membership.views import LoginRegistrationView +from .views import ContactView, IndexView, AboutView, HistoryView, LoginView, SignupView,\ + PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView,\ + MembershipPricingView, BookingSelectDatesView, BookingPaymentView, OrdersBookingDetailView,\ + BookingOrdersListView, MembershipOrdersListView, OrdersMembershipDetailView, \ + MembershipDeactivateView, MembershipDeactivateSuccessView, UserBillingAddressView +# from membership.views import LoginRegistrationView urlpatterns = [ url(_(r'^$'), IndexView.as_view(), name='landing'), url(_(r'contact/?$'), ContactView.as_view(), name='contact'), - url(_(r'login/?$'), LoginRegistrationView.as_view(), name='login'), + url(_(r'login/?$'), LoginView.as_view(), name='login'), + url(_(r'signup/?$'), SignupView.as_view(), name='signup'), + url(r'^logout/?$', 'django.contrib.auth.views.logout', + {'next_page': '/digitalglarus/login?logged_out=true'}, name='logout'), + url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'), + url(r'reset-password-confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', + PasswordResetConfirmView.as_view(), name='reset_password_confirm'), url(_(r'history/?$'), HistoryView.as_view(), name='history'), + url(_(r'users/billing_address/?$'), UserBillingAddressView.as_view(), name='user_billing_address'), + url(_(r'booking/?$'), BookingSelectDatesView.as_view(), name='booking'), + url(_(r'booking/payment/?$'), BookingPaymentView.as_view(), name='booking_payment'), + url(_(r'booking/orders/(?P\d+)/?$'), OrdersBookingDetailView.as_view(), + name='booking_orders_detail'), + url(_(r'booking/orders/?$'), BookingOrdersListView.as_view(), + name='booking_orders_list'), + url(_(r'membership/payment/?$'), MembershipPaymentView.as_view(), name='membership_payment'), + url(_(r'membership/activated/?$'), MembershipActivatedView.as_view(), + name='membership_activated'), + url(_(r'membership/deactivate/?$'), MembershipDeactivateView.as_view(), + name='membership_deactivate'), + url(_(r'membership/deactivate/success/?$'), MembershipDeactivateSuccessView.as_view(), + name='membership_deactivate_success'), + url(_(r'membership/pricing/?$'), MembershipPricingView.as_view(), + name='membership_pricing'), + url(_(r'membership/orders/(?P\d+)/?$'), OrdersMembershipDetailView.as_view(), + name='membership_orders_detail'), + url(_(r'membership/orders/?$'), MembershipOrdersListView.as_view(), + name='membership_orders_list'), url(_(r'supporters/?$'), views.supporters, name='supporters'), url(r'calendar_api/(?P\d+)/(?P\d+)?$', views.CalendarApi.as_view(),name='calendar_api_1'), url(r'calendar_api/', views.CalendarApi.as_view(),name='calendar_api'), diff --git a/digitalglarus/views.py b/digitalglarus/views.py index c8cbf336..73af4ca2 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -1,26 +1,585 @@ +import json import datetime +from django.conf import settings from django.shortcuts import get_object_or_404, render from django.forms import ModelForm from django.http import HttpResponseRedirect -from django.core.urlresolvers import reverse_lazy +from django.core.urlresolvers import reverse_lazy, reverse from django.utils.translation import ugettext_lazy as _ -from django.views.generic import TemplateView +from django.views.generic import TemplateView, UpdateView +from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.translation import get_language from djangocms_blog.models import Post from django.contrib import messages from django.http import JsonResponse -from django.views.generic import View +from django.views.generic import View, DetailView, ListView + from .models import Supporter from utils.forms import ContactUsForm +from utils.mailer import BaseEmail + from django.views.generic.edit import FormView from membership.calendar.calendar import BookCalendar -from membership.models import Calendar as CalendarModel -import json -from django.contrib.auth import logout +from membership.models import Calendar as CalendarModel, StripeCustomer +from utils.views import LoginViewMixin, SignupViewMixin, \ + PasswordResetViewMixin, PasswordResetConfirmViewMixin +from utils.forms import PasswordResetRequestForm, UserBillingAddressForm +from utils.stripe_utils import StripeUtils +from utils.models import UserBillingAddress + + +from .forms import LoginForm, SignupForm, MembershipBillingForm, BookingDateForm,\ + BookingBillingForm + +from .models import MembershipType, Membership, MembershipOrder, Booking, BookingPrice,\ + BookingOrder + +from .mixins import MembershipRequiredMixin, IsNotMemberMixin + + +class IndexView(TemplateView): + template_name = "digitalglarus/index.html" + + +class LoginView(LoginViewMixin): + template_name = "digitalglarus/login.html" + form_class = LoginForm + + def get_success_url(self): + # redirect to membership orders list if user has at least one. + if self.request.user \ + and MembershipOrder.objects.filter(customer__user=self.request.user): + + return reverse_lazy('digitalglarus:membership_orders_list') + + return reverse_lazy('digitalglarus:membership_pricing') + + +class SignupView(SignupViewMixin): + template_name = "digitalglarus/signup.html" + form_class = SignupForm + success_url = reverse_lazy('digitalglarus:login') + + +class PasswordResetView(PasswordResetViewMixin): + template_name = 'digitalglarus/reset_password.html' + success_url = reverse_lazy('digitalglarus:login') + form_class = PasswordResetRequestForm + template_email_path = 'digitalglarus/emails/' + + +class PasswordResetConfirmView(PasswordResetConfirmViewMixin): + template_name = 'digitalglarus/confirm_reset_password.html' + success_url = reverse_lazy('digitalglarus:login') + + +class HistoryView(TemplateView): + template_name = "digitalglarus/history.html" + + def get_context_data(self, *args, **kwargs): + context = super(HistoryView, self).get_context_data(**kwargs) + supporters = Supporter.objects.all() + context.update({ + 'supporters': supporters + }) + return context + + +class BookingSelectDatesView(LoginRequiredMixin, MembershipRequiredMixin, FormView): + template_name = "digitalglarus/booking.html" + form_class = BookingDateForm + membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing') + login_url = reverse_lazy('digitalglarus:login') + success_url = reverse_lazy('digitalglarus:booking_payment') + + def get_form_kwargs(self): + kwargs = super(BookingSelectDatesView, self).get_form_kwargs() + kwargs.update({'user': self.request.user}) + return kwargs + + def form_valid(self, form): + user = self.request.user + start_date = form.cleaned_data.get('start_date') + end_date = form.cleaned_data.get('end_date') + booking_days = (end_date - start_date).days + 1 + + price_per_day = BookingPrice.objects.get().price_per_day + + original_price, final_price, free_days = Booking.\ + booking_price(user, start_date, end_date) + + total_discount = price_per_day * free_days + + self.request.session.update({ + 'original_price': original_price, + 'final_price': final_price, + 'total_discount': total_discount, + 'booking_price_per_day': price_per_day, + 'booking_days': booking_days, + 'free_days': free_days, + 'start_date': start_date.strftime('%m/%d/%Y'), + 'end_date': end_date.strftime('%m/%d/%Y'), + 'is_free': final_price == 0 + }) + return super(BookingSelectDatesView, self).form_valid(form) + + +class BookingPaymentView(LoginRequiredMixin, MembershipRequiredMixin, FormView): + template_name = "digitalglarus/booking_payment.html" + form_class = BookingBillingForm + membership_redirect_url = reverse_lazy('digitalglarus:membership_pricing') + # success_url = reverse_lazy('digitalglarus:booking_payment') + booking_needed_fields = ['original_price', 'final_price', 'booking_days', 'free_days', + 'start_date', 'end_date', 'booking_price_per_day', + 'total_discount', 'is_free'] + + def dispatch(self, request, *args, **kwargs): + from_booking = all(field in request.session.keys() + for field in self.booking_needed_fields) + if not from_booking: + return HttpResponseRedirect(reverse('digitalglarus:booking')) + + return super(BookingPaymentView, self).dispatch(request, *args, **kwargs) + + def get_success_url(self, order_id): + return reverse('digitalglarus:booking_orders_detail', kwargs={'pk': order_id}) + + def get_form_kwargs(self): + current_billing_address = self.request.user.billing_addresses.first() + form_kwargs = super(BookingPaymentView, self).get_form_kwargs() + form_kwargs.update({ + 'initial': { + 'start_date': self.request.session.get('start_date'), + 'end_date': self.request.session.get('end_date'), + 'price': self.request.session.get('final_price'), + 'street_address': current_billing_address.street_address, + 'city': current_billing_address.city, + 'postal_code': current_billing_address.postal_code, + 'country': current_billing_address.country, + } + }) + return form_kwargs + + def get_context_data(self, *args, **kwargs): + context = super(BookingPaymentView, self).get_context_data(*args, **kwargs) + + booking_data = {key: self.request.session.get(key) + for key in self.booking_needed_fields} + user = self.request.user + last_booking_order = BookingOrder.objects.filter(customer__user=user).last() + last_membership_order = MembershipOrder.objects.filter(customer__user=user).last() + credit_card_data = last_booking_order.get_booking_cc_data() if last_booking_order \ + and last_booking_order.get_booking_cc_data() \ + else last_membership_order.get_membership_order_cc_data() + + booking_data.update({ + 'credit_card_data': credit_card_data if credit_card_data else None, + 'stripe_key': settings.STRIPE_API_PUBLIC_KEY + }) + context.update(booking_data) + return context + + def form_valid(self, form): + data = form.cleaned_data + context = self.get_context_data() + token = data.get('token') + start_date = data.get('start_date') + end_date = data.get('end_date') + is_free = context.get('is_free') + normal_price, final_price, free_days = Booking.\ + booking_price(self.request.user, start_date, end_date) + charge = None + + # if not credit_card_needed: + # Get or create stripe customer + customer = StripeCustomer.get_or_create(email=self.request.user.email, + token=token) + if not customer: + form.add_error("__all__", "Invalid credit card") + return self.render_to_response(self.get_context_data(form=form)) + + # If booking is not free, make the stripe charge + if not is_free: + # Make stripe charge to a customer + stripe_utils = StripeUtils() + charge_response = stripe_utils.make_charge(amount=final_price, + customer=customer.stripe_id) + charge = charge_response.get('response_object') + + # Check if the payment was approved + if not charge: + context.update({ + 'paymentError': charge_response.get('error'), + 'form': form + }) + return render(self.request, self.template_name, context) + + charge = charge_response.get('response_object') + + # Create Billing Address for Membership Order + billing_address = form.save() + + # Create Billing Address for User if he does not have one + if not customer.user.billing_addresses.count(): + data.update({ + 'user': customer.user.id + }) + billing_address_user_form = UserBillingAddressForm(data) + billing_address_user_form.is_valid() + billing_address_user_form.save() + + # Create Booking + booking_data = { + 'start_date': start_date, + 'end_date': end_date, + 'start_date': start_date, + 'free_days': free_days, + 'price': normal_price, + 'final_price': final_price, + } + booking = Booking.create(booking_data) + + # Create Booking order + order_data = { + 'booking': booking, + 'customer': customer, + 'billing_address': billing_address, + 'stripe_charge': charge, + 'amount': final_price, + 'original_price': normal_price, + 'special_month_price': BookingPrice.objects.last().special_month_price, + } + order = BookingOrder.create(order_data) + + return HttpResponseRedirect(self.get_success_url(order.id)) + + +class MembershipPricingView(TemplateView): + template_name = "digitalglarus/membership_pricing.html" + + def get_context_data(self, **kwargs): + context = super(MembershipPricingView, self).get_context_data(**kwargs) + membership_type = MembershipType.objects.last() + context.update({ + 'membership_type': membership_type + }) + return context + + +class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView): + template_name = "digitalglarus/membership_payment.html" + login_url = reverse_lazy('digitalglarus:signup') + form_class = MembershipBillingForm + already_member_redirect_url = reverse_lazy('digitalglarus:membership_orders_list') + + def get_form_kwargs(self): + self.membership_type = MembershipType.objects.get(name='standard') + form_kwargs = super(MembershipPaymentView, self).get_form_kwargs() + form_kwargs.update({ + 'initial': { + 'membership_type': self.membership_type.id + } + }) + return form_kwargs + + def get_context_data(self, **kwargs): + context = super(MembershipPaymentView, self).get_context_data(**kwargs) + context.update({ + 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, + 'membership_type': self.membership_type + }) + return context + + def post(self, request, *args, **kwargs): + form = self.get_form() + + if form.is_valid(): + data = form.cleaned_data + context = self.get_context_data() + token = data.get('token') + membership_type = data.get('membership_type') + + # Get or create stripe customer + customer = StripeCustomer.get_or_create(email=self.request.user.email, + token=token) + if not customer: + form.add_error("__all__", "Invalid credit card") + return self.render_to_response(self.get_context_data(form=form)) + + # Make stripe charge to a customer + stripe_utils = StripeUtils() + charge_response = stripe_utils.make_charge(amount=membership_type.first_month_price, + customer=customer.stripe_id) + charge = charge_response.get('response_object') + + # Check if the payment was approved + if not charge: + context.update({ + 'paymentError': charge_response.get('error'), + 'form': form + }) + return render(request, self.template_name, context) + + charge = charge_response.get('response_object') + + # Create Billing Address + billing_address = form.save() + + # Create Billing Address for User if he does not have one + if not customer.user.billing_addresses.count(): + data.update({ + 'user': customer.user.id + }) + billing_address_user_form = UserBillingAddressForm(data) + billing_address_user_form.is_valid() + billing_address_user_form.save() + + # Get membership dates + membership_start_date, membership_end_date = membership_type.first_month_range + + # Create membership plan + membership_data = { + 'type': membership_type, + 'start_date': membership_start_date, + 'end_date': membership_end_date + } + membership = Membership.create(membership_data) + + # Create membership order + order_data = { + 'membership': membership, + 'customer': customer, + 'billing_address': billing_address, + 'stripe_charge': charge, + 'amount': membership_type.first_month_price, + 'start_date': membership_start_date, + 'end_date': membership_end_date + } + + membership_order = MembershipOrder.create(order_data) + + request.session.update({ + 'membership_price': membership.type.first_month_price, + 'membership_dates': membership.type.first_month_formated_range + }) + + context = { + 'membership': membership, + 'order': membership_order, + 'membership_start_date': membership_start_date, + 'membership_end_date': membership_end_date, + 'base_url': "{0}://{1}".format(request.scheme, request.get_host()) + + } + email_data = { + 'subject': 'Your membership has been charged', + 'to': request.user.email, + 'context': context, + 'template_name': 'membership_charge', + 'template_path': 'digitalglarus/emails/' + } + email = BaseEmail(**email_data) + email.send() + + return HttpResponseRedirect(reverse('digitalglarus:membership_activated')) + + else: + return self.form_invalid(form) + + +class MembershipActivatedView(TemplateView): + template_name = "digitalglarus/membership_activated.html" + + def get_context_data(self, **kwargs): + context = super(MembershipActivatedView, self).get_context_data(**kwargs) + membership_price = self.request.session.get('membership_price') + membership_dates = self.request.session.get('membership_dates') + context.update({ + 'membership_price': membership_price, + 'membership_dates': membership_dates, + }) + return context + + +class MembershipDeactivateView(LoginRequiredMixin, UpdateView): + template_name = "digitalglarus/membership_deactivated.html" + model = Membership + success_message = "Your membership has been deactivated :(" + success_url = reverse_lazy('digitalglarus:membership_orders_list') + login_url = reverse_lazy('digitalglarus:login') + fields = '__all__' + + def get_object(self): + membership_order = MembershipOrder.objects.\ + filter(customer__user=self.request.user).last() + if not membership_order: + raise AttributeError("Membership does not exists") + membership = membership_order.membership + return membership + + def post(self, *args, **kwargs): + membership = self.get_object() + membership.deactivate() + + messages.add_message(self.request, messages.SUCCESS, self.success_message) + + return HttpResponseRedirect(self.success_url) + + +class UserBillingAddressView(LoginRequiredMixin, UpdateView): + model = UserBillingAddress + form_class = UserBillingAddressForm + template_name = "digitalglarus/user_billing_address.html" + success_url = reverse_lazy('digitalglarus:user_billing_address') + success_message = "Billing Address Updated" + + def get_success_url(self): + next_url = self.request.POST.get('next') if self.request.POST.get('next')\ + else self.success_url + + return next_url + + def form_valid(self, form): + """ + If the form is valid, save the associated model. + """ + messages.add_message(self.request, messages.SUCCESS, self.success_message) + self.object = form.save() + return super(UserBillingAddressView, self).form_valid(form) + + def get_form_kwargs(self): + current_billing_address = self.request.user.billing_addresses.first() + form_kwargs = super(UserBillingAddressView, self).get_form_kwargs() + + if not current_billing_address: + return form_kwargs + + form_kwargs.update({ + 'initial': { + 'street_address': current_billing_address.street_address, + 'city': current_billing_address.city, + 'postal_code': current_billing_address.postal_code, + 'country': current_billing_address.country, + } + }) + return form_kwargs + + def get_object(self): + current_billing_address = self.request.user.billing_addresses.filter(current=True).last() + if not current_billing_address: + raise AttributeError("Billing Address does not exists") + return current_billing_address + + +class MembershipDeactivateSuccessView(LoginRequiredMixin, TemplateView): + template_name = "digitalglarus/membership_deactivated_success.html" + + +class MembershipOrdersListView(LoginRequiredMixin, ListView): + template_name = "digitalglarus/membership_orders_list.html" + context_object_name = "orders" + login_url = reverse_lazy('digitalglarus:login') + model = MembershipOrder + paginate_by = 10 + + def get_context_data(self, **kwargs): + context = super(MembershipOrdersListView, self).get_context_data(**kwargs) + start_date, end_date = MembershipOrder.current_membership_dates(self.request.user) + next_start_date, next_end_date = MembershipOrder.next_membership_dates(self.request.user) + current_billing_address = self.request.user.billing_addresses.filter(current=True).last() + context.update({ + 'membership_start_date': start_date, + 'membership_end_date': end_date, + 'next_membership_start_date': next_start_date, + 'next_membership_end_date': next_end_date, + 'billing_address': current_billing_address + }) + return context + + def get_queryset(self): + queryset = super(MembershipOrdersListView, self).get_queryset() + queryset = queryset.filter(customer__user=self.request.user) + return queryset + + +class OrdersMembershipDetailView(LoginRequiredMixin, DetailView): + template_name = "digitalglarus/membership_orders_detail.html" + context_object_name = "order" + login_url = reverse_lazy('digitalglarus:login') + # permission_required = ['view_hostingorder'] + model = MembershipOrder + + def get_context_data(self, **kwargs): + context = super(OrdersMembershipDetailView, self).get_context_data(**kwargs) + start_date, end_date = self.object.get_membership_range_date() + context.update({ + 'membership_start_date': start_date, + 'membership_end_date': end_date, + }) + return context + + +class OrdersBookingDetailView(LoginRequiredMixin, DetailView): + template_name = "digitalglarus/booking_orders_detail.html" + context_object_name = "order" + login_url = reverse_lazy('digitalglarus:login') + # permission_required = ['view_hostingorder'] + model = BookingOrder + + def get_context_data(self, *args, **kwargs): + + context = super(OrdersBookingDetailView, self).get_context_data(**kwargs) + + bookig_order = self.object + booking = bookig_order.booking + + start_date = booking.start_date + end_date = booking.end_date + free_days = booking.free_days + + booking_days = (end_date - start_date).days + 1 + original_price = booking.price + final_price = booking.final_price + + context.update({ + 'original_price': original_price, + 'total_discount': original_price - final_price, + 'final_price': final_price, + 'booking_days': booking_days, + 'free_days': free_days, + 'start_date': start_date.strftime('%m/%d/%Y'), + 'end_date': end_date.strftime('%m/%d/%Y'), + }) + + return context + + +class BookingOrdersListView(LoginRequiredMixin, ListView): + template_name = "digitalglarus/booking_orders_list.html" + context_object_name = "orders" + login_url = reverse_lazy('digitalglarus:login') + model = BookingOrder + paginate_by = 10 + + def get_context_data(self, **kwargs): + context = super(BookingOrdersListView, self).get_context_data(**kwargs) + current_billing_address = self.request.user.billing_addresses.filter(current=True).last() + context.update({ + 'billing_address': current_billing_address + }) + return context + + def get_queryset(self): + queryset = super(BookingOrdersListView, self).get_queryset() + queryset = queryset.filter(customer__user=self.request.user) + return queryset + + +############## OLD VIEWS class CalendarApi(View): def get(self,request,month,year): calendar = BookCalendar(request.user,requested_month=month).formatmonth(int(year),int(month)) @@ -46,22 +605,6 @@ class ContactView(FormView): return super(ContactView, self).form_valid(form) -class IndexView(TemplateView): - template_name = "digitalglarus/old_index.html" - - -class HistoryView(TemplateView): - template_name = "digitalglarus/history.html" - - def get_context_data(self, **kwargs): - context = super(HistoryView, self).get_context_data(**kwargs) - supporters = Supporter.objects.all() - context.update({ - 'supporters': supporters - }) - return context - - class AboutView(TemplateView): template_name = "digitalglarus/about.html" diff --git a/dynamicweb/settings/prod.py b/dynamicweb/settings/prod.py index 0d77d1ab..74d3a013 100644 --- a/dynamicweb/settings/prod.py +++ b/dynamicweb/settings/prod.py @@ -7,6 +7,7 @@ ADMINS = ( ) # ('Sanghee Kim', 'sanghee.kim@ungleich.ch'), +DEBUG=False EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index c431f7ce..dd6c64d4 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -56,7 +56,7 @@ $( document ).ready(function() { //set token on a hidden input $('#id_token').val(token); - $('#donation-form').submit(); + $('#billing-form').submit(); } }); } diff --git a/hosting/views.py b/hosting/views.py index 1a6cbeb8..6b21c91b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -17,7 +17,7 @@ from stored_messages.api import mark_read from membership.models import CustomUser, StripeCustomer from utils.stripe_utils import StripeUtils from utils.forms import BillingAddressForm, PasswordResetRequestForm -from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin +from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin from utils.mailer import BaseEmail from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder from .forms import HostingUserSignupForm, HostingUserLoginForm @@ -137,31 +137,10 @@ class IndexView(View): return render(request, self.template_name, context) -class LoginView(FormView): - template_name = 'hosting/login.html' - success_url = reverse_lazy('hosting:orders') +class LoginView(LoginViewMixin): + template_name = "hosting/login.html" form_class = HostingUserLoginForm - moodel = CustomUser - - def get_success_url(self): - next_url = self.request.session.get('next', self.success_url) - return next_url - - def form_valid(self, form): - email = form.cleaned_data.get('email') - password = form.cleaned_data.get('password') - auth_user = authenticate(email=email, password=password) - - if auth_user: - login(self.request, auth_user) - return HttpResponseRedirect(self.get_success_url()) - - return HttpResponseRedirect(self.get_success_url()) - - def get(self, request, *args, **kwargs): - if self.request.user.is_authenticated(): - return HttpResponseRedirect(reverse('hosting:notifications')) - return super(LoginView, self).get(request, *args, **kwargs) + success_url = reverse_lazy('hosting:orders') class SignupView(CreateView): @@ -196,32 +175,6 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin): template_name = 'hosting/confirm_reset_password.html' 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.') - # 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(LoginRequiredMixin, TemplateView): template_name = 'hosting/notifications.html' diff --git a/membership/models.py b/membership/models.py index 7672a7b4..21dea515 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,5 +1,8 @@ from datetime import datetime + + + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User, AbstractBaseUser, BaseUserManager, AbstractUser, PermissionsMixin @@ -21,7 +24,6 @@ def get_anonymous_user_instance(User): validation_slug=make_password(None)) - class MyUserManager(BaseUserManager): def create_user(self, email, name, password=None): """ @@ -86,6 +88,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): else: return None + @classmethod + def get_all_members(cls): + return cls.objects.filter(stripecustomer__membershiporder__isnull=False) + @classmethod def validate_url(cls, validation_slug): user = cls.objects.filter(validation_slug=validation_slug).first() diff --git a/membership/templates/login.html b/membership/templates/login.html index d1f4172c..75545873 100644 --- a/membership/templates/login.html +++ b/membership/templates/login.html @@ -4,19 +4,6 @@ {% block content %} - -