diff --git a/lambdainst/admin.py b/lambdainst/admin.py index 01f6d2c..ad6dfe3 100644 --- a/lambdainst/admin.py +++ b/lambdainst/admin.py @@ -15,18 +15,29 @@ from payments.admin import UserCouponInline, UserLedgerInline def make_user_link(user): - change_url = resolve_url('admin:auth_user_change', user.id) + change_url = resolve_url("admin:auth_user_change", user.id) return format_html('{}', change_url, user.username) + class VPNUserInline(admin.StackedInline): model = VPNUser can_delete = False - fk_name = 'user' - - fields = ('notes', 'expiration', 'last_expiry_notice', 'notify_expiration', - 'last_vpn_auth_fail_notice', 'notify_vpn_auth_fail', - 'trial_periods_given', 'referrer_a', 'campaign', 'last_vpn_auth', 'last_core_sync') - readonly_fields = ('referrer_a', 'last_vpn_auth', 'last_core_sync', 'campaign') + fk_name = "user" + + fields = ( + "notes", + "expiration", + "last_expiry_notice", + "notify_expiration", + "last_vpn_auth_fail_notice", + "notify_vpn_auth_fail", + "trial_periods_given", + "referrer_a", + "campaign", + "last_vpn_auth", + "last_core_sync", + ) + readonly_fields = ("referrer_a", "last_vpn_auth", "last_core_sync", "campaign") def referrer_a(self, object): if not object.referrer: @@ -38,40 +49,58 @@ class VPNUserInline(admin.StackedInline): else: s += _("(not rewarded)") return s + referrer_a.allow_tags = True referrer_a.short_description = _("Referrer") def is_paid(self, object): return object.is_paid + is_paid.boolean = True is_paid.short_description = _("Is paid?") class UserAdmin(UserAdmin): inlines = (VPNUserInline, UserLedgerInline, UserCouponInline) - list_display = ('username', 'email', 'is_staff', 'date_joined', 'is_paid') - ordering = ('-date_joined', ) + list_display = ("username", "email", "is_staff", "date_joined", "is_paid") + ordering = ("-date_joined",) fieldsets = ( - (None, {'fields': ('username', 'password', 'email', 'links')}), - (_('Important dates'), {'fields': ('last_login', 'date_joined')}), - (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', - 'groups', 'user_permissions')}), + (None, {"fields": ("username", "password", "email", "links")}), + (_("Important dates"), {"fields": ("last_login", "date_joined")}), + ( + _("Permissions"), + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + "groups", + "user_permissions", + ) + }, + ), ) - readonly_fields = ('last_login', 'date_joined', 'links') - actions = (django_lcore.core_sync_action, ) + readonly_fields = ("last_login", "date_joined", "links") + actions = (django_lcore.core_sync_action,) def is_paid(self, object): return object.vpnuser.is_paid + is_paid.boolean = True is_paid.short_description = _("Is paid?") def links(self, object): - payments_url = resolve_url('admin:payments_payment_changelist') - tickets_url = resolve_url('admin:tickets_ticket_changelist') + payments_url = resolve_url("admin:payments_payment_changelist") + tickets_url = resolve_url("admin:tickets_ticket_changelist") fmt = '{}' - return format_html(fmt + " - " + fmt, - payments_url, object.id, "Payments", - tickets_url, object.id, "Tickets", + return format_html( + fmt + " - " + fmt, + payments_url, + object.id, + "Payments", + tickets_url, + object.id, + "Tickets", ) def save_model(self, request, obj, form, change): diff --git a/lambdainst/forms.py b/lambdainst/forms.py index fba6c2f..9c06608 100644 --- a/lambdainst/forms.py +++ b/lambdainst/forms.py @@ -7,14 +7,14 @@ from django.utils.safestring import mark_safe class FormPureRender: def as_pure_aligned(self): - html = '' + html = "" for f in self: html += '
\n' - html += str(f.label_tag()) + '\n' - html += str(f) + '\n' + html += str(f.label_tag()) + "\n" + html += str(f) + "\n" if f.errors: - html += str(f.errors) + '\n' - html += '
\n' + html += str(f.errors) + "\n" + html += "\n" return mark_safe(html) @@ -30,36 +30,43 @@ class UserField(forms.RegexField): class SignupForm(forms.Form, FormPureRender): username = UserField( - label=_("Username"), min_length=2, max_length=16, regex='^[a-zA-Z0-9_-]+$', - widget=forms.TextInput(attrs={'required': 'true', - 'pattern': '[a-zA-Z0-9_-]{2,32}', - 'placeholder': _("Username"), - 'autofocus': 'true'}) + label=_("Username"), + min_length=2, + max_length=16, + regex="^[a-zA-Z0-9_-]+$", + widget=forms.TextInput( + attrs={ + "required": "true", + "pattern": "[a-zA-Z0-9_-]{2,32}", + "placeholder": _("Username"), + "autofocus": "true", + } + ), ) password = forms.CharField( label=_("Password"), - widget=forms.PasswordInput(attrs={'placeholder': _("Anything")}) + widget=forms.PasswordInput(attrs={"placeholder": _("Anything")}), ) password2 = forms.CharField( label=_("Repeat"), - widget=forms.PasswordInput(attrs={'placeholder': _("Same Anything")}) + widget=forms.PasswordInput(attrs={"placeholder": _("Same Anything")}), ) email = forms.EmailField( label=_("E-Mail"), - widget=forms.EmailInput(attrs={'placeholder': _("E-Mail")}), + widget=forms.EmailInput(attrs={"placeholder": _("E-Mail")}), required=False, ) def clean_password(self): - if self.data['password'] != self.data['password2']: + if self.data["password"] != self.data["password2"]: raise forms.ValidationError(_("Passwords are not the same")) - return self.data['password'] + return self.data["password"] class ReqEmailForm(forms.Form, FormPureRender): email = forms.EmailField( label=_("E-Mail"), - widget=forms.EmailInput(attrs={'placeholder': _("E-Mail")}), + widget=forms.EmailInput(attrs={"placeholder": _("E-Mail")}), ) @@ -73,7 +80,11 @@ def wg_pk_validator(s): class WgPeerForm(forms.Form): - public_key = forms.CharField(min_length=3, max_length=100, strip=True, required=False, validators=[ - wg_pk_validator - ]) + public_key = forms.CharField( + min_length=3, + max_length=100, + strip=True, + required=False, + validators=[wg_pk_validator], + ) name = forms.CharField(max_length=21, required=False) diff --git a/lambdainst/graphs.py b/lambdainst/graphs.py index 52ff026..3e76d7d 100644 --- a/lambdainst/graphs.py +++ b/lambdainst/graphs.py @@ -8,8 +8,8 @@ from payments.models import Payment PERIOD_VERBOSE_NAME = { - 'y': "per month", - 'm': "per day", + "y": "per month", + "m": "per day", } @@ -18,9 +18,23 @@ def monthdelta(date, delta): y = date.year + (date.month + delta - 1) // 12 if not m: m = 12 - d = min(date.day, [31, 29 if y % 4 == 0 and not y % 400 == 0 - else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - ][m - 1]) + d = min( + date.day, + [ + 31, + 29 if y % 4 == 0 and not y % 400 == 0 else 28, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, + ][m - 1], + ) return date.replace(day=d, month=m, year=y) @@ -38,57 +52,64 @@ def last_months(n=12): def time_filter_future(period, m, df): def _filter(o): - if period == 'm': + if period == "m": return df(o).date() <= m - if period == 'y': + if period == "y": return df(o).date().replace(day=1) <= m + return _filter def time_filter_between(period, m, df): def _filter(o): - if period == 'm': - return df(o).year == m.year and df(o).month == m.month and df(o).day == m.day + if period == "m": + return ( + df(o).year == m.year and df(o).month == m.month and df(o).day == m.day + ) return df(o).date() <= m and df(o).date() > (m - timedelta(days=1)) - if period == 'y': + if period == "y": return df(o).year == m.year and df(o).month == m.month - return (df(o).date().replace(day=1) <= m and - df(o).date().replace(day=1) > (m - timedelta(days=30))) + return df(o).date().replace(day=1) <= m and df(o).date().replace(day=1) > ( + m - timedelta(days=30) + ) + return _filter def users_graph(period): chart = pygal.Line(fill=True, x_label_rotation=75, show_legend=False) - chart.title = 'Users %s' % PERIOD_VERBOSE_NAME[period] + chart.title = "Users %s" % PERIOD_VERBOSE_NAME[period] chart.x_labels = [] values = [] - gen = last_days(30) if period == 'm' else last_months(12) + gen = last_days(30) if period == "m" else last_months(12) users = User.objects.all() for m in gen: filter_ = time_filter_future(period, m, lambda o: o.date_joined) users_filtered = filter(filter_, users) values.append(len(list(users_filtered))) - chart.x_labels.append('%02d/%02d' % (m.month, m.day)) + chart.x_labels.append("%02d/%02d" % (m.month, m.day)) - chart.add('Users', values) + chart.add("Users", values) return chart.render() def payments_paid_graph(period): chart = pygal.StackedBar(x_label_rotation=75, show_legend=True) chart.x_labels = [] - gen = list(last_days(30) if period == 'm' else last_months(12)) + gen = list(last_days(30) if period == "m" else last_months(12)) - chart.title = 'Payments %s in €' % (PERIOD_VERBOSE_NAME[period]) + chart.title = "Payments %s in €" % (PERIOD_VERBOSE_NAME[period]) for m in gen: - chart.x_labels.append('%02d/%02d' % (m.month, m.day)) + chart.x_labels.append("%02d/%02d" % (m.month, m.day)) values = dict() for backend_id, backend in BACKENDS.items(): values = [] - payments = list(Payment.objects.filter(status='confirmed', backend_id=backend_id)) + payments = list( + Payment.objects.filter(status="confirmed", backend_id=backend_id) + ) for m in gen: filter_ = time_filter_between(period, m, lambda o: o.created) @@ -103,17 +124,19 @@ def payments_paid_graph(period): def payments_success_graph(period): chart = pygal.StackedBar(x_label_rotation=75, show_legend=True) chart.x_labels = [] - gen = list(last_days(30) if period == 'm' else last_months(12)) + gen = list(last_days(30) if period == "m" else last_months(12)) - chart.title = 'Successful payments %s' % (PERIOD_VERBOSE_NAME[period]) + chart.title = "Successful payments %s" % (PERIOD_VERBOSE_NAME[period]) for m in gen: - chart.x_labels.append('%02d/%02d' % (m.month, m.day)) + chart.x_labels.append("%02d/%02d" % (m.month, m.day)) values = dict() for backend_id, backend in BACKENDS.items(): values = [] - payments = list(Payment.objects.filter(status='confirmed', backend_id=backend_id)) + payments = list( + Payment.objects.filter(status="confirmed", backend_id=backend_id) + ) for m in gen: filter_ = time_filter_between(period, m, lambda o: o.created) @@ -123,4 +146,3 @@ def payments_success_graph(period): chart.add(backend_id, values) return chart.render() - diff --git a/lambdainst/middleware.py b/lambdainst/middleware.py index 67ce1b6..32c03a2 100644 --- a/lambdainst/middleware.py +++ b/lambdainst/middleware.py @@ -8,10 +8,10 @@ from .models import User class ReferrerMiddleware(MiddlewareMixin): def process_request(self, request): - if 'ref' in request.GET: - id = request.GET['ref'] - elif 'referrer' in request.COOKIES: - id = request.COOKIES['referrer'] + if "ref" in request.GET: + id = request.GET["ref"] + elif "referrer" in request.COOKIES: + id = request.COOKIES["referrer"] else: return @@ -25,33 +25,36 @@ class ReferrerMiddleware(MiddlewareMixin): except User.DoesNotExist: return - request.session['referrer'] = u.id + request.session["referrer"] = u.id def process_response(self, request, response): - id = request.session.get('referrer') + id = request.session.get("referrer") if not id: return response max_age = 365 * 24 * 60 * 60 - expires = (datetime.utcnow() + timedelta(seconds=max_age)) + expires = datetime.utcnow() + timedelta(seconds=max_age) expires = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT") - response.set_cookie('referrer', id, - max_age=max_age, - expires=expires, - domain=settings.SESSION_COOKIE_DOMAIN, - secure=settings.SESSION_COOKIE_SECURE or None) + response.set_cookie( + "referrer", + id, + max_age=max_age, + expires=expires, + domain=settings.SESSION_COOKIE_DOMAIN, + secure=settings.SESSION_COOKIE_SECURE or None, + ) return response class CampaignMiddleware(MiddlewareMixin): - GET_FIELDS = ['pk_campaign', 'utm_campaign', 'utm_medium', 'utm_source'] + GET_FIELDS = ["pk_campaign", "utm_campaign", "utm_medium", "utm_source"] def _get_name(self, request): for f in self.GET_FIELDS: if f in request.GET and request.GET[f]: return request.GET[f] - if 'campaign' in request.COOKIES: - return request.COOKIES['campaign'] + if "campaign" in request.COOKIES: + return request.COOKIES["campaign"] return None def process_request(self, request): @@ -61,22 +64,25 @@ class CampaignMiddleware(MiddlewareMixin): name = name.strip() - if len(name) >= 64 or not re.match('^[a-zA-Z0-9_.:-]+$', name): + if len(name) >= 64 or not re.match("^[a-zA-Z0-9_.:-]+$", name): return - request.session['campaign'] = name + request.session["campaign"] = name def process_response(self, request, response): - name = request.session.get('campaign') + name = request.session.get("campaign") if not name: return response max_age = 365 * 24 * 60 * 60 - expires = (datetime.utcnow() + timedelta(seconds=max_age)) + expires = datetime.utcnow() + timedelta(seconds=max_age) expires = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT") - response.set_cookie('campaign', name, - max_age=max_age, - expires=expires, - domain=settings.SESSION_COOKIE_DOMAIN, - secure=settings.SESSION_COOKIE_SECURE or None) + response.set_cookie( + "campaign", + name, + max_age=max_age, + expires=expires, + domain=settings.SESSION_COOKIE_DOMAIN, + secure=settings.SESSION_COOKIE_SECURE or None, + ) return response diff --git a/lambdainst/models.py b/lambdainst/models.py index 0a993fd..145c034 100644 --- a/lambdainst/models.py +++ b/lambdainst/models.py @@ -2,7 +2,11 @@ import random from django.db import models, IntegrityError from django.contrib.auth.models import User from django.utils.translation import ugettext as _ -from django_lcore.core import LcoreUserProfileMethods, setup_sync_hooks, VPN_AUTH_STORAGE +from django_lcore.core import ( + LcoreUserProfileMethods, + setup_sync_hooks, + VPN_AUTH_STORAGE, +) from payments.models import BaseSubUser @@ -11,7 +15,7 @@ prng = random.SystemRandom() def random_gift_code(): charset = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ" - return ''.join([prng.choice(charset) for n in range(10)]) + return "".join([prng.choice(charset) for n in range(10)]) class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods): @@ -33,8 +37,9 @@ class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods): last_vpn_auth = models.DateTimeField(blank=True, null=True) last_core_sync = models.DateTimeField(blank=True, null=True) - referrer = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL, - related_name='referrals') + referrer = models.ForeignKey( + User, blank=True, null=True, on_delete=models.SET_NULL, related_name="referrals" + ) referrer_used = models.BooleanField(default=False) campaign = models.CharField(blank=True, null=True, max_length=64) @@ -49,15 +54,20 @@ class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods): def on_payment_confirmed(self, payment): if self.referrer and not self.referrer_used: - self.referrer.vpnuser.add_paid_months(1, 'referrer', f"rewarded for {self.user.username} (payment #{payment.id})") + self.referrer.vpnuser.add_paid_months( + 1, + "referrer", + f"rewarded for {self.user.username} (payment #{payment.id})", + ) self.referrer.vpnuser.save() self.referrer_used = True self.save() def lcore_sync(self): - if VPN_AUTH_STORAGE == 'inst': + if VPN_AUTH_STORAGE == "inst": return from lambdainst.tasks import push_user + push_user.delay(user_id=self.user.id) def notify_payment(self, payment): @@ -72,12 +82,13 @@ class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods): def __str__(self): return self.user.username + setup_sync_hooks(User, VPNUser) -#from django.db.models.signals import post_save -#from django.dispatch import receiver -#@receiver(post_save, sender=User) -#def create_vpnuser(sender, instance, created, **kwargs): +# from django.db.models.signals import post_save +# from django.dispatch import receiver +# @receiver(post_save, sender=User) +# def create_vpnuser(sender, instance, created, **kwargs): # if created: # try: # VPNUser.objects.create(user=instance) diff --git a/lambdainst/openvpn.py b/lambdainst/openvpn.py index 4d25e05..2f3bec0 100644 --- a/lambdainst/openvpn.py +++ b/lambdainst/openvpn.py @@ -7,83 +7,86 @@ from django.conf import settings CA_CERT = settings.OPENVPN_CA CONFIG_OS = ( - ('windows', _("Windows")), - ('android', _("Android")), - ('ubuntu', _("Ubuntu")), - ('osx', _("OS X")), - ('ios', _("iOS")), - ('chromeos', _("Chrome OS")), - ('freebox', _("Freebox")), - ('other', _("Other / GNU/Linux")), + ("windows", _("Windows")), + ("android", _("Android")), + ("ubuntu", _("Ubuntu")), + ("osx", _("OS X")), + ("ios", _("iOS")), + ("chromeos", _("Chrome OS")), + ("freebox", _("Freebox")), + ("other", _("Other / GNU/Linux")), ) PROTOCOLS = ( - ('udp', _("UDP (default)")), - ('tcp', _("TCP")), - ('udpl', _("UDP (low MTU)")), + ("udp", _("UDP (default)")), + ("tcp", _("TCP")), + ("udpl", _("UDP (low MTU)")), ) def _make_onc(username, name, hostname, port, protocol, http_proxy=None, ipv6=True): - cert_id = '{%s}' % uuid.uuid4() - vpn_id = '{%s}' % uuid.uuid4() + cert_id = "{%s}" % uuid.uuid4() + vpn_id = "{%s}" % uuid.uuid4() openvpn_config = { - 'ServerCARef': cert_id, - 'ClientCertType': 'None', - 'CompLZO': 'true', - 'Port': port, - 'Proto': protocol, - 'ServerPollTimeout': 10, - 'NsCertType': 'server', - 'Username': username, + "ServerCARef": cert_id, + "ClientCertType": "None", + "CompLZO": "true", + "Port": port, + "Proto": protocol, + "ServerPollTimeout": 10, + "NsCertType": "server", + "Username": username, } cert = { - 'GUID': cert_id, - 'Type': 'Authority', - 'X509': CA_CERT.strip(), + "GUID": cert_id, + "Type": "Authority", + "X509": CA_CERT.strip(), } vpn = { - 'GUID': vpn_id, - 'Name': name, - 'Type': 'VPN', - 'VPN': { - 'Type': 'OpenVPN', - 'Host': hostname, - 'OpenVPN': openvpn_config, + "GUID": vpn_id, + "Name": name, + "Type": "VPN", + "VPN": { + "Type": "OpenVPN", + "Host": hostname, + "OpenVPN": openvpn_config, }, } - return json.dumps({ - 'type': 'UnencryptedConfiguration', - 'Certificates': [cert], - 'NetworkConfigurations': [vpn], - }, indent=2) + return json.dumps( + { + "type": "UnencryptedConfiguration", + "Certificates": [cert], + "NetworkConfigurations": [vpn], + }, + indent=2, + ) def make_config(username, gw_name, os, protocol, http_proxy=None, ipv6=True): - use_frag = protocol == 'udpl' and os != 'ios' - ipv6 = ipv6 and (os != 'freebox') - http_proxy = http_proxy if protocol == 'tcp' else None - resolvconf = os in ('ubuntu', 'other') + use_frag = protocol == "udpl" and os != "ios" + ipv6 = ipv6 and (os != "freebox") + http_proxy = http_proxy if protocol == "tcp" else None + resolvconf = os in ("ubuntu", "other") - openvpn_proto = {'udp': 'udp', 'udpl': 'udp', 'tcp': 'tcp'} - openvpn_ports = {'udp': 1196, 'udpl': 1194, 'tcp': 443} + openvpn_proto = {"udp": "udp", "udpl": "udp", "tcp": "tcp"} + openvpn_ports = {"udp": 1196, "udpl": 1194, "tcp": 443} - hostname = 'gw.%s.204vpn.net' % gw_name + hostname = "gw.%s.204vpn.net" % gw_name port = openvpn_ports[protocol] proto = openvpn_proto[protocol] - if os == 'chromeos': + if os == "chromeos": name = "CCrypto VPN" - if gw_name != 'random': + if gw_name != "random": name += " " + gw_name.upper() return _make_onc(username, name, hostname, port, proto, http_proxy, ipv6) remote = str(hostname) - remote += ' ' + str(port) - remote += ' ' + proto + remote += " " + str(port) + remote += " " + proto config = """\ # +----------------------------+ @@ -106,9 +109,11 @@ remote {remote} auth-user-pass -""".format(remote=remote) +""".format( + remote=remote + ) - if os == 'ios': + if os == "ios": # i'd like to note here how much i hate OpenVPN config += "redirect-gateway ipv6\n" config += 'push "route 0.0.0.0 128.0.0.0"\n' @@ -133,16 +138,13 @@ auth-user-pass config += "down /etc/openvpn/update-resolv-conf\n" config += "\n" - if os == 'windows': + if os == "windows": config += "register-dns\n" config += "\n" config += "\n%s\n" % CA_CERT - if os == 'windows': - config = config.replace('\n', '\r\n') + if os == "windows": + config = config.replace("\n", "\r\n") return config - - - diff --git a/lambdainst/tasks.py b/lambdainst/tasks.py index f8aa3ec..7674657 100644 --- a/lambdainst/tasks.py +++ b/lambdainst/tasks.py @@ -69,7 +69,7 @@ def notify_vpn_auth_fails(): NOTIFY_EVERY = timedelta(days=180) # users notified recently should be ignored def find_fails_for_user(u): - """ returns true if there are 'many' recent fails """ + """returns true if there are 'many' recent fails""" path = api.info["current_instance"] + "/users/" + u.username + "/auth-events/" try: r = api.get(path) @@ -181,7 +181,7 @@ def notify_vpn_auth_fails(): @shared_task def notify_account_expiration(): - """ Notify users near the end of their subscription """ + """Notify users near the end of their subscription""" from_email = settings.DEFAULT_FROM_EMAIL for v in parse_integer_list(site_config.NOTIFY_DAYS_BEFORE): @@ -213,7 +213,7 @@ def notify_account_expiration(): def get_next_expirations(days=3): - """ Gets users whose subscription will expire in some days """ + """Gets users whose subscription will expire in some days""" limit_date = timezone.now() + timedelta(days=days) diff --git a/lambdainst/templatetags/active.py b/lambdainst/templatetags/active.py index 07ff79f..7230169 100644 --- a/lambdainst/templatetags/active.py +++ b/lambdainst/templatetags/active.py @@ -9,11 +9,10 @@ register = template.Library() @register.simple_tag(takes_context=True) def active(context, pattern_or_urlname): try: - pattern = '^' + reverse(pattern_or_urlname) + pattern = "^" + reverse(pattern_or_urlname) except NoReverseMatch: pattern = pattern_or_urlname - path = context['request'].path + path = context["request"].path if re.search(pattern, path): - return 'active' - return '' - + return "active" + return "" diff --git a/lambdainst/templatetags/bw.py b/lambdainst/templatetags/bw.py index 04c5d9f..06d4dd4 100644 --- a/lambdainst/templatetags/bw.py +++ b/lambdainst/templatetags/bw.py @@ -11,7 +11,7 @@ def bwformat(bps): try: bps = float(bps) except (TypeError, ValueError, UnicodeDecodeError): - value = ungettext("%(bw)d bps", "%(bw)d bps", 0) % {'bw': 0} + value = ungettext("%(bw)d bps", "%(bw)d bps", 0) % {"bw": 0} return avoid_wrapping(value) filesize_number_format = lambda value: formats.number_format(round(value, 1), -1) @@ -23,7 +23,7 @@ def bwformat(bps): P = 1 * 10 ** 15 if bps < K: - value = ungettext("%(size)d bps", "%(size)d bps", bps) % {'size': bps} + value = ungettext("%(size)d bps", "%(size)d bps", bps) % {"size": bps} elif bps < M: value = ugettext("%s Kbps") % filesize_number_format(bps / K) elif bps < G: diff --git a/lambdainst/tests/online.py b/lambdainst/tests/online.py index a2ed413..6b65a04 100644 --- a/lambdainst/tests/online.py +++ b/lambdainst/tests/online.py @@ -103,12 +103,14 @@ class OnlineStripeTests(BaseOnlineTest): self.selenium.find_element_by_xpath('//button[@type="submit"]').click() self.wait.until( - EC.presence_of_element_located((By.XPATH, '//*[contains(text(), "Waiting for payment")]')) + EC.presence_of_element_located( + (By.XPATH, '//*[contains(text(), "Waiting for payment")]') + ) ) def check_active(): # refresh payment as we dont have a worker - p = Payment.objects.order_by('id').first() + p = Payment.objects.order_by("id").first() p.refresh() self.selenium.refresh() @@ -159,12 +161,14 @@ class OnlineStripeTests(BaseOnlineTest): self.selenium.find_element_by_xpath('//button[@type="submit"]').click() self.wait.until( - EC.presence_of_element_located((By.XPATH, '//*[contains(text(), "Your subscription is processing.")]')) + EC.presence_of_element_located( + (By.XPATH, '//*[contains(text(), "Your subscription is processing.")]') + ) ) def check_active(): # refresh sub as we dont have a worker - p = Subscription.objects.order_by('id').first() + p = Subscription.objects.order_by("id").first() p.refresh() self.selenium.refresh() diff --git a/lambdainst/tests/units.py b/lambdainst/tests/units.py index e392288..b492d86 100644 --- a/lambdainst/tests/units.py +++ b/lambdainst/tests/units.py @@ -14,24 +14,24 @@ from payments.models import Payment, Subscription class UserTestMixin: def assertRemaining(self, vpnuser, time, delta=5): - """ Check that the vpnuser will expire in time (+/- 5 seconds) """ + """Check that the vpnuser will expire in time (+/- 5 seconds)""" exp = vpnuser.expiration or timezone.now() seconds = (exp - timezone.now() - time).total_seconds() self.assertAlmostEqual(seconds, 0, delta=delta) - class UserModelReferrerTest(TestCase, UserTestMixin): def setUp(self): - self.referrer = User.objects.create_user('ref') + self.referrer = User.objects.create_user("ref") - self.without_ref = User.objects.create_user('aaaa') + self.without_ref = User.objects.create_user("aaaa") - self.with_ref = User.objects.create_user('bbbb') + self.with_ref = User.objects.create_user("bbbb") self.with_ref.vpnuser.referrer = self.referrer self.payment = Payment.objects.create( - user=self.with_ref, status='confirmed', amount=300, time=timedelta(days=30)) + user=self.with_ref, status="confirmed", amount=300, time=timedelta(days=30) + ) def test_no_ref(self): self.without_ref.vpnuser.on_payment_confirmed(self.payment) @@ -40,125 +40,153 @@ class UserModelReferrerTest(TestCase, UserTestMixin): self.with_ref.vpnuser.on_payment_confirmed(self.payment) self.assertTrue(self.with_ref.vpnuser.referrer_used) self.assertEqual(self.with_ref.vpnuser.referrer, self.referrer) - self.assertRemaining(self.referrer.vpnuser, timedelta(days=30), delta=24*3600*3) + self.assertRemaining( + self.referrer.vpnuser, timedelta(days=30), delta=24 * 3600 * 3 + ) class SignupViewTest(TestCase): def test_form(self): - response = self.client.get('/account/signup') + response = self.client.get("/account/signup") self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], SignupForm) + self.assertIsInstance(response.context["form"], SignupForm) def test_post(self): - response = self.client.post('/account/signup', { - 'username': 'test_un', 'password': 'test_pw', 'password2': 'test_pw'}) - self.assertRedirects(response, '/account/') + response = self.client.post( + "/account/signup", + {"username": "test_un", "password": "test_pw", "password2": "test_pw"}, + ) + self.assertRedirects(response, "/account/") - user = User.objects.get(username='test_un') - self.assertTrue(user.check_password('test_pw')) + user = User.objects.get(username="test_un") + self.assertTrue(user.check_password("test_pw")) def test_post_error(self): - response = self.client.post('/account/signup', { - 'username': 'test_un', 'password': 'test_pw', 'password2': 'qsdf'}) + response = self.client.post( + "/account/signup", + {"username": "test_un", "password": "test_pw", "password2": "qsdf"}, + ) self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], SignupForm) - self.assertFormError(response, 'form', 'password', - 'Passwords are not the same') + self.assertIsInstance(response.context["form"], SignupForm) + self.assertFormError(response, "form", "password", "Passwords are not the same") def test_post_referrer(self): - ref = User.objects.create_user('referrer') + ref = User.objects.create_user("referrer") - response = self.client.post('/account/signup?ref=%d' % ref.id, { - 'username': 'test_un', 'password': 'test_pw', 'password2': 'test_pw'}) - self.assertRedirects(response, '/account/') + response = self.client.post( + "/account/signup?ref=%d" % ref.id, + {"username": "test_un", "password": "test_pw", "password2": "test_pw"}, + ) + self.assertRedirects(response, "/account/") - user = User.objects.get(username='test_un') - self.assertTrue(user.check_password('test_pw')) + user = User.objects.get(username="test_un") + self.assertTrue(user.check_password("test_pw")) self.assertEqual(user.vpnuser.referrer, ref) class AccountViewsTest(TestCase, UserTestMixin): def setUp(self): - User.objects.create_user('test', None, 'test_pw') - self.client.login(username='test', password='test_pw') + User.objects.create_user("test", None, "test_pw") + self.client.login(username="test", password="test_pw") def test_account(self): - response = self.client.get('/account/') + response = self.client.get("/account/") self.assertEqual(response.status_code, 200) def test_settings_form(self): - response = self.client.get('/account/settings') + response = self.client.get("/account/settings") self.assertEqual(response.status_code, 200) def print_message(self, response): from django.contrib.messages import get_messages + messages = list(get_messages(response.wsgi_request)) for m in messages: print(f"[message: {m.message!r} level={m.level} tags={m.tags!r}]") def test_settings_post_email(self): - response = self.client.post('/account/settings', { - 'action': 'email', - 'current_password': 'test_pw', - 'email': 'new_email@example.com'}) + response = self.client.post( + "/account/settings", + { + "action": "email", + "current_password": "test_pw", + "email": "new_email@example.com", + }, + ) self.assertEqual(response.status_code, 302) - user = User.objects.get(username='test') - self.assertEqual(user.email, 'new_email@example.com') + user = User.objects.get(username="test") + self.assertEqual(user.email, "new_email@example.com") def test_settings_post_email_fail(self): - response = self.client.post('/account/settings', { - 'action': 'email', - 'current_password': 'not_test_pw', - 'email': 'new_email@example.com'}) + response = self.client.post( + "/account/settings", + { + "action": "email", + "current_password": "not_test_pw", + "email": "new_email@example.com", + }, + ) self.assertEqual(response.status_code, 302) - user = User.objects.get(username='test') - self.assertNotEqual(user.email, 'new_email@example.com') + user = User.objects.get(username="test") + self.assertNotEqual(user.email, "new_email@example.com") def test_settings_post_pw(self): - response = self.client.post('/account/settings', { - 'action': 'password', - 'current_password': 'test_pw', - 'password': 'new_test_pw', 'password2': 'new_test_pw'}) + response = self.client.post( + "/account/settings", + { + "action": "password", + "current_password": "test_pw", + "password": "new_test_pw", + "password2": "new_test_pw", + }, + ) self.assertEqual(response.status_code, 302) - user = User.objects.get(username='test') - self.assertTrue(user.check_password('new_test_pw')) + user = User.objects.get(username="test") + self.assertTrue(user.check_password("new_test_pw")) def test_settings_post_pw_fail(self): - response = self.client.post('/account/settings', { - 'action': 'password', - 'current_password': 'oops', - 'password': 'new_test_pw', - 'password2': 'new_test_pw'}) + response = self.client.post( + "/account/settings", + { + "action": "password", + "current_password": "oops", + "password": "new_test_pw", + "password2": "new_test_pw", + }, + ) self.assertEqual(response.status_code, 302) - response = self.client.post('/account/settings', { - 'action': 'password', - 'current_password': 'test_pw', - 'password': 'new_test_pw2', - 'password2': 'new_test_pw_qsdfg'}) + response = self.client.post( + "/account/settings", + { + "action": "password", + "current_password": "test_pw", + "password": "new_test_pw2", + "password2": "new_test_pw_qsdfg", + }, + ) self.assertEqual(response.status_code, 302) - user = User.objects.get(username='test') - self.assertFalse(user.check_password('new_test_pw')) - self.assertFalse(user.check_password('new_test_pw2')) - self.assertTrue(user.check_password('test_pw')) + user = User.objects.get(username="test") + self.assertFalse(user.check_password("new_test_pw")) + self.assertFalse(user.check_password("new_test_pw2")) + self.assertTrue(user.check_password("test_pw")) class CACrtViewTest(TestCase): def test_ca_crt(self): - with self.settings(OPENVPN_CA='test ca'): - response = self.client.get('/ca.crt') + with self.settings(OPENVPN_CA="test ca"): + response = self.client.get("/ca.crt") self.assertEqual(response.status_code, 200) - self.assertEqual(response['Content-Type'], 'application/x-x509-ca-cert') - self.assertEqual(response.content, b'test ca') + self.assertEqual(response["Content-Type"], "application/x-x509-ca-cert") + self.assertEqual(response.content, b"test ca") def email_text(body): - return body.replace('\n', ' ') \ - .replace('\xa0', ' ') # nbsp + return body.replace("\n", " ").replace("\xa0", " ") # nbsp class ExpireNotifyTest(TestCase): @@ -167,68 +195,68 @@ class ExpireNotifyTest(TestCase): def test_notify_first(self): out = StringIO() - u = User.objects.create_user('test_username', 'test@example.com', 'testpw') - u.vpnuser.add_paid_time(timedelta(days=2), 'initial') + u = User.objects.create_user("test_username", "test@example.com", "testpw") + u.vpnuser.add_paid_time(timedelta(days=2), "initial") u.vpnuser.save() - call_command('expire_notify', stdout=out) + call_command("expire_notify", stdout=out) self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ['test@example.com']) - self.assertIn('expire in 1 day', email_text(mail.outbox[0].body)) + self.assertEqual(mail.outbox[0].to, ["test@example.com"]) + self.assertIn("expire in 1 day", email_text(mail.outbox[0].body)) - u = User.objects.get(username='test_username') - self.assertAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(), - delta=timedelta(minutes=1)) + u = User.objects.get(username="test_username") + self.assertAlmostEqual( + u.vpnuser.last_expiry_notice, timezone.now(), delta=timedelta(minutes=1) + ) def test_notify_second(self): out = StringIO() - u = User.objects.create_user('test_username', 'test@example.com', 'testpw') + u = User.objects.create_user("test_username", "test@example.com", "testpw") u.vpnuser.last_expiry_notice = timezone.now() - timedelta(days=2) - u.vpnuser.add_paid_time(timedelta(days=1), 'initial') + u.vpnuser.add_paid_time(timedelta(days=1), "initial") u.vpnuser.save() - call_command('expire_notify', stdout=out) + call_command("expire_notify", stdout=out) self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].to, ['test@example.com']) - self.assertIn('expire in 23 hours, 59 minutes', email_text(mail.outbox[0].body)) + self.assertEqual(mail.outbox[0].to, ["test@example.com"]) + self.assertIn("expire in 23 hours, 59 minutes", email_text(mail.outbox[0].body)) - u = User.objects.get(username='test_username') - self.assertAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(), - delta=timedelta(minutes=1)) + u = User.objects.get(username="test_username") + self.assertAlmostEqual( + u.vpnuser.last_expiry_notice, timezone.now(), delta=timedelta(minutes=1) + ) def test_notify_subscription(self): out = StringIO() - u = User.objects.create_user('test_username', 'test@example.com', 'testpw') - u.vpnuser.add_paid_time(timedelta(days=2), 'initial') + u = User.objects.create_user("test_username", "test@example.com", "testpw") + u.vpnuser.add_paid_time(timedelta(days=2), "initial") u.vpnuser.save() - s = Subscription(user=u, backend_id='paypal', status='active') + s = Subscription(user=u, backend_id="paypal", status="active") s.save() - call_command('expire_notify', stdout=out) + call_command("expire_notify", stdout=out) self.assertEqual(len(mail.outbox), 0) - u = User.objects.get(username='test_username') + u = User.objects.get(username="test_username") # FIXME: # self.assertNotAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(), # delta=timedelta(minutes=1)) def test_notify_subscription_new(self): out = StringIO() - u = User.objects.create_user('test_username', 'test@example.com', 'testpw') - u.vpnuser.add_paid_time(timedelta(days=2), 'initial') + u = User.objects.create_user("test_username", "test@example.com", "testpw") + u.vpnuser.add_paid_time(timedelta(days=2), "initial") u.vpnuser.last_expiry_notice = timezone.now() - timedelta(days=5) u.vpnuser.save() - s = Subscription(user=u, backend_id='paypal', status='new') + s = Subscription(user=u, backend_id="paypal", status="new") s.save() - call_command('expire_notify', stdout=out) + call_command("expire_notify", stdout=out) self.assertEqual(len(mail.outbox), 1) - u = User.objects.get(username='test_username') + u = User.objects.get(username="test_username") # FIXME: # self.assertNotAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(), # delta=timedelta(minutes=1)) - - diff --git a/lambdainst/urls.py b/lambdainst/urls.py index 7edaa29..8499746 100644 --- a/lambdainst/urls.py +++ b/lambdainst/urls.py @@ -4,18 +4,17 @@ import django_lcore from . import views -app_name = 'lambdainst' +app_name = "lambdainst" urlpatterns = [ - path('login', auth_views.LoginView.as_view(), name='login'), - path('discourse_login', views.discourse_login, name='discourse_login'), - path('logout', views.logout, name='logout'), - path('signup', views.signup, name='signup'), - - path('settings', views.settings, name='account_settings'), - path('config', views.config), - path('config_dl', django_lcore.views.openvpn_dl), - path('wireguard', views.wireguard), - path('wireguard/new', views.wireguard_new, name='wireguard_new'), - path('', views.index, name='index'), + path("login", auth_views.LoginView.as_view(), name="login"), + path("discourse_login", views.discourse_login, name="discourse_login"), + path("logout", views.logout, name="logout"), + path("signup", views.signup, name="signup"), + path("settings", views.settings, name="account_settings"), + path("config", views.config), + path("config_dl", django_lcore.views.openvpn_dl), + path("wireguard", views.wireguard), + path("wireguard/new", views.wireguard_new, name="wireguard_new"), + path("", views.index, name="index"), ] diff --git a/lambdainst/views.py b/lambdainst/views.py index c6ede44..3ae1b1f 100644 --- a/lambdainst/views.py +++ b/lambdainst/views.py @@ -14,9 +14,12 @@ from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.models import User from django.db.models import Count from django.db import transaction -from django.http import (HttpResponse, - HttpResponseNotFound, HttpResponseRedirect, - JsonResponse) +from django.http import ( + HttpResponse, + HttpResponseNotFound, + HttpResponseRedirect, + JsonResponse, +) from django.shortcuts import redirect, render from django.utils import timezone from django.utils.translation import ugettext as _ @@ -33,16 +36,18 @@ from . import graphs def get_locations(): - """ Pretty bad thing that returns get_locations() with translated stuff + """Pretty bad thing that returns get_locations() with translated stuff that depends on the request """ countries_d = dict(countries) locations = django_lcore.get_clusters() for (_), loc in locations: - code = loc['country_code'].upper() - loc['country_name'] = countries_d.get(code, code) + code = loc["country_code"].upper() + loc["country_name"] = countries_d.get(code, code) - loc['load_percent'] = ceil((loc['usage'].get('net', 0) / (loc['bandwidth'] / 1e6)) * 100) + loc["load_percent"] = ceil( + (loc["usage"].get("net", 0) / (loc["bandwidth"] / 1e6)) * 100 + ) locations = list(sorted(locations, key=lambda l: l[0])) return locations @@ -56,58 +61,61 @@ def log_errors(request, form): def ca_crt(request): - return HttpResponse(content=project_settings.OPENVPN_CA, - content_type='application/x-x509-ca-cert') + return HttpResponse( + content=project_settings.OPENVPN_CA, content_type="application/x-x509-ca-cert" + ) def logout(request): auth.logout(request) - return redirect('index') + return redirect("index") def signup(request): if request.user.is_authenticated: - return redirect('account:index') + return redirect("account:index") - if request.method != 'POST': + if request.method != "POST": form = SignupForm() - return render(request, 'ccvpn/signup.html', dict(form=form)) + return render(request, "ccvpn/signup.html", dict(form=form)) form = SignupForm(request.POST) - grr = request.POST.get('g-recaptcha-response', '') + grr = request.POST.get("g-recaptcha-response", "") if captcha_test(grr, request): - request.session['signup_captcha_pass'] = True - elif not request.session.get('signup_captcha_pass'): + request.session["signup_captcha_pass"] = True + elif not request.session.get("signup_captcha_pass"): messages.error(request, _("Invalid captcha. Please try again")) - return render(request, 'ccvpn/signup.html', dict(form=form)) + return render(request, "ccvpn/signup.html", dict(form=form)) if not form.is_valid(): - return render(request, 'ccvpn/signup.html', dict(form=form)) + return render(request, "ccvpn/signup.html", dict(form=form)) - user = User.objects.create_user(form.cleaned_data['username'], - form.cleaned_data['email'], - form.cleaned_data['password']) + user = User.objects.create_user( + form.cleaned_data["username"], + form.cleaned_data["email"], + form.cleaned_data["password"], + ) user.save() try: - user.vpnuser.referrer = User.objects.get(id=request.session.get('referrer')) + user.vpnuser.referrer = User.objects.get(id=request.session.get("referrer")) except User.DoesNotExist: pass - user.vpnuser.campaign = request.session.get('campaign') - user.vpnuser.add_paid_time(timedelta(days=7), 'trial') + user.vpnuser.campaign = request.session.get("campaign") + user.vpnuser.add_paid_time(timedelta(days=7), "trial") user.vpnuser.save() user.vpnuser.lcore_sync() - user.backend = 'django.contrib.auth.backends.ModelBackend' + user.backend = "django.contrib.auth.backends.ModelBackend" auth.login(request, user) # invalidate that captcha - request.session['signup_captcha_pass'] = False + request.session["signup_captcha_pass"] = False - return redirect('account:index') + return redirect("account:index") @login_required @@ -118,70 +126,73 @@ def discourse_login(request): if project_settings.DISCOURSE_SSO is not True: return HttpResponseNotFound() - payload = request.GET.get('sso', '') - signature = request.GET.get('sig', '') + payload = request.GET.get("sso", "") + signature = request.GET.get("sig", "") - expected_signature = hmac.new(sso_secret.encode('utf-8'), - payload.encode('utf-8'), - sha256).hexdigest() + expected_signature = hmac.new( + sso_secret.encode("utf-8"), payload.encode("utf-8"), sha256 + ).hexdigest() if signature != expected_signature: return HttpResponseNotFound() - if request.method == 'POST' and 'email' in request.POST: + if request.method == "POST" and "email" in request.POST: form = ReqEmailForm(request.POST) if not form.is_valid(): - return render(request, 'ccvpn/require_email.html', dict(form=form)) + return render(request, "ccvpn/require_email.html", dict(form=form)) - request.user.email = form.cleaned_data['email'] + request.user.email = form.cleaned_data["email"] request.user.save() if not request.user.email: form = ReqEmailForm() - return render(request, 'ccvpn/require_email.html', dict(form=form)) + return render(request, "ccvpn/require_email.html", dict(form=form)) try: - payload = base64.b64decode(payload).decode('utf-8') + payload = base64.b64decode(payload).decode("utf-8") payload_data = dict(parse_qsl(payload)) except (TypeError, ValueError): return HttpResponseNotFound() - payload_data.update({ - 'external_id': request.user.id, - 'username': request.user.username, - 'email': request.user.email, - 'require_activation': 'true', - }) + payload_data.update( + { + "external_id": request.user.id, + "username": request.user.username, + "email": request.user.email, + "require_activation": "true", + } + ) payload = urlencode(payload_data) - payload = base64.b64encode(payload.encode('utf-8')) - signature = hmac.new(sso_secret.encode('utf-8'), payload, sha256).hexdigest() + payload = base64.b64encode(payload.encode("utf-8")) + signature = hmac.new(sso_secret.encode("utf-8"), payload, sha256).hexdigest() redirect_query = urlencode(dict(sso=payload, sig=signature)) - redirect_path = '/session/sso_login?' + redirect_query + redirect_path = "/session/sso_login?" + redirect_query return HttpResponseRedirect(discourse_url + redirect_path) @login_required def index(request): - ref_url = project_settings.ROOT_URL + '?ref=' + str(request.user.id) + ref_url = project_settings.ROOT_URL + "?ref=" + str(request.user.id) - twitter_url = 'https://twitter.com/intent/tweet?' + twitter_url = "https://twitter.com/intent/tweet?" twitter_args = { - 'text': _("Awesome VPN! 3€ per month, with a free 7 days trial!"), - 'via': 'CCrypto_VPN', - 'url': ref_url, - 'related': 'CCrypto_VPN,CCrypto_org' + "text": _("Awesome VPN! 3€ per month, with a free 7 days trial!"), + "via": "CCrypto_VPN", + "url": ref_url, + "related": "CCrypto_VPN,CCrypto_org", } class price_fn: - """ Clever hack to get the price in templates with {{price.3}} with + """Clever hack to get the price in templates with {{price.3}} with 3 an arbitrary number of months """ + def __getitem__(self, months): n = int(months) * get_price_float() c = project_settings.PAYMENTS_CURRENCY[1] - return '%.2f %s' % (n, c) + return "%.2f %s" % (n, c) context = dict( title=_("Account"), @@ -189,15 +200,16 @@ def index(request): twitter_link=twitter_url + urlencode(twitter_args), subscription=request.user.vpnuser.get_subscription(), backends=sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_display_name), - subscr_backends=sorted((b for b in ACTIVE_BACKENDS.values() - if b.backend_has_recurring), - key=lambda x: x.backend_id), - default_backend='paypal', + subscr_backends=sorted( + (b for b in ACTIVE_BACKENDS.values() if b.backend_has_recurring), + key=lambda x: x.backend_id, + ), + default_backend="paypal", hcaptcha_site_key=project_settings.HCAPTCHA_SITE_KEY, price=price_fn(), user_motd=site_config.MOTD_USER, ) - return render(request, 'lambdainst/account.html', context) + return render(request, "lambdainst/account.html", context) def captcha_test(grr, request): @@ -206,18 +218,17 @@ def captcha_test(grr, request): if not project_settings.HCAPTCHA_SITE_KEY: return True - if api_url == 'TEST' and grr == 'TEST-TOKEN': + if api_url == "TEST" and grr == "TEST-TOKEN": # FIXME: i'm sorry. return True - data = dict(secret=project_settings.HCAPTCHA_SECRET_KEY, - response=grr) + data = dict(secret=project_settings.HCAPTCHA_SECRET_KEY, response=grr) try: r = requests.post(api_url, data=data) r.raise_for_status() d = r.json() - return d.get('success') + return d.get("success") except (requests.ConnectionError, requests.HTTPError, ValueError): return False @@ -233,13 +244,29 @@ def make_export_zip(user, name): gw_cache = {} def process_wg_peer(item): - keys = {"gateway_port", "id", "local_ipv4", "local_ipv6", "name", "object", - "private_key", "public_key"} + keys = { + "gateway_port", + "id", + "local_ipv4", + "local_ipv6", + "name", + "object", + "private_key", + "public_key", + } return {k: v for (k, v) in item.items() if k in keys} def process_ovpn_sess(item): - keys = {"connect_date", "disconnect_date", "remote", "object", "protocol", "id", - "stats", "tunnel"} + keys = { + "connect_date", + "disconnect_date", + "remote", + "object", + "protocol", + "id", + "stats", + "tunnel", + } def convert(v): if isinstance(v, datetime): @@ -277,41 +304,50 @@ def make_export_zip(user, name): return obj with z.open(name + "/account.json", "w") as jf: - jf.write(json.dumps({ - "username": user.username, - "email": user.email, - "date_joined": user.date_joined.isoformat(), - "expiration": user.vpnuser.expiration.isoformat() if user.vpnuser.expiration else None, - }, indent=2).encode('ascii')) + jf.write( + json.dumps( + { + "username": user.username, + "email": user.email, + "date_joined": user.date_joined.isoformat(), + "expiration": user.vpnuser.expiration.isoformat() + if user.vpnuser.expiration + else None, + }, + indent=2, + ).encode("ascii") + ) with z.open(name + "/wireguard_peers.json", "w") as jf: try: - keys = list(map(process_wg_peer, django_lcore.api.get_wg_peers(user.username))) + keys = list( + map(process_wg_peer, django_lcore.api.get_wg_peers(user.username)) + ) except lcoreapi.APINotFoundError: keys = [] - jf.write(json.dumps(keys, indent=2).encode('ascii')) + jf.write(json.dumps(keys, indent=2).encode("ascii")) with z.open(name + "/openvpn_logs.json", "w") as jf: - base = django_lcore.api.info['current_instance'] - next_page = '/users/' + user.username + '/sessions/' + base = django_lcore.api.info["current_instance"] + next_page = "/users/" + user.username + "/sessions/" try: items = django_lcore.api.get(base + next_page).list_iter() except lcoreapi.APINotFoundError: items = [] items = list(map(process_ovpn_sess, items)) - jf.write(json.dumps(items, indent=2).encode('ascii')) + jf.write(json.dumps(items, indent=2).encode("ascii")) with z.open(name + "/payments.json", "w") as jf: items = user.payment_set.all() items = list(map(process_payments, items)) - jf.write(json.dumps(items, indent=2).encode('ascii')) - + jf.write(json.dumps(items, indent=2).encode("ascii")) + z.close() return f.getvalue() def deactivate_user(user): - """ clear most information from a user, keeping the username and id """ + """clear most information from a user, keeping the username and id""" user.vpnuser.clear_fields() user.vpnuser.save() @@ -330,33 +366,37 @@ def deactivate_user(user): def settings(request): can_delete = request.user.vpnuser.get_subscription() is None - if request.method != 'POST': - return render(request, 'lambdainst/settings.html', dict( - can_delete=can_delete, - )) + if request.method != "POST": + return render( + request, + "lambdainst/settings.html", + dict( + can_delete=can_delete, + ), + ) - action = request.POST.get('action') + action = request.POST.get("action") - current_pw = request.POST.get('current_password') + current_pw = request.POST.get("current_password") if not request.user.check_password(current_pw): messages.error(request, _("Invalid password")) - return redirect('lambdainst:account_settings') + return redirect("lambdainst:account_settings") - if action == 'email': - email = request.POST.get('email') + if action == "email": + email = request.POST.get("email") if email: request.user.email = email messages.success(request, _("OK! Email address changed.")) else: - request.user.email = '' + request.user.email = "" messages.success(request, _("OK! Email address unset.")) request.user.save() - return redirect('lambdainst:account_settings') + return redirect("lambdainst:account_settings") - elif action == 'password': - pw = request.POST.get('password') - pw2 = request.POST.get('password2') + elif action == "password": + pw = request.POST.get("password") + pw2 = request.POST.get("password2") if pw != pw2 or not pw: messages.error(request, _("Password and confirmation do not match")) else: @@ -364,95 +404,120 @@ def settings(request): messages.success(request, _("OK! Password changed.")) request.user.save() django_lcore.sync_user(request.user.vpnuser) - return redirect('lambdainst:account_settings') + return redirect("lambdainst:account_settings") - elif action == 'export': + elif action == "export": timestamp = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") data = make_export_zip(request.user, timestamp) r = HttpResponse(content=data, content_type="application/zip") - r["Content-Disposition"] = 'attachment; filename="ccvpn-export-{}-{}.zip"'.format(request.user.username, timestamp) + r[ + "Content-Disposition" + ] = 'attachment; filename="ccvpn-export-{}-{}.zip"'.format( + request.user.username, timestamp + ) return r - elif action == 'delete' and can_delete: + elif action == "delete" and can_delete: with transaction.atomic(): deactivate_user(request.user) logout(request) messages.success(request, _("OK! Your account has been deactivated.")) - return redirect('/') - - return render(request, 'lambdainst/settings.html', dict( - title=_("Settings"), - user=request.user, - can_delete=can_delete, - )) + return redirect("/") + + return render( + request, + "lambdainst/settings.html", + dict( + title=_("Settings"), + user=request.user, + can_delete=can_delete, + ), + ) @login_required def config(request): - return render(request, 'lambdainst/config.html', dict( - title=_("Config"), - config_os=django_lcore.openvpn.CONFIG_OS, - config_countries=(c for _, c in get_locations()), - config_protocols=django_lcore.openvpn.PROTOCOLS, - )) + return render( + request, + "lambdainst/config.html", + dict( + title=_("Config"), + config_os=django_lcore.openvpn.CONFIG_OS, + config_countries=(c for _, c in get_locations()), + config_protocols=django_lcore.openvpn.PROTOCOLS, + ), + ) def api_locations(request): def format_loc(cc, l): msg = "" - tags = l.get('tags', {}) - message = tags.get('message') + tags = l.get("tags", {}) + message = tags.get("message") if message: msg = " [%s]" % message return { - 'country_name': l['country_name'] + msg, - 'country_code': cc, - 'hostname': l['hostname'], - 'bandwidth': l['bandwidth'], - 'servers': l['servers'], + "country_name": l["country_name"] + msg, + "country_code": cc, + "hostname": l["hostname"], + "bandwidth": l["bandwidth"], + "servers": l["servers"], } - return JsonResponse(dict(locations=[format_loc(cc, l) for cc, l in get_locations()])) + + return JsonResponse( + dict(locations=[format_loc(cc, l) for cc, l in get_locations()]) + ) def status(request): locations = get_locations() ctx = { - 'title': _("Servers"), - 'locations': locations, + "title": _("Servers"), + "locations": locations, } - return render(request, 'lambdainst/status.html', ctx) + return render(request, "lambdainst/status.html", ctx) @user_passes_test(lambda user: user.is_staff) def admin_status(request): - graph_name = request.GET.get('graph_name') - graph_period = request.GET.get('period') - if graph_period not in ('y', 'm'): - graph_period = 'm' + graph_name = request.GET.get("graph_name") + graph_period = request.GET.get("period") + if graph_period not in ("y", "m"): + graph_period = "m" if graph_name: - if graph_name == 'users': + if graph_name == "users": content = graphs.users_graph(graph_period) - elif graph_name == 'payments_paid': + elif graph_name == "payments_paid": content = graphs.payments_paid_graph(graph_period) - elif graph_name == 'payments_success': + elif graph_name == "payments_success": content = graphs.payments_success_graph(graph_period) else: return HttpResponseNotFound() - return HttpResponse(content=content, content_type='image/svg+xml') + return HttpResponse(content=content, content_type="image/svg+xml") payment_status = ((b, b.get_info()) for b in ACTIVE_BACKENDS.values()) payment_status = ((b, i) for (b, i) in payment_status if i) - lcore_keys = {'core_name', 'core_now', 'core_version', 'current_instance', 'key_public'} + lcore_keys = { + "core_name", + "core_now", + "core_version", + "current_instance", + "key_public", + } ctx = { - 'api_status': {k: str(v) for k, v in django_lcore.api.info.items() if k in lcore_keys}, - 'payment_backends': sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id), - 'payment_status': payment_status, + "api_status": { + k: str(v) for k, v in django_lcore.api.info.items() if k in lcore_keys + }, + "payment_backends": sorted( + ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id + ), + "payment_status": payment_status, } ctx.update(site.each_context(request)) - return render(request, 'lambdainst/admin_status.html', ctx) + return render(request, "lambdainst/admin_status.html", ctx) @user_passes_test(lambda user: user.is_staff) @@ -460,36 +525,40 @@ def admin_ref(request): last_week = datetime.now() - timedelta(days=7) last_month = datetime.now() - timedelta(days=30) - top_ref = User.objects.annotate(n_ref=Count('referrals')).order_by('-n_ref')[:10] - top_ref_week = User.objects.filter(referrals__user__date_joined__gt=last_week) \ - .annotate(n_ref=Count('referrals')) \ - .order_by('-n_ref')[:10] - top_ref_month = User.objects.filter(referrals__user__date_joined__gt=last_month) \ - .annotate(n_ref=Count('referrals')) \ - .order_by('-n_ref')[:10] + top_ref = User.objects.annotate(n_ref=Count("referrals")).order_by("-n_ref")[:10] + top_ref_week = ( + User.objects.filter(referrals__user__date_joined__gt=last_week) + .annotate(n_ref=Count("referrals")) + .order_by("-n_ref")[:10] + ) + top_ref_month = ( + User.objects.filter(referrals__user__date_joined__gt=last_month) + .annotate(n_ref=Count("referrals")) + .order_by("-n_ref")[:10] + ) ctx = { - 'top_ref': top_ref, - 'top_ref_week': top_ref_week, - 'top_ref_month': top_ref_month, + "top_ref": top_ref, + "top_ref_week": top_ref_week, + "top_ref_month": top_ref_month, } ctx.update(site.each_context(request)) - return render(request, 'lambdainst/admin_ref.html', ctx) + return render(request, "lambdainst/admin_ref.html", ctx) @login_required def wireguard(request): api = django_lcore.api - if request.method == 'POST': - action = request.POST.get('action') - if action == 'delete_key': - key = api.get_wg_peer(request.user.username, request.POST.get('peer_id')) + if request.method == "POST": + action = request.POST.get("action") + if action == "delete_key": + key = api.get_wg_peer(request.user.username, request.POST.get("peer_id")) if key: key.delete() - elif action == 'set_name': - key = api.get_wg_peer(request.user.username, request.POST.get('peer_id')) + elif action == "set_name": + key = api.get_wg_peer(request.user.username, request.POST.get("peer_id")) if key: - name = request.POST.get('name') + name = request.POST.get("name") if name: name = name[:21] key.rename(name) @@ -503,19 +572,19 @@ def wireguard(request): context = dict( can_create_key=len(keys) < int(site_config.WIREGUARD_MAX_PEERS), - menu_item='wireguard', + menu_item="wireguard", enabled=request.user.vpnuser.is_paid, config_countries=[(k, v) for (k, v) in django_lcore.get_clusters()], keys=keys, locations=get_locations(), ) - return render(request, 'lambdainst/wireguard.html', context) + return render(request, "lambdainst/wireguard.html", context) @login_required def wireguard_new(request): if not request.user.vpnuser.is_paid: - return redirect('account:index') + return redirect("account:index") try: keys = django_lcore.api.get_wg_peers(request.user.username) @@ -523,25 +592,24 @@ def wireguard_new(request): django_lcore.sync_user(request.user.vpnuser) keys = [] if len(keys) >= int(site_config.WIREGUARD_MAX_PEERS): - return redirect('/account/wireguard') + return redirect("/account/wireguard") api = django_lcore.api - if request.method == 'POST': - action = request.POST.get('action') + if request.method == "POST": + action = request.POST.get("action") form = WgPeerForm(request.POST) - if action == 'add_key': + if action == "add_key": if form.is_valid(): api.create_wg_peer( request.user.username, - public_key=form.cleaned_data['public_key'], - name=form.cleaned_data['name'], + public_key=form.cleaned_data["public_key"], + name=form.cleaned_data["name"], ) else: log_errors(request, form) - return redirect('/account/wireguard') + return redirect("/account/wireguard") context = dict( - menu_item='wireguard', + menu_item="wireguard", locations=get_locations(), ) - return render(request, 'lambdainst/wireguard_new.html', context) - + return render(request, "lambdainst/wireguard_new.html", context) diff --git a/poetry.lock b/poetry.lock index 55c7ce5..c31004d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -71,25 +71,27 @@ python-versions = "*" [[package]] name = "black" -version = "20.8b1" +version = "21.7b0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] appdirs = "*" click = ">=7.1.2" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" +pathspec = ">=0.8.1,<1" regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +tomli = ">=0.2.6,<2.0.0" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +python2 = ["typed-ast (>=1.4.2)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cached-property" @@ -408,18 +410,18 @@ jsmin = "*" [[package]] name = "flower" -version = "0.9.5" +version = "1.0.0" description = "Celery Flower" category = "main" optional = false python-versions = "*" [package.dependencies] -celery = {version = ">=4.3.0", markers = "python_version >= \"3.7\""} +celery = ">=5.0.5" humanize = "*" -prometheus-client = "0.8.0" +prometheus-client = ">=0.8.0" pytz = "*" -tornado = {version = ">=5.0.0,<7.0.0", markers = "python_version >= \"3.5.2\""} +tornado = ">=5.0.0,<7.0.0" [[package]] name = "humanize" @@ -499,16 +501,17 @@ python-versions = "*" [[package]] name = "isort" -version = "5.8.0" +version = "5.9.2" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.6.1,<4.0" [package.extras] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] [[package]] name = "jedi" @@ -682,22 +685,22 @@ python-versions = "*" [[package]] name = "prometheus-client" -version = "0.8.0" +version = "0.11.0" description = "Python client for the Prometheus monitoring system." category = "main" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.3" +version = "3.0.19" description = "Library for building powerful interactive command lines in Python" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] wcwidth = "*" @@ -782,14 +785,6 @@ python-versions = "*" [package.dependencies] pylint = ">=1.7" -[[package]] -name = "python-bitcoinlib" -version = "0.11.0" -description = "The Swiss Army Knife of the Bitcoin protocol." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "python-crontab" version = "2.5.1" @@ -930,6 +925,14 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "1.1.0" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "tornado" version = "6.1" @@ -1020,7 +1023,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = ">=3.7,<4.0" -content-hash = "80258fdb7e8f9b695a3a5ad92efb52c47433243bccc905d701d1e377f38877d9" +content-hash = "5e7e869eb1d76bb7621e1653d293566a4ad761932fcc33e74c88602554c6d7f4" [metadata.files] amqp = [ @@ -1052,8 +1055,8 @@ billiard = [ {file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"}, ] black = [ - {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"}, - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, + {file = "black-21.7b0-py3-none-any.whl", hash = "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116"}, + {file = "black-21.7b0.tar.gz", hash = "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219"}, ] cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, @@ -1137,8 +1140,8 @@ django-tinymce4-lite = [ {file = "django_tinymce4_lite-1.8.0-py3-none-any.whl", hash = "sha256:2d53510ddb5fe20f25e525d4eaf7c8f8a567b85c9cc29f8ab2964419d9352d88"}, ] flower = [ - {file = "flower-0.9.5-py2.py3-none-any.whl", hash = "sha256:71be02bff7b2f56b0a07bd947fb3c748acba7f44f80ae88125d8954ce1a89697"}, - {file = "flower-0.9.5.tar.gz", hash = "sha256:56916d1d2892e25453d6023437427fc04706a1308e0bd4822321da34e1643f9c"}, + {file = "flower-1.0.0-py2.py3-none-any.whl", hash = "sha256:a4fcf959881135303e98a74cc7533298b7dfeb48abcd1d90c5bd52cb789430a8"}, + {file = "flower-1.0.0.tar.gz", hash = "sha256:2e17c4fb55c569508f3bfee7fe41f44b8362d30dbdf77b604a9d9f4740fe8cbd"}, ] humanize = [ {file = "humanize-3.10.0-py3-none-any.whl", hash = "sha256:aab7625d62dd5e0a054c8413a47d1fa257f3bdd8e9a2442c2fe36061bdd1d9bf"}, @@ -1161,8 +1164,8 @@ ipython-genutils = [ {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, ] isort = [ - {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, - {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, + {file = "isort-5.9.2-py3-none-any.whl", hash = "sha256:eed17b53c3e7912425579853d078a0832820f023191561fcee9d7cae424e0813"}, + {file = "isort-5.9.2.tar.gz", hash = "sha256:f65ce5bd4cbc6abdfbe29afc2f0245538ab358c14590912df638033f157d555e"}, ] jedi = [ {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, @@ -1237,12 +1240,12 @@ pickleshare = [ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] prometheus-client = [ - {file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"}, - {file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"}, + {file = "prometheus_client-0.11.0-py2.py3-none-any.whl", hash = "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2"}, + {file = "prometheus_client-0.11.0.tar.gz", hash = "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, - {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, + {file = "prompt_toolkit-3.0.19-py3-none-any.whl", hash = "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"}, + {file = "prompt_toolkit-3.0.19.tar.gz", hash = "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f"}, ] psycopg2-binary = [ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, @@ -1299,10 +1302,6 @@ pylint-plugin-utils = [ {file = "pylint-plugin-utils-0.6.tar.gz", hash = "sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a"}, {file = "pylint_plugin_utils-0.6-py3-none-any.whl", hash = "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a"}, ] -python-bitcoinlib = [ - {file = "python-bitcoinlib-0.11.0.tar.gz", hash = "sha256:3daafd63cb755f6e2067b7c9c514053856034c9f9363c80c37007744d54a2e06"}, - {file = "python_bitcoinlib-0.11.0-py3-none-any.whl", hash = "sha256:6e7982734637135599e2136d3c88d622f147e3b29201636665f799365784cd9e"}, -] python-crontab = [ {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"}, ] @@ -1420,6 +1419,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomli = [ + {file = "tomli-1.1.0-py3-none-any.whl", hash = "sha256:f4a182048010e89cbec0ae4686b21f550a7f2903f665e34a6de58ec15424f919"}, + {file = "tomli-1.1.0.tar.gz", hash = "sha256:33d7984738f8bb699c9b0a816eb646a8178a69eaa792d258486776a5d21b8ca5"}, +] tornado = [ {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, diff --git a/pyproject.toml b/pyproject.toml index 42f0169..88fdfea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ markdown = "^3.1" requests = "^2.21" pygal = "^2.4" pytz = "^2021.1" -python-bitcoinlib = "^0.11" stripe = "^2.24" django-constance = {version = "^2.7",extras = ["database"]} lcoreapi = {git = "https://git.packetimpact.net/lvpn/lcoreapi.git", tag = "v1.1.1"} @@ -28,13 +27,13 @@ django-pipayments = {git = "git@git.packetimpact.net:lvpn/django-pipayments.git" celery = "^5" django-celery-beat = "^2.0.0" redis = "^3.5.3" -flower = "^0.9.5" +flower = "^1" django-extensions = "^3.1.3" [tool.poetry.dev-dependencies] pylint = "^2.3" pylint-django = "^2.0.14" -black = {version = "^20.8b1", allow-prereleases = true} +black = {version = "^21.7b0", allow-prereleases = true} selenium = "^3.141.0" ipython = "^7.25.0"