diff --git a/digitalglarus/forms.py b/digitalglarus/forms.py index 10f73b31..03a5e6cd 100644 --- a/digitalglarus/forms.py +++ b/digitalglarus/forms.py @@ -1,6 +1,6 @@ from django import forms from django.utils.translation import ugettext_lazy as _ - +from datetime import datetime from utils.models import BillingAddress from utils.forms import LoginFormMixin, SignupFormMixin, BillingAddressForm @@ -34,3 +34,46 @@ class MembershipBillingForm(BillingAddressForm): 'postal_code': _('Postal Code'), 'country': _('Country'), } + + +class BookingBillingForm(BillingAddressForm): + token = forms.CharField(widget=forms.HiddenInput()) + 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) + end_date = forms.DateField(required=False) + date_range = forms.CharField(required=False) + + def clean_date_range(self): + date_range = self.cleaned_data.get('date_range') + dates = date_range.replace(' ', '').split('-') + try: + start_date, end_date = [datetime.strptime(date_string, "%m/%d/%Y").date() + for date_string in dates] + except ValueError: + raise forms.ValidationError("Submit valid dates.") + + if start_date >= end_date: + raise forms.ValidationError("Your end date must be greather than your start date.") + return start_date, end_date + + def clean(self): + start_date, end_date = self.cleaned_data.get('date_range') + self.cleaned_data['start_date'] = start_date + self.cleaned_data['end_date'] = end_date + return self.cleaned_data 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/mixins.py b/digitalglarus/mixins.py new file mode 100644 index 00000000..6d555f24 --- /dev/null +++ b/digitalglarus/mixins.py @@ -0,0 +1,25 @@ +from django.db import models +from membership.models import StripeCustomer +from utils.models import BillingAddress + + +class Ordereable(models.Model): + customer = models.ForeignKey(StripeCustomer) + billing_address = models.ForeignKey(BillingAddress) + 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) + + class Meta: + abstract = True + + @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 + return instance diff --git a/digitalglarus/models.py b/digitalglarus/models.py index e5b3ab88..e229fabd 100644 --- a/digitalglarus/models.py +++ b/digitalglarus/models.py @@ -6,9 +6,10 @@ 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 +# from membership.models import StripeCustomer +# from utils.models import BillingAddress class MembershipType(models.Model): @@ -64,15 +65,8 @@ class Membership(models.Model): return instance -class MembershipOrder(models.Model): +class MembershipOrder(Ordereable, models.Model): membership = models.ForeignKey(Membership) - customer = models.ForeignKey(StripeCustomer) - billing_address = models.ForeignKey(BillingAddress) - 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) @classmethod def create(cls, data): @@ -84,6 +78,70 @@ class MembershipOrder(models.Model): return instance +class BookingPrice(models.Model): + price_per_day = models.FloatField() + special_price_offer = models.FloatField() + + +class Booking(models.Model): + start_date = models.DateField() + end_date = models.DateField() + price = models.FloatField() + free_days = models.IntegerField(default=0) + + @classmethod + def create(cls, data): + instance = cls.objects.create(**data) + return instance + + @classmethod + def get_ramaining_free_days(cls, user): + # ZERO_DAYS = 0 + # ONE_DAY = 1 + TWO_DAYS = 2 + + current_date = datetime.today() + current_month_bookings = cls.objects.filter(bookingorder__customer__user=user, + start_date__month=current_date.month) + free_days = TWO_DAYS - sum(map(lambda x: x.days, current_month_bookings)) + return free_days + + # free_days = ZERO_DAYS if current_month_bookings.count() > 2 else TWO_DAYS + # if current_month_bookings.count() == 1: + # booking = current_month_bookings.get() + # booked_days = (booking.end_date - booking.start_date).days + # free_days = ONE_DAY if booked_days == 1 else ZERO_DAYS + # return free_days + + # free_days = ZERO_DAYS if current_month_bookings.count() > 2 else TWO_DAYS + # return free_days + + @classmethod + def booking_price(cls, user, start_date, end_date): + """ + Calculate the booking price for requested dates + How it does: + 1. Check if the user has booked the current month + 2. Get how many days user wants to book + 3. Get price per day from BookingPrices instance + 4. Get available free days + 5. Calculate price by this formula -> (booking_days - free_days) * price_per_day + """ + booking_prices = BookingPrice.objects.last() + price_per_day = booking_prices.price_per_day + booking_days = (end_date - start_date).days + + free_days = cls.get_ramaining_free_days(user) + final_booking_price = (booking_days - free_days) * price_per_day + original_booking_price = (booking_days) * price_per_day + + return original_booking_price, final_booking_price, free_days + + +class BookingOrder(Ordereable, models.Model): + booking = models.OneToOneField(Booking) + + class Supporter(models.Model): name = models.CharField(max_length=200) description = models.TextField(null=True, blank=True) diff --git a/digitalglarus/static/digitalglarus/js/booking.js b/digitalglarus/static/digitalglarus/js/booking.js new file mode 100644 index 00000000..ed1929d4 --- /dev/null +++ b/digitalglarus/static/digitalglarus/js/booking.js @@ -0,0 +1,22 @@ +$( document ).ready(function() { + + // $('#booking-date-range').daterangepicker(); + + + $('#booking-date-range').daterangepicker({ + autoUpdateInput: false, + locale: { + cancelLabel: 'Clear' + } + }); + + + $('#booking-date-range').on('apply.daterangepicker', function(ev, picker) { + $(this).val(picker.startDate.format('MM/DD/YYYY') + ' - ' + picker.endDate.format('MM/DD/YYYY')); + }); + + $('#booking-date-range').on('cancel.daterangepicker', function(ev, picker) { + $(this).val('Select your dates'); + }); + +}); \ 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..6a8d4374 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking.html @@ -0,0 +1,53 @@ +{% 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_payment.html b/digitalglarus/templates/digitalglarus/booking_payment.html new file mode 100644 index 00000000..7faee9c9 --- /dev/null +++ b/digitalglarus/templates/digitalglarus/booking_payment.html @@ -0,0 +1,176 @@ +{% extends "new_base_glarus.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} + + + +
+
+
+
+

Booking

+ +
+

Billing Adress

+ +

Credit Card

+ +
+
+
+
+
+

Booking 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

+

{{discount_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..fec32399 --- /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/signup.html b/digitalglarus/templates/digitalglarus/signup.html index 118057f8..aea83f4e 100644 --- a/digitalglarus/templates/digitalglarus/signup.html +++ b/digitalglarus/templates/digitalglarus/signup.html @@ -18,7 +18,7 @@
    {% csrf_token %} - + {% for field in form %} {% bootstrap_field field show_label=False type='fields'%} {% endfor %} diff --git a/digitalglarus/templates/new_base_glarus.html b/digitalglarus/templates/new_base_glarus.html index 74bcaf1d..b7223997 100644 --- a/digitalglarus/templates/new_base_glarus.html +++ b/digitalglarus/templates/new_base_glarus.html @@ -93,7 +93,7 @@ Log In
  • - Sign Up + Sign Up
  • @@ -167,11 +167,24 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/digitalglarus/urls.py b/digitalglarus/urls.py index e81bd8b5..45884468 100644 --- a/digitalglarus/urls.py +++ b/digitalglarus/urls.py @@ -3,7 +3,8 @@ from django.utils.translation import ugettext_lazy as _ from . import views from .views import ContactView, IndexView, AboutView, HistoryView, LoginView, SignupView,\ - PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView + PasswordResetView, PasswordResetConfirmView, MembershipPaymentView, MembershipActivatedView,\ + MembershipPricingView, BookingSelectDatesView, BookingPaymentView # from membership.views import LoginRegistrationView urlpatterns = [ @@ -11,13 +12,19 @@ urlpatterns = [ url(_(r'contact/?$'), ContactView.as_view(), name='contact'), 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'booking/?$'), BookingSelectDatesView.as_view(), name='booking'), + url(_(r'booking/payment/?$'), BookingPaymentView.as_view(), name='booking_payment'), url(_(r'membership/payment/?$'), MembershipPaymentView.as_view(), name='membership_payment'), url(_(r'membership/activated/?$'), MembershipActivatedView.as_view(), name='membership_activated'), + url(_(r'membership/pricing/?$'), MembershipPricingView.as_view(), + name='membership_pricing'), 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 d0edcd05..479eadfc 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -28,8 +28,11 @@ from utils.forms import PasswordResetRequestForm from utils.stripe_utils import StripeUtils -from .forms import LoginForm, SignupForm, MembershipBillingForm -from .models import MembershipType, Membership, MembershipOrder +from .forms import LoginForm, SignupForm, MembershipBillingForm, BookingDateForm,\ + BookingBillingForm + +from .models import MembershipType, Membership, MembershipOrder, Booking, BookingPrice,\ + BookingOrder class IndexView(TemplateView): @@ -63,7 +66,7 @@ class PasswordResetConfirmView(PasswordResetConfirmViewMixin): class HistoryView(TemplateView): template_name = "digitalglarus/history.html" - def get_context_data(self, **kwargs): + def get_context_data(self, *args, **kwargs): context = super(HistoryView, self).get_context_data(**kwargs) supporters = Supporter.objects.all() context.update({ @@ -72,9 +75,149 @@ class HistoryView(TemplateView): return context +class BookingSelectDatesView(LoginRequiredMixin, FormView): + template_name = "digitalglarus/booking.html" + form_class = BookingDateForm + login_url = reverse_lazy('digitalglarus:login') + success_url = reverse_lazy('digitalglarus:booking_payment') + + 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 + original_price, discount_price, free_days = Booking.\ + booking_price(user, start_date, end_date) + self.request.session.update({ + 'original_price': original_price, + 'discount_price': discount_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 super(BookingSelectDatesView, self).form_valid(form) + + +class BookingPaymentView(LoginRequiredMixin, FormView): + template_name = "digitalglarus/booking_payment.html" + form_class = BookingBillingForm + success_url = reverse_lazy('digitalglarus:booking_payment') + booking_needed_fields = ['original_price', 'discount_price', 'booking_days', 'free_days', + 'start_date', 'end_date'] + + 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_form_kwargs(self): + 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('discount_price'), + } + }) + 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} + booking_price_per_day = BookingPrice.objects.get().price_per_day + total_discount = booking_price_per_day * booking_data.get('free_days') + booking_data.update({ + 'booking_price_per_day': booking_price_per_day, + 'total_discount': total_discount, + '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') + + original_price, discount_price, free_days = Booking.\ + booking_price(self.request.user, start_date, end_date) + + # 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=original_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 + billing_address = form.save() + + # Create membership plan + booking_data = { + 'start_date': start_date, + 'end_date': end_date, + 'start_date': start_date, + 'free_days': free_days, + 'price': discount_price, + } + booking = Booking.create(booking_data) + + # Create membership order + order_data = { + 'booking': booking, + 'customer': customer, + 'billing_address': billing_address, + 'stripe_charge': charge + } + BookingOrder.create(order_data) + + # request.session.update({ + # 'membership_price': membership.type.first_month_price, + # 'membership_dates': membership.type.first_month_formated_range + # }) + return super(BookingPaymentView, self).form_valid(form) + # return HttpResponseRedirect(reverse('digitalglarus:membership_activated')) + + +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, FormView): template_name = "digitalglarus/membership_payment.html" - login_url = reverse_lazy('digitalglarus:login') + login_url = reverse_lazy('digitalglarus:signup') form_class = MembershipBillingForm def get_form_kwargs(self):