From 9df0cae5244f3384aba25b16ed0f6a57ecec47ed Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Fri, 7 Dec 2018 10:36:58 +0800 Subject: [PATCH 01/23] smaller docker image && update docker-compose file --- Dockerfile | 33 +++++++++++++-------------------- docker-compose.yml | 5 ++++- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index afa4df2069..23d1bc18ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,18 @@ -FROM python:3.6-slim +FROM python:3.6-alpine -LABEL Name=django-sspanel Version=0.0.2 +LABEL Name=django-sspanel Version=0.0.3 -COPY . /src/django-sspanel +COPY requirements.txt /tmp/requirements.txt -WORKDIR /src/django-sspanel +RUN apk update && apk add --no-cache gcc linux-headers \ + musl-dev python3-dev mariadb-dev jpeg-dev && \ + pip install --no-cache-dir -r /tmp/requirements.txt && \ + apk del gcc linux-headers \ + musl-dev python-dev jpeg-dev && \ + rm -Rf ~/.cache -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - build-essential \ - python3-dev \ - default-libmysqlclient-dev && \ - pip install --no-cache-dir -r requirements.txt - -EXPOSE 8080 - -# 如果是第一次运行需要手动exec进去执行如下命令 -# python3 manage.py collectstatic --no-input && \ -# python3 manage.py makemigrations && \ -# python3 manage.py migrate --run-syncdb && \ - -# server -CMD uwsgi uwsgi.ini \ No newline at end of file +# # 如果是第一次运行需要手动exec进去执行如下命令 +# # python3 manage.py collectstatic --no-input && \ +# # python3 manage.py makemigrations && \ +# # python3 manage.py migrate --run-syncdb && \ diff --git a/docker-compose.yml b/docker-compose.yml index d6fa2625b9..74a0fba71d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,11 +22,14 @@ services: MYSQL_HOST: db volumes: - .:/src/django-sspanel - - static:/src/django-sspanel/static depends_on: - db networks: - sspanel_network + ports: + - 8080:8080 + working_dir: /src/django-sspanel + command: uwsgi uwsgi.ini nginx: image: nginx restart: always From 6742080c28991e104441e96cf884be294a7503c5 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Sat, 8 Dec 2018 09:43:45 +0800 Subject: [PATCH 02/23] update readme --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8d4af2a9cc..b359df1362 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,14 @@ Wiki: [Wiki](https://github.com/Ehco1996/django-sspanel/wiki) ![](http://opj9lh0x4.bkt.clouddn.com/17-12-20/62343859.jpg) -## 重大升级说明 -如果要升级到dev最新的代码 -请按wiki里的步骤升级 -https://github.com/Ehco1996/django-sspanel/wiki/%E9%87%8D%E5%A4%A7%E6%9B%B4%E6%96%B0%E6%AD%A5%E9%AA%A4 ## 项目说明 该项目是用django作为后端框架,开发的一个shadowsocks多人用户面板,具有以下特点: * 轻量级css框架 -* 最新版本的Django作为后端 +* 最新版本的django作为后端 * 后端支援(shadowsocksr/shadowsocks) * 注册采用邀请系统,告别不良用户 * 完善的商品购买逻辑 @@ -101,4 +97,4 @@ https://github.com/Ehco1996/django-sspanel/wiki/%E9%87%8D%E5%A4%A7%E6%9B%B4%E6%9 萌新版: [部署教程](https://github.com/Ehco1996/django-sspanel/wiki/%E9%9D%A2%E6%9D%BF%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B-%E8%90%8C%E6%96%B0%E7%89%88) -Docker版: [部署教程](https://github.com/Ehco1996/django-sspanel/wiki/%E5%88%A9%E7%94%A8Dokcer-%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85) +Docker版: [部署教程](https://github.com/Ehco1996/django-sspanel/wiki/Docker-%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85) From 22738c5fd3202625c06638dbd75548b7400614c6 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Wed, 12 Dec 2018 15:59:34 +0800 Subject: [PATCH 03/23] add uk flag choice --- apps/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/constants.py b/apps/constants.py index 54b50f339b..9ce64bef28 100644 --- a/apps/constants.py +++ b/apps/constants.py @@ -27,6 +27,7 @@ COUNTRIES_CHOICES = ( ('US', '美国'), ('CN', '中国'), + ('GB', '英国'), ('TW', '台湾'), ('HK', '香港'), ('JP', '日本'), From b4d38bd4699657f0bb3261d40797dd8b39e34817 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Sat, 15 Dec 2018 20:31:30 +0800 Subject: [PATCH 04/23] fix template --- templates/backend/userstatus.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/backend/userstatus.html b/templates/backend/userstatus.html index 9c5cebe837..4ba02cbcb7 100644 --- a/templates/backend/userstatus.html +++ b/templates/backend/userstatus.html @@ -59,7 +59,7 @@

{% for u in coreUser %} {{ u.user.username }} - {{ u.used_traffic }} GB + {{ u.used_traffic }} {% endfor %} From 588d32797b95f111b752326148a1581784a3d9b0 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Mon, 17 Dec 2018 09:15:54 +0800 Subject: [PATCH 05/23] checkin tz to Asia/Shanghai --- apps/api/views.py | 11 +++-------- apps/constants.py | 1 + apps/ssserver/models.py | 16 ++++++++++++++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/api/views.py b/apps/api/views.py index ced777b15e..bcad19e53e 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -358,16 +358,11 @@ def alive_ip_api(request): def checkin(request): '''用户签到''' ss_user = request.user.ss_user - if not ss_user.today_is_checked: - # 距离上次签到时间大于一天 增加随机流量 - ll = randint(settings.MIN_CHECKIN_TRAFFIC, - settings.MAX_CHECKIN_TRAFFIC) - ss_user.transfer_enable += ll - ss_user.last_check_in_time = timezone.now() - ss_user.save() + res, traffic = Suser.checkin(ss_user) + if res: data = { 'title': '签到成功!', - 'subtitle': '获得{}流量!'.format(traffic_format(ll)), + 'subtitle': '获得{}流量!'.format(traffic_format(traffic)), 'status': 'success', } else: diff --git a/apps/constants.py b/apps/constants.py index 9ce64bef28..b833158d39 100644 --- a/apps/constants.py +++ b/apps/constants.py @@ -28,6 +28,7 @@ ('US', '美国'), ('CN', '中国'), ('GB', '英国'), + ('SG', '新加坡'), ('TW', '台湾'), ('HK', '香港'), ('JP', '日本'), diff --git a/apps/ssserver/models.py b/apps/ssserver/models.py index 1d5675d67a..fdda0611ce 100644 --- a/apps/ssserver/models.py +++ b/apps/ssserver/models.py @@ -1,6 +1,6 @@ import time import base64 -from random import choice +from random import choice, randint import pendulum from django_prometheus.models import ExportModelOperationsMixin @@ -74,7 +74,8 @@ def user(self): @property def today_is_checked(self): if self.last_check_in_time: - return self.last_check_in_time.day == pendulum.now().day + last_check_in_time = pendulum.parse(str(self.last_check_in_time)) + return last_check_in_time.day == get_current_time().day return False @property @@ -148,6 +149,17 @@ def get_random_port(cls): except IndexError: return max(port_list) + 1 + @staticmethod + def checkin(ss_user): + if not ss_user.today_is_checked: + traffic = randint(settings.MIN_CHECKIN_TRAFFIC, + settings.MAX_CHECKIN_TRAFFIC) + ss_user.transfer_enable = traffic + ss_user.last_check_in_time = get_current_time() + ss_user.save() + return True, traffic + return False, 0 + class Node(ExportModelOperationsMixin('node'), models.Model): '''线路节点''' From a3124d362931f22b2997b5e008cf40ddc1f34b66 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Mon, 17 Dec 2018 20:01:51 +0800 Subject: [PATCH 06/23] hotfix checkin --- apps/ssserver/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ssserver/models.py b/apps/ssserver/models.py index fdda0611ce..758c74320e 100644 --- a/apps/ssserver/models.py +++ b/apps/ssserver/models.py @@ -154,7 +154,7 @@ def checkin(ss_user): if not ss_user.today_is_checked: traffic = randint(settings.MIN_CHECKIN_TRAFFIC, settings.MAX_CHECKIN_TRAFFIC) - ss_user.transfer_enable = traffic + ss_user.transfer_enable += traffic ss_user.last_check_in_time = get_current_time() ss_user.save() return True, traffic From 8cfe01d1637d13125f3ca5775fdccfbc34edcc12 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Wed, 19 Dec 2018 11:50:26 +0800 Subject: [PATCH 07/23] fix cmd --- commands/croncmds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/croncmds.py b/commands/croncmds.py index 254bd412d8..bb2ab0ec14 100644 --- a/commands/croncmds.py +++ b/commands/croncmds.py @@ -65,7 +65,7 @@ def clean_online_log(): def clean_online_ip_log(): '''清空在线ip记录''' - count = TrafficLog.objects.count() + count = AliveIp.objects.count() AliveIp.truncate() print('Time: {} online ip log removed!:{}'.format(timezone.now(), count)) From f474377ed9bf6d5540595e16e31e2f0b1fdcb9eb Mon Sep 17 00:00:00 2001 From: Bluefissure Date: Wed, 19 Dec 2018 16:37:32 +0800 Subject: [PATCH 08/23] speed_limit --- .../migrations/0010_auto_20181219_1632.py | 28 +++++++++++++++++++ apps/ssserver/models.py | 3 ++ apps/utils.py | 7 +++++ 3 files changed, 38 insertions(+) create mode 100644 apps/ssserver/migrations/0010_auto_20181219_1632.py diff --git a/apps/ssserver/migrations/0010_auto_20181219_1632.py b/apps/ssserver/migrations/0010_auto_20181219_1632.py new file mode 100644 index 0000000000..71708a207b --- /dev/null +++ b/apps/ssserver/migrations/0010_auto_20181219_1632.py @@ -0,0 +1,28 @@ +# Generated by Django 2.0.4 on 2018-12-19 08:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ssserver', '0009_auto_20181122_0930'), + ] + + operations = [ + migrations.AddField( + model_name='node', + name='speed_limit', + field=models.IntegerField(default=0, verbose_name='限速'), + ), + migrations.AddField( + model_name='suser', + name='speed_limit', + field=models.IntegerField(db_column='speed_limit', default=0, verbose_name='限速'), + ), + migrations.AlterField( + model_name='node', + name='country', + field=models.CharField(choices=[('US', '美国'), ('CN', '中国'), ('GB', '英国'), ('SG', '新加坡'), ('TW', '台湾'), ('HK', '香港'), ('JP', '日本'), ('FR', '法国'), ('DE', '德国'), ('KR', '韩国'), ('JE', '泽西岛'), ('NZ', '新西兰'), ('MX', '墨西哥'), ('CA', '加拿大'), ('BR', '巴西'), ('CU', '古巴'), ('CZ', '捷克'), ('EG', '埃及'), ('FI', '芬兰'), ('GR', '希腊'), ('GU', '关岛'), ('IS', '冰岛'), ('MO', '澳门'), ('NL', '荷兰'), ('NO', '挪威'), ('PL', '波兰'), ('IT', '意大利'), ('IE', '爱尔兰'), ('AR', '阿根廷'), ('PT', '葡萄牙'), ('AU', '澳大利亚'), ('RU', '俄罗斯联邦'), ('CF', '中非共和国')], default='CN', max_length=2, verbose_name='国家'), + ), + ] diff --git a/apps/ssserver/models.py b/apps/ssserver/models.py index 758c74320e..0d66280797 100644 --- a/apps/ssserver/models.py +++ b/apps/ssserver/models.py @@ -37,6 +37,8 @@ class Suser(ExportModelOperationsMixin('ss_user'), models.Model): verbose_name='下载流量', default=0, db_column='d') transfer_enable = models.BigIntegerField( verbose_name='总流量', default=settings.DEFAULT_TRAFFIC, db_column='transfer_enable') + speed_limit = models.IntegerField( + verbose_name='限速', default=0, db_column='speed_limit') switch = models.BooleanField( verbose_name='保留字段switch', default=True, db_column='switch') enable = models.BooleanField( @@ -223,6 +225,7 @@ def get_node_ids(cls, all=False): validators=[MaxValueValidator(9), MinValueValidator(0)]) total_traffic = models.BigIntegerField('总流量', default=settings.GB) used_traffic = models.BigIntegerField('已用流量', default=0,) + speed_limit = models.IntegerField('限速', default=0) order = models.PositiveSmallIntegerField('排序', default=1) group = models.CharField('分组名', max_length=32, default='谜之屋') diff --git a/apps/utils.py b/apps/utils.py index 6bed64c934..343f826482 100644 --- a/apps/utils.py +++ b/apps/utils.py @@ -122,7 +122,14 @@ def get_node_user(node_id): 'obfs_param': user.obfs_param, 'protocol': user.protocol, 'protocol_param': user.protocol_param, + 'speed_limit_per_user': user.speed_limit } + if node.speed_limit > 0: + if user.speed_limit > 0: + cfg['speed_limit_per_user'] = min(user.speed_limit, node.speed_limit) + else: + cfg['speed_limit_per_user'] = node.speed_limit + data.append(cfg) return data From 63da96cf2f4dc303aea167e41027470feca237d2 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Thu, 27 Dec 2018 08:44:04 +0800 Subject: [PATCH 09/23] fix checkin timezone --- apps/ssserver/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/ssserver/models.py b/apps/ssserver/models.py index 0d66280797..475def3797 100644 --- a/apps/ssserver/models.py +++ b/apps/ssserver/models.py @@ -76,8 +76,7 @@ def user(self): @property def today_is_checked(self): if self.last_check_in_time: - last_check_in_time = pendulum.parse(str(self.last_check_in_time)) - return last_check_in_time.day == get_current_time().day + return self.last_check_in_time.date() == timezone.now().date() return False @property @@ -225,7 +224,7 @@ def get_node_ids(cls, all=False): validators=[MaxValueValidator(9), MinValueValidator(0)]) total_traffic = models.BigIntegerField('总流量', default=settings.GB) used_traffic = models.BigIntegerField('已用流量', default=0,) - speed_limit = models.IntegerField('限速', default=0) + speed_limit = models.IntegerField('限速', default=0) order = models.PositiveSmallIntegerField('排序', default=1) group = models.CharField('分组名', max_length=32, default='谜之屋') From def7701a4ad82b268b8e16114042b8fe38c22522 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Sat, 29 Dec 2018 09:08:42 +0800 Subject: [PATCH 10/23] black code --- apps/api/apps.py | 2 +- apps/api/urls.py | 34 +- apps/api/views.py | 284 ++++---- apps/cachext.py | 12 +- apps/constants.py | 98 +-- apps/custom_views.py | 36 +- apps/payments.py | 7 +- apps/sspanel/admin.py | 36 +- apps/sspanel/apps.py | 2 +- apps/sspanel/backends.py | 8 +- apps/sspanel/forms.py | 89 ++- apps/sspanel/migrations/0001_initial.py | 648 +++++++++++++---- .../migrations/0002_payrequest_qrcode_url.py | 12 +- .../migrations/0003_auto_20181009_0909.py | 26 +- apps/sspanel/models.py | 399 +++++----- apps/sspanel/templatetags/ehcofilter.py | 9 +- apps/sspanel/urls.py | 122 ++-- apps/sspanel/views.py | 625 +++++++--------- apps/ssserver/admin.py | 29 +- apps/ssserver/apps.py | 2 +- apps/ssserver/forms.py | 33 +- apps/ssserver/migrations/0001_initial.py | 685 +++++++++++++++--- .../migrations/0002_auto_20180707_2200.py | 48 +- .../migrations/0003_auto_20180730_0916.py | 25 +- .../migrations/0004_auto_20180930_1537.py | 187 ++++- .../migrations/0005_auto_20181003_1549.py | 31 +- .../migrations/0006_auto_20181003_1759.py | 13 +- .../migrations/0007_auto_20181004_1032.py | 24 +- .../migrations/0008_auto_20181009_0909.py | 42 +- .../migrations/0009_auto_20181122_0930.py | 49 +- .../migrations/0010_auto_20181219_1632.py | 63 +- apps/ssserver/models.py | 401 +++++----- apps/ssserver/urls.py | 16 +- apps/ssserver/views.py | 178 +++-- apps/urls.py | 17 +- apps/utils.py | 92 +-- apps/wsgi.py | 2 +- commands/__init__.py | 8 +- commands/add_invidecode_num.py | 8 +- commands/clear_zombie_user.py | 13 +- commands/croncmds.py | 53 +- commands/export_node_host.py | 12 +- commands/print_user_count.py | 8 +- commands/redeem.py | 18 +- configs/default/__init__.py | 2 +- configs/default/common.py | 142 ++-- configs/default/cron.py | 49 +- configs/default/db.py | 22 +- configs/default/email.py | 8 +- configs/default/sentry.py | 4 +- configs/default/sites.py | 24 +- configs/development.py | 11 +- configs/production.py | 14 +- 53 files changed, 2980 insertions(+), 1802 deletions(-) diff --git a/apps/api/apps.py b/apps/api/apps.py index d87006dd60..14b89a8298 100644 --- a/apps/api/apps.py +++ b/apps/api/apps.py @@ -2,4 +2,4 @@ class ApiConfig(AppConfig): - name = 'api' + name = "api" diff --git a/apps/api/urls.py b/apps/api/urls.py index 9e515d0171..f1c56280ee 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -3,23 +3,23 @@ app_name = "api" urlpatterns = [ - path('user/data/', views.userData, name='userdata'), - path('node/data/', views.nodeData, name='nodedata'), - path('donate/data/', views.donateData, name='donatedata'), - path('random/port/', views.change_ss_port, name='changessport'), - path('gen/invitecode/', views.gen_invite_code, name='geninvitecode'), - path('shop/', views.purchase, name='purchase'), - path('pay/request/', views.pay_request, name='pay_request'), - path('pay/query/', views.pay_query, name='pay_query'), - path('traffic/query/', views.traffic_query, name='traffic_query'), - path('change/theme/', views.change_theme, name='change_theme'), - path('checkin/', views.checkin, name='checkin'), + path("user/data/", views.userData, name="userdata"), + path("node/data/", views.nodeData, name="nodedata"), + path("donate/data/", views.donateData, name="donatedata"), + path("random/port/", views.change_ss_port, name="changessport"), + path("gen/invitecode/", views.gen_invite_code, name="geninvitecode"), + path("shop/", views.purchase, name="purchase"), + path("pay/request/", views.pay_request, name="pay_request"), + path("pay/query/", views.pay_query, name="pay_query"), + path("traffic/query/", views.traffic_query, name="traffic_query"), + path("change/theme/", views.change_theme, name="change_theme"), + path("checkin/", views.checkin, name="checkin"), # 邀请码接口 - path('get/invitecode/', views.get_invitecode, name='get_invitecode'), + path("get/invitecode/", views.get_invitecode, name="get_invitecode"), # web api 接口 - path('nodes/', views.node_api, name='get_node_info'), - path('nodes/online', views.node_online_api, name='post_onlineip'), - path('users/nodes/', views.user_api, name='get_userinfo'), - path('traffic/upload', views.traffic_api, name='post_traffic'), - path('nodes/aliveip', views.alive_ip_api, name='post_aliveip'), + path("nodes/", views.node_api, name="get_node_info"), + path("nodes/online", views.node_online_api, name="post_onlineip"), + path("users/nodes/", views.user_api, name="get_userinfo"), + path("traffic/upload", views.traffic_api, name="post_traffic"), + path("nodes/aliveip", views.alive_ip_api, name="post_aliveip"), ] diff --git a/apps/api/views.py b/apps/api/views.py index bcad19e53e..565cca6b7d 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -13,20 +13,25 @@ from django.contrib.auth.decorators import login_required, permission_required from apps.constants import NODE_USER_INFO_TTL -from apps.utils import ( - traffic_format, simple_cached_view, get_node_user, authorized) -from apps.ssserver.models import (Suser, TrafficLog, Node, NodeOnlineLog, - AliveIp) -from apps.sspanel.models import (InviteCode, PurchaseHistory, RebateRecord, - Goods, User, Donate, PayRequest) - - -@permission_required('sspanel') +from apps.utils import traffic_format, simple_cached_view, get_node_user, authorized +from apps.ssserver.models import Suser, TrafficLog, Node, NodeOnlineLog, AliveIp +from apps.sspanel.models import ( + InviteCode, + PurchaseHistory, + RebateRecord, + Goods, + User, + Donate, + PayRequest, +) + + +@permission_required("sspanel") def userData(request): - ''' + """ 返回用户信息: 在线人数、今日签到、从未签到、从未使用 - ''' + """ data = [ NodeOnlineLog.totalOnlineUser(), @@ -35,16 +40,16 @@ def userData(request): Suser.get_never_checked_user_num(), Suser.get_never_used_num(), ] - return JsonResponse({'data': data}) + return JsonResponse({"data": data}) -@permission_required('sspanel') +@permission_required("sspanel") def nodeData(request): - ''' + """ 返回节点信息 所有节点名 各自消耗的流量 - ''' + """ nodeName = [node.name for node in Node.objects.filter(show=1)] nodeTraffic = [ @@ -52,49 +57,46 @@ def nodeData(request): for node in Node.objects.filter(show=1) ] - data = { - 'nodeName': nodeName, - 'nodeTraffic': nodeTraffic, - } + data = {"nodeName": nodeName, "nodeTraffic": nodeTraffic} return JsonResponse(data) -@permission_required('sspanel') +@permission_required("sspanel") def donateData(request): - ''' + """ 返回捐赠信息 捐赠笔数 捐赠总金额 - ''' + """ data = [Donate.totalDonateNums(), int(Donate.totalDonateMoney())] - return JsonResponse({'data': data}) + return JsonResponse({"data": data}) @login_required def change_ss_port(request): - ''' + """ 随机重置用户用端口 返回是否成功 - ''' + """ user = request.user.ss_user # 找到端口池中最大的端口 port = Suser.get_random_port() user.port = port user.save() registerinfo = { - 'title': '修改成功!', - 'subtitle': '端口修改为:{}!'.format(port), - 'status': 'success', + "title": "修改成功!", + "subtitle": "端口修改为:{}!".format(port), + "status": "success", } return JsonResponse(registerinfo) @login_required def gen_invite_code(request): - ''' + """ 生成用户的邀请码 返回是否成功 - ''' + """ u = request.user if u.is_superuser is True: # 针对管理员特出处理,每次生成5个邀请码 @@ -106,269 +108,253 @@ def gen_invite_code(request): code = InviteCode(code_type=0, code_id=u.pk) code.save() registerinfo = { - 'title': '成功', - 'subtitle': '添加邀请码{}个,请刷新页面'.format(num), - 'status': 'success', + "title": "成功", + "subtitle": "添加邀请码{}个,请刷新页面".format(num), + "status": "success", } else: - registerinfo = { - 'title': '失败', - 'subtitle': '已经不能生成更多的邀请码了', - 'status': 'error', - } + registerinfo = {"title": "失败", "subtitle": "已经不能生成更多的邀请码了", "status": "error"} return JsonResponse(registerinfo) @login_required def purchase(request): if request.method == "POST": - good_id = request.POST.get('goodId') + good_id = request.POST.get("goodId") if Goods.purchase(request.user, good_id) is False: - return JsonResponse({'title': '金额不足!', 'status': 'error', - 'subtitle': '请去捐赠界面/联系站长充值'}) + return JsonResponse( + {"title": "金额不足!", "status": "error", "subtitle": "请去捐赠界面/联系站长充值"} + ) else: - return JsonResponse({'title': '购买成功', 'status': 'success', - 'subtitle': '请在用户中心检查最新信息'}) + return JsonResponse( + {"title": "购买成功", "status": "success", "subtitle": "请在用户中心检查最新信息"} + ) else: - return HttpResponse('errors') + return HttpResponse("errors") @login_required def pay_request(request): - ''' + """ 当面付请求逻辑 - ''' - amount = int(request.POST.get('num')) + """ + amount = int(request.POST.get("num")) if amount < 1: - info = { - 'title': '失败', - 'subtitle': '请保证金额大于1元', - 'status': 'error', - } + info = {"title": "失败", "subtitle": "请保证金额大于1元", "status": "error"} else: req = PayRequest.make_pay_request(request.user, amount) if req is not None: info = { - 'title': '请求成功!', - 'subtitle': '支付宝扫描下方二维码付款,付款完成记得按确认哟!', - 'status': 'success', + "title": "请求成功!", + "subtitle": "支付宝扫描下方二维码付款,付款完成记得按确认哟!", + "status": "success", } else: info = { - 'title': '糟糕,当面付插件可能出现问题了', - 'subtitle': '如果一直失败,请后台联系站长', - 'status': 'error', + "title": "糟糕,当面付插件可能出现问题了", + "subtitle": "如果一直失败,请后台联系站长", + "status": "error", } - return JsonResponse({'info': info}) + return JsonResponse({"info": info}) @login_required def pay_query(request): - ''' + """ 当面付结果查询逻辑 - ''' + """ user = request.user info_code = PayRequest.get_user_recent_pay_req(user).info_code paid = PayRequest.pay_query(user, info_code) if paid in (True, -1): - info = { - 'title': '充值成功!', - 'subtitle': '请去商品界面购买商品!', - 'status': 'success', - } + info = {"title": "充值成功!", "subtitle": "请去商品界面购买商品!", "status": "success"} else: - info = { - 'title': '支付查询失败!请稍候再试', - 'subtitle': '亲,确认支付了么?', - 'status': 'error', - } - return JsonResponse({'info': info}) + info = {"title": "支付查询失败!请稍候再试", "subtitle": "亲,确认支付了么?", "status": "error"} + return JsonResponse({"info": info}) @login_required def traffic_query(request): - ''' + """ 流量查请求 - ''' - node_id = request.POST.get('node_id', 0) - node_name = request.POST.get('node_name', '') + """ + node_id = request.POST.get("node_id", 0) + node_name = request.POST.get("node_name", "") user_id = request.user.pk now = pendulum.now() last_week = [now.subtract(days=i).date() for i in range(6, -1, -1)] - labels = ['{}-{}'.format(t.month, t.day) for t in last_week] + labels = ["{}-{}".format(t.month, t.day) for t in last_week] traffic_data = [ TrafficLog.get_traffic_by_date(node_id, user_id, t) for t in last_week ] total = TrafficLog.get_user_traffic(node_id, user_id) - title = '节点 {} 当月共消耗:{}'.format(node_name, total) + title = "节点 {} 当月共消耗:{}".format(node_name, total) configs = { - 'title': title, - 'labels': labels, - 'data': traffic_data, - 'data_title': node_name, - 'x_label': '日期 最近七天', - 'y_label': '流量 单位:MB' + "title": title, + "labels": labels, + "data": traffic_data, + "data_title": node_name, + "x_label": "日期 最近七天", + "y_label": "流量 单位:MB", } return JsonResponse(configs) @login_required def change_theme(request): - ''' + """ 更换用户主题 - ''' - theme = request.POST.get('theme', 'default') + """ + theme = request.POST.get("theme", "default") user = request.user user.theme = theme user.save() - registerinfo = { - 'title': '修改成功!', - 'subtitle': '主题更换成功,刷新页面可见', - 'status': 'success', - } + registerinfo = {"title": "修改成功!", "subtitle": "主题更换成功,刷新页面可见", "status": "success"} return JsonResponse(registerinfo) @authorized @csrf_exempt -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def get_invitecode(request): - ''' + """ 获取邀请码接口 只开放给管理员账号 返回一个没用过的邀请码 需要验证token - ''' + """ admin_user = User.objects.filter(is_superuser=True).first() - code = InviteCode.objects.filter( - code_id=admin_user.pk, isused=False).first() + code = InviteCode.objects.filter(code_id=admin_user.pk, isused=False).first() if code: - return JsonResponse({'msg': code.code}) + return JsonResponse({"msg": code.code}) else: - return JsonResponse({'msg': '邀请码用光啦'}) + return JsonResponse({"msg": "邀请码用光啦"}) @authorized @simple_cached_view() -@require_http_methods(['GET']) +@require_http_methods(["GET"]) def node_api(request, node_id): - ''' + """ 返回节点信息 筛选节点是否用光 - ''' + """ node = Node.objects.filter(node_id=node_id).first() if node and node.used_traffic < node.total_traffic: - data = (node.traffic_rate, ) + data = (node.traffic_rate,) else: data = None - res = {'ret': 1, 'data': data} + res = {"ret": 1, "data": data} return JsonResponse(res) @authorized @csrf_exempt -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def node_online_api(request): - ''' + """ 接受节点在线人数上报 - ''' + """ data = request.json - node = Node.objects.filter(node_id=data['node_id']).first() + node = Node.objects.filter(node_id=data["node_id"]).first() if node: NodeOnlineLog.objects.create( - node_id=data['node_id'], - online_user=data['online_user'], - log_time=int(time.time())) - res = {'ret': 1, 'data': []} + node_id=data["node_id"], + online_user=data["online_user"], + log_time=int(time.time()), + ) + res = {"ret": 1, "data": []} return JsonResponse(res) @authorized @simple_cached_view(ttl=NODE_USER_INFO_TTL) -@require_http_methods(['GET']) +@require_http_methods(["GET"]) def user_api(request, node_id): - ''' + """ 返回符合节点要求的用户信息 - ''' + """ data = get_node_user(node_id) - res = {'ret': 1, 'data': data} + res = {"ret": 1, "data": data} return JsonResponse(res) @authorized @csrf_exempt -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def traffic_api(request): - ''' + """ 接受服务端的用户流量上报 - ''' + """ data = request.json - node_id = data['node_id'] - traffic_list = data['data'] + node_id = data["node_id"] + traffic_list = data["data"] log_time = int(time.time()) node_total_traffic = 0 trafficlog_model_list = [] for rec in traffic_list: - user_id = rec['user_id'] - u = rec['u'] - d = rec['d'] + user_id = rec["user_id"] + u = rec["u"] + d = rec["d"] # 个人流量增量 Suser.objects.filter(user_id=user_id).update( - download_traffic=F("download_traffic")+d, - upload_traffic=F('upload_traffic') + u, - last_use_time=log_time) + download_traffic=F("download_traffic") + d, + upload_traffic=F("upload_traffic") + u, + last_use_time=log_time, + ) # 个人流量记录 trafficlog_model_list.append( - TrafficLog(node_id=node_id, user_id=user_id, - traffic=traffic_format(u + d), - download_traffic=u, upload_traffic=d, - log_time=log_time)) + TrafficLog( + node_id=node_id, + user_id=user_id, + traffic=traffic_format(u + d), + download_traffic=u, + upload_traffic=d, + log_time=log_time, + ) + ) # 节点流量增量 - node_total_traffic += (u+d) + node_total_traffic += u + d # 节点流量记录 Node.objects.filter(node_id=node_id).update( - used_traffic=F('used_traffic')+node_total_traffic) + used_traffic=F("used_traffic") + node_total_traffic + ) # 流量记录 TrafficLog.objects.bulk_create(trafficlog_model_list) - return JsonResponse({'ret': 1, 'data': []}) + return JsonResponse({"ret": 1, "data": []}) @authorized @csrf_exempt -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def alive_ip_api(request): data = request.json - node_id = data['node_id'] + node_id = data["node_id"] model_list = [] - for user_id, ip_list in data['data'].items(): + for user_id, ip_list in data["data"].items(): user = User.objects.get(id=user_id) for ip in ip_list: - model_list.append( - AliveIp(node_id=node_id, user=user.username, ip=ip)) + model_list.append(AliveIp(node_id=node_id, user=user.username, ip=ip)) AliveIp.objects.bulk_create(model_list) - res = {'ret': 1, 'data': []} + res = {"ret": 1, "data": []} return JsonResponse(res) @login_required def checkin(request): - '''用户签到''' + """用户签到""" ss_user = request.user.ss_user res, traffic = Suser.checkin(ss_user) if res: data = { - 'title': '签到成功!', - 'subtitle': '获得{}流量!'.format(traffic_format(traffic)), - 'status': 'success', + "title": "签到成功!", + "subtitle": "获得{}流量!".format(traffic_format(traffic)), + "status": "success", } else: - data = { - 'title': '签到失败!', - 'subtitle': '距离上次签到不足一天', - 'status': 'error', - } + data = {"title": "签到失败!", "subtitle": "距离上次签到不足一天", "status": "error"} return JsonResponse(data) diff --git a/apps/cachext.py b/apps/cachext.py index 831aa29b1a..5ce047cfd9 100644 --- a/apps/cachext.py +++ b/apps/cachext.py @@ -6,17 +6,15 @@ def norm_cache_key(v): return v.__name__ if isinstance(v, bytes): return v.decode() - if hasattr(v, 'path_info'): - return getattr(v, 'path_info') + if hasattr(v, "path_info"): + return getattr(v, "path_info") if v is None or isinstance(v, DEFAULT_KEY_TYPES): return str(v) else: - raise ValueError( - 'only str, int, float, bool, django.WSGIRequest can be key') + raise ValueError("only str, int, float, bool, django.WSGIRequest can be key") def make_default_key(f, *args, **kwargs): keys = [norm_cache_key(v) for v in args] - keys += sorted( - ['{}={}'.format(k, norm_cache_key(v)) for k, v in kwargs.items()]) - return 'default.{}.{}.{}'.format(f.__module__, f.__name__, '.'.join(keys)) + keys += sorted(["{}={}".format(k, norm_cache_key(v)) for k, v in kwargs.items()]) + return "default.{}.{}.{}".format(f.__module__, f.__name__, ".".join(keys)) diff --git a/apps/constants.py b/apps/constants.py index b833158d39..5f751012d1 100644 --- a/apps/constants.py +++ b/apps/constants.py @@ -1,63 +1,63 @@ METHOD_CHOICES = ( - ('aes-256-cfb', 'aes-256-cfb'), - ('aes-128-ctr', 'aes-128-ctr'), - ('rc4-md5', 'rc4-md5'), - ('salsa20', 'salsa20'), - ('chacha20', 'chacha20'), - ('none', 'none'), + ("aes-256-cfb", "aes-256-cfb"), + ("aes-128-ctr", "aes-128-ctr"), + ("rc4-md5", "rc4-md5"), + ("salsa20", "salsa20"), + ("chacha20", "chacha20"), + ("none", "none"), ) PROTOCOL_CHOICES = ( - ('auth_sha1_v4', 'auth_sha1_v4'), - ('auth_aes128_md5', 'auth_aes128_md5'), - ('auth_aes128_sha1', 'auth_aes128_sha1'), - ('auth_chain_a', 'auth_chain_a'), - ('origin', 'origin'), + ("auth_sha1_v4", "auth_sha1_v4"), + ("auth_aes128_md5", "auth_aes128_md5"), + ("auth_aes128_sha1", "auth_aes128_sha1"), + ("auth_chain_a", "auth_chain_a"), + ("origin", "origin"), ) OBFS_CHOICES = ( - ('plain', 'plain'), - ('http_simple', 'http_simple'), - ('http_simple_compatible', 'http_simple_compatible'), - ('http_post', 'http_post'), - ('tls1.2_ticket_auth', 'tls1.2_ticket_auth'), + ("plain", "plain"), + ("http_simple", "http_simple"), + ("http_simple_compatible", "http_simple_compatible"), + ("http_post", "http_post"), + ("tls1.2_ticket_auth", "tls1.2_ticket_auth"), ) COUNTRIES_CHOICES = ( - ('US', '美国'), - ('CN', '中国'), - ('GB', '英国'), - ('SG', '新加坡'), - ('TW', '台湾'), - ('HK', '香港'), - ('JP', '日本'), - ('FR', '法国'), - ('DE', '德国'), - ('KR', '韩国'), - ('JE', '泽西岛'), - ('NZ', '新西兰'), - ('MX', '墨西哥'), - ('CA', '加拿大'), - ('BR', '巴西'), - ('CU', '古巴'), - ('CZ', '捷克'), - ('EG', '埃及'), - ('FI', '芬兰'), - ('GR', '希腊'), - ('GU', '关岛'), - ('IS', '冰岛'), - ('MO', '澳门'), - ('NL', '荷兰'), - ('NO', '挪威'), - ('PL', '波兰'), - ('IT', '意大利'), - ('IE', '爱尔兰'), - ('AR', '阿根廷'), - ('PT', '葡萄牙'), - ('AU', '澳大利亚'), - ('RU', '俄罗斯联邦'), - ('CF', '中非共和国'), + ("US", "美国"), + ("CN", "中国"), + ("GB", "英国"), + ("SG", "新加坡"), + ("TW", "台湾"), + ("HK", "香港"), + ("JP", "日本"), + ("FR", "法国"), + ("DE", "德国"), + ("KR", "韩国"), + ("JE", "泽西岛"), + ("NZ", "新西兰"), + ("MX", "墨西哥"), + ("CA", "加拿大"), + ("BR", "巴西"), + ("CU", "古巴"), + ("CZ", "捷克"), + ("EG", "埃及"), + ("FI", "芬兰"), + ("GR", "希腊"), + ("GU", "关岛"), + ("IS", "冰岛"), + ("MO", "澳门"), + ("NL", "荷兰"), + ("NO", "挪威"), + ("PL", "波兰"), + ("IT", "意大利"), + ("IE", "爱尔兰"), + ("AR", "阿根廷"), + ("PT", "葡萄牙"), + ("AU", "澳大利亚"), + ("RU", "俄罗斯联邦"), + ("CF", "中非共和国"), ) THEME_CHOICES = ( diff --git a/apps/custom_views.py b/apps/custom_views.py index 5aa9f4537c..f0936e64c1 100644 --- a/apps/custom_views.py +++ b/apps/custom_views.py @@ -2,13 +2,13 @@ class Page_List_View(object): - ''' + """ 拥有翻页功能的通用类 Args: request : django request obj: 等待分分页的列表,例如 User.objects.all() page_num: 分页的页数 - ''' + """ def __init__(self, request, obj_list, page_num): self.request = request @@ -16,11 +16,11 @@ def __init__(self, request, obj_list, page_num): self.page_num = page_num def get_page_context(self): - '''返回分页context''' + """返回分页context""" # 每页显示10条记录 paginator = Paginator(self.obj_list, self.page_num) # 构造分页.获取当前页码数量 - page = self.request.GET.get('page') + page = self.request.GET.get("page") # 页码为1时,防止异常 try: contacts = paginator.page(page) @@ -44,7 +44,7 @@ def get_page_context(self): # 开始构造页码列表 if page == 1: # 当前页为第1页时 - right = page_list[page:page + 2] + right = page_list[page : page + 2] if len(right) > 0: # 当最后一页比总页数小时,我们应该显示省略号 if right[-1] < total - 1: @@ -54,14 +54,14 @@ def get_page_context(self): last = True elif page == total: # 当前页为最后一页时 - left = page_list[(page - 3) if (page - 3) > 0 else 0:page - 1] + left = page_list[(page - 3) if (page - 3) > 0 else 0 : page - 1] if left[0] > 2: left_has_more = True if left[0] > 1: first = True else: - left = page_list[(page - 2) if (page - 2) > 0 else 0:page - 1] - right = page_list[page:page + 2] + left = page_list[(page - 2) if (page - 2) > 0 else 0 : page - 1] + right = page_list[page : page + 2] # 是否需要显示最后一页和最后一页前的省略号 if right[-1] < total - 1: right_has_more = True @@ -73,15 +73,15 @@ def get_page_context(self): if left[0] > 1: first = True context = { - 'contacts': contacts, - 'page_list': page_list, - 'left': left, - 'right': right, - 'left_has_more': left_has_more, - 'right_has_more': right_has_more, - 'first': first, - 'last': last, - 'total': total, - 'page': page, + "contacts": contacts, + "page_list": page_list, + "left": left, + "right": right, + "left_has_more": left_has_more, + "right_has_more": right_has_more, + "first": first, + "last": last, + "total": total, + "page": page, } return context diff --git a/apps/payments.py b/apps/payments.py index f7aa97d416..cdf348fcef 100644 --- a/apps/payments.py +++ b/apps/payments.py @@ -5,16 +5,15 @@ # AppId -APPID = '' +APPID = "" path = os.path.split(os.path.realpath(__file__))[0] # Pub pem path -PUBLIC_KEY_PATH = path + '/Alipay_pub.pem' +PUBLIC_KEY_PATH = path + "/Alipay_pub.pem" # Private pem path -PRIVATE_KEY_PATH = path + '/App_pravite.pem' +PRIVATE_KEY_PATH = path + "/App_pravite.pem" class Alipayments: - def __init__(self, app_id, pub_key_path, pri_key_path): self.app_id = app_id self.pub_key_path = pub_key_path diff --git a/apps/sspanel/admin.py b/apps/sspanel/admin.py index dd84339b98..4003fe8a42 100644 --- a/apps/sspanel/admin.py +++ b/apps/sspanel/admin.py @@ -5,45 +5,45 @@ class UserAdmin(admin.ModelAdmin): - list_display = ['username', 'id', 'level', 'balance', 'level_expire_time'] - search_fields = ['username', 'email', 'id'] - list_filter = ['level', ] + list_display = ["username", "id", "level", "balance", "level_expire_time"] + search_fields = ["username", "email", "id"] + list_filter = ["level"] class PurchaseHistoryAdmin(admin.ModelAdmin): - list_display = ['good', 'user', 'money', 'purchtime', ] - search_fields = ['user', ] - list_filter = ['good', 'purchtime'] + list_display = ["good", "user", "money", "purchtime"] + search_fields = ["user"] + list_filter = ["good", "purchtime"] class InviteCodeAdmin(admin.ModelAdmin): - list_display = ['code', 'time_created', 'isused', 'code_type'] - search_fields = ['code'] + list_display = ["code", "time_created", "isused", "code_type"] + search_fields = ["code"] class MoneyCodeAdmin(admin.ModelAdmin): - list_display = ['user', 'code', 'isused'] + list_display = ["user", "code", "isused"] class AlipayAdmin(admin.ModelAdmin): - list_display = ['username', 'info_code', 'amount', 'money_code', 'time', ] - search_fields = ['info_code', ] - list_filter = ['time', 'amount', ] + list_display = ["username", "info_code", "amount", "money_code", "time"] + search_fields = ["info_code"] + list_filter = ["time", "amount"] class AlipayRequestAdmin(admin.ModelAdmin): - list_display = ['username', 'amount', 'info_code', 'time', ] - search_fields = ['info_code', ] - list_filter = ['time', 'amount', ] + list_display = ["username", "amount", "info_code", "time"] + search_fields = ["info_code"] + list_filter = ["time", "amount"] class DonateAdmin(admin.ModelAdmin): - list_display = ['user', 'money', 'time', ] - list_filter = ['time', 'money', ] + list_display = ["user", "money", "time"] + list_filter = ["time", "money"] class GoodsAdmin(admin.ModelAdmin): - list_display = ['name', 'transfer', 'money', 'level', ] + list_display = ["name", "transfer", "money", "level"] # Register your models here. diff --git a/apps/sspanel/apps.py b/apps/sspanel/apps.py index b89846c49b..cfde28be0c 100644 --- a/apps/sspanel/apps.py +++ b/apps/sspanel/apps.py @@ -2,4 +2,4 @@ class SspanelConfig(AppConfig): - name = 'sspanel' + name = "sspanel" diff --git a/apps/sspanel/backends.py b/apps/sspanel/backends.py index 9be0813113..226329fff3 100644 --- a/apps/sspanel/backends.py +++ b/apps/sspanel/backends.py @@ -3,19 +3,19 @@ class EmailBackend(object): def authenticate(self, requests, **credentials): - email = credentials.get('email', credentials.get('username')) + email = credentials.get("email", credentials.get("username")) try: user = User.objects.get(email=email) except User.DoesNotExist: pass else: - if user.check_password(credentials['password']): + if user.check_password(credentials["password"]): return user def get_user(self, user_id): - ''' + """ 该方法是必须的 - ''' + """ try: return User.objects.get(pk=user_id) except User.DoesNotExist: diff --git a/apps/sspanel/forms.py b/apps/sspanel/forms.py index 1ea4d7f113..5d8ab7d38a 100644 --- a/apps/sspanel/forms.py +++ b/apps/sspanel/forms.py @@ -8,76 +8,69 @@ class RegisterForm(UserCreationForm): - '''注册时渲染的表单''' + """注册时渲染的表单""" username = forms.CharField( - label='用户名', - help_text='必填。150个字符或者更少。包含字母,数字和仅有的@/./+/-/_符号。', - widget=forms.TextInput( - attrs={'class': 'input is-info'}) + label="用户名", + help_text="必填。150个字符或者更少。包含字母,数字和仅有的@/./+/-/_符号。", + widget=forms.TextInput(attrs={"class": "input is-info"}), ) - email = forms.EmailField(label='邮箱', - widget=forms.TextInput( - attrs={'class': 'input is-warning'})) - invitecode = forms.CharField(label='邀请码', help_text='邀请码必须填写', - widget=forms.TextInput( - attrs={'class': 'input is-success'})) - password1 = forms.CharField(label='密码', help_text='''你的密码不能与其他个人信息太相似。 + email = forms.EmailField( + label="邮箱", widget=forms.TextInput(attrs={"class": "input is-warning"}) + ) + invitecode = forms.CharField( + label="邀请码", + help_text="邀请码必须填写", + widget=forms.TextInput(attrs={"class": "input is-success"}), + ) + password1 = forms.CharField( + label="密码", + help_text="""你的密码不能与其他个人信息太相似。 你的密码必须包含至少 8 个字符。 你的密码不能是大家都爱用的常见密码 - 你的密码不能全部为数字。''', - widget=forms.TextInput( - attrs={ - 'class': 'input is-primary', - 'type': 'password'})) - password2 = forms.CharField(label='重复密码', - widget=forms.TextInput( - attrs={ - 'class': 'input is-danger', - 'type': 'password'})) + 你的密码不能全部为数字。""", + widget=forms.TextInput(attrs={"class": "input is-primary", "type": "password"}), + ) + password2 = forms.CharField( + label="重复密码", + widget=forms.TextInput(attrs={"class": "input is-danger", "type": "password"}), + ) def clean_email(self): - email = self.cleaned_data.get('email') + email = self.cleaned_data.get("email") if User.objects.filter(email=email).first(): - raise forms.ValidationError('该邮箱已经注册过了') + raise forms.ValidationError("该邮箱已经注册过了") else: return email def clean_invitecode(self): - code = self.cleaned_data.get('invitecode') + code = self.cleaned_data.get("invitecode") if InviteCode.objects.filter(code=code, isused=False).first(): return code else: - raise forms.ValidationError('该邀请码失效') + raise forms.ValidationError("该邀请码失效") class Meta(UserCreationForm.Meta): model = User - fields = ('username', 'email', 'password1', 'password2', 'invitecode',) + fields = ("username", "email", "password1", "password2", "invitecode") class LoginForm(forms.Form): username = forms.CharField( required=True, label=u"用户名", - error_messages={'required': '请输入用户名'}, + error_messages={"required": "请输入用户名"}, widget=forms.TextInput( - attrs={ - 'class': 'input is-primary', - 'placeholder': "用户名", - } + attrs={"class": "input is-primary", "placeholder": "用户名"} ), ) password = forms.CharField( required=True, label=u"密码", - error_messages={'required': u'请输入密码'}, + error_messages={"required": u"请输入密码"}, widget=forms.PasswordInput( - attrs={ - 'class': 'input is-primary', - 'placeholder': "密码", - 'type': 'password', - } + attrs={"class": "input is-primary", "placeholder": "密码", "type": "password"} ), ) @@ -89,37 +82,37 @@ def clean(self): class NodeForm(ModelForm): - total_traffic = forms.IntegerField(label='节点总流量(GB)') + total_traffic = forms.IntegerField(label="节点总流量(GB)") def clean(self): data = self.cleaned_data - data['total_traffic'] = data['total_traffic'] * settings.GB + data["total_traffic"] = data["total_traffic"] * settings.GB return data class Meta: model = Node - fields = '__all__' - exclude = ['used_traffic', ] + fields = "__all__" + exclude = ["used_traffic"] class GoodsForm(ModelForm): class Meta: model = Goods - fields = '__all__' + fields = "__all__" class AnnoForm(ModelForm): class Meta: model = Announcement - fields = '__all__' + fields = "__all__" class UserForm(ModelForm): class Meta: model = User - fields = ['balance', 'level', 'level_expire_time', ] + fields = ["balance", "level", "level_expire_time"] widgets = { - 'balance': forms.NumberInput(attrs={'class': 'input'}), - 'level': forms.NumberInput(attrs={'class': 'input'}), - 'level_expire_time': forms.DateTimeInput(attrs={'class': 'input'}), + "balance": forms.NumberInput(attrs={"class": "input"}), + "level": forms.NumberInput(attrs={"class": "input"}), + "level_expire_time": forms.DateTimeInput(attrs={"class": "input"}), } diff --git a/apps/sspanel/migrations/0001_initial.py b/apps/sspanel/migrations/0001_initial.py index 3e6799b737..e17b6afcb8 100644 --- a/apps/sspanel/migrations/0001_initial.py +++ b/apps/sspanel/migrations/0001_initial.py @@ -14,186 +14,570 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ('auth', '0009_alter_user_last_name_max_length'), - ] + dependencies = [("auth", "0009_alter_user_last_name_max_length")] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('invitecode', models.CharField(max_length=40, verbose_name='邀请码')), - ('invited_by', models.PositiveIntegerField(default=1, verbose_name='邀请人id')), - ('balance', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='余额')), - ('invitecode_num', models.PositiveIntegerField(default=5, verbose_name='可生成的邀请码数量')), - ('level', models.PositiveIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(9), django.core.validators.MinValueValidator(0)], verbose_name='用户等级')), - ('level_expire_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='等级有效期')), - ('theme', models.CharField(choices=[('default', 'default'), ('darkly', 'darkly'), ('flatly', 'flatly'), ('journal', 'journal'), ('materia', 'materia'), ('minty', 'minty'), ('spacelab', 'spacelab'), ('superhero', 'superhero')], default='superhero', max_length=10, verbose_name='主题')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=30, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ("invitecode", models.CharField(max_length=40, verbose_name="邀请码")), + ( + "invited_by", + models.PositiveIntegerField(default=1, verbose_name="邀请人id"), + ), + ( + "balance", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + verbose_name="余额", + ), + ), + ( + "invitecode_num", + models.PositiveIntegerField(default=5, verbose_name="可生成的邀请码数量"), + ), + ( + "level", + models.PositiveIntegerField( + default=0, + validators=[ + django.core.validators.MaxValueValidator(9), + django.core.validators.MinValueValidator(0), + ], + verbose_name="用户等级", + ), + ), + ( + "level_expire_time", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="等级有效期" + ), + ), + ( + "theme", + models.CharField( + choices=[ + ("default", "default"), + ("darkly", "darkly"), + ("flatly", "flatly"), + ("journal", "journal"), + ("materia", "materia"), + ("minty", "minty"), + ("spacelab", "spacelab"), + ("superhero", "superhero"), + ], + default="superhero", + max_length=10, + verbose_name="主题", + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), ], options={ - 'verbose_name': '用户', - 'verbose_name_plural': 'users', - 'abstract': False, + "verbose_name": "用户", + "verbose_name_plural": "users", + "abstract": False, }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], + managers=[("objects", django.contrib.auth.models.UserManager())], ), migrations.CreateModel( - name='Announcement', + name="Announcement", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('time', models.DateTimeField(auto_now_add=True, verbose_name='时间')), - ('body', models.TextField(verbose_name='主体')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("time", models.DateTimeField(auto_now_add=True, verbose_name="时间")), + ("body", models.TextField(verbose_name="主体")), ], - options={ - 'verbose_name_plural': '系统公告', - 'ordering': ('-time',), - }, + options={"verbose_name_plural": "系统公告", "ordering": ("-time",)}, ), migrations.CreateModel( - name='Donate', + name="Donate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('time', models.DateTimeField(auto_now_add=True, verbose_name='捐赠时间')), - ('money', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='捐赠金额')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='捐赠人')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("time", models.DateTimeField(auto_now_add=True, verbose_name="捐赠时间")), + ( + "money", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + verbose_name="捐赠金额", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="捐赠人", + ), + ), ], - options={ - 'verbose_name_plural': '捐赠记录', - 'ordering': ('-time',), - }, + options={"verbose_name_plural": "捐赠记录", "ordering": ("-time",)}, ), migrations.CreateModel( - name='Goods', + name="Goods", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='待编辑', max_length=128, verbose_name='商品名字')), - ('content', models.CharField(default='待编辑', max_length=256, verbose_name='商品描述')), - ('transfer', models.BigIntegerField(default=1073741824, verbose_name='增加的流量')), - ('money', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='金额')), - ('level', models.PositiveIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(9), django.core.validators.MinValueValidator(0)], verbose_name='设置等级')), - ('days', models.PositiveIntegerField(default=1, validators=[django.core.validators.MaxValueValidator(365), django.core.validators.MinValueValidator(1)], verbose_name='设置等级时间(天)')), - ('status', models.SmallIntegerField(choices=[(1, '上架'), (-1, '下架')], default=1, verbose_name='商品状态')), - ('order', models.PositiveSmallIntegerField(default=1, verbose_name='排序')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + default="待编辑", max_length=128, verbose_name="商品名字" + ), + ), + ( + "content", + models.CharField( + default="待编辑", max_length=256, verbose_name="商品描述" + ), + ), + ( + "transfer", + models.BigIntegerField(default=1073741824, verbose_name="增加的流量"), + ), + ( + "money", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + verbose_name="金额", + ), + ), + ( + "level", + models.PositiveIntegerField( + default=0, + validators=[ + django.core.validators.MaxValueValidator(9), + django.core.validators.MinValueValidator(0), + ], + verbose_name="设置等级", + ), + ), + ( + "days", + models.PositiveIntegerField( + default=1, + validators=[ + django.core.validators.MaxValueValidator(365), + django.core.validators.MinValueValidator(1), + ], + verbose_name="设置等级时间(天)", + ), + ), + ( + "status", + models.SmallIntegerField( + choices=[(1, "上架"), (-1, "下架")], default=1, verbose_name="商品状态" + ), + ), + ( + "order", + models.PositiveSmallIntegerField(default=1, verbose_name="排序"), + ), ], - options={ - 'verbose_name_plural': '商品', - 'ordering': ['order'], - }, + options={"verbose_name_plural": "商品", "ordering": ["order"]}, ), migrations.CreateModel( - name='InviteCode', + name="InviteCode", fields=[ - ('code_type', models.IntegerField(choices=[(1, '公开'), (0, '不公开')], default=0, verbose_name='类型')), - ('code_id', models.PositiveIntegerField(default=1, verbose_name='邀请人ID')), - ('code', models.CharField(blank=True, default=apps.utils.get_long_random_string, max_length=40, primary_key=True, serialize=False, verbose_name='邀请码')), - ('time_created', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('isused', models.BooleanField(default=False, verbose_name='是否使用')), + ( + "code_type", + models.IntegerField( + choices=[(1, "公开"), (0, "不公开")], default=0, verbose_name="类型" + ), + ), + ( + "code_id", + models.PositiveIntegerField(default=1, verbose_name="邀请人ID"), + ), + ( + "code", + models.CharField( + blank=True, + default=apps.utils.get_long_random_string, + max_length=40, + primary_key=True, + serialize=False, + verbose_name="邀请码", + ), + ), + ( + "time_created", + models.DateTimeField(auto_now_add=True, verbose_name="创建时间"), + ), + ("isused", models.BooleanField(default=False, verbose_name="是否使用")), ], options={ - 'verbose_name_plural': '邀请码', - 'ordering': ('isused', '-time_created'), + "verbose_name_plural": "邀请码", + "ordering": ("isused", "-time_created"), }, ), migrations.CreateModel( - name='MoneyCode', + name="MoneyCode", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('user', models.CharField(blank=True, max_length=128, null=True, verbose_name='用户名')), - ('time', models.DateTimeField(auto_now_add=True, verbose_name='捐赠时间')), - ('code', models.CharField(blank=True, default=apps.utils.get_long_random_string, max_length=40, unique=True, verbose_name='充值码')), - ('number', models.DecimalField(blank=True, decimal_places=2, default=10, max_digits=10, null=True, verbose_name='捐赠金额')), - ('isused', models.BooleanField(default=False, verbose_name='是否使用')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "user", + models.CharField( + blank=True, max_length=128, null=True, verbose_name="用户名" + ), + ), + ("time", models.DateTimeField(auto_now_add=True, verbose_name="捐赠时间")), + ( + "code", + models.CharField( + blank=True, + default=apps.utils.get_long_random_string, + max_length=40, + unique=True, + verbose_name="充值码", + ), + ), + ( + "number", + models.DecimalField( + blank=True, + decimal_places=2, + default=10, + max_digits=10, + null=True, + verbose_name="捐赠金额", + ), + ), + ("isused", models.BooleanField(default=False, verbose_name="是否使用")), ], - options={ - 'verbose_name_plural': '充值码', - 'ordering': ('isused',), - }, + options={"verbose_name_plural": "充值码", "ordering": ("isused",)}, ), migrations.CreateModel( - name='PayRecord', + name="PayRecord", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('username', models.CharField(max_length=64, verbose_name='用户名')), - ('info_code', models.CharField(max_length=64, unique=True, verbose_name='流水号')), - ('time', models.DateTimeField(auto_now_add=True, verbose_name='时间')), - ('amount', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='金额')), - ('money_code', models.CharField(max_length=64, unique=True, verbose_name='充值码')), - ('charge_type', models.CharField(default=1, help_text='1:支付宝 2:QQ钱包 3:微信支付', max_length=10, verbose_name='充值类型')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("username", models.CharField(max_length=64, verbose_name="用户名")), + ( + "info_code", + models.CharField(max_length=64, unique=True, verbose_name="流水号"), + ), + ("time", models.DateTimeField(auto_now_add=True, verbose_name="时间")), + ( + "amount", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + verbose_name="金额", + ), + ), + ( + "money_code", + models.CharField(max_length=64, unique=True, verbose_name="充值码"), + ), + ( + "charge_type", + models.CharField( + default=1, + help_text="1:支付宝 2:QQ钱包 3:微信支付", + max_length=10, + verbose_name="充值类型", + ), + ), ], - options={ - 'verbose_name_plural': '支付转账记录', - 'ordering': ('-time',), - }, + options={"verbose_name_plural": "支付转账记录", "ordering": ("-time",)}, ), migrations.CreateModel( - name='PayRequest', + name="PayRequest", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('username', models.CharField(max_length=64, verbose_name='用户名')), - ('info_code', models.CharField(max_length=64, unique=True, verbose_name='流水号')), - ('time', models.DateTimeField(auto_now_add=True, verbose_name='时间')), - ('amount', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='金额')), - ('charge_type', models.CharField(default=1, help_text='1:支付宝 2:QQ钱包 3:微信支付', max_length=10, verbose_name='充值类型')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("username", models.CharField(max_length=64, verbose_name="用户名")), + ( + "info_code", + models.CharField(max_length=64, unique=True, verbose_name="流水号"), + ), + ("time", models.DateTimeField(auto_now_add=True, verbose_name="时间")), + ( + "amount", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + verbose_name="金额", + ), + ), + ( + "charge_type", + models.CharField( + default=1, + help_text="1:支付宝 2:QQ钱包 3:微信支付", + max_length=10, + verbose_name="充值类型", + ), + ), ], - options={ - 'verbose_name_plural': '支付申请记录', - 'ordering': ('-time',), - }, + options={"verbose_name_plural": "支付申请记录", "ordering": ("-time",)}, ), migrations.CreateModel( - name='PurchaseHistory', + name="PurchaseHistory", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('user', models.CharField(max_length=128, verbose_name='购买者')), - ('money', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='金额')), - ('purchtime', models.DateTimeField(auto_now_add=True, verbose_name='购买时间')), - ('good', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sspanel.Goods', verbose_name='商品名')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("user", models.CharField(max_length=128, verbose_name="购买者")), + ( + "money", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + verbose_name="金额", + ), + ), + ( + "purchtime", + models.DateTimeField(auto_now_add=True, verbose_name="购买时间"), + ), + ( + "good", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="sspanel.Goods", + verbose_name="商品名", + ), + ), ], - options={ - 'verbose_name_plural': '购买记录', - 'ordering': ('-purchtime',), - }, + options={"verbose_name_plural": "购买记录", "ordering": ("-purchtime",)}, ), migrations.CreateModel( - name='RebateRecord', + name="RebateRecord", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('user_id', models.PositiveIntegerField(default=1, verbose_name='返利人ID')), - ('rebatetime', models.DateTimeField(auto_now_add=True, verbose_name='返利时间')), - ('money', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, null=True, verbose_name='金额')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "user_id", + models.PositiveIntegerField(default=1, verbose_name="返利人ID"), + ), + ( + "rebatetime", + models.DateTimeField(auto_now_add=True, verbose_name="返利时间"), + ), + ( + "money", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + verbose_name="金额", + ), + ), ], - options={ - 'ordering': ('-rebatetime',), - }, + options={"ordering": ("-rebatetime",)}, ), migrations.CreateModel( - name='Ticket', + name="Ticket", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('time', models.DateTimeField(auto_now_add=True, verbose_name='时间')), - ('title', models.CharField(max_length=128, verbose_name='标题')), - ('body', models.TextField(verbose_name='内容主体')), - ('status', models.SmallIntegerField(choices=[(1, '开启'), (-1, '关闭')], default=1, verbose_name='状态')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("time", models.DateTimeField(auto_now_add=True, verbose_name="时间")), + ("title", models.CharField(max_length=128, verbose_name="标题")), + ("body", models.TextField(verbose_name="内容主体")), + ( + "status", + models.SmallIntegerField( + choices=[(1, "开启"), (-1, "关闭")], default=1, verbose_name="状态" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="用户", + ), + ), ], - options={ - 'verbose_name_plural': '工单', - 'ordering': ('-time',), - }, + options={"verbose_name_plural": "工单", "ordering": ("-time",)}, ), ] diff --git a/apps/sspanel/migrations/0002_payrequest_qrcode_url.py b/apps/sspanel/migrations/0002_payrequest_qrcode_url.py index 8ef4066d51..af7f4ffc5d 100644 --- a/apps/sspanel/migrations/0002_payrequest_qrcode_url.py +++ b/apps/sspanel/migrations/0002_payrequest_qrcode_url.py @@ -5,14 +5,12 @@ class Migration(migrations.Migration): - dependencies = [ - ('sspanel', '0001_initial'), - ] + dependencies = [("sspanel", "0001_initial")] operations = [ migrations.AddField( - model_name='payrequest', - name='qrcode_url', - field=models.CharField(max_length=64, null=True, verbose_name='支付连接'), - ), + model_name="payrequest", + name="qrcode_url", + field=models.CharField(max_length=64, null=True, verbose_name="支付连接"), + ) ] diff --git a/apps/sspanel/migrations/0003_auto_20181009_0909.py b/apps/sspanel/migrations/0003_auto_20181009_0909.py index 5f0adb493f..0cd48cd3fb 100644 --- a/apps/sspanel/migrations/0003_auto_20181009_0909.py +++ b/apps/sspanel/migrations/0003_auto_20181009_0909.py @@ -5,14 +5,26 @@ class Migration(migrations.Migration): - dependencies = [ - ('sspanel', '0002_payrequest_qrcode_url'), - ] + dependencies = [("sspanel", "0002_payrequest_qrcode_url")] operations = [ migrations.AlterField( - model_name='user', - name='theme', - field=models.CharField(choices=[('default', 'default'), ('darkly', 'darkly'), ('flatly', 'flatly'), ('journal', 'journal'), ('materia', 'materia'), ('minty', 'minty'), ('spacelab', 'spacelab'), ('superhero', 'superhero')], default='default', max_length=10, verbose_name='主题'), - ), + model_name="user", + name="theme", + field=models.CharField( + choices=[ + ("default", "default"), + ("darkly", "darkly"), + ("flatly", "flatly"), + ("journal", "journal"), + ("materia", "materia"), + ("minty", "minty"), + ("spacelab", "spacelab"), + ("superhero", "superhero"), + ], + default="default", + max_length=10, + verbose_name="主题", + ), + ) ] diff --git a/apps/sspanel/models.py b/apps/sspanel/models.py index 5ea1d5be7f..890ab8e8da 100644 --- a/apps/sspanel/models.py +++ b/apps/sspanel/models.py @@ -19,25 +19,37 @@ class User(AbstractUser): - '''SS账户模型''' - - invitecode = models.CharField(verbose_name='邀请码', max_length=40) - invited_by = models.PositiveIntegerField(verbose_name='邀请人id', default=1) - balance = models.DecimalField(verbose_name='余额', decimal_places=2, - max_digits=10, default=0, editable=True, - null=True, blank=True,) + """SS账户模型""" + + invitecode = models.CharField(verbose_name="邀请码", max_length=40) + invited_by = models.PositiveIntegerField(verbose_name="邀请人id", default=1) + balance = models.DecimalField( + verbose_name="余额", + decimal_places=2, + max_digits=10, + default=0, + editable=True, + null=True, + blank=True, + ) invitecode_num = models.PositiveIntegerField( - verbose_name='可生成的邀请码数量', default=settings.INVITE_NUM) - level = models.PositiveIntegerField(verbose_name='用户等级', default=0, - validators=[MaxValueValidator(9), - MinValueValidator(0), ]) - level_expire_time = models.DateTimeField(verbose_name='等级有效期', - default=timezone.now) - theme = models.CharField(verbose_name='主题', choices=THEME_CHOICES, - default=settings.DEFAULT_THEME, max_length=10) + verbose_name="可生成的邀请码数量", default=settings.INVITE_NUM + ) + level = models.PositiveIntegerField( + verbose_name="用户等级", + default=0, + validators=[MaxValueValidator(9), MinValueValidator(0)], + ) + level_expire_time = models.DateTimeField(verbose_name="等级有效期", default=timezone.now) + theme = models.CharField( + verbose_name="主题", + choices=THEME_CHOICES, + default=settings.DEFAULT_THEME, + max_length=10, + ) class Meta(AbstractUser.Meta): - verbose_name = '用户' + verbose_name = "用户" def delete(self): self.ss_user.delete() @@ -48,44 +60,45 @@ def __str__(self): @property def expire_time(self): - '''返回等级到期时间''' + """返回等级到期时间""" return self.level_expire_time @property def sub_link(self): - '''生成该用户的订阅地址''' + """生成该用户的订阅地址""" p = PreparedRequest() token = base64.b64encode(self.username.encode()).decode() - url = settings.HOST + 'server/subscribe/' - params = {'token': token} + url = settings.HOST + "server/subscribe/" + params = {"token": token} p.prepare_url(url, params) return p.url @property def ss_user(self): from apps.ssserver.models import Suser + return Suser.objects.get(user_id=self.id) @classmethod def get_user_num(cls): - '''返回用户总数''' + """返回用户总数""" return cls.objects.all().count() @classmethod def get_today_register_user(cls): - '''返回今日注册的用户''' - today = datetime.datetime.combine(datetime.date.today(), - datetime.time.min) + """返回今日注册的用户""" + today = datetime.datetime.combine(datetime.date.today(), datetime.time.min) return cls.objects.filter(date_joined__gt=today) @classmethod def add_new_user(cls, cleaned_data): from apps.ssserver.models import Suser + with transaction.atomic(): - username = cleaned_data['username'] - email = cleaned_data['email'] - password = cleaned_data['password1'] - invitecode = cleaned_data['invitecode'] + username = cleaned_data["username"] + email = cleaned_data["email"] + password = cleaned_data["password1"] + invitecode = cleaned_data["invitecode"] user = cls.objects.create_user(username, email, password) code = InviteCode.objects.get(code=invitecode) code.isused = True @@ -100,57 +113,68 @@ def add_new_user(cls, cleaned_data): class InviteCode(models.Model): - '''邀请码''' + """邀请码""" - INVITE_CODE_TYPE = ((1, '公开'), (0, '不公开')) + INVITE_CODE_TYPE = ((1, "公开"), (0, "不公开")) code_type = models.IntegerField( - verbose_name='类型', choices=INVITE_CODE_TYPE, default=0,) - code_id = models.PositiveIntegerField(verbose_name='邀请人ID', default=1) - code = models.CharField(verbose_name='邀请码', primary_key=True, blank=True, - max_length=40, default=get_long_random_string) + verbose_name="类型", choices=INVITE_CODE_TYPE, default=0 + ) + code_id = models.PositiveIntegerField(verbose_name="邀请人ID", default=1) + code = models.CharField( + verbose_name="邀请码", + primary_key=True, + blank=True, + max_length=40, + default=get_long_random_string, + ) time_created = models.DateTimeField( - verbose_name='创建时间', editable=False, auto_now_add=True) - isused = models.BooleanField(verbose_name='是否使用', default=False,) + verbose_name="创建时间", editable=False, auto_now_add=True + ) + isused = models.BooleanField(verbose_name="是否使用", default=False) def __str__(self): return str(self.code) class Meta: - verbose_name_plural = '邀请码' - ordering = ( - 'isused', - '-time_created', - ) + verbose_name_plural = "邀请码" + ordering = ("isused", "-time_created") class RebateRecord(models.Model): - '''返利记录''' + """返利记录""" - user_id = models.PositiveIntegerField(verbose_name='返利人ID', default=1) + user_id = models.PositiveIntegerField(verbose_name="返利人ID", default=1) rebatetime = models.DateTimeField( - verbose_name='返利时间', editable=False, auto_now_add=True) + verbose_name="返利时间", editable=False, auto_now_add=True + ) money = models.DecimalField( - verbose_name='金额', decimal_places=2, null=True, default=0, max_digits=10, blank=True) + verbose_name="金额", + decimal_places=2, + null=True, + default=0, + max_digits=10, + blank=True, + ) class Meta: - ordering = ('-rebatetime', ) + ordering = ("-rebatetime",) class Donate(models.Model): @classmethod def totalDonateMoney(cls): - '''返回捐赠总金额''' + """返回捐赠总金额""" return sum([d.money for d in cls.objects.all()]) @classmethod def totalDonateNums(cls): - '''返回捐赠总数量''' + """返回捐赠总数量""" return len(cls.objects.all()) @classmethod def richPeople(cls): - '''返回捐赠金额最多的前10名''' + """返回捐赠金额最多的前10名""" rec = {} for d in cls.objects.all(): if d.user not in rec.keys(): @@ -159,37 +183,53 @@ def richPeople(cls): rec[d.user] += d.money return sorted(rec.items(), key=lambda rec: rec[1], reverse=True)[:10] - '''捐赠记录''' - user = models.ForeignKey( - User, on_delete=models.CASCADE, verbose_name='捐赠人') - time = models.DateTimeField('捐赠时间', editable=False, auto_now_add=True) + """捐赠记录""" + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="捐赠人") + time = models.DateTimeField("捐赠时间", editable=False, auto_now_add=True) money = models.DecimalField( - verbose_name='捐赠金额', decimal_places=2, max_digits=10, default=0, null=True, blank=True) + verbose_name="捐赠金额", + decimal_places=2, + max_digits=10, + default=0, + null=True, + blank=True, + ) def __str__(self): - return '{}-{}'.format(self.user, self.money) + return "{}-{}".format(self.user, self.money) class Meta: - verbose_name_plural = '捐赠记录' - ordering = ('-time', ) + verbose_name_plural = "捐赠记录" + ordering = ("-time",) class MoneyCode(models.Model): - '''充值码''' - user = models.CharField( - verbose_name='用户名', max_length=128, blank=True, null=True) - time = models.DateTimeField('捐赠时间', editable=False, auto_now_add=True) - code = models.CharField(verbose_name='充值码', unique=True, - blank=True, max_length=40, default=get_long_random_string) + """充值码""" + + user = models.CharField(verbose_name="用户名", max_length=128, blank=True, null=True) + time = models.DateTimeField("捐赠时间", editable=False, auto_now_add=True) + code = models.CharField( + verbose_name="充值码", + unique=True, + blank=True, + max_length=40, + default=get_long_random_string, + ) number = models.DecimalField( - verbose_name='捐赠金额', decimal_places=2, max_digits=10, default=10, null=True, blank=True) - isused = models.BooleanField(verbose_name='是否使用', default=False) + verbose_name="捐赠金额", + decimal_places=2, + max_digits=10, + default=10, + null=True, + blank=True, + ) + isused = models.BooleanField(verbose_name="是否使用", default=False) def clean(self): # 保证充值码不会重复 - code_length = len(self.code or '') + code_length = len(self.code or "") if 0 < code_length < 12: - self.code = '{}{}'.format(self.code, get_long_random_string()) + self.code = "{}{}".format(self.code, get_long_random_string()) else: self.code = get_long_random_string() @@ -197,47 +237,54 @@ def __str__(self): return self.code class Meta: - verbose_name_plural = '充值码' - ordering = ('isused', ) + verbose_name_plural = "充值码" + ordering = ("isused",) class Goods(models.Model): - '''商品''' + """商品""" - STATUS_TYPE = ( - (1, '上架'), - (-1, '下架'), - ) + STATUS_TYPE = ((1, "上架"), (-1, "下架")) - name = models.CharField(verbose_name='商品名字', max_length=128, default='待编辑') - content = models.CharField( - verbose_name='商品描述', max_length=256, default='待编辑') - transfer = models.BigIntegerField( - verbose_name='增加的流量', default=settings.GB) + name = models.CharField(verbose_name="商品名字", max_length=128, default="待编辑") + content = models.CharField(verbose_name="商品描述", max_length=256, default="待编辑") + transfer = models.BigIntegerField(verbose_name="增加的流量", default=settings.GB) money = models.DecimalField( - verbose_name='金额', decimal_places=2, max_digits=10, default=0, null=True, blank=True) - level = models.PositiveIntegerField(verbose_name='设置等级', default=0, validators=[ - MaxValueValidator(9), MinValueValidator(0), ]) - days = models.PositiveIntegerField(verbose_name='设置等级时间(天)', default=1, validators=[ - MaxValueValidator(365), MinValueValidator(1)]) - status = models.SmallIntegerField('商品状态', default=1, choices=STATUS_TYPE) - order = models.PositiveSmallIntegerField('排序', default=1) + verbose_name="金额", + decimal_places=2, + max_digits=10, + default=0, + null=True, + blank=True, + ) + level = models.PositiveIntegerField( + verbose_name="设置等级", + default=0, + validators=[MaxValueValidator(9), MinValueValidator(0)], + ) + days = models.PositiveIntegerField( + verbose_name="设置等级时间(天)", + default=1, + validators=[MaxValueValidator(365), MinValueValidator(1)], + ) + status = models.SmallIntegerField("商品状态", default=1, choices=STATUS_TYPE) + order = models.PositiveSmallIntegerField("排序", default=1) def __str__(self): return self.name @property def total_transfer(self): - '''增加的流量''' + """增加的流量""" return traffic_format(self.transfer) def get_days(self): - '''返回增加的天数''' - return '{}'.format(self.days) + """返回增加的天数""" + return "{}".format(self.days) @classmethod def purchase(cls, user, good_id): - '''购买商品 返回是否成功''' + """购买商品 返回是否成功""" good = cls.objects.get(pk=good_id) if user.balance < good.money: return False @@ -248,7 +295,7 @@ def purchase(cls, user, good_id): user.balance -= good.money now = pendulum.now() days = pendulum.duration(days=good.days) - if (user.level == good.level and user.level_expire_time > now): + if user.level == good.level and user.level_expire_time > now: user.level_expire_time += days else: user.level_expire_time = now + days @@ -256,115 +303,133 @@ def purchase(cls, user, good_id): user.save() ss_user.save() # 增加购买记录 - PurchaseHistory.objects.create(good=good, user=user, money=good.money, - purchtime=now) + PurchaseHistory.objects.create( + good=good, user=user, money=good.money, purchtime=now + ) # 增加返利记录 inviter = User.objects.filter(pk=user.invited_by).first() if inviter: rebaterecord = RebateRecord( - user_id=inviter.pk, - money=good.money * Decimal(settings.INVITE_PERCENT)) + user_id=inviter.pk, money=good.money * Decimal(settings.INVITE_PERCENT) + ) inviter.balance += rebaterecord.money inviter.save() rebaterecord.save() return True class Meta: - verbose_name_plural = '商品' - ordering = [ - 'order', - ] + verbose_name_plural = "商品" + ordering = ["order"] class PurchaseHistory(models.Model): - '''购买记录''' + """购买记录""" - good = models.ForeignKey( - Goods, on_delete=models.CASCADE, verbose_name='商品名') - user = models.CharField(verbose_name='购买者', max_length=128) + good = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name="商品名") + user = models.CharField(verbose_name="购买者", max_length=128) money = models.DecimalField( - verbose_name='金额', decimal_places=2, max_digits=10, default=0, null=True, blank=True) - purchtime = models.DateTimeField('购买时间', editable=False, auto_now_add=True) + verbose_name="金额", + decimal_places=2, + max_digits=10, + default=0, + null=True, + blank=True, + ) + purchtime = models.DateTimeField("购买时间", editable=False, auto_now_add=True) def __str__(self): return self.user class Meta: - verbose_name_plural = '购买记录' - ordering = ('-purchtime', ) + verbose_name_plural = "购买记录" + ordering = ("-purchtime",) @classmethod def cost_statistics(cls, good_id, start, end): start = pendulum.parse(start, tz=timezone.get_current_timezone()) end = pendulum.parse(end, tz=timezone.get_current_timezone()) query = cls.objects.filter( - good__id=good_id, purchtime__gte=start, purchtime__lte=end) + good__id=good_id, purchtime__gte=start, purchtime__lte=end + ) for obj in query: print(obj.user, obj.good) count = query.count() amount = count * obj.money - print('{} ~ {} 时间内 商品: {} 共销售 {} 次 总金额 {} 元'.format( - start.date(), end.date(), obj.good, count, amount)) + print( + "{} ~ {} 时间内 商品: {} 共销售 {} 次 总金额 {} 元".format( + start.date(), end.date(), obj.good, count, amount + ) + ) class PayRecord(models.Model): - '''充值流水单号记录''' + """充值流水单号记录""" username = models.CharField( - verbose_name='用户名', max_length=64, blank=False, null=False) - info_code = models.CharField( - verbose_name='流水号', max_length=64, unique=True) - time = models.DateTimeField(verbose_name='时间', auto_now_add=True) + verbose_name="用户名", max_length=64, blank=False, null=False + ) + info_code = models.CharField(verbose_name="流水号", max_length=64, unique=True) + time = models.DateTimeField(verbose_name="时间", auto_now_add=True) amount = models.DecimalField( - verbose_name='金额', decimal_places=2, max_digits=10, default=0, null=True, blank=True) - money_code = models.CharField( - verbose_name='充值码', max_length=64, unique=True) - charge_type = models.CharField(verbose_name='充值类型', max_length=10, - default=1, - help_text='1:支付宝 2:QQ钱包 3:微信支付') + verbose_name="金额", + decimal_places=2, + max_digits=10, + default=0, + null=True, + blank=True, + ) + money_code = models.CharField(verbose_name="充值码", max_length=64, unique=True) + charge_type = models.CharField( + verbose_name="充值类型", max_length=10, default=1, help_text="1:支付宝 2:QQ钱包 3:微信支付" + ) def __str__(self): return self.info_code class Meta: - verbose_name_plural = '支付转账记录' - ordering = ('-time', ) + verbose_name_plural = "支付转账记录" + ordering = ("-time",) class PayRequest(models.Model): - '''支付申请记录''' + """支付申请记录""" username = models.CharField( - verbose_name='用户名', max_length=64, blank=False, null=False) - info_code = models.CharField( - verbose_name='流水号', max_length=64, unique=True) - time = models.DateTimeField('时间', auto_now_add=True) - qrcode_url = models.CharField( - verbose_name='支付连接', max_length=64, null=True) - amount = models.DecimalField(verbose_name='金额', decimal_places=2, - max_digits=10, default=0, - null=True, blank=True) - charge_type = models.CharField(verbose_name='充值类型', max_length=10, - default=1, - help_text='1:支付宝 2:QQ钱包 3:微信支付') + verbose_name="用户名", max_length=64, blank=False, null=False + ) + info_code = models.CharField(verbose_name="流水号", max_length=64, unique=True) + time = models.DateTimeField("时间", auto_now_add=True) + qrcode_url = models.CharField(verbose_name="支付连接", max_length=64, null=True) + amount = models.DecimalField( + verbose_name="金额", + decimal_places=2, + max_digits=10, + default=0, + null=True, + blank=True, + ) + charge_type = models.CharField( + verbose_name="充值类型", max_length=10, default=1, help_text="1:支付宝 2:QQ钱包 3:微信支付" + ) def __str__(self): return self.username @classmethod def make_pay_request(cls, user, amount): - '''生成一个支付请求''' - info_code = datetime.datetime.fromtimestamp( - time.time()).strftime('%Y%m%d%H%M%S%s') + """生成一个支付请求""" + info_code = datetime.datetime.fromtimestamp(time.time()).strftime( + "%Y%m%d%H%M%S%s" + ) try: # 生成订单 trade = pay.alipay.api_alipay_trade_precreate( subject=settings.ALIPAY_TRADE_INFO.format(amount), out_trade_no=info_code, total_amount=amount, - timeout_express='60s', + timeout_express="60s", ) - qrcode_url = trade.get('qr_code') + qrcode_url = trade.get("qr_code") req = cls.objects.create( username=user.username, info_code=info_code, @@ -378,13 +443,12 @@ def make_pay_request(cls, user, amount): @classmethod def get_user_recent_pay_req(cls, user): - req = cls.objects.filter( - username=user.username).order_by('-time').first() + req = cls.objects.filter(username=user.username).order_by("-time").first() return req @classmethod def pay_query(cls, user, info_code): - '''支付结果查询''' + """支付结果查询""" paid = False if PayRecord.objects.filter(info_code=info_code).first() is not None: # 已经为该用户充值过了 @@ -404,52 +468,53 @@ def pay_query(cls, user, info_code): code.save() # 将充值记录和捐赠绑定 Donate.objects.create(user=user, money=amount) - PayRecord.objects.create(username=user, info_code=info_code, - amount=amount, money_code=code) + PayRecord.objects.create( + username=user, info_code=info_code, amount=amount, money_code=code + ) return paid class Meta: - verbose_name_plural = '支付申请记录' - ordering = ('-time', ) + verbose_name_plural = "支付申请记录" + ordering = ("-time",) class Announcement(models.Model): - '''公告界面''' - time = models.DateTimeField('时间', auto_now_add=True) - body = models.TextField('主体') + """公告界面""" + + time = models.DateTimeField("时间", auto_now_add=True) + body = models.TextField("主体") def __str__(self): - return '日期:{}'.format(str(self.time)[:9]) + return "日期:{}".format(str(self.time)[:9]) # 重写save函数,将文本渲染成markdown格式存入数据库 def save(self, *args, **kwargs): # 首先实例化一个MarkDown类,来渲染一下body的文本 成为html文本 - md = markdown.Markdown(extensions=[ - 'markdown.extensions.extra', - ]) + md = markdown.Markdown(extensions=["markdown.extensions.extra"]) self.body = md.convert(self.body) # 调动父类save 将数据保存到数据库中 super(Announcement, self).save(*args, **kwargs) class Meta: - verbose_name_plural = '系统公告' - ordering = ('-time', ) + verbose_name_plural = "系统公告" + ordering = ("-time",) class Ticket(models.Model): - '''工单''' - TICKET_CHOICE = ((1, '开启'), (-1, '关闭')) - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户') - time = models.DateTimeField( - verbose_name='时间', editable=False, auto_now_add=True) - title = models.CharField(verbose_name='标题', max_length=128) - body = models.TextField(verbose_name='内容主体') + """工单""" + + TICKET_CHOICE = ((1, "开启"), (-1, "关闭")) + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户") + time = models.DateTimeField(verbose_name="时间", editable=False, auto_now_add=True) + title = models.CharField(verbose_name="标题", max_length=128) + body = models.TextField(verbose_name="内容主体") status = models.SmallIntegerField( - verbose_name='状态', choices=TICKET_CHOICE, default=1,) + verbose_name="状态", choices=TICKET_CHOICE, default=1 + ) def __str__(self): return self.title class Meta: - verbose_name_plural = '工单' - ordering = ('-time', ) + verbose_name_plural = "工单" + ordering = ("-time",) diff --git a/apps/sspanel/templatetags/ehcofilter.py b/apps/sspanel/templatetags/ehcofilter.py index 1a27f841a4..629a37a570 100644 --- a/apps/sspanel/templatetags/ehcofilter.py +++ b/apps/sspanel/templatetags/ehcofilter.py @@ -5,17 +5,18 @@ # 增加模板class -@register.filter(name='add_class') +@register.filter(name="add_class") def add_class(value, arg): - return value.as_widget(attrs={'class': arg}) + return value.as_widget(attrs={"class": arg}) + # 捐赠名混淆 -@register.filter(name='mix_name') +@register.filter(name="mix_name") def mix_name(value, arg): value = str(value) - mix_name = value[0]+'***'+value[-1] + mix_name = value[0] + "***" + value[-1] return mix_name diff --git a/apps/sspanel/urls.py b/apps/sspanel/urls.py index deb04cdc4f..1b6e715a9d 100644 --- a/apps/sspanel/urls.py +++ b/apps/sspanel/urls.py @@ -1,91 +1,79 @@ from django.urls import path -from .import views +from . import views app_name = "sspanel" urlpatterns = [ # 网站用户面板 - path('sshelp/', views.sshelp, name='sshelp'), - path('ssclient/', views.ssclient, name='ssclient'), - path('ssinvite/', views.ssinvite, name='ssinvite'), - path('passinvite/()/', - views.pass_invitecode, name='passinvitecode'), + path("sshelp/", views.sshelp, name="sshelp"), + path("ssclient/", views.ssclient, name="ssclient"), + path("ssinvite/", views.ssinvite, name="ssinvite"), + path("passinvite/()/", views.pass_invitecode, name="passinvitecode"), # 注册/登录 - path('register/', views.register, name='register'), - path('login/', views.user_login, name='login'), - path('logout/', views.user_logout, name='logout'), + path("register/", views.register, name="register"), + path("login/", views.user_login, name="login"), + path("logout/", views.user_logout, name="logout"), # 节点 - path('nodeinfo/', views.nodeinfo, name='nodeinfo'), - path('trafficlog/', views.trafficlog, name='trafficlog'), + path("nodeinfo/", views.nodeinfo, name="nodeinfo"), + path("trafficlog/", views.trafficlog, name="trafficlog"), # 用户信息 - path('users/userinfo/', views.userinfo, name='userinfo'), - path('users/userinfoedit/', views.userinfo_edit, name='userinfo_edit'), + path("users/userinfo/", views.userinfo, name="userinfo"), + path("users/userinfoedit/", views.userinfo_edit, name="userinfo_edit"), # 二维码 - path('qrcode/ssr//', - views.get_ssr_qrcode, name='ssrqrcode'), - path('qrcode/ss//', - views.get_ss_qrcode, name='ssqrcode'), + path("qrcode/ssr//", views.get_ssr_qrcode, name="ssrqrcode"), + path("qrcode/ss//", views.get_ss_qrcode, name="ssqrcode"), # 捐赠/充值 - path('donate/', views.donate, name='donate'), - path('shop/', views.shop, name='shop'), - path('purchaselog/', views.purchaselog, name='purchaselog'), - path('chargecenter/', views.chargecenter, name='chargecenter'), - path('charge/', views.charge, name='charge'), + path("donate/", views.donate, name="donate"), + path("shop/", views.shop, name="shop"), + path("purchaselog/", views.purchaselog, name="purchaselog"), + path("chargecenter/", views.chargecenter, name="chargecenter"), + path("charge/", views.charge, name="charge"), # 公告 - path('announcement/', views.announcement, name='announcement'), + path("announcement/", views.announcement, name="announcement"), # 工单 - path('ticket/', views.ticket, name='ticket'), - path('ticket/create/', views.ticket_create, name='ticket_create'), - path('ticket/edit/()/', - views.ticket_edit, name='ticket_edit'), - path('ticket/delete/)/', - views.ticket_delete, name='ticket_delete'), + path("ticket/", views.ticket, name="ticket"), + path("ticket/create/", views.ticket_create, name="ticket_create"), + path("ticket/edit/()/", views.ticket_edit, name="ticket_edit"), + path("ticket/delete/)/", views.ticket_delete, name="ticket_delete"), # 推广相关 - path('affiliate/', views.affiliate, name='affiliate'), - path('rebate/record/', views.rebate_record, name='rebate'), + path("affiliate/", views.affiliate, name="affiliate"), + path("rebate/record/", views.rebate_record, name="rebate"), # 网站后台面板 - path('backend/', views.backend_index, name='backend_index'), + path("backend/", views.backend_index, name="backend_index"), # 邀请码相关 - path('backend/invite/', views.backend_invite, name='backend_invite'), - path('invite_gen_code/', views.gen_invite_code, name='geninvitecode'), + path("backend/invite/", views.backend_invite, name="backend_invite"), + path("invite_gen_code/", views.gen_invite_code, name="geninvitecode"), # 节点相关 - path('backend/nodeinfo/', views.backend_node_info, - name='backend_node_info'), - path('backend/node/delete//', - views.node_delete, name='node_delete'), - path('backend/node/edit//', - views.node_edit, name='node_edit'), - path('backend/node/create/', views.node_create, name='node_create'), + path("backend/nodeinfo/", views.backend_node_info, name="backend_node_info"), + path("backend/node/delete//", views.node_delete, name="node_delete"), + path("backend/node/edit//", views.node_edit, name="node_edit"), + path("backend/node/create/", views.node_create, name="node_create"), # 用户相关 - path('backend/userlist/', views.backend_userlist, name='user_list'), - path('backend/user/delete//', - views.user_delete, name='user_delete'), - path('backend/user/search/', views.user_search, name='user_search'), - path('backend/user/status/', views.user_status, name='user_status'), + path("backend/userlist/", views.backend_userlist, name="user_list"), + path("backend/user/delete//", views.user_delete, name="user_delete"), + path("backend/user/search/", views.user_search, name="user_search"), + path("backend/user/status/", views.user_status, name="user_status"), # 商品充值相关 - path('backend/charge/', views.backend_charge, name='backend_charge'), - path('backend/shop/', views.backend_shop, name='backend_shop'), - path('backend/shop/delete//', - views.good_delete, name='good_delete'), - path('backend/good/create/', views.good_create, name='good_create'), - path('backend/good/edit//', - views.good_edit, name='good_edit'), - path('backend/purchase/history/', - views.purchase_history, name='purchase_history'), + path("backend/charge/", views.backend_charge, name="backend_charge"), + path("backend/shop/", views.backend_shop, name="backend_shop"), + path("backend/shop/delete//", views.good_delete, name="good_delete"), + path("backend/good/create/", views.good_create, name="good_create"), + path("backend/good/edit//", views.good_edit, name="good_edit"), + path("backend/purchase/history/", views.purchase_history, name="purchase_history"), # 支付宝当面付相关: - path('facepay/qrcode/', views.gen_face_pay_qrcode, name='facepay_qrcode'), + path("facepay/qrcode/", views.gen_face_pay_qrcode, name="facepay_qrcode"), # 公告管理相关 - path('backend/anno/', views.backend_anno, name='backend_anno'), - path('backend/anno/delete//', - views.anno_delete, name='anno_delete'), - path('backend/anno/create/', views.anno_create, name='anno_create'), - path('backend/anno/edit//', - views.anno_edit, name='anno_edit'), + path("backend/anno/", views.backend_anno, name="backend_anno"), + path("backend/anno/delete//", views.anno_delete, name="anno_delete"), + path("backend/anno/create/", views.anno_create, name="anno_create"), + path("backend/anno/edit//", views.anno_edit, name="anno_edit"), # 工单相关 - path('backend/ticket/', views.backend_ticket, name='backend_ticket'), - path('backend/ticket/edit//', - views.backend_ticketedit, name='backend_ticketedit'), + path("backend/ticket/", views.backend_ticket, name="backend_ticket"), + path( + "backend/ticket/edit//", + views.backend_ticketedit, + name="backend_ticketedit", + ), # 在线ip - path('backend/aliveuser/', views.backend_alive_user, name='alive_user'), - + path("backend/aliveuser/", views.backend_alive_user, name="alive_user"), ] diff --git a/apps/sspanel/views.py b/apps/sspanel/views.py index 84f700d170..879e9b67ba 100644 --- a/apps/sspanel/views.py +++ b/apps/sspanel/views.py @@ -15,104 +15,103 @@ from apps.custom_views import Page_List_View from .forms import RegisterForm, LoginForm, NodeForm, GoodsForm, AnnoForm from apps.ssserver.models import Suser, Node, NodeOnlineLog, AliveIp -from .models import (InviteCode, User, Donate, Goods, MoneyCode, - PurchaseHistory, PayRequest, Announcement, Ticket, - RebateRecord) -from apps.constants import (METHOD_CHOICES, PROTOCOL_CHOICES, OBFS_CHOICES, - THEME_CHOICES) +from .models import ( + InviteCode, + User, + Donate, + Goods, + MoneyCode, + PurchaseHistory, + PayRequest, + Announcement, + Ticket, + RebateRecord, +) +from apps.constants import METHOD_CHOICES, PROTOCOL_CHOICES, OBFS_CHOICES, THEME_CHOICES def index(request): - '''跳转到首页''' + """跳转到首页""" - return render(request, 'sspanel/index.html', - {'allow_register': settings.ALLOW_REGISET}) + return render( + request, "sspanel/index.html", {"allow_register": settings.ALLOW_REGISET} + ) def sshelp(request): - '''跳转到帮助界面''' - return render(request, 'sspanel/help.html') + """跳转到帮助界面""" + return render(request, "sspanel/help.html") @login_required def ssclient(request): - '''跳转到客户端界面''' - return render(request, 'sspanel/client.html') + """跳转到客户端界面""" + return render(request, "sspanel/client.html") def ssinvite(request): - '''跳转到邀请码界面''' - codelist = InviteCode.objects.filter( - code_type=1, isused=False, code_id=1)[:20] + """跳转到邀请码界面""" + codelist = InviteCode.objects.filter(code_type=1, isused=False, code_id=1)[:20] - context = { - 'codelist': codelist, - } + context = {"codelist": codelist} - return render(request, 'sspanel/invite.html', context=context) + return render(request, "sspanel/invite.html", context=context) def pass_invitecode(request, invitecode): - '''提供点击邀请码连接之后自动填写邀请码''' - form = RegisterForm(initial={'invitecode': invitecode}) - return render(request, 'sspanel/register.html', {'form': form}) + """提供点击邀请码连接之后自动填写邀请码""" + form = RegisterForm(initial={"invitecode": invitecode}) + return render(request, "sspanel/register.html", {"form": form}) def register(request): - '''用户注册时的函数''' + """用户注册时的函数""" if settings.ALLOW_REGISET is False: - return HttpResponse('已经关闭注册了喵') - if request.method == 'POST': + return HttpResponse("已经关闭注册了喵") + if request.method == "POST": form = RegisterForm(request.POST) if form.is_valid(): user = User.add_new_user(form.cleaned_data) if not user: - messages.error(request, "服务出现了点小问题", - extra_tags="请尝试或者联系站长~") - return render( - request, 'sspanel/register.html', {'form': form}) + messages.error(request, "服务出现了点小问题", extra_tags="请尝试或者联系站长~") + return render(request, "sspanel/register.html", {"form": form}) else: - messages.success(request, "请登录使用吧!", - extra_tags="注册成功!") + messages.success(request, "请登录使用吧!", extra_tags="注册成功!") - return HttpResponseRedirect(reverse('index')) + return HttpResponseRedirect(reverse("index")) else: form = RegisterForm() - return render(request, 'sspanel/register.html', {'form': form}) + return render(request, "sspanel/register.html", {"form": form}) def user_login(request): - '''用户登录函数''' - if request.method == 'POST': + """用户登录函数""" + if request.method == "POST": form = LoginForm(request.POST) if form.is_valid(): # 获取表单用户名和密码 - username = form.cleaned_data['username'] - password = form.cleaned_data['password'] + username = form.cleaned_data["username"] + password = form.cleaned_data["password"] # 进行用户验证 user = authenticate(username=username, password=password) if user is not None and user.is_active: login(request, user) messages.success(request, "自动跳转到用户中心", extra_tags="登录成功!") - return HttpResponseRedirect(reverse('sspanel:userinfo')) + return HttpResponseRedirect(reverse("sspanel:userinfo")) else: form = LoginForm() messages.error(request, "请重新填写信息!", extra_tags="登录失败!") - context = { - 'form': form, - } - return render(request, 'sspanel/login.html', context=context) + context = {"form": form} + return render(request, "sspanel/login.html", context=context) else: - context = { - 'form': LoginForm(), - } + context = {"form": LoginForm()} - return render(request, 'sspanel/login.html', context=context) + return render(request, "sspanel/login.html", context=context) def user_logout(request): - '''用户登出函数''' + """用户登出函数""" logout(request) messages.success(request, "欢迎下次再来", extra_tags="注销成功") return HttpResponseRedirect(reverse("index")) @@ -120,29 +119,29 @@ def user_logout(request): @login_required def userinfo(request): - '''用户中心''' + """用户中心""" user = request.user # 获取公告 anno = Announcement.objects.first() min_traffic = traffic_format(settings.MIN_CHECKIN_TRAFFIC) max_traffic = traffic_format(settings.MAX_CHECKIN_TRAFFIC) - remain_traffic = '{:.2f}'.format(100 - user.ss_user.used_percentage) + remain_traffic = "{:.2f}".format(100 - user.ss_user.used_percentage) context = { - 'user': user, - 'anno': anno, - 'remain_traffic': remain_traffic, - 'min_traffic': min_traffic, - 'max_traffic': max_traffic, - 'sub_link': user.sub_link, - 'import_code': Node.get_import_code(user), - 'themes': THEME_CHOICES + "user": user, + "anno": anno, + "remain_traffic": remain_traffic, + "min_traffic": min_traffic, + "max_traffic": max_traffic, + "sub_link": user.sub_link, + "import_code": Node.get_import_code(user), + "themes": THEME_CHOICES, } - return render(request, 'sspanel/userinfo.html', context=context) + return render(request, "sspanel/userinfo.html", context=context) @login_required def get_ssr_qrcode(request, node_id): - '''返回节点配置信息的ssr二维码''' + """返回节点配置信息的ssr二维码""" # 获取用户对象 ss_user = request.user.ss_user @@ -151,7 +150,7 @@ def get_ssr_qrcode(request, node_id): node = Node.objects.get(node_id=node_id) # 加入节点信息等级判断 if user.level < node.level: - return HttpResponse('哟小伙子,可以啊!但是投机取巧是不对的哦!') + return HttpResponse("哟小伙子,可以啊!但是投机取巧是不对的哦!") ssr_link = node.get_ssr_link(ss_user) ssr_img = qrcode.make(ssr_link) buf = BytesIO() @@ -164,7 +163,7 @@ def get_ssr_qrcode(request, node_id): @login_required def get_ss_qrcode(request, node_id): - '''返回节点配置信息的ss二维码''' + """返回节点配置信息的ss二维码""" # 获取用户对象 ss_user = request.user.ss_user @@ -173,7 +172,7 @@ def get_ss_qrcode(request, node_id): node = Node.objects.get(node_id=node_id) # 加入节点信息等级判断 if user.level < node.level: - return HttpResponse('哟小伙子,可以啊!但是投机取巧是不对的哦!') + return HttpResponse("哟小伙子,可以啊!但是投机取巧是不对的哦!") ss_link = node.get_ss_link(ss_user) ss_img = qrcode.make(ss_link) buf = BytesIO() @@ -186,39 +185,37 @@ def get_ss_qrcode(request, node_id): @login_required def userinfo_edit(request): - '''跳转到资料编辑界面''' + """跳转到资料编辑界面""" ss_user = request.user.ss_user methods = [m[0] for m in METHOD_CHOICES] protocols = [p[0] for p in PROTOCOL_CHOICES] obfss = [o[0] for o in OBFS_CHOICES] context = { - 'ss_user': ss_user, - 'methods': methods, - 'protocols': protocols, - 'obfss': obfss, + "ss_user": ss_user, + "methods": methods, + "protocols": protocols, + "obfss": obfss, } - return render(request, 'sspanel/userinfoedit.html', context=context) + return render(request, "sspanel/userinfoedit.html", context=context) @login_required def donate(request): - '''捐赠界面和支付宝当面付功能''' + """捐赠界面和支付宝当面付功能""" donatelist = Donate.objects.all()[:8] - context = { - 'donatelist': donatelist, - } + context = {"donatelist": donatelist} if settings.USE_ALIPAY is True: - context['alipay'] = True + context["alipay"] = True else: # 关闭支付宝支付 - context['alipay'] = False - return render(request, 'sspanel/donate.html', context=context) + context["alipay"] = False + return render(request, "sspanel/donate.html", context=context) @login_required def gen_face_pay_qrcode(request): - '''生成当面付的二维码''' + """生成当面付的二维码""" req = PayRequest.get_user_recent_pay_req(request.user) if req: @@ -231,12 +228,12 @@ def gen_face_pay_qrcode(request): response = HttpResponse(image_stream, content_type="image/png") return response else: - return HttpResponse('wrong') + return HttpResponse("wrong") @login_required def nodeinfo(request): - '''跳转到节点信息的页面''' + """跳转到节点信息的页面""" nodelists = [] ss_user = request.user.ss_user @@ -246,115 +243,106 @@ def nodeinfo(request): # 循环遍历每一条线路的在线人数 for node in nodes: # 生成SSR和SS的链接 - obj = Node.objects.get(node_id=node['node_id']) - node['ssrlink'] = obj.get_ssr_link(ss_user) - node['sslink'] = obj.get_ss_link(ss_user) - node['country'] = obj.country.lower() - node['node_type'] = obj.get_node_type_display()[:-3] + obj = Node.objects.get(node_id=node["node_id"]) + node["ssrlink"] = obj.get_ssr_link(ss_user) + node["sslink"] = obj.get_ss_link(ss_user) + node["country"] = obj.country.lower() + node["node_type"] = obj.get_node_type_display()[:-3] if obj.node_type == 1: # 单端口的情况下 - node['port'] = obj.port - node['method'] = obj.method - node['password'] = obj.password - node['protocol'] = obj.protocol - node['node_color'] = 'warning' - node['protocol_param'] = '{}:{}'.format(ss_user.port, - ss_user.password) - node['obfs'] = obj.obfs - node['obfs_param'] = obj.obfs_param + node["port"] = obj.port + node["method"] = obj.method + node["password"] = obj.password + node["protocol"] = obj.protocol + node["node_color"] = "warning" + node["protocol_param"] = "{}:{}".format(ss_user.port, ss_user.password) + node["obfs"] = obj.obfs + node["obfs_param"] = obj.obfs_param else: - node['port'] = ss_user.port - node['method'] = ss_user.method - node['password'] = ss_user.password - node['protocol'] = ss_user.protocol - node['node_color'] = 'info' - node['protocol_param'] = ss_user.protocol_param - node['obfs'] = ss_user.obfs - node['obfs_param'] = ss_user.obfs_param + node["port"] = ss_user.port + node["method"] = ss_user.method + node["password"] = ss_user.password + node["protocol"] = ss_user.protocol + node["node_color"] = "info" + node["protocol_param"] = ss_user.protocol_param + node["obfs"] = ss_user.obfs + node["obfs_param"] = ss_user.obfs_param # 得到在线人数 - log = NodeOnlineLog.objects.filter(node_id=node['node_id']).last() + log = NodeOnlineLog.objects.filter(node_id=node["node_id"]).last() if log: - node['online'] = log.get_oneline_status() - node['count'] = log.get_online_user() + node["online"] = log.get_oneline_status() + node["count"] = log.get_online_user() else: - node['online'] = False - node['count'] = 0 + node["online"] = False + node["count"] = 0 # 添加ss_type - node['ss_type_info'] = obj.get_ss_type_display() + node["ss_type_info"] = obj.get_ss_type_display() nodelists.append(node) context = { - 'nodelists': nodelists, - 'ss_user': ss_user, - 'user': user, - 'sub_link': user.sub_link + "nodelists": nodelists, + "ss_user": ss_user, + "user": user, + "sub_link": user.sub_link, } - return render(request, 'sspanel/nodeinfo.html', context=context) + return render(request, "sspanel/nodeinfo.html", context=context) @login_required def trafficlog(request): - '''跳转到流量记录的页面''' + """跳转到流量记录的页面""" ss_user = request.user.ss_user nodes = Node.objects.filter(show=1) - context = { - 'ss_user': ss_user, - 'nodes': nodes, - } - return render(request, 'sspanel/trafficlog.html', context=context) + context = {"ss_user": ss_user, "nodes": nodes} + return render(request, "sspanel/trafficlog.html", context=context) @login_required def shop(request): - '''跳转到商品界面''' + """跳转到商品界面""" ss_user = request.user goods = Goods.objects.filter(status=1) - context = { - 'ss_user': ss_user, - 'goods': goods, - } - return render(request, 'sspanel/shop.html', context=context) + context = {"ss_user": ss_user, "goods": goods} + return render(request, "sspanel/shop.html", context=context) @login_required def purchaselog(request): - '''用户购买记录页面''' + """用户购买记录页面""" records = PurchaseHistory.objects.filter(user=request.user)[:10] - context = { - 'records': records, - } - return render(request, 'sspanel/purchaselog.html', context=context) + context = {"records": records} + return render(request, "sspanel/purchaselog.html", context=context) @login_required def chargecenter(request): - '''充值界面的跳转''' + """充值界面的跳转""" user = request.user codelist = MoneyCode.objects.filter(user=user) - context = {'user': user, 'codelist': codelist} + context = {"user": user, "codelist": codelist} - return render(request, 'sspanel/chargecenter.html', context=context) + return render(request, "sspanel/chargecenter.html", context=context) @login_required def charge(request): user = request.user - if request.method == 'POST': - input_code = request.POST.get('chargecode') + if request.method == "POST": + input_code = request.POST.get("chargecode") # 在数据库里检索充值 code = MoneyCode.objects.filter(code=input_code).first() # 判断充值码是否存在 if not code: messages.error(request, "请重新获取充值码", extra_tags="充值码失效") - return HttpResponseRedirect(reverse('sspanel:chargecenter')) + return HttpResponseRedirect(reverse("sspanel:chargecenter")) else: # 判断充值码是否被使用 if code.isused is True: # 当被使用的是时候 messages.error(request, "请重新获取充值码", extra_tags="充值码失效") - return HttpResponseRedirect(reverse('sspanel:chargecenter')) + return HttpResponseRedirect(reverse("sspanel:chargecenter")) else: # 充值操作 user.balance += code.number @@ -365,134 +353,126 @@ def charge(request): # 将充值记录和捐赠绑定 Donate.objects.create(user=user, money=code.number) messages.success(request, "请去商店购买商品!", extra_tags="充值成功!") - return HttpResponseRedirect(reverse('sspanel:chargecenter')) + return HttpResponseRedirect(reverse("sspanel:chargecenter")) @login_required def announcement(request): - '''网站公告列表''' + """网站公告列表""" anno = Announcement.objects.all() - return render(request, 'sspanel/announcement.html', {'anno': anno}) + return render(request, "sspanel/announcement.html", {"anno": anno}) @login_required def ticket(request): - '''工单系统''' + """工单系统""" ticket = Ticket.objects.filter(user=request.user) - context = {'ticket': ticket} - return render(request, 'sspanel/ticket.html', context=context) + context = {"ticket": ticket} + return render(request, "sspanel/ticket.html", context=context) @login_required def ticket_create(request): - '''工单提交''' + """工单提交""" if request.method == "POST": - title = request.POST.get('title', '') - body = request.POST.get('body', '') + title = request.POST.get("title", "") + body = request.POST.get("body", "") Ticket.objects.create(user=request.user, title=title, body=body) messages.success(request, "数据更新成功!", extra_tags="添加成功") - return HttpResponseRedirect(reverse('sspanel:ticket')) + return HttpResponseRedirect(reverse("sspanel:ticket")) else: - return render(request, 'sspanel/ticketcreate.html') + return render(request, "sspanel/ticketcreate.html") @login_required def ticket_delete(request, pk): - '''删除指定''' + """删除指定""" ticket = Ticket.objects.get(pk=pk) ticket.delete() messages.success(request, "该工单已经删除", extra_tags="删除成功") - return HttpResponseRedirect(reverse('sspanel:ticket')) + return HttpResponseRedirect(reverse("sspanel:ticket")) @login_required def ticket_edit(request, pk): - '''工单编辑''' + """工单编辑""" ticket = Ticket.objects.get(pk=pk) # 当为post请求时,修改数据 if request.method == "POST": - title = request.POST.get('title', '') - body = request.POST.get('body', '') + title = request.POST.get("title", "") + body = request.POST.get("body", "") ticket.title = title ticket.body = body ticket.save() messages.success(request, "数据更新成功", extra_tags="修改成功") - return HttpResponseRedirect(reverse('sspanel:ticket')) + return HttpResponseRedirect(reverse("sspanel:ticket")) else: - context = { - 'ticket': ticket, - } - return render(request, 'sspanel/ticketedit.html', context=context) + context = {"ticket": ticket} + return render(request, "sspanel/ticketedit.html", context=context) @login_required def affiliate(request): - '''推广页面''' + """推广页面""" if request.user.is_superuser is not True: - invidecodes = InviteCode.objects.filter( - code_id=request.user.pk, code_type=0) + invidecodes = InviteCode.objects.filter(code_id=request.user.pk, code_type=0) inviteNum = request.user.invitecode_num - len(invidecodes) else: # 如果是管理员,特殊处理 invidecodes = InviteCode.objects.filter( - code_id=request.user.pk, code_type=0, isused=False) + code_id=request.user.pk, code_type=0, isused=False + ) inviteNum = 5 context = { - 'invitecodes': invidecodes, - 'invitePercent': settings.INVITE_PERCENT * 100, - 'inviteNumn': inviteNum + "invitecodes": invidecodes, + "invitePercent": settings.INVITE_PERCENT * 100, + "inviteNumn": inviteNum, } - return render(request, 'sspanel/affiliate.html', context=context) + return render(request, "sspanel/affiliate.html", context=context) @login_required def rebate_record(request): - '''返利记录''' + """返利记录""" u = request.user records = RebateRecord.objects.filter(user_id=u.pk)[:10] - context = { - 'records': records, - 'user': request.user, - } - return render(request, 'sspanel/rebaterecord.html', context=context) + context = {"records": records, "user": request.user} + return render(request, "sspanel/rebaterecord.html", context=context) + # ================================== # 网站后台界面 # ================================== -@permission_required('sspanel') +@permission_required("sspanel") def backend_index(request): - '''跳转到后台界面''' - context = { - 'userNum': User.get_user_num(), - } + """跳转到后台界面""" + context = {"userNum": User.get_user_num()} - return render(request, 'backend/index.html', context=context) + return render(request, "backend/index.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def backend_node_info(request): - '''节点编辑界面''' + """节点编辑界面""" nodes = Node.objects.all() - context = { - 'nodes': nodes, - } - return render(request, 'backend/nodeinfo.html', context=context) + context = {"nodes": nodes} + return render(request, "backend/nodeinfo.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def node_delete(request, node_id): - '''删除节点''' + """删除节点""" node = Node.objects.filter(node_id=node_id) node.delete() messages.success(request, "成功啦", extra_tags="删除节点") - return HttpResponseRedirect(reverse('sspanel:backend_node_info')) + return HttpResponseRedirect(reverse("sspanel:backend_node_info")) -@permission_required('sspanel') +@permission_required("sspanel") def node_edit(request, node_id): - '''编辑节点''' + """编辑节点""" node = Node.objects.get(node_id=node_id) # 当为post请求时,修改数据 if request.method == "POST": @@ -500,280 +480,246 @@ def node_edit(request, node_id): if form.is_valid(): form.save() messages.success(request, "数据更新成功", extra_tags="修改成功") - return HttpResponseRedirect(reverse('sspanel:backend_node_info')) + return HttpResponseRedirect(reverse("sspanel:backend_node_info")) else: messages.error(request, "数据填写错误", extra_tags="错误") - context = { - 'form': form, - 'node': node, - } - return render(request, 'backend/nodeedit.html', context=context) + context = {"form": form, "node": node} + return render(request, "backend/nodeedit.html", context=context) # 当请求不是post时,渲染form else: - form = NodeForm(instance=node, initial={ - 'total_traffic': node.total_traffic // settings.GB}) - context = { - 'form': form, - 'node': node, - } - return render(request, 'backend/nodeedit.html', context=context) + form = NodeForm( + instance=node, initial={"total_traffic": node.total_traffic // settings.GB} + ) + context = {"form": form, "node": node} + return render(request, "backend/nodeedit.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def node_create(request): - '''创建节点''' + """创建节点""" if request.method == "POST": form = NodeForm(request.POST) if form.is_valid(): form.save() messages.success(request, "数据更新成功!", extra_tags="添加成功") - return HttpResponseRedirect(reverse('sspanel:backend_node_info')) + return HttpResponseRedirect(reverse("sspanel:backend_node_info")) else: messages.error(request, "数据填写错误", extra_tags="错误") - context = { - 'form': form, - } - return render(request, 'backend/nodecreate.html', context=context) + context = {"form": form} + return render(request, "backend/nodecreate.html", context=context) else: form = NodeForm() - return render( - request, 'backend/nodecreate.html', context={ - 'form': form, - }) + return render(request, "backend/nodecreate.html", context={"form": form}) -@permission_required('sspanel') +@permission_required("sspanel") def backend_userlist(request): - '''返回所有用户的View''' - obj = User.objects.all().order_by('-date_joined') + """返回所有用户的View""" + obj = User.objects.all().order_by("-date_joined") page_num = 15 context = Page_List_View(request, obj, page_num).get_page_context() - return render(request, 'backend/userlist.html', context) + return render(request, "backend/userlist.html", context) -@permission_required('sspanel') +@permission_required("sspanel") def user_delete(request, pk): - '''删除user''' + """删除user""" user = User.objects.get(pk=pk) user.delete() messages.success(request, "成功啦", extra_tags="删除用户") - return HttpResponseRedirect(reverse('sspanel:user_list')) + return HttpResponseRedirect(reverse("sspanel:user_list")) -@permission_required('sspanel') +@permission_required("sspanel") def user_search(request): - '''用户搜索结果''' - q = request.GET.get('q') + """用户搜索结果""" + q = request.GET.get("q") contacts = User.objects.filter( - Q(username__icontains=q) | Q(email__icontains=q) | Q(pk__icontains=q)) - context = { - 'contacts': contacts, - } - return render(request, 'backend/userlist.html', context=context) + Q(username__icontains=q) | Q(email__icontains=q) | Q(pk__icontains=q) + ) + context = {"contacts": contacts} + return render(request, "backend/userlist.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def user_status(request): - '''站内用户分析''' + """站内用户分析""" # 查询今日注册的用户 todayRegistered = User.get_today_register_user().values() for t in todayRegistered: try: - t['inviter'] = User.objects.get(pk=t['invited_by']) + t["inviter"] = User.objects.get(pk=t["invited_by"]) except User.DoesNotExist: - t['inviter'] = 'ehco' + t["inviter"] = "ehco" todayRegisteredNum = len(todayRegistered) # 查询消费水平前十的用户 richUser = Donate.richPeople() # 查询流量用的最多的用户 coreUser = Suser.get_user_by_traffic(num=10) context = { - 'userNum': User.get_user_num(), - 'todayChecked': Suser.get_today_checked_user_num(), - 'aliveUser': NodeOnlineLog.totalOnlineUser(), - 'todayRegistered': todayRegistered[:10], - 'todayRegisteredNum': todayRegisteredNum, - 'richUser': richUser, - 'coreUser': coreUser, + "userNum": User.get_user_num(), + "todayChecked": Suser.get_today_checked_user_num(), + "aliveUser": NodeOnlineLog.totalOnlineUser(), + "todayRegistered": todayRegistered[:10], + "todayRegisteredNum": todayRegisteredNum, + "richUser": richUser, + "coreUser": coreUser, } - return render(request, 'backend/userstatus.html', context=context) + return render(request, "backend/userstatus.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def backend_invite(request): - '''邀请码生成''' + """邀请码生成""" code_list = InviteCode.objects.filter(code_type=0, isused=False, code_id=1) - return render(request, 'backend/invitecode.html', { - 'code_list': code_list, - }) + return render(request, "backend/invitecode.html", {"code_list": code_list}) -@permission_required('sspanel') +@permission_required("sspanel") def gen_invite_code(request): - Num = request.GET.get('num') - code_type = request.GET.get('type') + Num = request.GET.get("num") + code_type = request.GET.get("type") for i in range(int(Num)): code = InviteCode(code_type=code_type) code.save() - messages.success(request, '添加邀请码{}个'.format(Num), extra_tags="成功") - return HttpResponseRedirect(reverse('sspanel:backend_invite')) + messages.success(request, "添加邀请码{}个".format(Num), extra_tags="成功") + return HttpResponseRedirect(reverse("sspanel:backend_invite")) -@permission_required('sspanel') +@permission_required("sspanel") def backend_charge(request): - '''后台充值码界面''' + """后台充值码界面""" # 获取所有充值码记录 obj = MoneyCode.objects.all() page_num = 10 context = Page_List_View(request, obj, page_num).get_page_context() # 获取充值的金额和数量 - Num = request.GET.get('num') - money = request.GET.get('money') + Num = request.GET.get("num") + money = request.GET.get("money") if Num and money: for i in range(int(Num)): code = MoneyCode(number=money) code.save() - messages.success(request, '添加{}元充值码{}个'.format( - money, Num), extra_tags="成功") - return HttpResponseRedirect(reverse('sspanel:backend_charge')) - return render(request, 'backend/charge.html', context=context) + messages.success(request, "添加{}元充值码{}个".format(money, Num), extra_tags="成功") + return HttpResponseRedirect(reverse("sspanel:backend_charge")) + return render(request, "backend/charge.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def backend_shop(request): - '''商品管理界面''' + """商品管理界面""" goods = Goods.objects.all() - context = { - 'goods': goods, - } - return render(request, 'backend/shop.html', context=context) + context = {"goods": goods} + return render(request, "backend/shop.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def good_delete(request, pk): - '''删除商品''' + """删除商品""" good = Goods.objects.filter(pk=pk) good.delete() messages.success(request, "成功啦", extra_tags="删除商品") - return HttpResponseRedirect(reverse('sspanel:backend_shop')) + return HttpResponseRedirect(reverse("sspanel:backend_shop")) -@permission_required('sspanel') +@permission_required("sspanel") def good_edit(request, pk): - '''商品编辑''' + """商品编辑""" good = Goods.objects.get(pk=pk) # 当为post请求时,修改数据 if request.method == "POST": # 转换为GB data = request.POST.copy() - data['transfer'] = eval(data['transfer']) * settings.GB + data["transfer"] = eval(data["transfer"]) * settings.GB form = GoodsForm(data, instance=good) if form.is_valid(): form.save() messages.success(request, "数据更新成功", extra_tags="修改成功") - return HttpResponseRedirect(reverse('sspanel:backend_shop')) + return HttpResponseRedirect(reverse("sspanel:backend_shop")) else: messages.error(request, "数据填写错误", extra_tags="错误") - context = { - 'form': form, - 'good': good, - } - return render(request, 'backend/goodedit.html', context=context) + context = {"form": form, "good": good} + return render(request, "backend/goodedit.html", context=context) # 当请求不是post时,渲染form else: - data = {'transfer': round(good.transfer / settings.GB)} + data = {"transfer": round(good.transfer / settings.GB)} form = GoodsForm(initial=data, instance=good) - context = { - 'form': form, - 'good': good, - } - return render(request, 'backend/goodedit.html', context=context) + context = {"form": form, "good": good} + return render(request, "backend/goodedit.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def good_create(request): - '''商品创建''' + """商品创建""" if request.method == "POST": # 转换为GB data = request.POST.copy() - data['transfer'] = eval(data['transfer']) * settings.GB + data["transfer"] = eval(data["transfer"]) * settings.GB form = GoodsForm(data) if form.is_valid(): form.save() messages.success(request, "数据更新成功!", extra_tags="添加成功") - return HttpResponseRedirect(reverse('sspanel:backend_shop')) + return HttpResponseRedirect(reverse("sspanel:backend_shop")) else: messages.error(request, "数据填写错误", extra_tags="错误") - context = { - 'form': form, - } - return render(request, 'backend/goodcreate.html', context=context) + context = {"form": form} + return render(request, "backend/goodcreate.html", context=context) else: form = GoodsForm() - return render( - request, 'backend/goodcreate.html', context={ - 'form': form, - }) + return render(request, "backend/goodcreate.html", context={"form": form}) -@permission_required('sspanel') +@permission_required("sspanel") def purchase_history(request): - '''购买历史''' + """购买历史""" obj = PurchaseHistory.objects.all() page_num = 10 context = Page_List_View(request, obj, page_num).get_page_context() - return render(request, 'backend/purchasehistory.html', context=context) + return render(request, "backend/purchasehistory.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def backend_anno(request): - '''公告管理界面''' + """公告管理界面""" anno = Announcement.objects.all() - context = { - 'anno': anno, - } - return render(request, 'backend/annolist.html', context=context) + context = {"anno": anno} + return render(request, "backend/annolist.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def anno_delete(request, pk): - '''删除公告''' + """删除公告""" anno = Announcement.objects.filter(pk=pk) anno.delete() messages.success(request, "成功啦", extra_tags="删除公告") - return HttpResponseRedirect(reverse('sspanel:backend_anno')) + return HttpResponseRedirect(reverse("sspanel:backend_anno")) -@permission_required('sspanel') +@permission_required("sspanel") def anno_create(request): - '''公告创建''' + """公告创建""" if request.method == "POST": form = AnnoForm(request.POST) if form.is_valid(): form.save() messages.success(request, "数据更新成功", extra_tags="添加成功") - return HttpResponseRedirect(reverse('sspanel:backend_anno')) + return HttpResponseRedirect(reverse("sspanel:backend_anno")) else: messages.error(request, "数据填写错误", extra_tags="错误") - context = { - 'form': form, - } - return render(request, 'backend/annocreate.html', context=context) + context = {"form": form} + return render(request, "backend/annocreate.html", context=context) else: form = AnnoForm() - return render( - request, 'backend/annocreate.html', context={ - 'form': form, - }) + return render(request, "backend/annocreate.html", context={"form": form}) -@permission_required('sspanel') +@permission_required("sspanel") def anno_edit(request, pk): - '''公告编辑''' + """公告编辑""" anno = Announcement.objects.get(pk=pk) # 当为post请求时,修改数据 if request.method == "POST": @@ -781,56 +727,49 @@ def anno_edit(request, pk): if form.is_valid(): form.save() messages.success(request, "数据更新成功", extra_tags="修改成功") - return HttpResponseRedirect(reverse('sspanel:backend_anno')) + return HttpResponseRedirect(reverse("sspanel:backend_anno")) else: messages.error(request, "数据填写错误", extra_tags="错误") - context = { - 'form': form, - 'anno': anno, - } - return render(request, 'backend/annoedit.html', context=context) + context = {"form": form, "anno": anno} + return render(request, "backend/annoedit.html", context=context) # 当请求不是post时,渲染form else: anno.body = tomd.convert(anno.body) - context = { - 'anno': anno, - } - return render(request, 'backend/annoedit.html', context=context) + context = {"anno": anno} + return render(request, "backend/annoedit.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def backend_ticket(request): - '''工单系统''' + """工单系统""" ticket = Ticket.objects.filter(status=1) - context = {'ticket': ticket} - return render(request, 'backend/ticket.html', context=context) + context = {"ticket": ticket} + return render(request, "backend/ticket.html", context=context) -@permission_required('sspanel') +@permission_required("sspanel") def backend_ticketedit(request, pk): - '''后台工单编辑''' + """后台工单编辑""" ticket = Ticket.objects.get(pk=pk) # 当为post请求时,修改数据 if request.method == "POST": - title = request.POST.get('title', '') - body = request.POST.get('body', '') - status = request.POST.get('status', 1) + title = request.POST.get("title", "") + body = request.POST.get("body", "") + status = request.POST.get("status", 1) ticket.title = title ticket.body = body ticket.status = status ticket.save() messages.success(request, "数据更新成功", extra_tags="修改成功") - return HttpResponseRedirect(reverse('sspanel:backend_ticket')) + return HttpResponseRedirect(reverse("sspanel:backend_ticket")) # 当请求不是post时,渲染 else: - context = { - 'ticket': ticket, - } - return render(request, 'backend/ticketedit.html', context=context) + context = {"ticket": ticket} + return render(request, "backend/ticketedit.html", context=context) -@permission_required('ssserver') +@permission_required("ssserver") def backend_alive_user(request): user_list = [] for node_id in Node.get_node_ids(): @@ -838,4 +777,4 @@ def backend_alive_user(request): page_num = 15 context = Page_List_View(request, user_list, page_num).get_page_context() - return render(request, 'backend/aliveuser.html', context=context) + return render(request, "backend/aliveuser.html", context=context) diff --git a/apps/ssserver/admin.py b/apps/ssserver/admin.py index 9e316cd70a..c7b8b92aaa 100644 --- a/apps/ssserver/admin.py +++ b/apps/ssserver/admin.py @@ -3,29 +3,36 @@ class SUserAdmin(admin.ModelAdmin): - list_display = ['user', 'user_id', 'port', - 'used_traffic', 'totla_transfer'] - search_fields = ['user_id', 'port'] - list_filter = ['enable', ] + list_display = ["user", "user_id", "port", "used_traffic", "totla_transfer"] + search_fields = ["user_id", "port"] + list_filter = ["enable"] class TrafficLogAdmin(admin.ModelAdmin): - search_fields = ['user_id', 'node_id'] - list_display = ['user', 'user_id', 'node_id', 'traffic', 'log_date', ] + search_fields = ["user_id", "node_id"] + list_display = ["user", "user_id", "node_id", "traffic", "log_date"] class NodeAdmin(admin.ModelAdmin): - list_display = ['name', 'node_id', 'level', 'traffic_rate', 'order', - 'human_used_traffic', 'human_total_traffic', 'show'] + list_display = [ + "name", + "node_id", + "level", + "traffic_rate", + "order", + "human_used_traffic", + "human_total_traffic", + "show", + ] class NodeOnlineAdmin(admin.ModelAdmin): - list_display = ['node_id', 'online_user'] + list_display = ["node_id", "online_user"] class AliveIpAdmin(admin.ModelAdmin): - list_display = ['node_id', 'user', 'ip', 'log_time'] - list_filter = ['node_id', 'log_time'] + list_display = ["node_id", "user", "ip", "log_time"] + list_filter = ["node_id", "log_time"] # Register your models here. diff --git a/apps/ssserver/apps.py b/apps/ssserver/apps.py index e017f46ad9..d60c8048c3 100644 --- a/apps/ssserver/apps.py +++ b/apps/ssserver/apps.py @@ -2,4 +2,4 @@ class SsserverConfig(AppConfig): - name = 'ssserver' + name = "ssserver" diff --git a/apps/ssserver/forms.py b/apps/ssserver/forms.py index 51e1d1483b..1cb00f0e49 100644 --- a/apps/ssserver/forms.py +++ b/apps/ssserver/forms.py @@ -8,19 +8,15 @@ class ChangeSsPassForm(forms.Form): password = forms.CharField( required=True, label="连接密码", - error_messages={'required': '请输入密码'}, + error_messages={"required": "请输入密码"}, widget=forms.PasswordInput( - attrs={ - 'class': 'input is-danger', - 'placeholder': "密码", - 'type': 'text', - } + attrs={"class": "input is-danger", "placeholder": "密码", "type": "text"} ), ) def clean(self): if not self.is_valid(): - raise forms.ValidationError('太短啦!') + raise forms.ValidationError("太短啦!") else: self.cleaned_data = super(ChangeSsPassForm, self).clean() @@ -28,14 +24,19 @@ def clean(self): class SuserForm(ModelForm): class Meta: model = Suser - fields = ['port', 'password', - 'upload_traffic', 'download_traffic', - 'transfer_enable', 'enable'] + fields = [ + "port", + "password", + "upload_traffic", + "download_traffic", + "transfer_enable", + "enable", + ] widgets = { - 'enable': forms.CheckboxInput(attrs={'class': 'checkbox'}), - 'port': forms.NumberInput(attrs={'class': 'input'}), - 'password': forms.TextInput(attrs={'class': 'input'}), - 'upload_traffic': forms.NumberInput(attrs={'class': 'input'}), - 'download_traffic': forms.NumberInput(attrs={'class': 'input'}), - 'transfer_enable': forms.NumberInput(attrs={'class': 'input'}), + "enable": forms.CheckboxInput(attrs={"class": "checkbox"}), + "port": forms.NumberInput(attrs={"class": "input"}), + "password": forms.TextInput(attrs={"class": "input"}), + "upload_traffic": forms.NumberInput(attrs={"class": "input"}), + "download_traffic": forms.NumberInput(attrs={"class": "input"}), + "transfer_enable": forms.NumberInput(attrs={"class": "input"}), } diff --git a/apps/ssserver/migrations/0001_initial.py b/apps/ssserver/migrations/0001_initial.py index 53ce938e94..e405882133 100644 --- a/apps/ssserver/migrations/0001_initial.py +++ b/apps/ssserver/migrations/0001_initial.py @@ -13,131 +13,630 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] + dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] operations = [ migrations.CreateModel( - name='AliveIp', + name="AliveIp", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('node_id', models.IntegerField(verbose_name='节点id')), - ('ip', models.CharField(max_length=128, verbose_name='设备ip')), - ('user', models.CharField(max_length=128, verbose_name='用户名')), - ('log_time', models.DateTimeField(auto_now=True, verbose_name='日志时间')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("node_id", models.IntegerField(verbose_name="节点id")), + ("ip", models.CharField(max_length=128, verbose_name="设备ip")), + ("user", models.CharField(max_length=128, verbose_name="用户名")), + ("log_time", models.DateTimeField(auto_now=True, verbose_name="日志时间")), ], - options={ - 'verbose_name_plural': '节点在线IP', - 'ordering': ['-log_time'], - }, + options={"verbose_name_plural": "节点在线IP", "ordering": ["-log_time"]}, ), migrations.CreateModel( - name='Node', + name="Node", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('node_id', models.IntegerField(unique=True, verbose_name='节点id')), - ('port', models.IntegerField(blank=True, help_text='单端口多用户时需要', null=True, verbose_name='节点端口')), - ('password', models.CharField(default='password', help_text='单端口多用户时需要', max_length=32, verbose_name='节点密码')), - ('country', models.CharField(choices=[('AF', '阿富汗'), ('AX', '奥兰群岛'), ('AL', '阿尔巴尼亚'), ('DZ', '阿尔及利亚'), ('AS', '美属萨摩亚'), ('AD', '安道尔'), ('AO', '安哥拉'), ('AI', '安圭拉'), ('AQ', '南极洲'), ('AG', '安提瓜和巴布达'), ('AR', '阿根廷'), ('AM', '亚美尼亚'), ('AW', '阿鲁巴'), ('AU', '澳大利亚'), ('AT', '奥地利'), ('AZ', '阿塞拜疆'), ('BS', '巴哈马'), ('BH', '巴林'), ('BD', '孟加拉国'), ('BB', '巴巴多斯'), ('BY', '白俄罗斯'), ('BE', '比利时'), ('BZ', '伯利兹'), ('BJ', '贝宁'), ('BM', '百慕大'), ('BT', '不丹'), ('BO', '玻利维亚(多民族国)'), ('BA', '波斯尼亚和黑塞哥维那'), ('BW', '博茨瓦纳'), ('BV', '布维岛'), ('BR', '巴西'), ('IO', '英属印度洋领地'), ('BN', '文莱达鲁萨兰国'), ('BG', '保加利亚'), ('BF', '布基纳法索'), ('BI', '布隆迪'), ('CV', 'Cabo Verde'), ('KH', '柬埔寨'), ('CM', '喀麦隆'), ('CA', '加拿大'), ('KY', '开曼群岛'), ('CF', '中非共和国'), ('TD', '乍得'), ('CL', '智利'), ('CN', '中国'), ('CX', '圣诞岛'), ('CC', '科科斯(基林)群岛'), ('CO', '哥伦比亚'), ('KM', '科摩罗'), ('CD', '刚果(该民主共和国)'), ('CG', '刚果'), ('CK', '库克群岛'), ('CR', '哥斯达黎加'), ('CI', '科特迪瓦'), ('HR', '克罗地亚'), ('CU', '古巴'), ('CW', '库拉索'), ('CY', '塞浦路斯'), ('CZ', '捷克'), ('DK', '丹麦'), ('DJ', '吉布提'), ('DM', '多米尼克'), ('DO', '多米尼加共和国'), ('EC', '厄瓜多尔'), ('EG', '埃及'), ('SV', '萨尔瓦多'), ('GQ', '赤道几内亚'), ('ER', '厄立特里亚'), ('EE', '爱沙尼亚'), ('ET', '埃塞俄比亚'), ('FK', '福克兰群岛[马尔维纳斯]'), ('FO', '法罗群岛'), ('FJ', '斐济'), ('FI', '芬兰'), ('FR', '法国'), ('GF', '法属圭亚那'), ('PF', '法属波利尼西亚'), ('TF', '法国南部领土'), ('GA', '加蓬'), ('GM', '冈比亚'), ('GE', '格鲁吉亚'), ('DE', '德国'), ('GH', '加纳'), ('GI', '直布罗陀'), ('GR', '希腊'), ('GL', '格陵兰'), ('GD', '格林纳达'), ('GP', 'Guadeloupe'), ('GU', '关岛'), ('GT', '危地马拉'), ('GG', '根西岛'), ('GN', '几内亚'), ('GW', '几内亚比绍'), ('GY', '圭亚那'), ('HT', '海地'), ('HM', '赫德岛和麦克唐纳群岛'), ('VA', '罗马教廷'), ('HN', '洪都拉斯'), ('HK', '香港'), ('HU', '匈牙利'), ('IS', '冰岛'), ('ID', '印度尼西亚'), ('IR', '伊朗(伊斯兰共和国)'), ('IQ', '伊拉克'), ('IE', '爱尔兰'), ('IM', '马恩岛'), ('IL', '以色列'), ('IT', '意大利'), ('JM', '牙买加'), ('JP', '日本'), ('JE', '泽西岛'), ('JO', '约旦'), ('KZ', '哈萨克斯坦'), ('KE', '肯尼亚'), ('KI', '基里巴斯'), ('KP', '韩国(朝鲜民主主义人民共和国)'), ('KR', '韩国(共和国)'), ('KW', '科威特'), ('KG', '吉尔吉斯斯坦'), ('LA', '老挝人民民主共和国'), ('LV', '拉脱维亚'), ('LB', '黎巴嫩'), ('LS', '莱索托'), ('LR', '利比里亚'), ('LY', '利比亚'), ('LI', '列支敦士登'), ('LT', '立陶宛'), ('LU', '卢森堡'), ('MO', '澳门'), ('MK', '马其顿(前南斯拉夫共和国)'), ('MG', '马达加斯加'), ('MW', '马拉维'), ('MY', '马来西亚'), ('MV', '马尔代夫'), ('ML', 'Mali'), ('MT', '马耳他'), ('MH', '马绍尔群岛'), ('MQ', '马提尼克岛'), ('MR', '毛里塔尼亚'), ('MU', '毛里求斯'), ('YT', '马约特岛'), ('MX', '墨西哥'), ('FM', '密克罗尼西亚联邦'), ('MD', '摩尔多瓦共和国'), ('MC', '摩纳哥'), ('MN', '蒙古'), ('ME', '黑山'), ('MS', '蒙特塞拉特'), ('MA', '摩洛哥'), ('MZ', '莫桑比克'), ('MM', '缅甸'), ('NA', '纳米比亚'), ('NR', '瑙鲁'), ('NP', '尼泊尔'), ('NL', '荷兰'), ('NC', '新喀里多尼亚'), ('NZ', '新西兰'), ('NI', '尼加拉瓜'), ('NE', '尼日尔'), ('NG', '尼日利亚'), ('NU', '纽埃'), ('NF', '诺福克岛'), ('MP', '北马里亚纳群岛'), ('NO', '挪威'), ('OM', '阿曼'), ('PK', '巴基斯坦'), ('PW', '帕劳'), ('PS', '巴勒斯坦,国家'), ('PA', '巴拿马'), ('PG', '巴布亚新几内亚'), ('PY', '巴拉圭'), ('PE', '秘鲁'), ('PH', '菲律宾'), ('PN', '皮特凯恩'), ('PL', '波兰'), ('PT', '葡萄牙'), ('PR', '波多黎各'), ('QA', '卡塔尔'), ('RE', '留尼汪'), ('RO', '罗马尼亚'), ('RU', '俄罗斯联邦'), ('RW', '卢旺达'), ('BL', '圣巴泰勒米'), ('SH', '圣赫勒拿,阿森松和特里斯坦达库尼亚'), ('KN', '圣基茨和尼维斯')], default='CN', max_length=2, verbose_name='国家')), - ('custom_method', models.SmallIntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='自定义加密')), - ('show', models.SmallIntegerField(choices=[(1, '显示'), (-1, '不显示')], default=1, verbose_name='是否显示')), - ('node_type', models.SmallIntegerField(choices=[(0, '多端口多用户'), (1, '单端口多用户')], default=0, verbose_name='节点类型')), - ('name', models.CharField(max_length=32, verbose_name='名字')), - ('info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='节点说明')), - ('server', models.CharField(max_length=128, verbose_name='服务器IP')), - ('method', models.CharField(choices=[('aes-256-cfb', 'aes-256-cfb'), ('aes-128-ctr', 'aes-128-ctr'), ('rc4-md5', 'rc4-md5'), ('salsa20', 'salsa20'), ('chacha20', 'chacha20'), ('none', 'none')], default='aes-128-ctr', max_length=32, verbose_name='加密类型')), - ('traffic_rate', models.FloatField(default=1.0, verbose_name='流量比例')), - ('protocol', models.CharField(choices=[('auth_sha1_v4', 'auth_sha1_v4'), ('auth_aes128_md5', 'auth_aes128_md5'), ('auth_aes128_sha1', 'auth_aes128_sha1'), ('auth_chain_a', 'auth_chain_a'), ('origin', 'origin')], default='auth_chain_a', max_length=32, verbose_name='协议')), - ('protocol_param', models.CharField(blank=True, max_length=128, null=True, verbose_name='协议参数')), - ('obfs', models.CharField(choices=[('plain', 'plain'), ('http_simple', 'http_simple'), ('http_simple_compatible', 'http_simple_compatible'), ('http_post', 'http_post'), ('tls1.2_ticket_auth', 'tls1.2_ticket_auth')], default='http_simple', max_length=32, verbose_name='混淆')), - ('obfs_param', models.CharField(blank=True, default='', max_length=128, null=True, verbose_name='混淆参数')), - ('level', models.PositiveIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(9), django.core.validators.MinValueValidator(0)], verbose_name='节点等级')), - ('total_traffic', models.BigIntegerField(default=1073741824, verbose_name='总流量')), - ('human_total_traffic', models.CharField(blank=True, default='1GB', max_length=255, null=True, verbose_name='节点总流量')), - ('used_traffic', models.BigIntegerField(default=0, verbose_name='已用流量')), - ('human_used_traffic', models.CharField(blank=True, max_length=255, null=True, verbose_name='已用流量')), - ('order', models.PositiveSmallIntegerField(default=1, verbose_name='排序')), - ('group', models.CharField(default='谜之屋', max_length=32, verbose_name='分组名')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("node_id", models.IntegerField(unique=True, verbose_name="节点id")), + ( + "port", + models.IntegerField( + blank=True, + help_text="单端口多用户时需要", + null=True, + verbose_name="节点端口", + ), + ), + ( + "password", + models.CharField( + default="password", + help_text="单端口多用户时需要", + max_length=32, + verbose_name="节点密码", + ), + ), + ( + "country", + models.CharField( + choices=[ + ("AF", "阿富汗"), + ("AX", "奥兰群岛"), + ("AL", "阿尔巴尼亚"), + ("DZ", "阿尔及利亚"), + ("AS", "美属萨摩亚"), + ("AD", "安道尔"), + ("AO", "安哥拉"), + ("AI", "安圭拉"), + ("AQ", "南极洲"), + ("AG", "安提瓜和巴布达"), + ("AR", "阿根廷"), + ("AM", "亚美尼亚"), + ("AW", "阿鲁巴"), + ("AU", "澳大利亚"), + ("AT", "奥地利"), + ("AZ", "阿塞拜疆"), + ("BS", "巴哈马"), + ("BH", "巴林"), + ("BD", "孟加拉国"), + ("BB", "巴巴多斯"), + ("BY", "白俄罗斯"), + ("BE", "比利时"), + ("BZ", "伯利兹"), + ("BJ", "贝宁"), + ("BM", "百慕大"), + ("BT", "不丹"), + ("BO", "玻利维亚(多民族国)"), + ("BA", "波斯尼亚和黑塞哥维那"), + ("BW", "博茨瓦纳"), + ("BV", "布维岛"), + ("BR", "巴西"), + ("IO", "英属印度洋领地"), + ("BN", "文莱达鲁萨兰国"), + ("BG", "保加利亚"), + ("BF", "布基纳法索"), + ("BI", "布隆迪"), + ("CV", "Cabo Verde"), + ("KH", "柬埔寨"), + ("CM", "喀麦隆"), + ("CA", "加拿大"), + ("KY", "开曼群岛"), + ("CF", "中非共和国"), + ("TD", "乍得"), + ("CL", "智利"), + ("CN", "中国"), + ("CX", "圣诞岛"), + ("CC", "科科斯(基林)群岛"), + ("CO", "哥伦比亚"), + ("KM", "科摩罗"), + ("CD", "刚果(该民主共和国)"), + ("CG", "刚果"), + ("CK", "库克群岛"), + ("CR", "哥斯达黎加"), + ("CI", "科特迪瓦"), + ("HR", "克罗地亚"), + ("CU", "古巴"), + ("CW", "库拉索"), + ("CY", "塞浦路斯"), + ("CZ", "捷克"), + ("DK", "丹麦"), + ("DJ", "吉布提"), + ("DM", "多米尼克"), + ("DO", "多米尼加共和国"), + ("EC", "厄瓜多尔"), + ("EG", "埃及"), + ("SV", "萨尔瓦多"), + ("GQ", "赤道几内亚"), + ("ER", "厄立特里亚"), + ("EE", "爱沙尼亚"), + ("ET", "埃塞俄比亚"), + ("FK", "福克兰群岛[马尔维纳斯]"), + ("FO", "法罗群岛"), + ("FJ", "斐济"), + ("FI", "芬兰"), + ("FR", "法国"), + ("GF", "法属圭亚那"), + ("PF", "法属波利尼西亚"), + ("TF", "法国南部领土"), + ("GA", "加蓬"), + ("GM", "冈比亚"), + ("GE", "格鲁吉亚"), + ("DE", "德国"), + ("GH", "加纳"), + ("GI", "直布罗陀"), + ("GR", "希腊"), + ("GL", "格陵兰"), + ("GD", "格林纳达"), + ("GP", "Guadeloupe"), + ("GU", "关岛"), + ("GT", "危地马拉"), + ("GG", "根西岛"), + ("GN", "几内亚"), + ("GW", "几内亚比绍"), + ("GY", "圭亚那"), + ("HT", "海地"), + ("HM", "赫德岛和麦克唐纳群岛"), + ("VA", "罗马教廷"), + ("HN", "洪都拉斯"), + ("HK", "香港"), + ("HU", "匈牙利"), + ("IS", "冰岛"), + ("ID", "印度尼西亚"), + ("IR", "伊朗(伊斯兰共和国)"), + ("IQ", "伊拉克"), + ("IE", "爱尔兰"), + ("IM", "马恩岛"), + ("IL", "以色列"), + ("IT", "意大利"), + ("JM", "牙买加"), + ("JP", "日本"), + ("JE", "泽西岛"), + ("JO", "约旦"), + ("KZ", "哈萨克斯坦"), + ("KE", "肯尼亚"), + ("KI", "基里巴斯"), + ("KP", "韩国(朝鲜民主主义人民共和国)"), + ("KR", "韩国(共和国)"), + ("KW", "科威特"), + ("KG", "吉尔吉斯斯坦"), + ("LA", "老挝人民民主共和国"), + ("LV", "拉脱维亚"), + ("LB", "黎巴嫩"), + ("LS", "莱索托"), + ("LR", "利比里亚"), + ("LY", "利比亚"), + ("LI", "列支敦士登"), + ("LT", "立陶宛"), + ("LU", "卢森堡"), + ("MO", "澳门"), + ("MK", "马其顿(前南斯拉夫共和国)"), + ("MG", "马达加斯加"), + ("MW", "马拉维"), + ("MY", "马来西亚"), + ("MV", "马尔代夫"), + ("ML", "Mali"), + ("MT", "马耳他"), + ("MH", "马绍尔群岛"), + ("MQ", "马提尼克岛"), + ("MR", "毛里塔尼亚"), + ("MU", "毛里求斯"), + ("YT", "马约特岛"), + ("MX", "墨西哥"), + ("FM", "密克罗尼西亚联邦"), + ("MD", "摩尔多瓦共和国"), + ("MC", "摩纳哥"), + ("MN", "蒙古"), + ("ME", "黑山"), + ("MS", "蒙特塞拉特"), + ("MA", "摩洛哥"), + ("MZ", "莫桑比克"), + ("MM", "缅甸"), + ("NA", "纳米比亚"), + ("NR", "瑙鲁"), + ("NP", "尼泊尔"), + ("NL", "荷兰"), + ("NC", "新喀里多尼亚"), + ("NZ", "新西兰"), + ("NI", "尼加拉瓜"), + ("NE", "尼日尔"), + ("NG", "尼日利亚"), + ("NU", "纽埃"), + ("NF", "诺福克岛"), + ("MP", "北马里亚纳群岛"), + ("NO", "挪威"), + ("OM", "阿曼"), + ("PK", "巴基斯坦"), + ("PW", "帕劳"), + ("PS", "巴勒斯坦,国家"), + ("PA", "巴拿马"), + ("PG", "巴布亚新几内亚"), + ("PY", "巴拉圭"), + ("PE", "秘鲁"), + ("PH", "菲律宾"), + ("PN", "皮特凯恩"), + ("PL", "波兰"), + ("PT", "葡萄牙"), + ("PR", "波多黎各"), + ("QA", "卡塔尔"), + ("RE", "留尼汪"), + ("RO", "罗马尼亚"), + ("RU", "俄罗斯联邦"), + ("RW", "卢旺达"), + ("BL", "圣巴泰勒米"), + ("SH", "圣赫勒拿,阿森松和特里斯坦达库尼亚"), + ("KN", "圣基茨和尼维斯"), + ], + default="CN", + max_length=2, + verbose_name="国家", + ), + ), + ( + "custom_method", + models.SmallIntegerField( + choices=[(0, "否"), (1, "是")], default=0, verbose_name="自定义加密" + ), + ), + ( + "show", + models.SmallIntegerField( + choices=[(1, "显示"), (-1, "不显示")], default=1, verbose_name="是否显示" + ), + ), + ( + "node_type", + models.SmallIntegerField( + choices=[(0, "多端口多用户"), (1, "单端口多用户")], + default=0, + verbose_name="节点类型", + ), + ), + ("name", models.CharField(max_length=32, verbose_name="名字")), + ( + "info", + models.CharField( + blank=True, max_length=1024, null=True, verbose_name="节点说明" + ), + ), + ("server", models.CharField(max_length=128, verbose_name="服务器IP")), + ( + "method", + models.CharField( + choices=[ + ("aes-256-cfb", "aes-256-cfb"), + ("aes-128-ctr", "aes-128-ctr"), + ("rc4-md5", "rc4-md5"), + ("salsa20", "salsa20"), + ("chacha20", "chacha20"), + ("none", "none"), + ], + default="aes-128-ctr", + max_length=32, + verbose_name="加密类型", + ), + ), + ("traffic_rate", models.FloatField(default=1.0, verbose_name="流量比例")), + ( + "protocol", + models.CharField( + choices=[ + ("auth_sha1_v4", "auth_sha1_v4"), + ("auth_aes128_md5", "auth_aes128_md5"), + ("auth_aes128_sha1", "auth_aes128_sha1"), + ("auth_chain_a", "auth_chain_a"), + ("origin", "origin"), + ], + default="auth_chain_a", + max_length=32, + verbose_name="协议", + ), + ), + ( + "protocol_param", + models.CharField( + blank=True, max_length=128, null=True, verbose_name="协议参数" + ), + ), + ( + "obfs", + models.CharField( + choices=[ + ("plain", "plain"), + ("http_simple", "http_simple"), + ("http_simple_compatible", "http_simple_compatible"), + ("http_post", "http_post"), + ("tls1.2_ticket_auth", "tls1.2_ticket_auth"), + ], + default="http_simple", + max_length=32, + verbose_name="混淆", + ), + ), + ( + "obfs_param", + models.CharField( + blank=True, + default="", + max_length=128, + null=True, + verbose_name="混淆参数", + ), + ), + ( + "level", + models.PositiveIntegerField( + default=0, + validators=[ + django.core.validators.MaxValueValidator(9), + django.core.validators.MinValueValidator(0), + ], + verbose_name="节点等级", + ), + ), + ( + "total_traffic", + models.BigIntegerField(default=1073741824, verbose_name="总流量"), + ), + ( + "human_total_traffic", + models.CharField( + blank=True, + default="1GB", + max_length=255, + null=True, + verbose_name="节点总流量", + ), + ), + ( + "used_traffic", + models.BigIntegerField(default=0, verbose_name="已用流量"), + ), + ( + "human_used_traffic", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="已用流量" + ), + ), + ( + "order", + models.PositiveSmallIntegerField(default=1, verbose_name="排序"), + ), + ( + "group", + models.CharField(default="谜之屋", max_length=32, verbose_name="分组名"), + ), ], options={ - 'verbose_name_plural': '节点', - 'db_table': 'ss_node', - 'ordering': ['-show', 'order'], + "verbose_name_plural": "节点", + "db_table": "ss_node", + "ordering": ["-show", "order"], }, ), migrations.CreateModel( - name='NodeInfoLog', + name="NodeInfoLog", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('node_id', models.IntegerField(verbose_name='节点id')), - ('uptime', models.FloatField(verbose_name='更新时间')), - ('load', models.CharField(max_length=32, verbose_name='负载')), - ('log_time', models.IntegerField(verbose_name='日志时间')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("node_id", models.IntegerField(verbose_name="节点id")), + ("uptime", models.FloatField(verbose_name="更新时间")), + ("load", models.CharField(max_length=32, verbose_name="负载")), + ("log_time", models.IntegerField(verbose_name="日志时间")), ], options={ - 'verbose_name_plural': '节点日志', - 'db_table': 'ss_node_info_log', - 'ordering': ('-log_time',), + "verbose_name_plural": "节点日志", + "db_table": "ss_node_info_log", + "ordering": ("-log_time",), }, ), migrations.CreateModel( - name='NodeOnlineLog', + name="NodeOnlineLog", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('node_id', models.IntegerField(verbose_name='节点id')), - ('online_user', models.IntegerField(verbose_name='在线人数')), - ('log_time', models.IntegerField(verbose_name='日志时间')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("node_id", models.IntegerField(verbose_name="节点id")), + ("online_user", models.IntegerField(verbose_name="在线人数")), + ("log_time", models.IntegerField(verbose_name="日志时间")), ], - options={ - 'verbose_name_plural': '节点在线记录', - 'db_table': 'ss_node_online_log', - }, + options={"verbose_name_plural": "节点在线记录", "db_table": "ss_node_online_log"}, ), migrations.CreateModel( - name='SSUser', + name="SSUser", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('last_check_in_time', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 8, 0), editable=False, null=True, verbose_name='最后签到时间')), - ('password', models.CharField(db_column='passwd', default=apps.utils.get_short_random_string, max_length=32, validators=[django.core.validators.MinLengthValidator(6)], verbose_name='sspanel密码')), - ('port', models.IntegerField(db_column='port', unique=True, verbose_name='端口')), - ('last_use_time', models.IntegerField(db_column='t', default=0, editable=False, help_text='时间戳', verbose_name='最后使用时间')), - ('upload_traffic', models.BigIntegerField(db_column='u', default=0, verbose_name='上传流量')), - ('download_traffic', models.BigIntegerField(db_column='d', default=0, verbose_name='下载流量')), - ('transfer_enable', models.BigIntegerField(db_column='transfer_enable', default=5368709120, verbose_name='总流量')), - ('switch', models.BooleanField(db_column='switch', default=True, verbose_name='保留字段switch')), - ('enable', models.BooleanField(db_column='enable', default=True, verbose_name='开启与否')), - ('method', models.CharField(choices=[('aes-256-cfb', 'aes-256-cfb'), ('aes-128-ctr', 'aes-128-ctr'), ('rc4-md5', 'rc4-md5'), ('salsa20', 'salsa20'), ('chacha20', 'chacha20'), ('none', 'none')], default='aes-128-ctr', max_length=32, verbose_name='加密类型')), - ('protocol', models.CharField(choices=[('auth_sha1_v4', 'auth_sha1_v4'), ('auth_aes128_md5', 'auth_aes128_md5'), ('auth_aes128_sha1', 'auth_aes128_sha1'), ('auth_chain_a', 'auth_chain_a'), ('origin', 'origin')], default='auth_chain_a', max_length=32, verbose_name='协议')), - ('protocol_param', models.CharField(blank=True, max_length=128, null=True, verbose_name='协议参数')), - ('obfs', models.CharField(choices=[('plain', 'plain'), ('http_simple', 'http_simple'), ('http_simple_compatible', 'http_simple_compatible'), ('http_post', 'http_post'), ('tls1.2_ticket_auth', 'tls1.2_ticket_auth')], default='http_simple', max_length=32, verbose_name='混淆')), - ('obfs_param', models.CharField(blank=True, max_length=128, null=True, verbose_name='混淆参数')), - ('level', models.PositiveIntegerField(default=0, verbose_name='用户等级')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='ss_user', to=settings.AUTH_USER_MODEL, verbose_name='用户名')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "last_check_in_time", + models.DateTimeField( + default=datetime.datetime(1970, 1, 1, 8, 0), + editable=False, + null=True, + verbose_name="最后签到时间", + ), + ), + ( + "password", + models.CharField( + db_column="passwd", + default=apps.utils.get_short_random_string, + max_length=32, + validators=[django.core.validators.MinLengthValidator(6)], + verbose_name="sspanel密码", + ), + ), + ( + "port", + models.IntegerField( + db_column="port", unique=True, verbose_name="端口" + ), + ), + ( + "last_use_time", + models.IntegerField( + db_column="t", + default=0, + editable=False, + help_text="时间戳", + verbose_name="最后使用时间", + ), + ), + ( + "upload_traffic", + models.BigIntegerField( + db_column="u", default=0, verbose_name="上传流量" + ), + ), + ( + "download_traffic", + models.BigIntegerField( + db_column="d", default=0, verbose_name="下载流量" + ), + ), + ( + "transfer_enable", + models.BigIntegerField( + db_column="transfer_enable", + default=5368709120, + verbose_name="总流量", + ), + ), + ( + "switch", + models.BooleanField( + db_column="switch", default=True, verbose_name="保留字段switch" + ), + ), + ( + "enable", + models.BooleanField( + db_column="enable", default=True, verbose_name="开启与否" + ), + ), + ( + "method", + models.CharField( + choices=[ + ("aes-256-cfb", "aes-256-cfb"), + ("aes-128-ctr", "aes-128-ctr"), + ("rc4-md5", "rc4-md5"), + ("salsa20", "salsa20"), + ("chacha20", "chacha20"), + ("none", "none"), + ], + default="aes-128-ctr", + max_length=32, + verbose_name="加密类型", + ), + ), + ( + "protocol", + models.CharField( + choices=[ + ("auth_sha1_v4", "auth_sha1_v4"), + ("auth_aes128_md5", "auth_aes128_md5"), + ("auth_aes128_sha1", "auth_aes128_sha1"), + ("auth_chain_a", "auth_chain_a"), + ("origin", "origin"), + ], + default="auth_chain_a", + max_length=32, + verbose_name="协议", + ), + ), + ( + "protocol_param", + models.CharField( + blank=True, max_length=128, null=True, verbose_name="协议参数" + ), + ), + ( + "obfs", + models.CharField( + choices=[ + ("plain", "plain"), + ("http_simple", "http_simple"), + ("http_simple_compatible", "http_simple_compatible"), + ("http_post", "http_post"), + ("tls1.2_ticket_auth", "tls1.2_ticket_auth"), + ], + default="http_simple", + max_length=32, + verbose_name="混淆", + ), + ), + ( + "obfs_param", + models.CharField( + blank=True, max_length=128, null=True, verbose_name="混淆参数" + ), + ), + ("level", models.PositiveIntegerField(default=0, verbose_name="用户等级")), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="ss_user", + to=settings.AUTH_USER_MODEL, + verbose_name="用户名", + ), + ), ], options={ - 'verbose_name_plural': 'SS用户', - 'db_table': 'user', - 'ordering': ('-last_check_in_time',), + "verbose_name_plural": "SS用户", + "db_table": "user", + "ordering": ("-last_check_in_time",), }, ), migrations.CreateModel( - name='TrafficLog', + name="TrafficLog", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('user_id', models.IntegerField(verbose_name='用户id')), - ('node_id', models.IntegerField(verbose_name='节点id')), - ('upload_traffic', models.BigIntegerField(db_column='u', default=0, verbose_name='上传流量')), - ('download_traffic', models.BigIntegerField(db_column='d', default=0, verbose_name='下载流量')), - ('rate', models.FloatField(default=1.0, verbose_name='流量比例')), - ('traffic', models.CharField(max_length=32, verbose_name='流量记录')), - ('log_time', models.IntegerField(verbose_name='日志时间')), - ('log_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='记录日期')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("user_id", models.IntegerField(verbose_name="用户id")), + ("node_id", models.IntegerField(verbose_name="节点id")), + ( + "upload_traffic", + models.BigIntegerField( + db_column="u", default=0, verbose_name="上传流量" + ), + ), + ( + "download_traffic", + models.BigIntegerField( + db_column="d", default=0, verbose_name="下载流量" + ), + ), + ("rate", models.FloatField(default=1.0, verbose_name="流量比例")), + ("traffic", models.CharField(max_length=32, verbose_name="流量记录")), + ("log_time", models.IntegerField(verbose_name="日志时间")), + ( + "log_date", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="记录日期" + ), + ), ], options={ - 'verbose_name_plural': '流量记录', - 'db_table': 'user_traffic_log', - 'ordering': ('-log_time',), + "verbose_name_plural": "流量记录", + "db_table": "user_traffic_log", + "ordering": ("-log_time",), }, ), ] diff --git a/apps/ssserver/migrations/0002_auto_20180707_2200.py b/apps/ssserver/migrations/0002_auto_20180707_2200.py index bdd17bee18..b4f53f5581 100644 --- a/apps/ssserver/migrations/0002_auto_20180707_2200.py +++ b/apps/ssserver/migrations/0002_auto_20180707_2200.py @@ -5,14 +5,48 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0001_initial'), - ] + dependencies = [("ssserver", "0001_initial")] operations = [ migrations.AlterField( - model_name='node', - name='country', - field=models.CharField(choices=[('US', '美国'), ('CN', '中国'), ('HK', '香港'), ('JP', '日本'), ('FR', '法国'), ('DE', '德国'), ('KR', '韩国'), ('JE', '泽西岛'), ('NZ', '新西兰'), ('MX', '墨西哥'), ('CA', '加拿大'), ('BR', '巴西'), ('CU', '古巴'), ('CZ', '捷克'), ('EG', '埃及'), ('FI', '芬兰'), ('GR', '希腊'), ('GU', '关岛'), ('IS', '冰岛'), ('MO', '澳门'), ('NL', '荷兰'), ('NO', '挪威'), ('PL', '波兰'), ('IT', '意大利'), ('IE', '爱尔兰'), ('AR', '阿根廷'), ('PT', '葡萄牙'), ('AU', '澳大利亚'), ('RU', '俄罗斯联邦'), ('CF', '中非共和国')], default='CN', max_length=2, verbose_name='国家'), - ), + model_name="node", + name="country", + field=models.CharField( + choices=[ + ("US", "美国"), + ("CN", "中国"), + ("HK", "香港"), + ("JP", "日本"), + ("FR", "法国"), + ("DE", "德国"), + ("KR", "韩国"), + ("JE", "泽西岛"), + ("NZ", "新西兰"), + ("MX", "墨西哥"), + ("CA", "加拿大"), + ("BR", "巴西"), + ("CU", "古巴"), + ("CZ", "捷克"), + ("EG", "埃及"), + ("FI", "芬兰"), + ("GR", "希腊"), + ("GU", "关岛"), + ("IS", "冰岛"), + ("MO", "澳门"), + ("NL", "荷兰"), + ("NO", "挪威"), + ("PL", "波兰"), + ("IT", "意大利"), + ("IE", "爱尔兰"), + ("AR", "阿根廷"), + ("PT", "葡萄牙"), + ("AU", "澳大利亚"), + ("RU", "俄罗斯联邦"), + ("CF", "中非共和国"), + ], + default="CN", + max_length=2, + verbose_name="国家", + ), + ) ] diff --git a/apps/ssserver/migrations/0003_auto_20180730_0916.py b/apps/ssserver/migrations/0003_auto_20180730_0916.py index 4e1a983a34..42934ebd41 100644 --- a/apps/ssserver/migrations/0003_auto_20180730_0916.py +++ b/apps/ssserver/migrations/0003_auto_20180730_0916.py @@ -5,22 +5,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0002_auto_20180707_2200'), - ] + dependencies = [("ssserver", "0002_auto_20180707_2200")] operations = [ - migrations.RemoveField( - model_name='node', - name='human_total_traffic', - ), - migrations.RemoveField( - model_name='node', - name='human_used_traffic', - ), + migrations.RemoveField(model_name="node", name="human_total_traffic"), + migrations.RemoveField(model_name="node", name="human_used_traffic"), migrations.AlterField( - model_name='node', - name='password', - field=models.CharField(default='password', help_text='单端口时需要', max_length=32, verbose_name='节点密码'), + model_name="node", + name="password", + field=models.CharField( + default="password", + help_text="单端口时需要", + max_length=32, + verbose_name="节点密码", + ), ), ] diff --git a/apps/ssserver/migrations/0004_auto_20180930_1537.py b/apps/ssserver/migrations/0004_auto_20180930_1537.py index 9d06af8caf..4b9f8d2000 100644 --- a/apps/ssserver/migrations/0004_auto_20180930_1537.py +++ b/apps/ssserver/migrations/0004_auto_20180930_1537.py @@ -8,45 +8,174 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0003_auto_20180730_0916'), - ] + dependencies = [("ssserver", "0003_auto_20180730_0916")] operations = [ migrations.CreateModel( - name='Suser', + name="Suser", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('user_id', models.IntegerField(db_column='user_id', db_index=True, unique=True, verbose_name='user_id')), - ('last_check_in_time', models.DateTimeField(default=datetime.datetime(1970, 1, 1, 8, 0), editable=False, null=True, verbose_name='最后签到时间')), - ('password', models.CharField(db_column='passwd', default=apps.utils.get_short_random_string, max_length=32, validators=[django.core.validators.MinLengthValidator(6)], verbose_name='sspanel密码')), - ('port', models.IntegerField(db_column='port', unique=True, verbose_name='端口')), - ('last_use_time', models.IntegerField(db_column='t', default=0, editable=False, help_text='时间戳', verbose_name='最后使用时间')), - ('upload_traffic', models.BigIntegerField(db_column='u', default=0, verbose_name='上传流量')), - ('download_traffic', models.BigIntegerField(db_column='d', default=0, verbose_name='下载流量')), - ('transfer_enable', models.BigIntegerField(db_column='transfer_enable', default=5368709120, verbose_name='总流量')), - ('switch', models.BooleanField(db_column='switch', default=True, verbose_name='保留字段switch')), - ('enable', models.BooleanField(db_column='enable', default=True, verbose_name='开启与否')), - ('method', models.CharField(choices=[('aes-256-cfb', 'aes-256-cfb'), ('aes-128-ctr', 'aes-128-ctr'), ('rc4-md5', 'rc4-md5'), ('salsa20', 'salsa20'), ('chacha20', 'chacha20'), ('none', 'none')], default='aes-128-ctr', max_length=32, verbose_name='加密类型')), - ('protocol', models.CharField(choices=[('auth_sha1_v4', 'auth_sha1_v4'), ('auth_aes128_md5', 'auth_aes128_md5'), ('auth_aes128_sha1', 'auth_aes128_sha1'), ('auth_chain_a', 'auth_chain_a'), ('origin', 'origin')], default='auth_chain_a', max_length=32, verbose_name='协议')), - ('protocol_param', models.CharField(blank=True, max_length=128, null=True, verbose_name='协议参数')), - ('obfs', models.CharField(choices=[('plain', 'plain'), ('http_simple', 'http_simple'), ('http_simple_compatible', 'http_simple_compatible'), ('http_post', 'http_post'), ('tls1.2_ticket_auth', 'tls1.2_ticket_auth')], default='http_simple', max_length=32, verbose_name='混淆')), - ('obfs_param', models.CharField(blank=True, max_length=255, null=True, verbose_name='混淆参数')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "user_id", + models.IntegerField( + db_column="user_id", + db_index=True, + unique=True, + verbose_name="user_id", + ), + ), + ( + "last_check_in_time", + models.DateTimeField( + default=datetime.datetime(1970, 1, 1, 8, 0), + editable=False, + null=True, + verbose_name="最后签到时间", + ), + ), + ( + "password", + models.CharField( + db_column="passwd", + default=apps.utils.get_short_random_string, + max_length=32, + validators=[django.core.validators.MinLengthValidator(6)], + verbose_name="sspanel密码", + ), + ), + ( + "port", + models.IntegerField( + db_column="port", unique=True, verbose_name="端口" + ), + ), + ( + "last_use_time", + models.IntegerField( + db_column="t", + default=0, + editable=False, + help_text="时间戳", + verbose_name="最后使用时间", + ), + ), + ( + "upload_traffic", + models.BigIntegerField( + db_column="u", default=0, verbose_name="上传流量" + ), + ), + ( + "download_traffic", + models.BigIntegerField( + db_column="d", default=0, verbose_name="下载流量" + ), + ), + ( + "transfer_enable", + models.BigIntegerField( + db_column="transfer_enable", + default=5368709120, + verbose_name="总流量", + ), + ), + ( + "switch", + models.BooleanField( + db_column="switch", default=True, verbose_name="保留字段switch" + ), + ), + ( + "enable", + models.BooleanField( + db_column="enable", default=True, verbose_name="开启与否" + ), + ), + ( + "method", + models.CharField( + choices=[ + ("aes-256-cfb", "aes-256-cfb"), + ("aes-128-ctr", "aes-128-ctr"), + ("rc4-md5", "rc4-md5"), + ("salsa20", "salsa20"), + ("chacha20", "chacha20"), + ("none", "none"), + ], + default="aes-128-ctr", + max_length=32, + verbose_name="加密类型", + ), + ), + ( + "protocol", + models.CharField( + choices=[ + ("auth_sha1_v4", "auth_sha1_v4"), + ("auth_aes128_md5", "auth_aes128_md5"), + ("auth_aes128_sha1", "auth_aes128_sha1"), + ("auth_chain_a", "auth_chain_a"), + ("origin", "origin"), + ], + default="auth_chain_a", + max_length=32, + verbose_name="协议", + ), + ), + ( + "protocol_param", + models.CharField( + blank=True, max_length=128, null=True, verbose_name="协议参数" + ), + ), + ( + "obfs", + models.CharField( + choices=[ + ("plain", "plain"), + ("http_simple", "http_simple"), + ("http_simple_compatible", "http_simple_compatible"), + ("http_post", "http_post"), + ("tls1.2_ticket_auth", "tls1.2_ticket_auth"), + ], + default="http_simple", + max_length=32, + verbose_name="混淆", + ), + ), + ( + "obfs_param", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="混淆参数" + ), + ), ], options={ - 'verbose_name_plural': 'ss_db_user', - 'db_table': 's_user', - 'ordering': ('-last_check_in_time',), + "verbose_name_plural": "ss_db_user", + "db_table": "s_user", + "ordering": ("-last_check_in_time",), }, ), migrations.AlterField( - model_name='node', - name='obfs_param', - field=models.CharField(blank=True, default='', max_length=255, null=True, verbose_name='混淆参数'), + model_name="node", + name="obfs_param", + field=models.CharField( + blank=True, default="", max_length=255, null=True, verbose_name="混淆参数" + ), ), migrations.AlterField( - model_name='ssuser', - name='obfs_param', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='混淆参数'), + model_name="ssuser", + name="obfs_param", + field=models.CharField( + blank=True, max_length=255, null=True, verbose_name="混淆参数" + ), ), ] diff --git a/apps/ssserver/migrations/0005_auto_20181003_1549.py b/apps/ssserver/migrations/0005_auto_20181003_1549.py index 411a1224ca..88158a8b09 100644 --- a/apps/ssserver/migrations/0005_auto_20181003_1549.py +++ b/apps/ssserver/migrations/0005_auto_20181003_1549.py @@ -7,23 +7,32 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0004_auto_20180930_1537'), - ] + dependencies = [("ssserver", "0004_auto_20180930_1537")] operations = [ migrations.AlterModelOptions( - name='suser', - options={'ordering': ('-last_check_in_time',), 'verbose_name_plural': 'Ss用户'}, + name="suser", + options={ + "ordering": ("-last_check_in_time",), + "verbose_name_plural": "Ss用户", + }, ), migrations.AlterField( - model_name='suser', - name='last_check_in_time', - field=models.DateTimeField(editable=False, null=True, verbose_name='最后签到时间'), + model_name="suser", + name="last_check_in_time", + field=models.DateTimeField( + editable=False, null=True, verbose_name="最后签到时间" + ), ), migrations.AlterField( - model_name='suser', - name='password', - field=models.CharField(db_column='passwd', default=apps.utils.get_short_random_string, max_length=32, validators=[django.core.validators.MinLengthValidator(6)], verbose_name='ss密码'), + model_name="suser", + name="password", + field=models.CharField( + db_column="passwd", + default=apps.utils.get_short_random_string, + max_length=32, + validators=[django.core.validators.MinLengthValidator(6)], + verbose_name="ss密码", + ), ), ] diff --git a/apps/ssserver/migrations/0006_auto_20181003_1759.py b/apps/ssserver/migrations/0006_auto_20181003_1759.py index 826bb7cd14..da8942f060 100644 --- a/apps/ssserver/migrations/0006_auto_20181003_1759.py +++ b/apps/ssserver/migrations/0006_auto_20181003_1759.py @@ -5,16 +5,9 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0005_auto_20181003_1549'), - ] + dependencies = [("ssserver", "0005_auto_20181003_1549")] operations = [ - migrations.RemoveField( - model_name='ssuser', - name='user', - ), - migrations.DeleteModel( - name='SSUser', - ), + migrations.RemoveField(model_name="ssuser", name="user"), + migrations.DeleteModel(name="SSUser"), ] diff --git a/apps/ssserver/migrations/0007_auto_20181004_1032.py b/apps/ssserver/migrations/0007_auto_20181004_1032.py index f64fb1fac8..9490445a3f 100644 --- a/apps/ssserver/migrations/0007_auto_20181004_1032.py +++ b/apps/ssserver/migrations/0007_auto_20181004_1032.py @@ -6,24 +6,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0006_auto_20181003_1759'), - ] + dependencies = [("ssserver", "0006_auto_20181003_1759")] operations = [ migrations.AlterField( - model_name='trafficlog', - name='log_date', - field=models.DateField(db_index=True, default=django.utils.timezone.now, verbose_name='记录日期'), + model_name="trafficlog", + name="log_date", + field=models.DateField( + db_index=True, default=django.utils.timezone.now, verbose_name="记录日期" + ), ), migrations.AlterField( - model_name='trafficlog', - name='node_id', - field=models.IntegerField(db_index=True, verbose_name='节点id'), + model_name="trafficlog", + name="node_id", + field=models.IntegerField(db_index=True, verbose_name="节点id"), ), migrations.AlterField( - model_name='trafficlog', - name='user_id', - field=models.IntegerField(db_index=True, verbose_name='用户id'), + model_name="trafficlog", + name="user_id", + field=models.IntegerField(db_index=True, verbose_name="用户id"), ), ] diff --git a/apps/ssserver/migrations/0008_auto_20181009_0909.py b/apps/ssserver/migrations/0008_auto_20181009_0909.py index ffd21ff3e9..6e60ddde13 100644 --- a/apps/ssserver/migrations/0008_auto_20181009_0909.py +++ b/apps/ssserver/migrations/0008_auto_20181009_0909.py @@ -5,32 +5,38 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0007_auto_20181004_1032'), - ] + dependencies = [("ssserver", "0007_auto_20181004_1032")] operations = [ - migrations.DeleteModel( - name='NodeInfoLog', - ), + migrations.DeleteModel(name="NodeInfoLog"), migrations.AddField( - model_name='node', - name='ss_type', - field=models.SmallIntegerField(choices=[(0, 'SS'), (1, 'SSR'), (2, 'SS/SSR')], default=2, verbose_name='SS类型'), + model_name="node", + name="ss_type", + field=models.SmallIntegerField( + choices=[(0, "SS"), (1, "SSR"), (2, "SS/SSR")], + default=2, + verbose_name="SS类型", + ), ), migrations.AlterField( - model_name='node', - name='obfs_param', - field=models.CharField(blank=True, default='', max_length=255, verbose_name='混淆参数'), + model_name="node", + name="obfs_param", + field=models.CharField( + blank=True, default="", max_length=255, verbose_name="混淆参数" + ), ), migrations.AlterField( - model_name='node', - name='port', - field=models.IntegerField(blank=True, default=443, help_text='单端口多用户时需要', verbose_name='节点端口'), + model_name="node", + name="port", + field=models.IntegerField( + blank=True, default=443, help_text="单端口多用户时需要", verbose_name="节点端口" + ), ), migrations.AlterField( - model_name='node', - name='protocol_param', - field=models.CharField(blank=True, default='', max_length=128, verbose_name='协议参数'), + model_name="node", + name="protocol_param", + field=models.CharField( + blank=True, default="", max_length=128, verbose_name="协议参数" + ), ), ] diff --git a/apps/ssserver/migrations/0009_auto_20181122_0930.py b/apps/ssserver/migrations/0009_auto_20181122_0930.py index 74833c34a8..959d9678e6 100644 --- a/apps/ssserver/migrations/0009_auto_20181122_0930.py +++ b/apps/ssserver/migrations/0009_auto_20181122_0930.py @@ -5,14 +5,49 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0008_auto_20181009_0909'), - ] + dependencies = [("ssserver", "0008_auto_20181009_0909")] operations = [ migrations.AlterField( - model_name='node', - name='country', - field=models.CharField(choices=[('US', '美国'), ('CN', '中国'), ('TW', '台湾'), ('HK', '香港'), ('JP', '日本'), ('FR', '法国'), ('DE', '德国'), ('KR', '韩国'), ('JE', '泽西岛'), ('NZ', '新西兰'), ('MX', '墨西哥'), ('CA', '加拿大'), ('BR', '巴西'), ('CU', '古巴'), ('CZ', '捷克'), ('EG', '埃及'), ('FI', '芬兰'), ('GR', '希腊'), ('GU', '关岛'), ('IS', '冰岛'), ('MO', '澳门'), ('NL', '荷兰'), ('NO', '挪威'), ('PL', '波兰'), ('IT', '意大利'), ('IE', '爱尔兰'), ('AR', '阿根廷'), ('PT', '葡萄牙'), ('AU', '澳大利亚'), ('RU', '俄罗斯联邦'), ('CF', '中非共和国')], default='CN', max_length=2, verbose_name='国家'), - ), + model_name="node", + name="country", + field=models.CharField( + choices=[ + ("US", "美国"), + ("CN", "中国"), + ("TW", "台湾"), + ("HK", "香港"), + ("JP", "日本"), + ("FR", "法国"), + ("DE", "德国"), + ("KR", "韩国"), + ("JE", "泽西岛"), + ("NZ", "新西兰"), + ("MX", "墨西哥"), + ("CA", "加拿大"), + ("BR", "巴西"), + ("CU", "古巴"), + ("CZ", "捷克"), + ("EG", "埃及"), + ("FI", "芬兰"), + ("GR", "希腊"), + ("GU", "关岛"), + ("IS", "冰岛"), + ("MO", "澳门"), + ("NL", "荷兰"), + ("NO", "挪威"), + ("PL", "波兰"), + ("IT", "意大利"), + ("IE", "爱尔兰"), + ("AR", "阿根廷"), + ("PT", "葡萄牙"), + ("AU", "澳大利亚"), + ("RU", "俄罗斯联邦"), + ("CF", "中非共和国"), + ], + default="CN", + max_length=2, + verbose_name="国家", + ), + ) ] diff --git a/apps/ssserver/migrations/0010_auto_20181219_1632.py b/apps/ssserver/migrations/0010_auto_20181219_1632.py index 71708a207b..2d308939e9 100644 --- a/apps/ssserver/migrations/0010_auto_20181219_1632.py +++ b/apps/ssserver/migrations/0010_auto_20181219_1632.py @@ -5,24 +5,63 @@ class Migration(migrations.Migration): - dependencies = [ - ('ssserver', '0009_auto_20181122_0930'), - ] + dependencies = [("ssserver", "0009_auto_20181122_0930")] operations = [ migrations.AddField( - model_name='node', - name='speed_limit', - field=models.IntegerField(default=0, verbose_name='限速'), + model_name="node", + name="speed_limit", + field=models.IntegerField(default=0, verbose_name="限速"), ), migrations.AddField( - model_name='suser', - name='speed_limit', - field=models.IntegerField(db_column='speed_limit', default=0, verbose_name='限速'), + model_name="suser", + name="speed_limit", + field=models.IntegerField( + db_column="speed_limit", default=0, verbose_name="限速" + ), ), migrations.AlterField( - model_name='node', - name='country', - field=models.CharField(choices=[('US', '美国'), ('CN', '中国'), ('GB', '英国'), ('SG', '新加坡'), ('TW', '台湾'), ('HK', '香港'), ('JP', '日本'), ('FR', '法国'), ('DE', '德国'), ('KR', '韩国'), ('JE', '泽西岛'), ('NZ', '新西兰'), ('MX', '墨西哥'), ('CA', '加拿大'), ('BR', '巴西'), ('CU', '古巴'), ('CZ', '捷克'), ('EG', '埃及'), ('FI', '芬兰'), ('GR', '希腊'), ('GU', '关岛'), ('IS', '冰岛'), ('MO', '澳门'), ('NL', '荷兰'), ('NO', '挪威'), ('PL', '波兰'), ('IT', '意大利'), ('IE', '爱尔兰'), ('AR', '阿根廷'), ('PT', '葡萄牙'), ('AU', '澳大利亚'), ('RU', '俄罗斯联邦'), ('CF', '中非共和国')], default='CN', max_length=2, verbose_name='国家'), + model_name="node", + name="country", + field=models.CharField( + choices=[ + ("US", "美国"), + ("CN", "中国"), + ("GB", "英国"), + ("SG", "新加坡"), + ("TW", "台湾"), + ("HK", "香港"), + ("JP", "日本"), + ("FR", "法国"), + ("DE", "德国"), + ("KR", "韩国"), + ("JE", "泽西岛"), + ("NZ", "新西兰"), + ("MX", "墨西哥"), + ("CA", "加拿大"), + ("BR", "巴西"), + ("CU", "古巴"), + ("CZ", "捷克"), + ("EG", "埃及"), + ("FI", "芬兰"), + ("GR", "希腊"), + ("GU", "关岛"), + ("IS", "冰岛"), + ("MO", "澳门"), + ("NL", "荷兰"), + ("NO", "挪威"), + ("PL", "波兰"), + ("IT", "意大利"), + ("IE", "爱尔兰"), + ("AR", "阿根廷"), + ("PT", "葡萄牙"), + ("AU", "澳大利亚"), + ("RU", "俄罗斯联邦"), + ("CF", "中非共和国"), + ], + default="CN", + max_length=2, + verbose_name="国家", + ), ), ] diff --git a/apps/ssserver/models.py b/apps/ssserver/models.py index 475def3797..dc9c3ed84e 100644 --- a/apps/ssserver/models.py +++ b/apps/ssserver/models.py @@ -12,65 +12,97 @@ from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator -from apps.utils import (get_short_random_string, - traffic_format, get_current_time) -from apps.constants import (METHOD_CHOICES, PROTOCOL_CHOICES, OBFS_CHOICES, - COUNTRIES_CHOICES, NODE_TIME_OUT) +from apps.utils import get_short_random_string, traffic_format, get_current_time +from apps.constants import ( + METHOD_CHOICES, + PROTOCOL_CHOICES, + OBFS_CHOICES, + COUNTRIES_CHOICES, + NODE_TIME_OUT, +) -class Suser(ExportModelOperationsMixin('ss_user'), models.Model): - '''与user通过user_id作为虚拟外键关联''' +class Suser(ExportModelOperationsMixin("ss_user"), models.Model): + """与user通过user_id作为虚拟外键关联""" user_id = models.IntegerField( - verbose_name='user_id', db_column='user_id', unique=True, db_index=True) + verbose_name="user_id", db_column="user_id", unique=True, db_index=True + ) last_check_in_time = models.DateTimeField( - verbose_name='最后签到时间', null=True, editable=False) - password = models.CharField(verbose_name='ss密码', max_length=32, default=get_short_random_string, - db_column='passwd', validators=[validators.MinLengthValidator(6), ]) - port = models.IntegerField( - verbose_name='端口', db_column='port', unique=True,) + verbose_name="最后签到时间", null=True, editable=False + ) + password = models.CharField( + verbose_name="ss密码", + max_length=32, + default=get_short_random_string, + db_column="passwd", + validators=[validators.MinLengthValidator(6)], + ) + port = models.IntegerField(verbose_name="端口", db_column="port", unique=True) last_use_time = models.IntegerField( - verbose_name='最后使用时间', default=0, editable=False, help_text='时间戳', db_column='t') + verbose_name="最后使用时间", default=0, editable=False, help_text="时间戳", db_column="t" + ) upload_traffic = models.BigIntegerField( - verbose_name='上传流量', default=0, db_column='u') + verbose_name="上传流量", default=0, db_column="u" + ) download_traffic = models.BigIntegerField( - verbose_name='下载流量', default=0, db_column='d') + verbose_name="下载流量", default=0, db_column="d" + ) transfer_enable = models.BigIntegerField( - verbose_name='总流量', default=settings.DEFAULT_TRAFFIC, db_column='transfer_enable') + verbose_name="总流量", + default=settings.DEFAULT_TRAFFIC, + db_column="transfer_enable", + ) speed_limit = models.IntegerField( - verbose_name='限速', default=0, db_column='speed_limit') + verbose_name="限速", default=0, db_column="speed_limit" + ) switch = models.BooleanField( - verbose_name='保留字段switch', default=True, db_column='switch') - enable = models.BooleanField( - verbose_name='开启与否', default=True, db_column='enable') + verbose_name="保留字段switch", default=True, db_column="switch" + ) + enable = models.BooleanField(verbose_name="开启与否", default=True, db_column="enable") method = models.CharField( - verbose_name='加密类型', default=settings.DEFAULT_METHOD, max_length=32, choices=METHOD_CHOICES,) + verbose_name="加密类型", + default=settings.DEFAULT_METHOD, + max_length=32, + choices=METHOD_CHOICES, + ) protocol = models.CharField( - verbose_name='协议', default=settings.DEFAULT_PROTOCOL, max_length=32, choices=PROTOCOL_CHOICES) + verbose_name="协议", + default=settings.DEFAULT_PROTOCOL, + max_length=32, + choices=PROTOCOL_CHOICES, + ) protocol_param = models.CharField( - verbose_name='协议参数', max_length=128, null=True, blank=True) + verbose_name="协议参数", max_length=128, null=True, blank=True + ) obfs = models.CharField( - verbose_name='混淆', default=settings.DEFAULT_OBFS, max_length=32, choices=OBFS_CHOICES) + verbose_name="混淆", + default=settings.DEFAULT_OBFS, + max_length=32, + choices=OBFS_CHOICES, + ) obfs_param = models.CharField( - verbose_name='混淆参数', max_length=255, null=True, blank=True) + verbose_name="混淆参数", max_length=255, null=True, blank=True + ) class Meta: - verbose_name_plural = 'Ss用户' - ordering = ('-last_check_in_time', ) - db_table = 's_user' + verbose_name_plural = "Ss用户" + ordering = ("-last_check_in_time",) + db_table = "s_user" def __str__(self): return self.user.username def clean(self): - '''保证端口在1024<50000之间''' + """保证端口在1024<50000之间""" if self.port: if not 1024 < self.port < 50000: - raise ValidationError('端口必须在1024和50000之间') + raise ValidationError("端口必须在1024和50000之间") @property def user(self): from apps.sspanel.models import User + return User.objects.get(pk=self.user_id) @property @@ -95,7 +127,8 @@ def totla_transfer(self): @property def unused_traffic(self): return traffic_format( - self.transfer_enable - self.upload_traffic - self.download_traffic) + self.transfer_enable - self.upload_traffic - self.download_traffic + ) @property def used_percentage(self): @@ -109,7 +142,8 @@ def used_percentage(self): def get_today_checked_user_num(cls): now = get_current_time() midnight = pendulum.datetime( - year=now.year, month=now.month, day=now.day, tz=now.tz) + year=now.year, month=now.month, day=now.day, tz=now.tz + ) query = cls.objects.filter(last_check_in_time__gte=midnight) return query.count() @@ -119,26 +153,29 @@ def get_never_checked_user_num(cls): @classmethod def get_never_used_num(cls): - '''返回从未使用过的人数''' + """返回从未使用过的人数""" return cls.objects.filter(last_use_time=0).count() @classmethod def get_user_by_traffic(cls, num=10): - '''返回流量用的最多的前num名用户''' - return cls.objects.all().order_by('-download_traffic')[:10] + """返回流量用的最多的前num名用户""" + return cls.objects.all().order_by("-download_traffic")[:10] @classmethod def get_vaild_user(cls, level): - '''返回指大于等于指定等级的所有合法用户''' + """返回指大于等于指定等级的所有合法用户""" from apps.sspanel.models import User - user_ids = User.objects.filter(level__gte=level).values_list('id') - users = cls.objects.filter(transfer_enable__gte=( - F('upload_traffic') + F('download_traffic')), user_id__in=user_ids) + + user_ids = User.objects.filter(level__gte=level).values_list("id") + users = cls.objects.filter( + transfer_enable__gte=(F("upload_traffic") + F("download_traffic")), + user_id__in=user_ids, + ) return users @classmethod def get_random_port(cls): - users = cls.objects.all().values_list('port') + users = cls.objects.all().values_list("port") port_list = [] for user in users: port_list.append(user[0]) @@ -153,8 +190,9 @@ def get_random_port(cls): @staticmethod def checkin(ss_user): if not ss_user.today_is_checked: - traffic = randint(settings.MIN_CHECKIN_TRAFFIC, - settings.MAX_CHECKIN_TRAFFIC) + traffic = randint( + settings.MIN_CHECKIN_TRAFFIC, settings.MAX_CHECKIN_TRAFFIC + ) ss_user.transfer_enable += traffic ss_user.last_check_in_time = get_current_time() ss_user.save() @@ -162,124 +200,150 @@ def checkin(ss_user): return False, 0 -class Node(ExportModelOperationsMixin('node'), models.Model): - '''线路节点''' - SHOW_CHOICES = ((1, '显示'), (-1, '不显示')) +class Node(ExportModelOperationsMixin("node"), models.Model): + """线路节点""" - NODE_TYPE_CHOICES = ((0, '多端口多用户'), (1, '单端口多用户')) + SHOW_CHOICES = ((1, "显示"), (-1, "不显示")) - CUSTOM_METHOD_CHOICES = ((0, '否'), (1, '是')) + NODE_TYPE_CHOICES = ((0, "多端口多用户"), (1, "单端口多用户")) - SS_TYPE_CHOICES = ((0, 'SS'), (1, 'SSR'), (2, 'SS/SSR')) + CUSTOM_METHOD_CHOICES = ((0, "否"), (1, "是")) + + SS_TYPE_CHOICES = ((0, "SS"), (1, "SSR"), (2, "SS/SSR")) @classmethod def get_import_code(cls, user): - '''获取该用户的所有节点的导入信息''' + """获取该用户的所有节点的导入信息""" ss_user = user.ss_user sub_code_list = [] node_list = cls.objects.filter(level__lte=user.level, show=1) for node in node_list: sub_code_list.append(node.get_node_link(ss_user)) - return '\n'.join(sub_code_list) + return "\n".join(sub_code_list) @classmethod def get_node_ids(cls, all=False): - '''返回所有节点的id''' + """返回所有节点的id""" if all is False: nodes = cls.objects.filter(show=1) else: nodes = cls.objects.all() return [node.node_id for node in nodes] - node_id = models.IntegerField('节点id', unique=True) - port = models.IntegerField( - '节点端口', default=443, blank=True, help_text='单端口多用户时需要') + node_id = models.IntegerField("节点id", unique=True) + port = models.IntegerField("节点端口", default=443, blank=True, help_text="单端口多用户时需要") password = models.CharField( - '节点密码', max_length=32, default='password', help_text='单端口时需要') + "节点密码", max_length=32, default="password", help_text="单端口时需要" + ) country = models.CharField( - '国家', default='CN', max_length=2, choices=COUNTRIES_CHOICES) + "国家", default="CN", max_length=2, choices=COUNTRIES_CHOICES + ) custom_method = models.SmallIntegerField( - '自定义加密', choices=CUSTOM_METHOD_CHOICES, default=0) - show = models.SmallIntegerField('是否显示', choices=SHOW_CHOICES, default=1) - node_type = models.SmallIntegerField( - '节点类型', choices=NODE_TYPE_CHOICES, default=0) - ss_type = models.SmallIntegerField( - 'SS类型', choices=SS_TYPE_CHOICES, default=2) - name = models.CharField('名字', max_length=32) - info = models.CharField('节点说明', max_length=1024, blank=True, null=True) - server = models.CharField('服务器IP', max_length=128) - method = models.CharField('加密类型', default=settings.DEFAULT_METHOD, - max_length=32, choices=METHOD_CHOICES,) - traffic_rate = models.FloatField('流量比例', default=1.0) - protocol = models.CharField('协议', default=settings.DEFAULT_PROTOCOL, - max_length=32, choices=PROTOCOL_CHOICES,) - protocol_param = models.CharField( - '协议参数', max_length=128, default="", blank=True) - obfs = models.CharField('混淆', default=settings.DEFAULT_OBFS, - max_length=32, choices=OBFS_CHOICES,) - obfs_param = models.CharField( - '混淆参数', max_length=255, default="", blank=True) + "自定义加密", choices=CUSTOM_METHOD_CHOICES, default=0 + ) + show = models.SmallIntegerField("是否显示", choices=SHOW_CHOICES, default=1) + node_type = models.SmallIntegerField("节点类型", choices=NODE_TYPE_CHOICES, default=0) + ss_type = models.SmallIntegerField("SS类型", choices=SS_TYPE_CHOICES, default=2) + name = models.CharField("名字", max_length=32) + info = models.CharField("节点说明", max_length=1024, blank=True, null=True) + server = models.CharField("服务器IP", max_length=128) + method = models.CharField( + "加密类型", default=settings.DEFAULT_METHOD, max_length=32, choices=METHOD_CHOICES + ) + traffic_rate = models.FloatField("流量比例", default=1.0) + protocol = models.CharField( + "协议", default=settings.DEFAULT_PROTOCOL, max_length=32, choices=PROTOCOL_CHOICES + ) + protocol_param = models.CharField("协议参数", max_length=128, default="", blank=True) + obfs = models.CharField( + "混淆", default=settings.DEFAULT_OBFS, max_length=32, choices=OBFS_CHOICES + ) + obfs_param = models.CharField("混淆参数", max_length=255, default="", blank=True) level = models.PositiveIntegerField( - '节点等级', default=0, - validators=[MaxValueValidator(9), MinValueValidator(0)]) - total_traffic = models.BigIntegerField('总流量', default=settings.GB) - used_traffic = models.BigIntegerField('已用流量', default=0,) - speed_limit = models.IntegerField('限速', default=0) - order = models.PositiveSmallIntegerField('排序', default=1) - group = models.CharField('分组名', max_length=32, default='谜之屋') + "节点等级", default=0, validators=[MaxValueValidator(9), MinValueValidator(0)] + ) + total_traffic = models.BigIntegerField("总流量", default=settings.GB) + used_traffic = models.BigIntegerField("已用流量", default=0) + speed_limit = models.IntegerField("限速", default=0) + order = models.PositiveSmallIntegerField("排序", default=1) + group = models.CharField("分组名", max_length=32, default="谜之屋") def __str__(self): return self.name def get_ssr_link(self, ss_user): - '''返回ssr链接''' - ssr_password = base64.urlsafe_b64encode( - bytes(ss_user.password, 'utf8')).decode('utf8') - ssr_remarks = base64.urlsafe_b64encode(bytes(self.name, - 'utf8')).decode('utf8') - ssr_group = base64.urlsafe_b64encode(bytes(self.group, - 'utf8')).decode('utf8') + """返回ssr链接""" + ssr_password = base64.urlsafe_b64encode(bytes(ss_user.password, "utf8")).decode( + "utf8" + ) + ssr_remarks = base64.urlsafe_b64encode(bytes(self.name, "utf8")).decode("utf8") + ssr_group = base64.urlsafe_b64encode(bytes(self.group, "utf8")).decode("utf8") if self.node_type == 1: # 单端口多用户 ssr_password = base64.urlsafe_b64encode( - bytes(self.password, 'utf8')).decode('utf8') - info = '{}:{}'.format(ss_user.port, ss_user.password) - protocol_param = base64.urlsafe_b64encode(bytes( - info, 'utf8')).decode('utf8') + bytes(self.password, "utf8") + ).decode("utf8") + info = "{}:{}".format(ss_user.port, ss_user.password) + protocol_param = base64.urlsafe_b64encode(bytes(info, "utf8")).decode( + "utf8" + ) obfs_param = base64.urlsafe_b64encode( - bytes(str(self.obfs_param), 'utf8')).decode('utf8') - ssr_code = '{}:{}:{}:{}:{}:{}/?obfsparam={}&protoparam={}&remarks={}&group={}'.format( - self.server, self.port, self.protocol, self.method, self.obfs, - ssr_password, obfs_param, protocol_param, ssr_remarks, - ssr_group) + bytes(str(self.obfs_param), "utf8") + ).decode("utf8") + ssr_code = "{}:{}:{}:{}:{}:{}/?obfsparam={}&protoparam={}&remarks={}&group={}".format( + self.server, + self.port, + self.protocol, + self.method, + self.obfs, + ssr_password, + obfs_param, + protocol_param, + ssr_remarks, + ssr_group, + ) elif self.custom_method == 1: - ssr_code = '{}:{}:{}:{}:{}:{}/?remarks={}&group={}'.format( - self.server, ss_user.port, ss_user.protocol, ss_user.method, - ss_user.obfs, ssr_password, ssr_remarks, ssr_group) + ssr_code = "{}:{}:{}:{}:{}:{}/?remarks={}&group={}".format( + self.server, + ss_user.port, + ss_user.protocol, + ss_user.method, + ss_user.obfs, + ssr_password, + ssr_remarks, + ssr_group, + ) else: - ssr_code = '{}:{}:{}:{}:{}:{}/?remarks={}&group={}'.format( - self.server, ss_user.port, self.protocol, self.method, - self.obfs, ssr_password, ssr_remarks, ssr_group) - ssr_pass = base64.urlsafe_b64encode(bytes(ssr_code, - 'utf8')).decode('utf8') - ssr_link = 'ssr://{}'.format(ssr_pass) + ssr_code = "{}:{}:{}:{}:{}:{}/?remarks={}&group={}".format( + self.server, + ss_user.port, + self.protocol, + self.method, + self.obfs, + ssr_password, + ssr_remarks, + ssr_group, + ) + ssr_pass = base64.urlsafe_b64encode(bytes(ssr_code, "utf8")).decode("utf8") + ssr_link = "ssr://{}".format(ssr_pass) return ssr_link def get_ss_link(self, ss_user): - '''返回ss链接''' + """返回ss链接""" if self.custom_method == 1: - ss_code = '{}:{}@{}:{}'.format(ss_user.method, ss_user.password, - self.server, ss_user.port) + ss_code = "{}:{}@{}:{}".format( + ss_user.method, ss_user.password, self.server, ss_user.port + ) else: - ss_code = '{}:{}@{}:{}'.format(self.method, ss_user.password, - self.server, ss_user.port) - ss_pass = base64.urlsafe_b64encode(bytes(ss_code, - 'utf8')).decode('utf8') - ss_link = 'ss://{}#{}'.format(ss_pass, self.name) + ss_code = "{}:{}@{}:{}".format( + self.method, ss_user.password, self.server, ss_user.port + ) + ss_pass = base64.urlsafe_b64encode(bytes(ss_code, "utf8")).decode("utf8") + ss_link = "ss://{}#{}".format(ss_pass, self.name) return ss_link def get_node_link(self, ss_user): - '''获取当前的节点链接''' + """获取当前的节点链接""" if self.ss_type == 0: return self.get_ss_link(ss_user) else: @@ -291,52 +355,53 @@ def save(self, *args, **kwargs): super(Node, self).save(*args, **kwargs) def human_total_traffic(self): - '''总流量''' + """总流量""" return traffic_format(self.total_traffic) def human_used_traffic(self): - '''已用流量''' + """已用流量""" return traffic_format(self.used_traffic) @classmethod def get_by_node_id(cls, node_id): return cls.objects.get(node_id=node_id) + # verbose_name - human_total_traffic.short_description = '总流量' - human_used_traffic.short_description = '使用流量' + human_total_traffic.short_description = "总流量" + human_used_traffic.short_description = "使用流量" class Meta: - ordering = ['-show', 'order'] - verbose_name_plural = '节点' - db_table = 'ss_node' + ordering = ["-show", "order"] + verbose_name_plural = "节点" + db_table = "ss_node" -class TrafficLog(ExportModelOperationsMixin('traffic_log'), models.Model): - '''用户流量记录''' +class TrafficLog(ExportModelOperationsMixin("traffic_log"), models.Model): + """用户流量记录""" - user_id = models.IntegerField( - '用户id', blank=False, null=False, db_index=True) - node_id = models.IntegerField( - '节点id', blank=False, null=False, db_index=True) - upload_traffic = models.BigIntegerField('上传流量', default=0, db_column='u') - download_traffic = models.BigIntegerField('下载流量', default=0, db_column='d') - rate = models.FloatField('流量比例', default=1.0, null=False) - traffic = models.CharField('流量记录', max_length=32, null=False) - log_time = models.IntegerField('日志时间', blank=False, null=False) + user_id = models.IntegerField("用户id", blank=False, null=False, db_index=True) + node_id = models.IntegerField("节点id", blank=False, null=False, db_index=True) + upload_traffic = models.BigIntegerField("上传流量", default=0, db_column="u") + download_traffic = models.BigIntegerField("下载流量", default=0, db_column="d") + rate = models.FloatField("流量比例", default=1.0, null=False) + traffic = models.CharField("流量记录", max_length=32, null=False) + log_time = models.IntegerField("日志时间", blank=False, null=False) log_date = models.DateField( - '记录日期', default=timezone.now, blank=False, null=False, db_index=True) + "记录日期", default=timezone.now, blank=False, null=False, db_index=True + ) def __str__(self): return self.traffic class Meta: - verbose_name_plural = '流量记录' - ordering = ('-log_time', ) - db_table = 'user_traffic_log' + verbose_name_plural = "流量记录" + ordering = ("-log_time",) + db_table = "user_traffic_log" @property def user(self): from apps.sspanel.models import User + return User.objects.get(pk=self.user_id) @property @@ -350,29 +415,25 @@ def get_user_traffic(cls, node_id, user_id): @classmethod def get_traffic_by_date(cls, node_id, user_id, date): - logs = cls.objects.filter(node_id=node_id, user_id=user_id, - log_date=date) + logs = cls.objects.filter(node_id=node_id, user_id=user_id, log_date=date) return round(sum([l.used_traffic for l in logs]) / settings.MB, 1) @classmethod def truncate(cls): with connection.cursor() as cursor: - cursor.execute( - 'TRUNCATE TABLE {}'.format(cls._meta.db_table)) + cursor.execute("TRUNCATE TABLE {}".format(cls._meta.db_table)) -class NodeOnlineLog(ExportModelOperationsMixin('node_onlie_log'), models.Model): - '''节点在线记录''' +class NodeOnlineLog(ExportModelOperationsMixin("node_onlie_log"), models.Model): + """节点在线记录""" @classmethod def totalOnlineUser(cls): - '''返回所有节点的在线人数总和''' + """返回所有节点的在线人数总和""" count = 0 - node_ids = [ - o['node_id'] for o in Node.objects.filter(show=1).values('node_id') - ] + node_ids = [o["node_id"] for o in Node.objects.filter(show=1).values("node_id")] for node_id in node_ids: - o = cls.objects.filter(node_id=node_id).order_by('-log_time')[:1] + o = cls.objects.filter(node_id=node_id).order_by("-log_time")[:1] if o: count += o[0].get_online_user() return count @@ -380,41 +441,40 @@ def totalOnlineUser(cls): @classmethod def truncate(cls): with connection.cursor() as cursor: - cursor.execute( - 'TRUNCATE TABLE {}'.format(cls._meta.db_table)) + cursor.execute("TRUNCATE TABLE {}".format(cls._meta.db_table)) - node_id = models.IntegerField('节点id', blank=False, null=False) - online_user = models.IntegerField('在线人数', blank=False, null=False) - log_time = models.IntegerField('日志时间', blank=False, null=False) + node_id = models.IntegerField("节点id", blank=False, null=False) + online_user = models.IntegerField("在线人数", blank=False, null=False) + log_time = models.IntegerField("日志时间", blank=False, null=False) def __str__(self): - return '节点:{}'.format(self.node_id) + return "节点:{}".format(self.node_id) def get_oneline_status(self): - '''检测是否在线''' + """检测是否在线""" if int(time.time()) - self.log_time > NODE_TIME_OUT: return False else: return True def get_online_user(self): - '''返回在线人数''' + """返回在线人数""" if self.get_oneline_status() is True: return self.online_user else: return 0 class Meta: - verbose_name_plural = '节点在线记录' - db_table = 'ss_node_online_log' + verbose_name_plural = "节点在线记录" + db_table = "ss_node_online_log" -class AliveIp(ExportModelOperationsMixin('aliveip_log'), models.Model): +class AliveIp(ExportModelOperationsMixin("aliveip_log"), models.Model): @classmethod def recent_alive(cls, node_id): - ''' + """ 返回节点最近一分钟的在线ip - ''' + """ ret = [] seen = [] now = pendulum.now() @@ -430,18 +490,17 @@ def recent_alive(cls, node_id): @classmethod def truncate(cls): with connection.cursor() as cursor: - cursor.execute( - 'TRUNCATE TABLE {}'.format(cls._meta.db_table)) + cursor.execute("TRUNCATE TABLE {}".format(cls._meta.db_table)) @property def node_name(self): return Node.get_by_node_id(self.node_id).name - node_id = models.IntegerField(verbose_name='节点id', blank=False, null=False) - ip = models.CharField(verbose_name='设备ip', max_length=128) - user = models.CharField(verbose_name='用户名', max_length=128) - log_time = models.DateTimeField('日志时间', auto_now=True) + node_id = models.IntegerField(verbose_name="节点id", blank=False, null=False) + ip = models.CharField(verbose_name="设备ip", max_length=128) + user = models.CharField(verbose_name="用户名", max_length=128) + log_time = models.DateTimeField("日志时间", auto_now=True) class Meta: - verbose_name_plural = '节点在线IP' - ordering = ['-log_time'] + verbose_name_plural = "节点在线IP" + ordering = ["-log_time"] diff --git a/apps/ssserver/urls.py b/apps/ssserver/urls.py index c18f9056fb..3da2e0f878 100644 --- a/apps/ssserver/urls.py +++ b/apps/ssserver/urls.py @@ -1,14 +1,14 @@ from django.urls import path -from .import views +from . import views app_name = "ssserver" urlpatterns = [ - path('user/edit//', views.user_edit, name='user_edit'), - path('changesspass/', views.change_ss_pass, name='changesspass'), - path('changessmethod/', views.change_ss_method, name='changessmethod'), - path('changessprotocol/', views.change_ss_protocol, name='changessprotocol'), - path('changessobfs/', views.change_ss_obfs, name='changessobfs'), - path('subscribe/', views.subscribe, name='subscribe'), - path('node/config/', views.node_config, name='node_config'), + path("user/edit//", views.user_edit, name="user_edit"), + path("changesspass/", views.change_ss_pass, name="changesspass"), + path("changessmethod/", views.change_ss_method, name="changessmethod"), + path("changessprotocol/", views.change_ss_protocol, name="changessprotocol"), + path("changessobfs/", views.change_ss_obfs, name="changessobfs"), + path("subscribe/", views.subscribe, name="subscribe"), + path("node/config/", views.node_config, name="node_config"), ] diff --git a/apps/ssserver/views.py b/apps/ssserver/views.py index ff3354237d..93f3acf17e 100644 --- a/apps/ssserver/views.py +++ b/apps/ssserver/views.py @@ -16,23 +16,22 @@ from .forms import ChangeSsPassForm, SuserForm -@permission_required('ssesrver') +@permission_required("ssesrver") def user_edit(request, user_id): - '''编辑ss_user的信息''' + """编辑ss_user的信息""" ss_user = Suser.objects.get(user_id=user_id) # 当为post请求时,修改数据 if request.method == "POST": # 对总流量部分进行修改,转换单GB data = request.POST.copy() - data['transfer_enable'] = int(eval( - data['transfer_enable']) * settings.GB) + data["transfer_enable"] = int(eval(data["transfer_enable"]) * settings.GB) ssform = SuserForm(data, instance=ss_user) userform = UserForm(data, instance=ss_user.user) if ssform.is_valid() and userform.is_valid(): ssform.save() userform.save() # 修改账户密码 - passwd = request.POST.get('resetpass') + passwd = request.POST.get("resetpass") if len(passwd) > 0: user = ss_user.user user.set_password(passwd) @@ -41,91 +40,83 @@ def user_edit(request, user_id): return HttpResponseRedirect(reverse("sspanel:user_list")) else: messages.error(request, "数据填写错误", extra_tags="错误") - context = { - 'ssform': ssform, - 'userform': userform, - 'ss_user': ss_user, - } - return render(request, 'backend/useredit.html', context=context) + context = {"ssform": ssform, "userform": userform, "ss_user": ss_user} + return render(request, "backend/useredit.html", context=context) # 当请求不是post时,渲染form else: # 特别初始化总流量字段 - data = {'transfer_enable': ss_user.transfer_enable // settings.GB} + data = {"transfer_enable": ss_user.transfer_enable // settings.GB} ssform = SuserForm(initial=data, instance=ss_user) userform = UserForm(instance=ss_user.user) - context = { - 'ssform': ssform, - 'userform': userform, - 'ss_user': ss_user, - } - return render(request, 'backend/useredit.html', context=context) + context = {"ssform": ssform, "userform": userform, "ss_user": ss_user} + return render(request, "backend/useredit.html", context=context) @login_required -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def change_ss_pass(request): - '''改变用户ss连接密码''' + """改变用户ss连接密码""" ss_user = request.user.ss_user - if request.method == 'POST': + if request.method == "POST": form = ChangeSsPassForm(request.POST) if form.is_valid(): # 获取用户提交的password - ss_pass = request.POST.get('password') + ss_pass = request.POST.get("password") ss_user.password = ss_pass ss_user.save() messages.success(request, "请及时更换客户端密码!", extra_tags="修改成功!") - return HttpResponseRedirect(reverse('sspanel:userinfo_edit')) + return HttpResponseRedirect(reverse("sspanel:userinfo_edit")) else: messages.error(request, "新的客户端密码格式不正确!", extra_tags="修改失败!") - return HttpResponseRedirect(reverse('sspanel:userinfo_edit')) + return HttpResponseRedirect(reverse("sspanel:userinfo_edit")) else: form = ChangeSsPassForm() - return render(request, 'sspanel/sspasschanged.html', {'form': form}) + return render(request, "sspanel/sspasschanged.html", {"form": form}) @login_required -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def change_ss_method(request): - '''改变用户ss加密''' + """改变用户ss加密""" ss_user = request.user.ss_user - ss_method = request.POST.get('method') + ss_method = request.POST.get("method") ss_user.method = ss_method ss_user.save() messages.success(request, "请及时更换客户端配置!", extra_tags="修改成功!") - return HttpResponseRedirect(reverse('sspanel:userinfo_edit')) + return HttpResponseRedirect(reverse("sspanel:userinfo_edit")) @login_required -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def change_ss_protocol(request): - '''改变用户ss协议''' + """改变用户ss协议""" ss_user = request.user.ss_user - ss_protocol = request.POST.get('protocol') + ss_protocol = request.POST.get("protocol") ss_user.protocol = ss_protocol ss_user.save() messages.success(request, "请及时更换客户端配置!", extra_tags="修改成功!") - return HttpResponseRedirect(reverse('sspanel:userinfo_edit')) + return HttpResponseRedirect(reverse("sspanel:userinfo_edit")) @login_required -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def change_ss_obfs(request): - '''改变用户ss连接混淆''' + """改变用户ss连接混淆""" ss_user = request.user.ss_user - ss_obfs = request.POST.get('obfs') + ss_obfs = request.POST.get("obfs") ss_user.obfs = ss_obfs ss_user.save() messages.success(request, "请及时更换客户端配置!", extra_tags="修改成功!") - return HttpResponseRedirect(reverse('sspanel:userinfo_edit')) + return HttpResponseRedirect(reverse("sspanel:userinfo_edit")) def subscribe(request): - ''' + """ 返回ssr订阅链接 - ''' - token = request.GET.get('token', '') + """ + token = request.GET.get("token", "") username = base64.b64decode(token).decode() # 验证token user = get_object_or_404(User, username=username) @@ -133,71 +124,78 @@ def subscribe(request): # 遍历该用户所有的节点 node_list = Node.objects.filter(level__lte=user.level, show=1) # 生成订阅链接部分 - sub_code = 'MAX={}\n'.format(len(node_list)) + sub_code = "MAX={}\n".format(len(node_list)) for node in node_list: sub_code = sub_code + node.get_node_link(ss_user) + "\n" - sub_code = base64.b64encode(bytes(sub_code, 'utf8')).decode('ascii') + sub_code = base64.b64encode(bytes(sub_code, "utf8")).decode("ascii") resp_ok = StreamingHttpResponse(sub_code) - resp_ok['Content-Type'] = 'application/octet-stream; charset=utf-8' - resp_ok['Content-Disposition'] = 'attachment; filename={}.txt'.format( - token) - resp_ok['Cache-Control'] = 'no-store, no-cache, must-revalidate' - resp_ok['Content-Length'] = len(sub_code) + resp_ok["Content-Type"] = "application/octet-stream; charset=utf-8" + resp_ok["Content-Disposition"] = "attachment; filename={}.txt".format(token) + resp_ok["Cache-Control"] = "no-store, no-cache, must-revalidate" + resp_ok["Content-Length"] = len(sub_code) return resp_ok @login_required def node_config(request): - '''返回节点json配置''' + """返回节点json配置""" user = request.user ss_user = user.ss_user node_list = Node.objects.filter(level__lte=user.level, show=1) - data = {'configs': []} + data = {"configs": []} for node in node_list: if node.node_type == 1: # 单端口模式 - data['configs'].append({ - "remarks": node.name, - "server_port": node.port, - "remarks_base64": base64.b64encode( - bytes(node.name, 'utf8')).decode('ascii'), - "enable": True, - "password": node.password, - "method": node.method, - "server": node.server, - "obfs": node.obfs, - 'obfs_param': node.obfs_param, - "protocol": node.protocol, - 'protocol_param': '{}:{}'.format(ss_user.port, - ss_user.password), - }) + data["configs"].append( + { + "remarks": node.name, + "server_port": node.port, + "remarks_base64": base64.b64encode(bytes(node.name, "utf8")).decode( + "ascii" + ), + "enable": True, + "password": node.password, + "method": node.method, + "server": node.server, + "obfs": node.obfs, + "obfs_param": node.obfs_param, + "protocol": node.protocol, + "protocol_param": "{}:{}".format(ss_user.port, ss_user.password), + } + ) elif node.custom_method == 1: - data['configs'].append({ - "remarks": node.name, - "server_port": ss_user.port, - "remarks_base64": base64.b64encode( - bytes(node.name, 'utf8')).decode('ascii'), - "enable": True, - "password": ss_user.password, - "method": ss_user.method, - "server": node.server, - "obfs": ss_user.obfs, - "protocol": ss_user.protocol, - }) + data["configs"].append( + { + "remarks": node.name, + "server_port": ss_user.port, + "remarks_base64": base64.b64encode(bytes(node.name, "utf8")).decode( + "ascii" + ), + "enable": True, + "password": ss_user.password, + "method": ss_user.method, + "server": node.server, + "obfs": ss_user.obfs, + "protocol": ss_user.protocol, + } + ) else: - data['configs'].append({ - "remarks": node.name, - "server_port": ss_user.port, - "remarks_base64": base64.b64encode( - bytes(node.name, 'utf8')).decode('ascii'), - "enable": True, - "password": ss_user.password, - "method": node.method, - "server": node.server, - "obfs": node.obfs, - "protocol": node.protocol, - }) + data["configs"].append( + { + "remarks": node.name, + "server_port": ss_user.port, + "remarks_base64": base64.b64encode(bytes(node.name, "utf8")).decode( + "ascii" + ), + "enable": True, + "password": ss_user.password, + "method": node.method, + "server": node.server, + "obfs": node.obfs, + "protocol": node.protocol, + } + ) response = StreamingHttpResponse(json.dumps(data, ensure_ascii=False)) - response['Content-Type'] = 'application/octet-stream' - response['Content-Disposition'] = 'attachment; filename="ss.json"' + response["Content-Type"] = "application/octet-stream" + response["Content-Disposition"] = 'attachment; filename="ss.json"' return response diff --git a/apps/urls.py b/apps/urls.py index 3f396a166c..9a2ab687ff 100644 --- a/apps/urls.py +++ b/apps/urls.py @@ -4,13 +4,12 @@ from apps.sspanel.views import index urlpatterns = [ - path('', index, name="index"), - path('admin/', admin.site.urls, name='admin'), - path('', include('django.contrib.auth.urls')), - path('jet/', include('jet.urls', 'jet')), - path('prom/', include('django_prometheus.urls')), - - path('api/', include('apps.api.urls', namespace='api')), - path('sspanel/', include('apps.sspanel.urls', namespace='sspanel')), - path('server/', include('apps.ssserver.urls', namespace='ssserver')), + path("", index, name="index"), + path("admin/", admin.site.urls, name="admin"), + path("", include("django.contrib.auth.urls")), + path("jet/", include("jet.urls", "jet")), + path("prom/", include("django_prometheus.urls")), + path("api/", include("apps.api.urls", namespace="api")), + path("sspanel/", include("apps.sspanel.urls", namespace="sspanel")), + path("server/", include("apps.ssserver.urls", namespace="ssserver")), ] diff --git a/apps/utils.py b/apps/utils.py index 343f826482..23524ef254 100644 --- a/apps/utils.py +++ b/apps/utils.py @@ -13,17 +13,21 @@ from apps.cachext import make_default_key -def get_random_string(length=12, - allowed_chars='abcdefghijklmnopqrstuvwxyz' - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): - ''' +def get_random_string( + length=12, + allowed_chars="abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", +): + """ 创建指定长度的完全不会重复字符串的 - ''' + """ random.seed( hashlib.sha256( - ("%s%s%s" % (random.getstate(), time.time(), - 'SCRWEWYOURBITCHES')).encode('utf-8')).digest()) - return ''.join(random.choice(allowed_chars) for i in range(length)) + ("%s%s%s" % (random.getstate(), time.time(), "SCRWEWYOURBITCHES")).encode( + "utf-8" + ) + ).digest() + ) + return "".join(random.choice(allowed_chars) for i in range(length)) def get_long_random_string(): @@ -48,17 +52,17 @@ def traffic_format(traffic): def reverse_traffic(str): - ''' + """ 将流量字符串转换为整数类型 - ''' - if 'GB' in str: - num = float(str.replace('GB', '')) * 1024 * 1024 * 1024 - elif 'MB' in str: - num = float(str.replace('MB', '')) * 1024 * 1024 - elif 'KB' in str: - num = float(str.replace('KB', '')) * 1024 + """ + if "GB" in str: + num = float(str.replace("GB", "")) * 1024 * 1024 * 1024 + elif "MB" in str: + num = float(str.replace("MB", "")) * 1024 * 1024 + elif "KB" in str: + num = float(str.replace("KB", "")) * 1024 else: - num = num = float(str.replace('B', '')) + num = num = float(str.replace("B", "")) return round(num) @@ -75,6 +79,7 @@ def cached_view(*agrs, **kwagrs): resp = func(*agrs, **kwagrs) cache.set(cache_key, resp, cache_ttl) return resp + return cached_view return decorator @@ -83,25 +88,26 @@ def cached_view(*agrs, **kwagrs): def authorized(view_func): @wraps(view_func) def wrapper(request, *args, **kwargs): - if request.method == 'GET': - token = request.GET.get('token', '') + if request.method == "GET": + token = request.GET.get("token", "") else: data = json.loads(request.body) - token = data.get('token', '') + token = data.get("token", "") request.json = data if token == settings.TOKEN: return view_func(request, *args, **kwargs) else: - return JsonResponse({'ret': -1, - 'msg': 'auth error'}) + return JsonResponse({"ret": -1, "msg": "auth error"}) + return wrapper def get_node_user(node_id): - ''' + """ 返回所有当前节点可以使用的用户信息 - ''' + """ from apps.ssserver.models import Node, Suser + node = Node.objects.filter(node_id=node_id).first() if node: data = [] @@ -109,26 +115,28 @@ def get_node_user(node_id): user_list = Suser.get_vaild_user(level) for user in user_list: cfg = { - 'port': user.port, - 'u': user.upload_traffic, - 'd': user.download_traffic, - 'transfer_enable': user.transfer_enable, - 'passwd': user.password, - 'enable': user.enable, - 'user_id': user.user_id, - 'id': user.user_id, - 'method': user.method, - 'obfs': user.obfs, - 'obfs_param': user.obfs_param, - 'protocol': user.protocol, - 'protocol_param': user.protocol_param, - 'speed_limit_per_user': user.speed_limit + "port": user.port, + "u": user.upload_traffic, + "d": user.download_traffic, + "transfer_enable": user.transfer_enable, + "passwd": user.password, + "enable": user.enable, + "user_id": user.user_id, + "id": user.user_id, + "method": user.method, + "obfs": user.obfs, + "obfs_param": user.obfs_param, + "protocol": user.protocol, + "protocol_param": user.protocol_param, + "speed_limit_per_user": user.speed_limit, } if node.speed_limit > 0: if user.speed_limit > 0: - cfg['speed_limit_per_user'] = min(user.speed_limit, node.speed_limit) + cfg["speed_limit_per_user"] = min( + user.speed_limit, node.speed_limit + ) else: - cfg['speed_limit_per_user'] = node.speed_limit + cfg["speed_limit_per_user"] = node.speed_limit data.append(cfg) return data @@ -139,8 +147,6 @@ def get_current_time(): def global_settings(request): - global_variable = { - "USE_SMTP": settings.USE_SMTP - } + global_variable = {"USE_SMTP": settings.USE_SMTP} return global_variable diff --git a/apps/wsgi.py b/apps/wsgi.py index 4d21ee7a63..abd6e30220 100644 --- a/apps/wsgi.py +++ b/apps/wsgi.py @@ -11,7 +11,7 @@ from django.core.wsgi import get_wsgi_application -env = os.getenv('DJANGO_ENV', 'production') +env = os.getenv("DJANGO_ENV", "production") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "configs.{}".format(env)) application = get_wsgi_application() diff --git a/commands/__init__.py b/commands/__init__.py index f3d3aa1c0b..6cf8779c06 100644 --- a/commands/__init__.py +++ b/commands/__init__.py @@ -1,10 +1,10 @@ -''' +""" 这里定义了一些计划任务 可以手动执行,也可以放到crontab job里去用 ex: python print_user_count.py -''' +""" import os import sys @@ -14,10 +14,10 @@ def ready(): - '''ready for cmds''' + """ready for cmds""" path = dirname((abspath(dirname(__file__)))) sys.path.insert(0, path) - env = os.getenv('DJANGO_ENV', 'development') + env = os.getenv("DJANGO_ENV", "development") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "configs.{}".format(env)) django.setup() diff --git a/commands/add_invidecode_num.py b/commands/add_invidecode_num.py index 7df1ec611e..2f46846c69 100644 --- a/commands/add_invidecode_num.py +++ b/commands/add_invidecode_num.py @@ -1,12 +1,14 @@ def add_user_invitecode_num(): from apps.sspanel.models import User + for user in User.objects.all(): user.invitecode_num = 10 user.save() - print('job is down') + print("job is down") -if __name__ == '__main__': +if __name__ == "__main__": from importlib import import_module - import_module('__init__', 'commands') + + import_module("__init__", "commands") add_user_invitecode_num() diff --git a/commands/clear_zombie_user.py b/commands/clear_zombie_user.py index a4cf4d45c3..671533f492 100644 --- a/commands/clear_zombie_user.py +++ b/commands/clear_zombie_user.py @@ -1,12 +1,12 @@ - from django.core.exceptions import ObjectDoesNotExist def clear_zombie_user(): - ''' + """ 删除僵尸用户 - ''' + """ from apps.sspanel.models import User + users = User.objects.all() count = 0 for user in users: @@ -17,10 +17,11 @@ def clear_zombie_user(): except ObjectDoesNotExist: user.delete() count += 1 - print('clear user count: ', count) + print("clear user count: ", count) -if __name__ == '__main__': +if __name__ == "__main__": from importlib import import_module - import_module('__init__', 'commands') + + import_module("__init__", "commands") clear_zombie_user() diff --git a/commands/croncmds.py b/commands/croncmds.py index bb2ab0ec14..e89b291c54 100644 --- a/commands/croncmds.py +++ b/commands/croncmds.py @@ -8,13 +8,12 @@ def check_user_state(): - '''检测用户状态,将所有账号到期的用户状态重置''' + """检测用户状态,将所有账号到期的用户状态重置""" users = User.objects.filter(level__gt=0) expire_user = [] for user in users: # 判断用户过期 - if timezone.now() - timezone.timedelta(days=1) > \ - user.level_expire_time: + if timezone.now() - timezone.timedelta(days=1) > user.level_expire_time: user.level = 0 user.save() ss_user = user.ss_user @@ -24,19 +23,23 @@ def check_user_state(): ss_user.transfer_enable = settings.DEFAULT_TRAFFIC ss_user.save() expire_user.append(user.email) - print('time: {} user: {} level timeout ' - .format(timezone.now().strftime('%Y-%m-%d'), - user.username)) + print( + "time: {} user: {} level timeout ".format( + timezone.now().strftime("%Y-%m-%d"), user.username + ) + ) if expire_user and settings.EXPIRE_EMAIL_NOTICE: - send_mail('您的{0}账号已到期'.format(settings.TITLE), - "您的{0}账号已到期,现被暂停使用。如需继续使用请前往 {1} 充值".format(settings.TITLE, settings.HOST), - settings.DEFAULT_FROM_EMAIL, - expire_user) - print('Time: {} CHECKED'.format(timezone.now())) + send_mail( + "您的{0}账号已到期".format(settings.TITLE), + "您的{0}账号已到期,现被暂停使用。如需继续使用请前往 {1} 充值".format(settings.TITLE, settings.HOST), + settings.DEFAULT_FROM_EMAIL, + expire_user, + ) + print("Time: {} CHECKED".format(timezone.now())) def auto_reset_traffic(): - '''重置所有免费用户流量''' + """重置所有免费用户流量""" users = User.objects.filter(level=0) for user in users: @@ -45,46 +48,46 @@ def auto_reset_traffic(): ss_user.upload_traffic = 0 ss_user.transfer_enable = settings.DEFAULT_TRAFFIC ss_user.save() - print('Time {} all free user traffic reset! '.format(timezone.now())) + print("Time {} all free user traffic reset! ".format(timezone.now())) def clean_traffic_log(): - '''清空七天前的所有流量记录''' + """清空七天前的所有流量记录""" dt = pendulum.now().subtract(days=7).date() query = TrafficLog.objects.filter(log_date__lt=dt) count, res = query.delete() - print('Time: {} traffic record removed!:{}'.format(timezone.now(), count)) + print("Time: {} traffic record removed!:{}".format(timezone.now(), count)) def clean_online_log(): - '''清空所有在线记录''' + """清空所有在线记录""" count = TrafficLog.objects.count() NodeOnlineLog.truncate() - print('Time {} online record removed!:{}'.format(timezone.now(), count)) + print("Time {} online record removed!:{}".format(timezone.now(), count)) def clean_online_ip_log(): - '''清空在线ip记录''' + """清空在线ip记录""" count = AliveIp.objects.count() AliveIp.truncate() - print('Time: {} online ip log removed!:{}'.format(timezone.now(), count)) + print("Time: {} online ip log removed!:{}".format(timezone.now(), count)) def reset_node_traffic(): - '''月初重置节点使用流量''' + """月初重置节点使用流量""" for node in Node.objects.all(): node.used_traffic = 0 node.save() - print('Time: {} all node traffic removed!'.format(timezone.now())) + print("Time: {} all node traffic removed!".format(timezone.now())) def check_pay_request(): - '''定时检查支付请求''' + """定时检查支付请求""" # 每次检查新的五条记录 - querys = PayRequest.objects.order_by('-time')[:5] + querys = PayRequest.objects.order_by("-time")[:5] for req in querys: user = User.objects.filter(username=req.username).first() paid = PayRequest.pay_query(user, req.info_code) if paid is True: - print('用户:{} 掉单,已经补偿'.format(user.username)) - print('{} 检查过支付请求'.format(timezone.now().strftime("%Y-%m-%d %H:%M"))) + print("用户:{} 掉单,已经补偿".format(user.username)) + print("{} 检查过支付请求".format(timezone.now().strftime("%Y-%m-%d %H:%M"))) diff --git a/commands/export_node_host.py b/commands/export_node_host.py index 19bc9057c4..c820e471c5 100644 --- a/commands/export_node_host.py +++ b/commands/export_node_host.py @@ -1,14 +1,16 @@ def export_node_host(): from apps.ssserver.models import Node + hosts = [] for node in Node.objects.all(): hosts.append("'{}'".format(node.server)) - with open('node_host.txt', 'w') as f: - f.writelines('\n'.join(hosts)) - print('export node host down ! node num: ', len(hosts)) + with open("node_host.txt", "w") as f: + f.writelines("\n".join(hosts)) + print("export node host down ! node num: ", len(hosts)) -if __name__ == '__main__': +if __name__ == "__main__": from importlib import import_module - import_module('__init__', 'commands') + + import_module("__init__", "commands") export_node_host() diff --git a/commands/print_user_count.py b/commands/print_user_count.py index f75fc71430..7b73f8e035 100644 --- a/commands/print_user_count.py +++ b/commands/print_user_count.py @@ -1,9 +1,11 @@ def print_user_count(): from apps.sspanel.models import User - print('total user count is : {}'.format(User.objects.all().count())) + print("total user count is : {}".format(User.objects.all().count())) -if __name__ == '__main__': + +if __name__ == "__main__": from importlib import import_module - import_module('__init__', 'commands') + + import_module("__init__", "commands") print_user_count() diff --git a/commands/redeem.py b/commands/redeem.py index 1d66ea16f0..e67f11f740 100644 --- a/commands/redeem.py +++ b/commands/redeem.py @@ -2,22 +2,24 @@ def pay_redeem(): - '''给大于3级的用户每人发10元''' + """给大于3级的用户每人发10元""" from apps.sspanel.models import User - user_check = input('即将给所有大于3级的用户发送10元红包,键入 y键 确认操作\n') - if user_check != 'y': - print('取消操作') + + user_check = input("即将给所有大于3级的用户发送10元红包,键入 y键 确认操作\n") + if user_check != "y": + print("取消操作") sys.exit() users = User.objects.filter(level__gte=3) - print('pay user count is: {}'.format(users.count())) + print("pay user count is: {}".format(users.count())) for user in users: user.balance += 10 user.save() - print(user.username, 'balance + 10 down') + print(user.username, "balance + 10 down") -if __name__ == '__main__': +if __name__ == "__main__": from importlib import import_module - import_module('__init__', 'commands') + + import_module("__init__", "commands") pay_redeem() diff --git a/configs/default/__init__.py b/configs/default/__init__.py index 70a00656c2..452f1a7268 100644 --- a/configs/default/__init__.py +++ b/configs/default/__init__.py @@ -3,4 +3,4 @@ from .db import * from .email import * from .sites import * -from .sentry import * \ No newline at end of file +from .sentry import * diff --git a/configs/default/common.py b/configs/default/common.py index bcd3883493..e9ea4b0f57 100644 --- a/configs/default/common.py +++ b/configs/default/common.py @@ -1,79 +1,71 @@ import os -BASE_DIR = os.path.dirname(os.path.dirname( - os.path.dirname(os.path.abspath(__file__)))) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) LOGIN_URL = "/sspanel/login" INSTALLED_APPS = [ - 'jet', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django_prometheus', - 'raven.contrib.django.raven_compat', # sentry support - 'django_crontab', # 定时任务相关 - 'apps.sspanel', # 前端网站 - 'apps.ssserver', + "jet", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_prometheus", + "raven.contrib.django.raven_compat", # sentry support + "django_crontab", # 定时任务相关 + "apps.sspanel", # 前端网站 + "apps.ssserver", ] MIDDLEWARE = [ - 'django_prometheus.middleware.PrometheusBeforeMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django_prometheus.middleware.PrometheusAfterMiddleware', + "django_prometheus.middleware.PrometheusBeforeMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django_prometheus.middleware.PrometheusAfterMiddleware", ] -ROOT_URLCONF = 'apps.urls' +ROOT_URLCONF = "apps.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", "apps.utils.global_settings", - ], + ] }, - }, + } ] -WSGI_APPLICATION = 'apps.wsgi.application' -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +WSGI_APPLICATION = "apps.wsgi.application" +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" }, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] -LANGUAGE_CODE = 'Zh-hans' +LANGUAGE_CODE = "Zh-hans" -TIME_ZONE = 'Asia/Shanghai' +TIME_ZONE = "Asia/Shanghai" USE_I18N = True @@ -85,50 +77,30 @@ SESSION_COOKIE_AGE = 60 * 60 * 24 # 一天 SESSION_SAVE_EVERY_REQUEST = True -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'static/') +STATIC_URL = "/static/" +STATIC_ROOT = os.path.join(BASE_DIR, "static/") -MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') -MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, "media/") +MEDIA_URL = "/media/" # 自定义用户验证 AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', - 'apps.sspanel.backends.EmailBackend', + "django.contrib.auth.backends.ModelBackend", + "apps.sspanel.backends.EmailBackend", ) # 用户模型设置: -AUTH_USER_MODEL = 'sspanel.User' +AUTH_USER_MODEL = "sspanel.User" JET_THEMES = [ { - 'theme': 'default', # theme folder name - 'color': '#47bac1', # color of the theme's button in user menu - 'title': 'Default' # theme title - }, - { - 'theme': 'green', - 'color': '#44b78b', - 'title': 'Green' - }, - { - 'theme': 'light-green', - 'color': '#2faa60', - 'title': 'Light Green' - }, - { - 'theme': 'light-violet', - 'color': '#a464c4', - 'title': 'Light Violet' - }, - { - 'theme': 'light-blue', - 'color': '#5EADDE', - 'title': 'Light Blue' - }, - { - 'theme': 'light-gray', - 'color': '#222', - 'title': 'Light Gray' + "theme": "default", # theme folder name + "color": "#47bac1", # color of the theme's button in user menu + "title": "Default", # theme title }, + {"theme": "green", "color": "#44b78b", "title": "Green"}, + {"theme": "light-green", "color": "#2faa60", "title": "Light Green"}, + {"theme": "light-violet", "color": "#a464c4", "title": "Light Violet"}, + {"theme": "light-blue", "color": "#5EADDE", "title": "Light Blue"}, + {"theme": "light-gray", "color": "#222", "title": "Light Gray"}, ] diff --git a/configs/default/cron.py b/configs/default/cron.py index 89560bb111..171c6c3f64 100644 --- a/configs/default/cron.py +++ b/configs/default/cron.py @@ -2,18 +2,39 @@ # 定时任务相关 CRONJOBS = [ - ('* 1 * * *', 'commands.croncmds.check_user_state', - '>>' + BASE_DIR + '/logs/userstate.log'), - ('0 0 1 * *', 'commands.croncmds.auto_reset_traffic', - '>>' + BASE_DIR + '/logs/trafficrest.log'), # 每月月初重置免费用户流量,日志写入logs - ('0 2 * * *', 'commands.croncmds.clean_traffic_log', - '>>' + BASE_DIR + '/logs/trafficrest.log'), # 每天清空流量记录,日志写入logs - ('30 2 * * *', 'commands.croncmds.clean_online_log', - '>>' + BASE_DIR + '/logs/node_online.log'), # 每天凌晨2:30删除节点在线记录,日志写入logs - ('0 4 1 * *', 'commands.croncmds.reset_node_traffic', - '>>' + BASE_DIR + '/logs/node_reset.log'), # 每月第一天凌晨4点重置节点流量,日志写入logs - ('30 1 * * *', 'commands.croncmds.clean_online_ip_log', - '>>' + BASE_DIR + '/logs/onlineip_reset.log'), # 每天凌晨1点半清空ip记录 - ('*/30 * * * *', 'commands.croncmds.check_pay_request', # 每隔三十分钟检查一下有没有漏单 - '>>' + BASE_DIR + '/logs/payrequest.log'), + ( + "* 1 * * *", + "commands.croncmds.check_user_state", + ">>" + BASE_DIR + "/logs/userstate.log", + ), + ( + "0 0 1 * *", + "commands.croncmds.auto_reset_traffic", + ">>" + BASE_DIR + "/logs/trafficrest.log", + ), # 每月月初重置免费用户流量,日志写入logs + ( + "0 2 * * *", + "commands.croncmds.clean_traffic_log", + ">>" + BASE_DIR + "/logs/trafficrest.log", + ), # 每天清空流量记录,日志写入logs + ( + "30 2 * * *", + "commands.croncmds.clean_online_log", + ">>" + BASE_DIR + "/logs/node_online.log", + ), # 每天凌晨2:30删除节点在线记录,日志写入logs + ( + "0 4 1 * *", + "commands.croncmds.reset_node_traffic", + ">>" + BASE_DIR + "/logs/node_reset.log", + ), # 每月第一天凌晨4点重置节点流量,日志写入logs + ( + "30 1 * * *", + "commands.croncmds.clean_online_ip_log", + ">>" + BASE_DIR + "/logs/onlineip_reset.log", + ), # 每天凌晨1点半清空ip记录 + ( + "*/30 * * * *", + "commands.croncmds.check_pay_request", # 每隔三十分钟检查一下有没有漏单 + ">>" + BASE_DIR + "/logs/payrequest.log", + ), ] diff --git a/configs/default/db.py b/configs/default/db.py index 4a51ce27a3..d460d283bd 100644 --- a/configs/default/db.py +++ b/configs/default/db.py @@ -1,17 +1,17 @@ # mysql 设置 DATABASES = { - 'default': { - 'ENGINE': 'django_prometheus.db.backends.mysql', - 'NAME': 'sspanel', - 'USER': 'root', - 'PASSWORD': '', - 'HOST': '127.0.0.1', # 正常版本使用 + "default": { + "ENGINE": "django_prometheus.db.backends.mysql", + "NAME": "sspanel", + "USER": "root", + "PASSWORD": "", + "HOST": "127.0.0.1", # 正常版本使用 # 'HOST': 'db', # docker版本使用 - 'PORT': '3306', - 'OPTIONS': { - 'autocommit': True, - 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", - 'charset': 'utf8mb4', + "PORT": "3306", + "OPTIONS": { + "autocommit": True, + "init_command": "SET sql_mode='STRICT_TRANS_TABLES'", + "charset": "utf8mb4", }, } } diff --git a/configs/default/email.py b/configs/default/email.py index 99c6e54766..ca1b0bbe25 100644 --- a/configs/default/email.py +++ b/configs/default/email.py @@ -6,8 +6,8 @@ EMAIL_USE_SSL = False # 我使用163邮箱作为smtp服务器 -EMAIL_HOST = 'smtp.163.com' +EMAIL_HOST = "smtp.163.com" EMAIL_PORT = 25 -EMAIL_HOST_USER = 'USER' -EMAIL_HOST_PASSWORD = 'PASS' -DEFAULT_FROM_EMAIL = 'Ehco
' \ No newline at end of file +EMAIL_HOST_USER = "USER" +EMAIL_HOST_PASSWORD = "PASS" +DEFAULT_FROM_EMAIL = "Ehco
" diff --git a/configs/default/sentry.py b/configs/default/sentry.py index 327f4a53ca..d4f01f3b53 100644 --- a/configs/default/sentry.py +++ b/configs/default/sentry.py @@ -1,5 +1,3 @@ import os -RAVEN_CONFIG = { - 'dsn': os.environ.get('SENTRY_DSN'), -} +RAVEN_CONFIG = {"dsn": os.environ.get("SENTRY_DSN")} diff --git a/configs/default/sites.py b/configs/default/sites.py index 4d0fba1142..51aad5a693 100644 --- a/configs/default/sites.py +++ b/configs/default/sites.py @@ -1,15 +1,15 @@ # 网站密钥 -SECRET_KEY = 'aasdasdas' +SECRET_KEY = "aasdasdas" # 是否开启注册 ALLOW_REGISET = True # 默认的theme # 可选列表在 apps/constants.py 里的THEME_CHOICES里 -DEFAULT_THEME = 'default' +DEFAULT_THEME = "default" # 域名设置 -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] # SS面板设置: MB = 1024 * 1024 @@ -18,9 +18,9 @@ START_PORT = 1024 # 默认加密混淆协议 -DEFAULT_METHOD = 'aes-128-ctr' -DEFAULT_PROTOCOL = 'auth_chain_a' -DEFAULT_OBFS = 'http_simple' +DEFAULT_METHOD = "aes-128-ctr" +DEFAULT_PROTOCOL = "auth_chain_a" +DEFAULT_OBFS = "http_simple" # 签到流量设置 MIN_CHECKIN_TRAFFIC = 10 * MB @@ -29,11 +29,11 @@ # 是否启用支付宝系统 USE_ALIPAY = False # 支付订单提示信息 修改请保留 {} 用于动态生成金额 -ALIPAY_TRADE_INFO = '谜之屋的{}元充值码' +ALIPAY_TRADE_INFO = "谜之屋的{}元充值码" # 网站title -TITLE = '谜之屋' -SUBTITLE = '秘密的小屋test' +TITLE = "谜之屋" +SUBTITLE = "秘密的小屋test" # 用户邀请返利比例 INVITE_PERCENT = 0.2 @@ -41,13 +41,13 @@ INVITE_NUM = 5 # 网站邀请界面提示语 -INVITEINFO = '邀请码实时更新,如果用完了请关注公众号findyourownway获取' +INVITEINFO = "邀请码实时更新,如果用完了请关注公众号findyourownway获取" # 网站域名设置(请正确填写,不然订阅功能会失效: -HOST = 'http://127.0.0.1:8000/' +HOST = "http://127.0.0.1:8000/" # 部分API接口TOKEN -TOKEN = 'youowntoken' +TOKEN = "youowntoken" # 是否开启用户到期邮件通知 EXPIRE_EMAIL_NOTICE = False diff --git a/configs/development.py b/configs/development.py index 063216ad73..998a00eb6f 100644 --- a/configs/development.py +++ b/configs/development.py @@ -5,12 +5,11 @@ # DEBUG设置 DEBUG = True -MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD') +MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD") if MYSQL_PASSWORD: - DATABASES['default'].update( - {'HOST': os.getenv('MYSQL_HOST', '127.0.0.1'), - 'PASSWORD': MYSQL_PASSWORD - }) + DATABASES["default"].update( + {"HOST": os.getenv("MYSQL_HOST", "127.0.0.1"), "PASSWORD": MYSQL_PASSWORD} + ) -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" diff --git a/configs/production.py b/configs/production.py index 60536d2529..433ac8750a 100644 --- a/configs/production.py +++ b/configs/production.py @@ -4,11 +4,13 @@ DEBUG = False -DATABASES['default'].update( - {'PASSWORD': os.getenv('MYSQL_PASSWORD', 'yourpass'), - 'HOST': os.getenv('MYSQL_HOST', '127.0.0.1'), - 'USER': os.getenv('MYSQL_USER', 'root') - }) +DATABASES["default"].update( + { + "PASSWORD": os.getenv("MYSQL_PASSWORD", "yourpass"), + "HOST": os.getenv("MYSQL_HOST", "127.0.0.1"), + "USER": os.getenv("MYSQL_USER", "root"), + } +) -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" From cb563840b28e7b3981df8885155fcf97a595eac6 Mon Sep 17 00:00:00 2001 From: okeyja_win32 Date: Tue, 12 Feb 2019 17:09:37 +0800 Subject: [PATCH 11/23] =?UTF-8?q?bugfix:=20=E4=BF=AE=E5=A4=8D=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E9=93=BE=E6=8E=A5=E5=92=8CSSR=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E5=9C=A8=E7=A0=B4=E5=A8=83Windows=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E4=BD=BF=E7=94=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ssserver/models.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/ssserver/models.py b/apps/ssserver/models.py index dc9c3ed84e..37c61e0d07 100644 --- a/apps/ssserver/models.py +++ b/apps/ssserver/models.py @@ -273,23 +273,19 @@ def __str__(self): def get_ssr_link(self, ss_user): """返回ssr链接""" - ssr_password = base64.urlsafe_b64encode(bytes(ss_user.password, "utf8")).decode( - "utf8" - ) - ssr_remarks = base64.urlsafe_b64encode(bytes(self.name, "utf8")).decode("utf8") - ssr_group = base64.urlsafe_b64encode(bytes(self.group, "utf8")).decode("utf8") + ssr_password = base64.urlsafe_b64encode(bytes(ss_user.password, "utf8")).decode("utf8").replace('=', '') + ssr_remarks = base64.urlsafe_b64encode(bytes(self.name, "utf8")).decode("utf8").replace('=', '') + ssr_group = base64.urlsafe_b64encode(bytes(self.group, "utf8")).decode("utf8").replace('=', '') if self.node_type == 1: # 单端口多用户 ssr_password = base64.urlsafe_b64encode( bytes(self.password, "utf8") - ).decode("utf8") + ).decode("utf8").replace('=', '') info = "{}:{}".format(ss_user.port, ss_user.password) - protocol_param = base64.urlsafe_b64encode(bytes(info, "utf8")).decode( - "utf8" - ) + protocol_param = base64.urlsafe_b64encode(bytes(info, "utf8")).decode("utf8").replace('=', '') obfs_param = base64.urlsafe_b64encode( bytes(str(self.obfs_param), "utf8") - ).decode("utf8") + ).decode("utf8").replace('=', '') ssr_code = "{}:{}:{}:{}:{}:{}/?obfsparam={}&protoparam={}&remarks={}&group={}".format( self.server, self.port, @@ -324,7 +320,7 @@ def get_ssr_link(self, ss_user): ssr_remarks, ssr_group, ) - ssr_pass = base64.urlsafe_b64encode(bytes(ssr_code, "utf8")).decode("utf8") + ssr_pass = base64.urlsafe_b64encode(bytes(ssr_code, "utf8")).decode("utf8").replace('=', '') ssr_link = "ssr://{}".format(ssr_pass) return ssr_link From b3c776a571cba5cecc6eed14b5bd14023e387939 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Wed, 20 Feb 2019 09:07:00 +0800 Subject: [PATCH 12/23] remvoe pipenv file --- Pipfile | 30 --- Pipfile.lock | 455 ---------------------------------------- apps/ssserver/models.py | 46 +++- 3 files changed, 35 insertions(+), 496 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 8d9329615f..0000000000 --- a/Pipfile +++ /dev/null @@ -1,30 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -django = "==2.0.8" -django-prometheus = "*" -django-countries = "*" -django-crontab = "*" -django-jet = "*" -pillow = "*" -python-alipay-sdk = "*" -qrcode = "*" -requests = "*" -tomd = "*" -uwsgi = "*" -mysqlclient = "*" -pycrypto = "*" -gevent = "*" -raven = "*" -pendulum = "*" -ipython = "*" - -[dev-packages] -"autopep8" = "*" -"flake8" = "*" - -[requires] -python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 5c7026117d..0000000000 --- a/Pipfile.lock +++ /dev/null @@ -1,455 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "bdf3433d1712e78f0fbd283fb7981ac5366c37a038f54f5a48bf47c096865070" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" - }, - "backcall": { - "hashes": [ - "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", - "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" - ], - "version": "==0.1.0" - }, - "certifi": { - "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" - ], - "version": "==2018.10.15" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "decorator": { - "hashes": [ - "sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", - "sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c" - ], - "version": "==4.3.0" - }, - "django": { - "hashes": [ - "sha256:0c5b65847d00845ee404bbc0b4a85686f15eb3001ffddda3db4e9baa265bf136", - "sha256:68aeea369a8130259354b6ba1fa9babe0c5ee6bced505dea4afcd00f765ae38b" - ], - "index": "pypi", - "version": "==2.0.8" - }, - "django-countries": { - "hashes": [ - "sha256:2f684e2c2b8afdfd80137c8bcbb2d75f62a4a7863101cc62b4d84d3c9f27fdab", - "sha256:74ebe919aeccea818dafea17b0b243fb1280c55226cb315b9622f0c0416536d2" - ], - "index": "pypi", - "version": "==5.3.2" - }, - "django-crontab": { - "hashes": [ - "sha256:1201810a212460aaaa48eb6a766738740daf42c1a4f6aafecfb1525036929236" - ], - "index": "pypi", - "version": "==0.7.1" - }, - "django-jet": { - "hashes": [ - "sha256:f521dc0873023afa198d2b57687e0593ee62022bd50fa59506dfce69b101cd9e" - ], - "index": "pypi", - "version": "==1.0.8" - }, - "django-prometheus": { - "hashes": [ - "sha256:571d89a13e2547e1c3d19901f155bde8e101323fbcb0439363d670bc61c8c541", - "sha256:e8da2eb91cb20cfe0b8130305d34f3503766b7a08a244ce1031c36136beeb7a5" - ], - "index": "pypi", - "version": "==1.0.15" - }, - "gevent": { - "hashes": [ - "sha256:1f277c5cf060b30313c5f9b91588f4c645e11839e14a63c83fcf6f24b1bc9b95", - "sha256:298a04a334fb5e3dcd6f89d063866a09155da56041bc94756da59db412cb45b1", - "sha256:30e9b2878d5b57c68a40b3a08d496bcdaefc79893948989bb9b9fab087b3f3c0", - "sha256:33533bc5c6522883e4437e901059fe5afa3ea74287eeea27a130494ff130e731", - "sha256:3f06f4802824c577272960df003a304ce95b3e82eea01dad2637cc8609c80e2c", - "sha256:419fd562e4b94b91b58cccb3bd3f17e1a11f6162fca6c591a7822bc8a68f023d", - "sha256:4ea938f44b882e02cca9583069d38eb5f257cc15a03e918980c307e7739b1038", - "sha256:51143a479965e3e634252a0f4a1ea07e5307cf8dc773ef6bf9dfe6741785fb4c", - "sha256:5bf9bd1dd4951552d9207af3168f420575e3049016b9c10fe0c96760ce3555b7", - "sha256:6004512833707a1877cc1a5aea90fd182f569e089bc9ab22a81d480dad018f1b", - "sha256:640b3b52121ab519e0980cb38b572df0d2bc76941103a697e828c13d76ac8836", - "sha256:6951655cc18b8371d823e81c700883debb0f633aae76f425dfeb240f76b95a67", - "sha256:71eeb8d9146e8131b65c3364bb760b097c21b7b9fdbec91bf120685a510f997a", - "sha256:7c899e5a6f94d6310352716740f05e41eb8c52d995f27fc01e90380913aa8f22", - "sha256:8465f84ba31aaf52a080837e9c5ddd592ab0a21dfda3212239ce1e1796f4d503", - "sha256:99de2e38dde8669dd30a8a1261bdb39caee6bd00a5f928d01dfdb85ab0502562", - "sha256:9fa4284b44bc42bef6e437488d000ae37499ccee0d239013465638504c4565b7", - "sha256:a1beea0443d3404c03e069d4c4d9fc13d8ec001771c77f9a23f01911a41f0e49", - "sha256:a66a26b78d90d7c4e9bf9efb2b2bd0af49234604ac52eaca03ea79ac411e3f6d", - "sha256:a94e197bd9667834f7bb6bd8dff1736fab68619d0f8cd78a9c1cebe3c4944677", - "sha256:ac0331d3a3289a3d16627742be9c8969f293740a31efdedcd9087dadd6b2da57", - "sha256:d26b57c50bf52fb38dadf3df5bbecd2236f49d7ac98f3cf32d6b8a2d25afc27f", - "sha256:fd23b27387d76410eb6a01ea13efc7d8b4b95974ba212c336e8b1d6ab45a9578" - ], - "index": "pypi", - "version": "==1.3.7" - }, - "greenlet": { - "hashes": [ - "sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0", - "sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28", - "sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8", - "sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304", - "sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0", - "sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214", - "sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043", - "sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6", - "sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625", - "sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc", - "sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638", - "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163", - "sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4", - "sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490", - "sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248", - "sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939", - "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87", - "sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720", - "sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656" - ], - "markers": "platform_python_implementation == 'CPython'", - "version": "==0.4.15" - }, - "idna": { - "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" - ], - "version": "==2.7" - }, - "ipython": { - "hashes": [ - "sha256:a5781d6934a3341a1f9acb4ea5acdc7ea0a0855e689dbe755d070ca51e995435", - "sha256:b10a7ddd03657c761fc503495bc36471c8158e3fc948573fb9fe82a7029d8efd" - ], - "index": "pypi", - "version": "==7.1.1" - }, - "ipython-genutils": { - "hashes": [ - "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", - "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" - ], - "version": "==0.2.0" - }, - "jedi": { - "hashes": [ - "sha256:0191c447165f798e6a730285f2eee783fff81b0d3df261945ecb80983b5c3ca7", - "sha256:b7493f73a2febe0dc33d51c99b474547f7f6c0b2c8fb2b21f453eef204c12148" - ], - "version": "==0.13.1" - }, - "mysqlclient": { - "hashes": [ - "sha256:ff8ee1be84215e6c30a746b728c41eb0701a46ca76e343af445b35ce6250644f" - ], - "index": "pypi", - "version": "==1.3.13" - }, - "parso": { - "hashes": [ - "sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", - "sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24" - ], - "version": "==0.3.1" - }, - "pendulum": { - "hashes": [ - "sha256:0f43d963b27e92b04047ce8352e4c277db99f20d0b513df7d0ceafe674a2f727", - "sha256:14e60d26d7400980123dbb6e3f2a90b70d7c18c63742ffe5bd6d6a643f8c6ef1", - "sha256:5035a4e17504814a679f138374269cc7cc514aeac7ba6d9dc020abc224f25dbc", - "sha256:8c0b3d655c1e9205d4dacf42fffc929cde3b19b5fb544a7f7561e6896eb8a000", - "sha256:bfc7b33ae193a204ec0bec12ad0d2d3300cd7e51d91d992da525ba3b28f0d265", - "sha256:cd70b75800439794e1ad8dbfa24838845e171918df81fa98b68d0d5a6f9b8bf2", - "sha256:cf535d36c063575d4752af36df928882b2e0e31541b4482c97d63752785f9fcb" - ], - "index": "pypi", - "version": "==2.0.4" - }, - "pexpect": { - "hashes": [ - "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", - "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b" - ], - "markers": "sys_platform != 'win32'", - "version": "==4.6.0" - }, - "pickleshare": { - "hashes": [ - "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", - "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" - ], - "version": "==0.7.5" - }, - "pillow": { - "hashes": [ - "sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360", - "sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7", - "sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e", - "sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93", - "sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c", - "sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8", - "sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b", - "sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0", - "sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd", - "sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb", - "sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581", - "sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64", - "sha256:9577888ecc0ad7d06c3746afaba339c94d62b59da16f7a5d1cff9e491f23dace", - "sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956", - "sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60", - "sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16", - "sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37", - "sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae", - "sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a", - "sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073", - "sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8", - "sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb", - "sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409", - "sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f", - "sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2", - "sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4", - "sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74", - "sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e", - "sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1", - "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888" - ], - "index": "pypi", - "version": "==5.3.0" - }, - "prometheus-client": { - "hashes": [ - "sha256:046cb4fffe75e55ff0e6dfd18e2ea16e54d86cc330f369bebcc683475c8b68a9" - ], - "version": "==0.4.2" - }, - "prompt-toolkit": { - "hashes": [ - "sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34", - "sha256:d4c47f79b635a0e70b84fdb97ebd9a274203706b1ee5ed44c10da62755cf3ec9", - "sha256:fd17048d8335c1e6d5ee403c3569953ba3eb8555d710bfc548faf0712666ea39" - ], - "version": "==2.0.7" - }, - "ptyprocess": { - "hashes": [ - "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", - "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" - ], - "version": "==0.6.0" - }, - "pycrypto": { - "hashes": [ - "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c" - ], - "index": "pypi", - "version": "==2.6.1" - }, - "pycryptodomex": { - "hashes": [ - "sha256:01d0ad40a64997fdded11716ac6633fa5053f789fff85053ddad106827bbdb14", - "sha256:022977f3ba341e2befb4bdb226daf72ca0ab3a879c6e9d8f329ed6784a2b66c5", - "sha256:088a60c787bb399a7fa1db83869aa97c7c330c2e297105998f70d6aacd58ac67", - "sha256:1346d497a4919e0cfbe11592954200f00b42488d29a739d4e62e97c994c980cb", - "sha256:39dee40cb2ec0c3154b008bed9a3ebbbf12e3adb49e9ddbf0f69866eb2583673", - "sha256:472fe0d3fa00eb96cec42a0e44d2526d1e1fb081ea4d4360531c5b62a37a73e9", - "sha256:4ba053bea752ddc89b94d1f9f87008ef296a644b8dec0201c021d9636c069322", - "sha256:4efd33d01f4a88adf360d3b7af49a40f0b07d227b648311f6fd138e3bf8157c6", - "sha256:589e2a70deff7e7b0130bb231f1af3df97b98de1eb9a2ef88652a3b7d9498895", - "sha256:73a21f7d22a5eeed37a957052c35d436fc80ba30da7880bc51ad3ee4169ae8ab", - "sha256:75ef74d207d40122aeefaae3effa3820fc86224dcb1b09010fa086a5faa7ab01", - "sha256:816bcd25e8c7b76240e155cc7078aa42246adafbdb8cfbfc8838fa1a13eddb3d", - "sha256:83a42cc6b160128389657c6984055bc31e78bd64498ff601c6a33abe19e34263", - "sha256:8fd226179703a4acf32d25c82066dfcf912f0f0cbb1e888712b4f2042534e4b4", - "sha256:8fdcba28a54fb8167cf65b3bfe25184290941fb33a3034bf404df2e00b7bd1d4", - "sha256:91785e0515c804773d25490e2a85463b8cbea4e079339fa7c4d40e7c00f66dd5", - "sha256:9c9119efde08555160ab2346a8ae9a747aa9b6690d9afebf4e0730a4bdc570af", - "sha256:a7b8c644b2902133bced8d68f84ca60104e8251939749861bdb2157269901d8b", - "sha256:ad70efcb182223d4137227225493a8565914ad7064515ebeb8293d93e5dd0e74", - "sha256:adf9a84fae85952e80873f868dbcece741804d0a0973ffb91949229ca346e376", - "sha256:b505cf9784f5aead327db35a141c0b3866b03de15900d99daae851f871b23a63", - "sha256:c12ada4466b8283f933ef7358c73d80ba8f32c42c6b33e358d180cefd928bac0", - "sha256:e6bb783014f6aab9435fa94d5d4f3de5e116a39f9da2ef18dc30d709f9fe33e9", - "sha256:ed59ca5e75873452f3a3111dafda6399c520e800d10f94f24cf9f945e315dd7c", - "sha256:f6ecffbcbad9276f521572acc200db7325d12369e54182d31b7cef60023f8761", - "sha256:f6f268ada4ca4db76e584aa86da41ba4499d844b14dcf83c664df0ca20c7ab04", - "sha256:f7adfade841ddbf03824bcbdd31aa42160ef58ecdeac3227afd27b606d4b9cf0", - "sha256:fef1c0d440bf5adabf6560ff7d4279c11f87ab5459856274667f4960d721c033" - ], - "version": "==3.7.1" - }, - "pygments": { - "hashes": [ - "sha256:6301ecb0997a52d2d31385e62d0a4a4cf18d2f2da7054a5ddad5c366cd39cee7", - "sha256:82666aac15622bd7bb685a4ee7f6625dd716da3ef7473620c192c0168aae64fc" - ], - "version": "==2.3.0" - }, - "python-alipay-sdk": { - "hashes": [ - "sha256:40b7759644137adf70156ad6650b1523d5adc8b2d9ed990d8004a88004fd8f75" - ], - "index": "pypi", - "version": "==1.8.0" - }, - "python-dateutil": { - "hashes": [ - "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", - "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02" - ], - "version": "==2.7.5" - }, - "pytz": { - "hashes": [ - "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", - "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" - ], - "version": "==2018.7" - }, - "pytzdata": { - "hashes": [ - "sha256:10c74b0cfc51a9269031f86ecd11096c9c6a141f5bb15a3b8a88f9979f6361e2", - "sha256:279cbd9900d5da9a8f9053e60db0db7f42d9a799673744b76aaeb6b4f14abe77" - ], - "version": "==2018.7" - }, - "qrcode": { - "hashes": [ - "sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf", - "sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3" - ], - "index": "pypi", - "version": "==6.0" - }, - "raven": { - "hashes": [ - "sha256:3fd787d19ebb49919268f06f19310e8112d619ef364f7989246fc8753d469888", - "sha256:95f44f3ea2c1b176d5450df4becdb96c15bf2632888f9ab193e9dd22300ce46a" - ], - "index": "pypi", - "version": "==6.9.0" - }, - "requests": { - "hashes": [ - "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", - "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" - ], - "index": "pypi", - "version": "==2.20.1" - }, - "six": { - "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - ], - "version": "==1.11.0" - }, - "tomd": { - "hashes": [ - "sha256:23f21ae853157be49d163159bc7c6bc007dd5e87b69769ec5ba3e17f5de7a6d4" - ], - "index": "pypi", - "version": "==0.1.3" - }, - "traitlets": { - "hashes": [ - "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", - "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" - ], - "version": "==4.3.2" - }, - "urllib3": { - "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" - ], - "version": "==1.24.1" - }, - "uwsgi": { - "hashes": [ - "sha256:d2318235c74665a60021a4fc7770e9c2756f9fc07de7b8c22805efe85b5ab277" - ], - "index": "pypi", - "version": "==2.0.17.1" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - } - }, - "develop": { - "autopep8": { - "hashes": [ - "sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c" - ], - "index": "pypi", - "version": "==1.4.3" - }, - "flake8": { - "hashes": [ - "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", - "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" - ], - "index": "pypi", - "version": "==3.6.0" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "pycodestyle": { - "hashes": [ - "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", - "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" - ], - "version": "==2.4.0" - }, - "pyflakes": { - "hashes": [ - "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", - "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" - ], - "version": "==2.0.0" - } - } -} diff --git a/apps/ssserver/models.py b/apps/ssserver/models.py index 37c61e0d07..629340d474 100644 --- a/apps/ssserver/models.py +++ b/apps/ssserver/models.py @@ -273,19 +273,39 @@ def __str__(self): def get_ssr_link(self, ss_user): """返回ssr链接""" - ssr_password = base64.urlsafe_b64encode(bytes(ss_user.password, "utf8")).decode("utf8").replace('=', '') - ssr_remarks = base64.urlsafe_b64encode(bytes(self.name, "utf8")).decode("utf8").replace('=', '') - ssr_group = base64.urlsafe_b64encode(bytes(self.group, "utf8")).decode("utf8").replace('=', '') + ssr_password = ( + base64.urlsafe_b64encode(bytes(ss_user.password, "utf8")) + .decode("utf8") + .replace("=", "") + ) + ssr_remarks = ( + base64.urlsafe_b64encode(bytes(self.name, "utf8")) + .decode("utf8") + .replace("=", "") + ) + ssr_group = ( + base64.urlsafe_b64encode(bytes(self.group, "utf8")) + .decode("utf8") + .replace("=", "") + ) if self.node_type == 1: # 单端口多用户 - ssr_password = base64.urlsafe_b64encode( - bytes(self.password, "utf8") - ).decode("utf8").replace('=', '') + ssr_password = ( + base64.urlsafe_b64encode(bytes(self.password, "utf8")) + .decode("utf8") + .replace("=", "") + ) info = "{}:{}".format(ss_user.port, ss_user.password) - protocol_param = base64.urlsafe_b64encode(bytes(info, "utf8")).decode("utf8").replace('=', '') - obfs_param = base64.urlsafe_b64encode( - bytes(str(self.obfs_param), "utf8") - ).decode("utf8").replace('=', '') + protocol_param = ( + base64.urlsafe_b64encode(bytes(info, "utf8")) + .decode("utf8") + .replace("=", "") + ) + obfs_param = ( + base64.urlsafe_b64encode(bytes(str(self.obfs_param), "utf8")) + .decode("utf8") + .replace("=", "") + ) ssr_code = "{}:{}:{}:{}:{}:{}/?obfsparam={}&protoparam={}&remarks={}&group={}".format( self.server, self.port, @@ -320,7 +340,11 @@ def get_ssr_link(self, ss_user): ssr_remarks, ssr_group, ) - ssr_pass = base64.urlsafe_b64encode(bytes(ssr_code, "utf8")).decode("utf8").replace('=', '') + ssr_pass = ( + base64.urlsafe_b64encode(bytes(ssr_code, "utf8")) + .decode("utf8") + .replace("=", "") + ) ssr_link = "ssr://{}".format(ssr_pass) return ssr_link From 11062a311bb7e7f8e92c011d44d70eb53da27ed4 Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Wed, 20 Feb 2019 09:28:38 +0800 Subject: [PATCH 13/23] update readme --- README.md | 57 +++++++++---------------------------------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index b359df1362..9fa88af821 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,16 @@ Telegram group : [invite link](https://t.me/Ehcobreakwa11) Wiki: [Wiki](https://github.com/Ehco1996/django-sspanel/wiki) -**欢迎老板捐赠** - -![](http://opj9lh0x4.bkt.clouddn.com/17-12-20/62343859.jpg) - - ## 项目说明 -该项目是用django作为后端框架,开发的一个shadowsocks多人用户面板,具有以下特点: - -* 轻量级css框架 -* 最新版本的django作为后端 * 后端支援(shadowsocksr/shadowsocks) * 注册采用邀请系统,告别不良用户 -* 完善的商品购买逻辑 * 统一完善的后台管理界面 +* 完善的商品购买逻辑 * 支付宝当面付模块 * 邀请返利系统 -* 更多特性等待开发和探索.... +* ... ## 预览图 @@ -33,62 +24,32 @@ Wiki: [Wiki](https://github.com/Ehco1996/django-sspanel/wiki) * 注册界面: -![](http://opj9lh0x4.bkt.clouddn.com/18-1-20/21920210.jpg) +![](https://user-images.githubusercontent.com/24697284/53059324-5dfaf000-34f1-11e9-9454-a828237b263d.png) * 用户首页: -![](http://opj9lh0x4.bkt.clouddn.com/18-1-20/44962964.jpg) +![](https://user-images.githubusercontent.com/24697284/53059228-12e0dd00-34f1-11e9-8b15-e832c182d010.png) -* 支持多种主题切换: - -![](http://opj9lh0x4.bkt.clouddn.com/18-4-7/37704064.jpg) - * 节点信息: -![](http://opj9lh0x4.bkt.clouddn.com/18-10-15/57273903.jpg) +![](https://user-images.githubusercontent.com/24697284/53059231-13797380-34f1-11e9-8702-39c9063dd049.png) * 流量查询: -![](http://opj9lh0x4.bkt.clouddn.com/18-1-20/23097796.jpg) - - -* 充值捐增: - -![](http://opj9lh0x4.bkt.clouddn.com/18-1-20/84610707.jpg) - +![](https://user-images.githubusercontent.com/24697284/53059233-14120a00-34f1-11e9-94f3-b2ca2ab88882.png) **后台界面:** -* 后台首页: - -![](http://opj9lh0x4.bkt.clouddn.com/17-10-25/23766206.jpg) - -* 商品管理: - -![](http://opj9lh0x4.bkt.clouddn.com/17-9-17/76575609.jpg) - * 节点管理: -![](http://opj9lh0x4.bkt.clouddn.com/17-9-17/12003054.jpg) +![](https://user-images.githubusercontent.com/24697284/53059234-14120a00-34f1-11e9-8937-ea56cf4077ed.png) * Django-jet 后台: -![](http://opj9lh0x4.bkt.clouddn.com/18-4-7/67402906.jpg) - -## 项目组件 - -#### 前端相关: - -* BULMA(布玛) - -* SweetAlert(通知功能) - -#### 后端框架: - -* Django +![](https://user-images.githubusercontent.com/24697284/53059235-14120a00-34f1-11e9-81ea-69bbb2e445d2.png) ## 部署教程: @@ -97,4 +58,4 @@ Wiki: [Wiki](https://github.com/Ehco1996/django-sspanel/wiki) 萌新版: [部署教程](https://github.com/Ehco1996/django-sspanel/wiki/%E9%9D%A2%E6%9D%BF%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B-%E8%90%8C%E6%96%B0%E7%89%88) -Docker版: [部署教程](https://github.com/Ehco1996/django-sspanel/wiki/Docker-%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85) +Docker版: [部署教程](https://github.com/Ehco1996/django-sspanel/wiki/Docker-%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85) \ No newline at end of file From 0732bcde696b53dc0993806b6b07ef4f7fc681ae Mon Sep 17 00:00:00 2001 From: ehco1996 Date: Fri, 22 Feb 2019 22:10:30 +0800 Subject: [PATCH 14/23] add UserOrder --- apps/api/urls.py | 4 +- apps/api/views.py | 85 ++++------ apps/sspanel/admin.py | 15 +- .../migrations/0004_auto_20190222_2209.py | 35 ++++ apps/sspanel/models.py | 106 ++++++++++++ apps/sspanel/urls.py | 2 - apps/sspanel/views.py | 19 --- commands/croncmds.py | 14 +- configs/default/cron.py | 6 +- configs/default/sites.py | 2 +- manage.py | 2 +- templates/basehead.html | 1 + templates/sspanel/donate.html | 18 +- templates/sspanel/donate91.html | 155 ------------------ 14 files changed, 206 insertions(+), 258 deletions(-) create mode 100644 apps/sspanel/migrations/0004_auto_20190222_2209.py delete mode 100644 templates/sspanel/donate91.html diff --git a/apps/api/urls.py b/apps/api/urls.py index f1c56280ee..bb4066b074 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -9,8 +9,6 @@ path("random/port/", views.change_ss_port, name="changessport"), path("gen/invitecode/", views.gen_invite_code, name="geninvitecode"), path("shop/", views.purchase, name="purchase"), - path("pay/request/", views.pay_request, name="pay_request"), - path("pay/query/", views.pay_query, name="pay_query"), path("traffic/query/", views.traffic_query, name="traffic_query"), path("change/theme/", views.change_theme, name="change_theme"), path("checkin/", views.checkin, name="checkin"), @@ -22,4 +20,6 @@ path("users/nodes/", views.user_api, name="get_userinfo"), path("traffic/upload", views.traffic_api, name="post_traffic"), path("nodes/aliveip", views.alive_ip_api, name="post_aliveip"), + # 支付 + path("orders", views.OrderView.as_view(), name="order"), ] diff --git a/apps/api/views.py b/apps/api/views.py index 565cca6b7d..358141ef7b 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -1,29 +1,20 @@ import time -from random import randint import pendulum -from decimal import Decimal +from django.views import View from django.db.models import F from django.conf import settings -from django.utils import timezone from django.http import JsonResponse from django.shortcuts import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from django.contrib.auth.decorators import login_required, permission_required + from apps.constants import NODE_USER_INFO_TTL from apps.utils import traffic_format, simple_cached_view, get_node_user, authorized from apps.ssserver.models import Suser, TrafficLog, Node, NodeOnlineLog, AliveIp -from apps.sspanel.models import ( - InviteCode, - PurchaseHistory, - RebateRecord, - Goods, - User, - Donate, - PayRequest, -) +from apps.sspanel.models import InviteCode, Goods, User, Donate, UserOrder @permission_required("sspanel") @@ -133,47 +124,6 @@ def purchase(request): return HttpResponse("errors") -@login_required -def pay_request(request): - """ - 当面付请求逻辑 - """ - amount = int(request.POST.get("num")) - - if amount < 1: - info = {"title": "失败", "subtitle": "请保证金额大于1元", "status": "error"} - else: - req = PayRequest.make_pay_request(request.user, amount) - if req is not None: - info = { - "title": "请求成功!", - "subtitle": "支付宝扫描下方二维码付款,付款完成记得按确认哟!", - "status": "success", - } - else: - info = { - "title": "糟糕,当面付插件可能出现问题了", - "subtitle": "如果一直失败,请后台联系站长", - "status": "error", - } - return JsonResponse({"info": info}) - - -@login_required -def pay_query(request): - """ - 当面付结果查询逻辑 - """ - user = request.user - info_code = PayRequest.get_user_recent_pay_req(user).info_code - paid = PayRequest.pay_query(user, info_code) - if paid in (True, -1): - info = {"title": "充值成功!", "subtitle": "请去商品界面购买商品!", "status": "success"} - else: - info = {"title": "支付查询失败!请稍候再试", "subtitle": "亲,确认支付了么?", "status": "error"} - return JsonResponse({"info": info}) - - @login_required def traffic_query(request): """ @@ -358,3 +308,32 @@ def checkin(request): else: data = {"title": "签到失败!", "subtitle": "距离上次签到不足一天", "status": "error"} return JsonResponse(data) + + +class OrderView(View): + def get(self, request): + user = request.user + order = UserOrder.get_recent_created_order(user) + order.check_order_status() + if order and order.status == UserOrder.STATUS_FINISHED: + info = {"title": "充值成功!", "subtitle": "请去商品界面购买商品!", "status": "success"} + else: + info = {"title": "支付查询失败!", "subtitle": "亲,确认支付了么?", "status": "error"} + return JsonResponse({"info": info}) + + def post(self, request): + amount = int(request.POST.get("num")) + + if amount < 1: + info = {"title": "失败", "subtitle": "请保证金额大于1元", "status": "error"} + else: + order = UserOrder.get_or_create_order(request.user, amount) + info = { + "title": "请求成功!", + "subtitle": "支付宝扫描下方二维码付款,付款完成记得按确认哟!", + "status": "success", + } + return JsonResponse( + {"info": info, "qrcode_url": order.qrcode_url, "order_id": order.id} + ) + diff --git a/apps/sspanel/admin.py b/apps/sspanel/admin.py index 4003fe8a42..c5d996175a 100644 --- a/apps/sspanel/admin.py +++ b/apps/sspanel/admin.py @@ -46,6 +46,19 @@ class GoodsAdmin(admin.ModelAdmin): list_display = ["name", "transfer", "money", "level"] +class UserOrderAdmin(admin.ModelAdmin): + list_display = [ + "user", + "status", + "out_trade_no", + "amount", + "created_at", + "expired_at", + ] + search_fields = ["user"] + list_filter = ["user", "amount", "status"] + + # Register your models here. admin.site.register(models.User, UserAdmin) admin.site.register(models.InviteCode, InviteCodeAdmin) @@ -57,6 +70,6 @@ class GoodsAdmin(admin.ModelAdmin): admin.site.register(models.PayRequest, AlipayRequestAdmin) admin.site.register(models.Announcement) admin.site.register(models.Ticket) - +admin.site.register(models.UserOrder, UserOrderAdmin) admin.site.unregister(Group) diff --git a/apps/sspanel/migrations/0004_auto_20190222_2209.py b/apps/sspanel/migrations/0004_auto_20190222_2209.py new file mode 100644 index 0000000000..1e5e870dd2 --- /dev/null +++ b/apps/sspanel/migrations/0004_auto_20190222_2209.py @@ -0,0 +1,35 @@ +# Generated by Django 2.0 on 2019-02-22 14:09 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sspanel', '0003_auto_20181009_0909'), + ] + + operations = [ + migrations.CreateModel( + name='UserOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.SmallIntegerField(choices=[(0, 'created'), (1, 'paid'), (2, 'finished')], db_index=True, verbose_name='订单状态')), + ('out_trade_no', models.CharField(db_index=True, max_length=64, unique=True, verbose_name='流水号')), + ('qrcode_url', models.CharField(max_length=64, null=True, verbose_name='支付连接')), + ('amount', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='金额')), + ('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='时间')), + ('expired_at', models.DateTimeField(db_index=True, verbose_name='过期时间')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')), + ], + options={ + 'verbose_name_plural': '用户订单', + }, + ), + migrations.AlterIndexTogether( + name='userorder', + index_together={('user', 'status')}, + ), + ] diff --git a/apps/sspanel/models.py b/apps/sspanel/models.py index 890ab8e8da..0c23c522be 100644 --- a/apps/sspanel/models.py +++ b/apps/sspanel/models.py @@ -518,3 +518,109 @@ def __str__(self): class Meta: verbose_name_plural = "工单" ordering = ("-time",) + + +class UserOrder(models.Model): + + DEFAULT_ORDER_TIME_OUT = "24h" + STATUS_CREATED = 0 + STATUS_PAID = 1 + STATUS_FINISHED = 2 + STATUS_CHOICES = ( + (STATUS_CREATED, "created"), + (STATUS_PAID, "paid"), + (STATUS_FINISHED, "finished"), + ) + + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户") + status = models.SmallIntegerField( + verbose_name="订单状态", db_index=True, choices=STATUS_CHOICES + ) + out_trade_no = models.CharField( + verbose_name="流水号", max_length=64, unique=True, db_index=True + ) + qrcode_url = models.CharField(verbose_name="支付连接", max_length=64, null=True) + amount = models.DecimalField( + verbose_name="金额", decimal_places=2, max_digits=10, default=0 + ) + created_at = models.DateTimeField( + verbose_name="时间", auto_now_add=True, db_index=True + ) + expired_at = models.DateTimeField(verbose_name="过期时间", db_index=True) + + def __str__(self): + return f"<{self.id,self.user}>:{self.amount}" + + class Meta: + verbose_name_plural = "用户订单" + index_together = ["user", "status"] + + @classmethod + def gen_out_trade_no(cls): + return datetime.datetime.fromtimestamp(time.time()).strftime("%Y%m%d%H%M%S%s") + + @classmethod + def get_not_paid_order(cls, user, amount): + return cls.objects.filter(user=user, status=cls.STATUS_CREATED).first() + + @classmethod + def get_recent_created_order(cls, user): + return cls.objects.filter(user=user).order_by("-created_at").first() + + @classmethod + def make_up_lost_orders(cls): + now = pendulum.now() + for order in cls.objects.filter(status=cls.STATUS_CREATED, expired_at__gte=now): + changed = order.check_order_status() + if changed: + print(f"补单:{order.user,order.amount}") + + @classmethod + def get_or_create_order(cls, user, amount): + with transaction.atomic(): + now = pendulum.now() + order = cls.get_not_paid_order(user, amount) + if order and order.expired_at > now: + return order + out_trade_no = cls.gen_out_trade_no() + trade = pay.alipay.api_alipay_trade_precreate( + subject=settings.ALIPAY_TRADE_INFO.format(amount), + out_trade_no=out_trade_no, + total_amount=amount, + timeout_express=cls.DEFAULT_ORDER_TIME_OUT, + notify_url="", + ) + qrcode_url = trade.get("qr_code") + order = cls.objects.create( + user=user, + status=cls.STATUS_CREATED, + out_trade_no=out_trade_no, + amount=amount, + qrcode_url=qrcode_url, + expired_at=now.add(hours=24), + ) + return order + + def handle_paid(self): + if self.status != self.STATUS_PAID: + return + with transaction.atomic(): + self.user.balance += self.amount + self.user.save() + self.status = self.STATUS_FINISHED + self.save() + # 将充值记录和捐赠绑定 + Donate.objects.create(user=self.user, money=self.amount) + + def check_order_status(self): + changed = False + if self.status != self.STATUS_CREATED: + return + with transaction.atomic(): + res = pay.alipay.api_alipay_trade_query(out_trade_no=self.out_trade_no) + if res.get("trade_status", "") == "TRADE_SUCCESS": + self.status = self.STATUS_PAID + self.save() + changed = True + self.handle_paid() + return changed diff --git a/apps/sspanel/urls.py b/apps/sspanel/urls.py index 1b6e715a9d..f600b520b5 100644 --- a/apps/sspanel/urls.py +++ b/apps/sspanel/urls.py @@ -60,8 +60,6 @@ path("backend/good/create/", views.good_create, name="good_create"), path("backend/good/edit//", views.good_edit, name="good_edit"), path("backend/purchase/history/", views.purchase_history, name="purchase_history"), - # 支付宝当面付相关: - path("facepay/qrcode/", views.gen_face_pay_qrcode, name="facepay_qrcode"), # 公告管理相关 path("backend/anno/", views.backend_anno, name="backend_anno"), path("backend/anno/delete//", views.anno_delete, name="anno_delete"), diff --git a/apps/sspanel/views.py b/apps/sspanel/views.py index 879e9b67ba..72c44fb3f3 100644 --- a/apps/sspanel/views.py +++ b/apps/sspanel/views.py @@ -22,7 +22,6 @@ Goods, MoneyCode, PurchaseHistory, - PayRequest, Announcement, Ticket, RebateRecord, @@ -213,24 +212,6 @@ def donate(request): return render(request, "sspanel/donate.html", context=context) -@login_required -def gen_face_pay_qrcode(request): - """生成当面付的二维码""" - - req = PayRequest.get_user_recent_pay_req(request.user) - if req: - # 生成ss二维码 - img = qrcode.make(req.qrcode_url) - buf = BytesIO() - img.save(buf) - image_stream = buf.getvalue() - # 构造图片reponse - response = HttpResponse(image_stream, content_type="image/png") - return response - else: - return HttpResponse("wrong") - - @login_required def nodeinfo(request): """跳转到节点信息的页面""" diff --git a/commands/croncmds.py b/commands/croncmds.py index e89b291c54..b267fe7109 100644 --- a/commands/croncmds.py +++ b/commands/croncmds.py @@ -2,7 +2,7 @@ from django.conf import settings from django.utils import timezone -from apps.sspanel.models import User, PayRequest +from apps.sspanel.models import User, UserOrder from apps.ssserver.models import Node, NodeOnlineLog, TrafficLog, AliveIp from django.core.mail import send_mail @@ -81,13 +81,7 @@ def reset_node_traffic(): print("Time: {} all node traffic removed!".format(timezone.now())) -def check_pay_request(): - """定时检查支付请求""" - # 每次检查新的五条记录 - querys = PayRequest.objects.order_by("-time")[:5] - for req in querys: - user = User.objects.filter(username=req.username).first() - paid = PayRequest.pay_query(user, req.info_code) - if paid is True: - print("用户:{} 掉单,已经补偿".format(user.username)) +def make_up_lost_order(): + """定时补单""" + UserOrder.make_up_lost_orders() print("{} 检查过支付请求".format(timezone.now().strftime("%Y-%m-%d %H:%M"))) diff --git a/configs/default/cron.py b/configs/default/cron.py index 171c6c3f64..02960931f6 100644 --- a/configs/default/cron.py +++ b/configs/default/cron.py @@ -33,8 +33,8 @@ ">>" + BASE_DIR + "/logs/onlineip_reset.log", ), # 每天凌晨1点半清空ip记录 ( - "*/30 * * * *", - "commands.croncmds.check_pay_request", # 每隔三十分钟检查一下有没有漏单 - ">>" + BASE_DIR + "/logs/payrequest.log", + "*/5 * * * *", + "commands.croncmds.make_up_lost_order", # 每隔五分钟检查一下有没有漏单 + ">>" + BASE_DIR + "/logs/check_orders.log", ), ] diff --git a/configs/default/sites.py b/configs/default/sites.py index 51aad5a693..031ea5170e 100644 --- a/configs/default/sites.py +++ b/configs/default/sites.py @@ -27,7 +27,7 @@ MAX_CHECKIN_TRAFFIC = 200 * MB # 是否启用支付宝系统 -USE_ALIPAY = False +USE_ALIPAY = True # 支付订单提示信息 修改请保留 {} 用于动态生成金额 ALIPAY_TRADE_INFO = "谜之屋的{}元充值码" diff --git a/manage.py b/manage.py index 0bd540079c..7df3c0b474 100755 --- a/manage.py +++ b/manage.py @@ -3,7 +3,7 @@ import sys if __name__ == "__main__": - env = os.getenv('DJANGO_ENV', 'development') + env = os.getenv("DJANGO_ENV", "development") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "configs.{}".format(env)) try: from django.core.management import execute_from_command_line diff --git a/templates/basehead.html b/templates/basehead.html index 69c20a4202..c917ad7b23 100644 --- a/templates/basehead.html +++ b/templates/basehead.html @@ -27,4 +27,5 @@ + \ No newline at end of file diff --git a/templates/sspanel/donate.html b/templates/sspanel/donate.html index f26356adcd..b5f97e867f 100644 --- a/templates/sspanel/donate.html +++ b/templates/sspanel/donate.html @@ -31,17 +31,16 @@

- +