Replace some static settings by dynamic settings

master
Alice 7 years ago
parent 6574ba2640
commit 7d7742bdd0

@ -1,4 +1,6 @@
from django.conf import settings from django.conf import settings
from constance import config
from datetime import timedelta
def get_client_ip(request): def get_client_ip(request):
@ -11,3 +13,24 @@ def get_client_ip(request):
return value.split(',', 1)[0] return value.split(',', 1)[0]
return request.META.get('REMOTE_ADDR') 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

@ -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, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
from datetime import timedelta from django.core.validators import RegexValidator
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -43,6 +44,8 @@ INSTALLED_APPS = (
'lambdainst', 'lambdainst',
'payments', 'payments',
'tickets', 'tickets',
'constance',
'constance.backends.database',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@ -229,10 +232,24 @@ PAYMENTS_BACKENDS = {
} }
PAYMENTS_CURRENCY = ('eur', '') PAYMENTS_CURRENCY = ('eur', '')
PAYMENTS_MONTHLY_PRICE = 300 # in currency*100
TRIAL_PERIOD = timedelta(hours=2) # Time added on each trial awarded CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
TRIAL_PERIOD_LIMIT = 2 # 2 * 2h = 4h, still has to push the button twice CONSTANCE_CONFIG = {
NOTIFY_DAYS_BEFORE = (3, 1) # notify twice: 3 and 1 days before expiration '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 # Local settings

@ -10,14 +10,17 @@ from django.utils.http import is_safe_url
from django.utils.translation import ( from django.utils.translation import (
LANGUAGE_SESSION_KEY, check_for_language, 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)']) md = markdown.Markdown(extensions=['toc', 'meta', 'codehilite(noclasses=True)'])
def index(request): def index(request):
eur = '%.2f' % (settings.PAYMENTS_MONTHLY_PRICE / 100) eur = '%.2f' % get_price_float()
return render(request, 'ccvpn/index.html', dict(eur_price=eur)) return render(request, 'ccvpn/index.html', dict(eur_price=eur, motd=config.MOTD))
def chat(request): def chat(request):

@ -7,15 +7,14 @@ from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.template.loader import get_template from django.template.loader import get_template
from django.core.mail import send_mass_mail 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 from lambdainst.models import VPNUser
ROOT_URL = settings.ROOT_URL ROOT_URL = settings.ROOT_URL
SITE_NAME = settings.TICKETS_SITE_NAME 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): def get_next_expirations(days=3):
""" Gets users whose subscription will expire in some days """ """ Gets users whose subscription will expire in some days """
@ -40,7 +39,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
from_email = settings.DEFAULT_FROM_EMAIL from_email = settings.DEFAULT_FROM_EMAIL
for v in NOTIFY_DAYS_BEFORE: for v in parse_integer_list(site_config.NOTIFY_DAYS_BEFORE):
emails = [] emails = []
qs = get_next_expirations(v) qs = get_next_expirations(v)
users = list(qs) users = list(qs)

@ -4,16 +4,14 @@ from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils import timezone from django.utils import timezone
from django.conf import settings
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver 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 from payments.models import Subscription
assert isinstance(settings.TRIAL_PERIOD, timedelta)
assert isinstance(settings.TRIAL_PERIOD_LIMIT, int)
prng = random.SystemRandom() prng = random.SystemRandom()
@ -66,12 +64,12 @@ class VPNUser(models.Model):
core.update_user_expiration(self.user) core.update_user_expiration(self.user)
def give_trial_period(self): 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 self.trial_periods_given += 1
@property @property
def can_have_trial(self): 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 return False
if self.user.payment_set.filter(status='confirmed').count() > 0: if self.user.payment_set.filter(status='confirmed').count() > 0:
return False return False
@ -79,7 +77,7 @@ class VPNUser(models.Model):
@property @property
def remaining_trial_periods(self): 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): def on_payment_confirmed(self, payment):
if self.referrer and not self.referrer_used: if self.referrer and not self.referrer_used:

@ -4,6 +4,8 @@ from django.utils import timezone
from django.core.management import call_command from django.core.management import call_command
from django.core import mail from django.core import mail
from django.utils.six import StringIO from django.utils.six import StringIO
from constance import config as site_config
from constance.test import override_config
from .forms import SignupForm from .forms import SignupForm
from .models import VPNUser, User, random_gift_code, GiftCode, GiftCodeUser from .models import VPNUser, User, random_gift_code, GiftCode, GiftCodeUser
@ -80,7 +82,7 @@ class UserModelTest(TestCase, UserTestMixin):
u = User.objects.get(username='aaa') u = User.objects.get(username='aaa')
vu = u.vpnuser 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.assertEqual(vu.remaining_trial_periods, 2)
self.assertTrue(vu.can_have_trial) self.assertTrue(vu.can_have_trial)
vu.give_trial_period() vu.give_trial_period()
@ -103,7 +105,7 @@ class UserModelTest(TestCase, UserTestMixin):
vu = u.vpnuser 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.assertEqual(vu.remaining_trial_periods, 2)
self.assertFalse(vu.can_have_trial) self.assertFalse(vu.can_have_trial)
@ -186,7 +188,8 @@ class AccountViewsTest(TestCase, UserTestMixin):
def test_trial(self): def test_trial(self):
p = timedelta(days=1) p = timedelta(days=1)
with self.settings(RECAPTCHA_API='TEST', TRIAL_PERIOD=p): with self.settings(RECAPTCHA_API='TEST'):
with override_config(TRIAL_PERIOD_HOURS=24, TRIAL_PERIOD_MAX=2):
good_data = {'g-recaptcha-response': 'TEST-TOKEN'} good_data = {'g-recaptcha-response': 'TEST-TOKEN'}
response = self.client.post('/account/trial', good_data) response = self.client.post('/account/trial', good_data)
@ -197,7 +200,8 @@ class AccountViewsTest(TestCase, UserTestMixin):
def test_trial_fail(self): def test_trial_fail(self):
p = timedelta(days=1) p = timedelta(days=1)
with self.settings(RECAPTCHA_API='TEST', TRIAL_PERIOD=p): 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'} bad_data = {'g-recaptcha-response': 'TOTALLY-NOT-TEST-TOKEN'}
response = self.client.post('/account/trial', bad_data) response = self.client.post('/account/trial', bad_data)

@ -25,9 +25,10 @@ from django.db.models import Count
from django.contrib import auth from django.contrib import auth
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django_countries import countries from django_countries import countries
from constance import config as site_config
import lcoreapi 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 payments.models import ACTIVE_BACKENDS
from .forms import SignupForm, ReqEmailForm from .forms import SignupForm, ReqEmailForm
from .models import GiftCode, VPNUser from .models import GiftCode, VPNUser
@ -164,7 +165,7 @@ def index(request):
3 an arbitrary number of months 3 an arbitrary number of months
""" """
def __getitem__(self, 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] c = project_settings.PAYMENTS_CURRENCY[1]
return '%.2f %s' % (n, c) return '%.2f %s' % (n, c)
@ -180,6 +181,7 @@ def index(request):
default_backend='paypal', default_backend='paypal',
recaptcha_site_key=project_settings.RECAPTCHA_SITE_KEY, recaptcha_site_key=project_settings.RECAPTCHA_SITE_KEY,
price=price_fn(), price=price_fn(),
user_motd=site_config.MOTD_USER,
) )
return render(request, 'lambdainst/account.html', context) return render(request, 'lambdainst/account.html', context)

@ -1,10 +1,10 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.conf import settings from django.conf import settings
from ccvpn.common import get_price
from payments.models import ACTIVE_BACKENDS, SUBSCR_PERIOD_CHOICES, period_months from payments.models import ACTIVE_BACKENDS, SUBSCR_PERIOD_CHOICES, period_months
CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY
MONTHLY_PRICE = settings.PAYMENTS_MONTHLY_PRICE
class Command(BaseCommand): class Command(BaseCommand):
@ -26,11 +26,11 @@ class Command(BaseCommand):
for period_id, period_name in SUBSCR_PERIOD_CHOICES: for period_id, period_name in SUBSCR_PERIOD_CHOICES:
plan_id = backend.get_plan_id(period_id) plan_id = backend.get_plan_id(period_id)
months = period_months(period_id) months = period_months(period_id)
amount = months * MONTHLY_PRICE amount = months * get_price()
kwargs = dict( kwargs = dict(
id=plan_id, id=plan_id,
amount=months * MONTHLY_PRICE, amount=amount,
interval='month', interval='month',
interval_count=months, interval_count=months,
name=backend.name + " (%s)" % period_id, name=backend.name + " (%s)" % period_id,

@ -4,13 +4,13 @@ from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField from jsonfield import JSONField
from datetime import timedelta from datetime import timedelta
from ccvpn.common import get_price
from .backends import BackendBase from .backends import BackendBase
backend_settings = settings.PAYMENTS_BACKENDS backends_settings = settings.PAYMENTS_BACKENDS
assert isinstance(backend_settings, dict) assert isinstance(backends_settings, dict)
CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY
MONTHLY_PRICE = settings.PAYMENTS_MONTHLY_PRICE
STATUS_CHOICES = ( STATUS_CHOICES = (
('new', _("Waiting for payment")), ('new', _("Waiting for payment")),
@ -51,12 +51,17 @@ for cls in BackendBase.__subclasses__():
name = cls.backend_id name = cls.backend_id
assert isinstance(name, str) assert isinstance(name, str)
if name not in backend_settings: if name not in backends_settings:
continue 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 not obj.backend_enabled:
if name in backend_settings: if name in backends_settings:
raise Exception("Invalid settings for payment backend %r" % name) raise Exception("Invalid settings for payment backend %r" % name)
BACKENDS[name] = obj BACKENDS[name] = obj
@ -131,7 +136,7 @@ class Payment(models.Model):
backend_id=backend_id, backend_id=backend_id,
status='new', status='new',
time=timedelta(days=30 * months), time=timedelta(days=30 * months),
amount=MONTHLY_PRICE * months amount=get_price() * months
) )
return payment return payment
@ -161,7 +166,7 @@ class Subscription(models.Model):
@property @property
def period_amount(self): def period_amount(self):
return self.months * MONTHLY_PRICE return self.months * get_price()
@property @property
def next_renew(self): def next_renew(self):
@ -172,7 +177,7 @@ class Subscription(models.Model):
@property @property
def monthly_amount(self): def monthly_amount(self):
return MONTHLY_PRICE return get_price()
def create_payment(self): def create_payment(self):
payment = Payment( payment = Payment(
@ -180,7 +185,7 @@ class Subscription(models.Model):
backend_id=self.backend_id, backend_id=self.backend_id,
status='new', status='new',
time=timedelta(days=30 * self.months), time=timedelta(days=30 * self.months),
amount=MONTHLY_PRICE * self.months, amount=get_price() * self.months,
subscription=self, subscription=self,
) )
return payment return payment

@ -13,9 +13,6 @@ from .forms import NewPaymentForm
from .models import Payment, Subscription, BACKENDS, ACTIVE_BACKENDS from .models import Payment, Subscription, BACKENDS, ACTIVE_BACKENDS
monthly_price = settings.PAYMENTS_MONTHLY_PRICE
@login_required @login_required
def new(request): def new(request):
if request.method != 'POST': if request.method != 'POST':

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

@ -235,6 +235,9 @@ footer p { margin-top: 0.6em; margin-bottom: 0.5em; }
margin: 0; margin: 0;
color: white; color: white;
} }
.message.motd p {
color: black;
}
.message p.info, .message p.success { .message p.info, .message p.success {
background-color: #062D4D; background-color: #062D4D;
} }
@ -492,6 +495,15 @@ a.home-signup-button {
padding: 0.6em 2em; padding: 0.6em 2em;
margin: 2em 0 0 0; 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 { .account-payment-box form label, .account-giftcode-box form label {
width: 8em; width: 8em;

@ -7,6 +7,13 @@
{% block account_content %} {% block account_content %}
<div> <div>
{% if user_motd %}
<div class="account-motd">
<p> {{ user_motd | safe }} </p>
</div>
{% endif %}
<h1>{% trans 'Account' %} : {{user.username}}</h1> <h1>{% trans 'Account' %} : {{user.username}}</h1>
<div class="account-status"> <div class="account-status">

@ -65,6 +65,11 @@
</header> </header>
{% block wrap %} {% block wrap %}
{% if motd %}
<div class="message motd">
<p>{{ motd | safe }}</p>
</div>
{% endif %}
{% for message in messages %} {% for message in messages %}
<div class="message"> <div class="message">
<p class="{{message.tags}}">{{ message }}</p> <p class="{{message.tags}}">{{ message }}</p>

Loading…
Cancel
Save