Browse Source

Replace some static settings by dynamic settings

master
Alice 5 years ago
parent
commit
7d7742bdd0
14 changed files with 124 additions and 51 deletions
  1. +23
    -0
      ccvpn/common.py
  2. +22
    -5
      ccvpn/settings.py
  3. +5
    -2
      ccvpn/views.py
  4. +3
    -4
      lambdainst/management/commands/expire_notify.py
  5. +6
    -8
      lambdainst/models.py
  6. +18
    -14
      lambdainst/tests.py
  7. +4
    -2
      lambdainst/views.py
  8. +3
    -3
      payments/management/commands/update_stripe_plans.py
  9. +15
    -10
      payments/models.py
  10. +0
    -3
      payments/views.py
  11. +1
    -0
      requirements.txt
  12. +12
    -0
      static/css/style.css
  13. +7
    -0
      templates/lambdainst/account.html
  14. +5
    -0
      templates/layout.html

+ 23
- 0
ccvpn/common.py View File

@@ -1,4 +1,6 @@
from django.conf import settings
from constance import config
from datetime import timedelta


def get_client_ip(request):
@@ -11,3 +13,24 @@ def get_client_ip(request):
return value.split(',', 1)[0]

return request.META.get('REMOTE_ADDR')


def get_price():
return config.MONTHLY_PRICE_EUR


def get_price_float():
return get_price() / 100


def get_trial_period_duration():
return config.TRIAL_PERIOD_HOURS * timedelta(hours=1)


def parse_integer_list(ls):
l = ls.split(',')
l = [p.strip() for p in l]
l = [p for p in l if p]
l = [int(p) for p in l]
return l


+ 22
- 5
ccvpn/settings.py View File

@@ -13,7 +13,8 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os

from datetime import timedelta
from django.core.validators import RegexValidator


BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

@@ -43,6 +44,8 @@ INSTALLED_APPS = (
'lambdainst',
'payments',
'tickets',
'constance',
'constance.backends.database',
)

MIDDLEWARE_CLASSES = (
@@ -229,10 +232,24 @@ PAYMENTS_BACKENDS = {
}

PAYMENTS_CURRENCY = ('eur', '€')
PAYMENTS_MONTHLY_PRICE = 300 # in currency*100
TRIAL_PERIOD = timedelta(hours=2) # Time added on each trial awarded
TRIAL_PERIOD_LIMIT = 2 # 2 * 2h = 4h, still has to push the button twice
NOTIFY_DAYS_BEFORE = (3, 1) # notify twice: 3 and 1 days before expiration

CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
CONSTANCE_CONFIG = {
'MOTD': ('', "Public site message, displayed on homepage"),
'MOTD_USER': ('', "Message for users, displayed on account home"),
'MONTHLY_PRICE_EUR': (300, "Base subscription price per month (x0.01€)"),
'BTC_EUR_VALUE': (300000, "Current value of a bitcoin (x0.01€/btc)"),
'TRIAL_PERIOD_HOURS': (2, "Hours given for each trial period"),
'TRIAL_PERIOD_MAX': (84, "Maximum number of trial periods to give (84*2h=1w)"),
'NOTIFY_DAYS_BEFORE': ("3, 1", "When to send account expiration notifications. In number of days before, separated y commas",
'integer_list'),
}

CONSTANCE_ADDITIONAL_FIELDS = {
'integer_list': ['django.forms.fields.CharField', {
'validators': [RegexValidator(r'^([0-9]+[ ,]+)*([0-9]+)?$')],
}],
}


# Local settings


+ 5
- 2
ccvpn/views.py View File

@@ -10,14 +10,17 @@ from django.utils.http import is_safe_url
from django.utils.translation import (
LANGUAGE_SESSION_KEY, check_for_language,
)
from constance import config

from .common import get_price_float


md = markdown.Markdown(extensions=['toc', 'meta', 'codehilite(noclasses=True)'])


def index(request):
eur = '%.2f' % (settings.PAYMENTS_MONTHLY_PRICE / 100)
return render(request, 'ccvpn/index.html', dict(eur_price=eur))
eur = '%.2f' % get_price_float()
return render(request, 'ccvpn/index.html', dict(eur_price=eur, motd=config.MOTD))


def chat(request):


+ 3
- 4
lambdainst/management/commands/expire_notify.py View File

@@ -7,15 +7,14 @@ from django.conf import settings
from django.utils import timezone
from django.template.loader import get_template
from django.core.mail import send_mass_mail
from constance import config as site_config

from ccvpn.common import parse_integer_list
from lambdainst.models import VPNUser

ROOT_URL = settings.ROOT_URL
SITE_NAME = settings.TICKETS_SITE_NAME

NOTIFY_DAYS_BEFORE = settings.NOTIFY_DAYS_BEFORE
assert isinstance(NOTIFY_DAYS_BEFORE, (list, tuple, set))


def get_next_expirations(days=3):
""" Gets users whose subscription will expire in some days """
@@ -40,7 +39,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
from_email = settings.DEFAULT_FROM_EMAIL

for v in NOTIFY_DAYS_BEFORE:
for v in parse_integer_list(site_config.NOTIFY_DAYS_BEFORE):
emails = []
qs = get_next_expirations(v)
users = list(qs)


+ 6
- 8
lambdainst/models.py View File

@@ -4,16 +4,14 @@ from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from django.utils import timezone
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from . import core
from constance import config as site_config

from . import core
from ccvpn.common import get_trial_period_duration
from payments.models import Subscription

assert isinstance(settings.TRIAL_PERIOD, timedelta)
assert isinstance(settings.TRIAL_PERIOD_LIMIT, int)

prng = random.SystemRandom()


@@ -66,12 +64,12 @@ class VPNUser(models.Model):
core.update_user_expiration(self.user)

def give_trial_period(self):
self.add_paid_time(settings.TRIAL_PERIOD)
self.add_paid_time(get_trial_period_duration())
self.trial_periods_given += 1

@property
def can_have_trial(self):
if self.trial_periods_given >= settings.TRIAL_PERIOD_LIMIT:
if self.trial_periods_given >= site_config.TRIAL_PERIOD_MAX:
return False
if self.user.payment_set.filter(status='confirmed').count() > 0:
return False
@@ -79,7 +77,7 @@ class VPNUser(models.Model):

@property
def remaining_trial_periods(self):
return settings.TRIAL_PERIOD_LIMIT - self.trial_periods_given
return site_config.TRIAL_PERIOD_MAX - self.trial_periods_given

def on_payment_confirmed(self, payment):
if self.referrer and not self.referrer_used:


+ 18
- 14
lambdainst/tests.py View File

@@ -4,6 +4,8 @@ from django.utils import timezone
from django.core.management import call_command
from django.core import mail
from django.utils.six import StringIO
from constance import config as site_config
from constance.test import override_config

from .forms import SignupForm
from .models import VPNUser, User, random_gift_code, GiftCode, GiftCodeUser
@@ -80,7 +82,7 @@ class UserModelTest(TestCase, UserTestMixin):
u = User.objects.get(username='aaa')
vu = u.vpnuser

with self.settings(TRIAL_PERIOD=p, TRIAL_PERIOD_LIMIT=2):
with override_config(TRIAL_PERIOD_HOURS=24, TRIAL_PERIOD_MAX=2):
self.assertEqual(vu.remaining_trial_periods, 2)
self.assertTrue(vu.can_have_trial)
vu.give_trial_period()
@@ -103,7 +105,7 @@ class UserModelTest(TestCase, UserTestMixin):

vu = u.vpnuser

with self.settings(TRIAL_PERIOD=p, TRIAL_PERIOD_LIMIT=2):
with override_config(TRIAL_PERIOD_HOURS=24, TRIAL_PERIOD_MAX=2):
self.assertEqual(vu.remaining_trial_periods, 2)
self.assertFalse(vu.can_have_trial)

@@ -186,25 +188,27 @@ class AccountViewsTest(TestCase, UserTestMixin):

def test_trial(self):
p = timedelta(days=1)
with self.settings(RECAPTCHA_API='TEST', TRIAL_PERIOD=p):
good_data = {'g-recaptcha-response': 'TEST-TOKEN'}
with self.settings(RECAPTCHA_API='TEST'):
with override_config(TRIAL_PERIOD_HOURS=24, TRIAL_PERIOD_MAX=2):
good_data = {'g-recaptcha-response': 'TEST-TOKEN'}

response = self.client.post('/account/trial', good_data)
self.assertRedirects(response, '/account/')
response = self.client.post('/account/trial', good_data)
self.assertRedirects(response, '/account/')

user = User.objects.get(username='test')
self.assertRemaining(user.vpnuser, p)
user = User.objects.get(username='test')
self.assertRemaining(user.vpnuser, p)

def test_trial_fail(self):
p = timedelta(days=1)
with self.settings(RECAPTCHA_API='TEST', TRIAL_PERIOD=p):
bad_data = {'g-recaptcha-response': 'TOTALLY-NOT-TEST-TOKEN'}
with self.settings(RECAPTCHA_API='TEST'):
with override_config(TRIAL_PERIOD_HOURS=24, TRIAL_PERIOD_MAX=2):
bad_data = {'g-recaptcha-response': 'TOTALLY-NOT-TEST-TOKEN'}

response = self.client.post('/account/trial', bad_data)
self.assertRedirects(response, '/account/')
response = self.client.post('/account/trial', bad_data)
self.assertRedirects(response, '/account/')

user = User.objects.get(username='test')
self.assertRemaining(user.vpnuser, timedelta())
user = User.objects.get(username='test')
self.assertRemaining(user.vpnuser, timedelta())

def test_settings_form(self):
response = self.client.get('/account/settings')


+ 4
- 2
lambdainst/views.py View File

@@ -25,9 +25,10 @@ from django.db.models import Count
from django.contrib import auth
from django.contrib.auth.models import User
from django_countries import countries
from constance import config as site_config
import lcoreapi

from ccvpn.common import get_client_ip
from ccvpn.common import get_client_ip, get_price_float
from payments.models import ACTIVE_BACKENDS
from .forms import SignupForm, ReqEmailForm
from .models import GiftCode, VPNUser
@@ -164,7 +165,7 @@ def index(request):
3 an arbitrary number of months
"""
def __getitem__(self, months):
n = int(months) * project_settings.PAYMENTS_MONTHLY_PRICE / 100
n = int(months) * get_price_float()
c = project_settings.PAYMENTS_CURRENCY[1]
return '%.2f %s' % (n, c)

@@ -180,6 +181,7 @@ def index(request):
default_backend='paypal',
recaptcha_site_key=project_settings.RECAPTCHA_SITE_KEY,
price=price_fn(),
user_motd=site_config.MOTD_USER,
)
return render(request, 'lambdainst/account.html', context)



+ 3
- 3
payments/management/commands/update_stripe_plans.py View File

@@ -1,10 +1,10 @@
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings

from ccvpn.common import get_price
from payments.models import ACTIVE_BACKENDS, SUBSCR_PERIOD_CHOICES, period_months

CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY
MONTHLY_PRICE = settings.PAYMENTS_MONTHLY_PRICE


class Command(BaseCommand):
@@ -26,11 +26,11 @@ class Command(BaseCommand):
for period_id, period_name in SUBSCR_PERIOD_CHOICES:
plan_id = backend.get_plan_id(period_id)
months = period_months(period_id)
amount = months * MONTHLY_PRICE
amount = months * get_price()

kwargs = dict(
id=plan_id,
amount=months * MONTHLY_PRICE,
amount=amount,
interval='month',
interval_count=months,
name=backend.name + " (%s)" % period_id,


+ 15
- 10
payments/models.py View File

@@ -4,13 +4,13 @@ from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField
from datetime import timedelta

from ccvpn.common import get_price
from .backends import BackendBase

backend_settings = settings.PAYMENTS_BACKENDS
assert isinstance(backend_settings, dict)
backends_settings = settings.PAYMENTS_BACKENDS
assert isinstance(backends_settings, dict)

CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY
MONTHLY_PRICE = settings.PAYMENTS_MONTHLY_PRICE

STATUS_CHOICES = (
('new', _("Waiting for payment")),
@@ -51,12 +51,17 @@ for cls in BackendBase.__subclasses__():
name = cls.backend_id
assert isinstance(name, str)

if name not in backend_settings:
if name not in backends_settings:
continue

obj = cls(backend_settings.get(name, {}))
backend_settings = backends_settings.get(name, {})
for k, v in backend_settings.items():
if hasattr(v, '__call__'):
backend_settings[k] = v()

obj = cls(backend_settings)
if not obj.backend_enabled:
if name in backend_settings:
if name in backends_settings:
raise Exception("Invalid settings for payment backend %r" % name)

BACKENDS[name] = obj
@@ -131,7 +136,7 @@ class Payment(models.Model):
backend_id=backend_id,
status='new',
time=timedelta(days=30 * months),
amount=MONTHLY_PRICE * months
amount=get_price() * months
)
return payment

@@ -161,7 +166,7 @@ class Subscription(models.Model):

@property
def period_amount(self):
return self.months * MONTHLY_PRICE
return self.months * get_price()

@property
def next_renew(self):
@@ -172,7 +177,7 @@ class Subscription(models.Model):

@property
def monthly_amount(self):
return MONTHLY_PRICE
return get_price()

def create_payment(self):
payment = Payment(
@@ -180,7 +185,7 @@ class Subscription(models.Model):
backend_id=self.backend_id,
status='new',
time=timedelta(days=30 * self.months),
amount=MONTHLY_PRICE * self.months,
amount=get_price() * self.months,
subscription=self,
)
return payment


+ 0
- 3
payments/views.py View File

@@ -13,9 +13,6 @@ from .forms import NewPaymentForm
from .models import Payment, Subscription, BACKENDS, ACTIVE_BACKENDS


monthly_price = settings.PAYMENTS_MONTHLY_PRICE


@login_required
def new(request):
if request.method != 'POST':


+ 1
- 0
requirements.txt View File

@@ -1,5 +1,6 @@
django
django-jsonfield
django-constance[database]
django_countries
markdown
requests


+ 12
- 0
static/css/style.css View File

@@ -235,6 +235,9 @@ footer p { margin-top: 0.6em; margin-bottom: 0.5em; }
margin: 0;
color: white;
}
.message.motd p {
color: black;
}
.message p.info, .message p.success {
background-color: #062D4D;
}
@@ -492,6 +495,15 @@ a.home-signup-button {
padding: 0.6em 2em;
margin: 2em 0 0 0;
}
.account-motd {
background: #E6F5FF;
border-radius: 4px;
border: 1px solid #72B6ED;
box-shadow: 1px 1px 3px #aaa;
padding: 0.3em 2em;
text-align: center;
margin: 2em 0 0 0;
}

.account-payment-box form label, .account-giftcode-box form label {
width: 8em;


+ 7
- 0
templates/lambdainst/account.html View File

@@ -7,6 +7,13 @@

{% block account_content %}
<div>

{% if user_motd %}
<div class="account-motd">
<p> {{ user_motd | safe }} </p>
</div>
{% endif %}

<h1>{% trans 'Account' %} : {{user.username}}</h1>

<div class="account-status">


+ 5
- 0
templates/layout.html View File

@@ -65,6 +65,11 @@
</header>

{% block wrap %}
{% if motd %}
<div class="message motd">
<p>{{ motd | safe }}</p>
</div>
{% endif %}
{% for message in messages %}
<div class="message">
<p class="{{message.tags}}">{{ message }}</p>


Loading…
Cancel
Save