├── .gitignore ├── README.md ├── core ├── __init__.py ├── apps.py ├── consumer.py ├── models.py └── routing.py ├── db_tool ├── __init__.py ├── create_admin.py ├── create_user.py ├── data │ ├── __init__.py │ ├── banner_data.py │ ├── category_data.py │ ├── goods_data.py │ ├── goods_image_data.py │ ├── msg_data.py │ └── topic_data.py ├── import_banner_data.py ├── import_category_data.py ├── import_data.sh ├── import_goods_data.py ├── import_goods_image.py ├── import_msg_data.py └── import_topic_data.py ├── django_project.conf ├── django_project.nginx ├── django_project ├── __init__.py ├── asgi.py ├── routing.py ├── settings.py ├── urls.py └── wsgi.py ├── docs └── api_v1.md ├── goods ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_bidrecord_profile.py │ ├── 0003_auto_20181110_1759.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── h_utils ├── __init__.py ├── errors.py ├── errors_handler.py ├── generator.py ├── mutex.py ├── permissions.py ├── response_extra.py ├── scheduler_extra.py ├── serializers_extra.py ├── success.py ├── validator.py └── websocket_extra.py ├── homes ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── msg ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20181108_1437.py │ └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── restart.sh ├── stop.sh ├── test ├── __init__.py ├── test_int.py ├── test_json.py ├── test_random_str.py └── test_scheduler.py ├── user_operation ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20181108_1437.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py └── users ├── __init__.py ├── admin.py ├── apps.py ├── migrations ├── 0001_initial.py ├── 0002_auto_20181110_1759.py ├── 0003_profile_is_third.py ├── 0004_auto_20190117_1222.py └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.sqlite3 3 | env/ 4 | *__pycache__/ 5 | media/ 6 | .vscode 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 基于django框架的一个生产就绪模板。 2 | 3 | # 部署 4 | 使用了supervisor作为进程监控。 5 | 6 | 7 | 8 | 依赖安装命令:pip install -r reuqirement.txt即可把相关依赖装上,如果出现依赖无法安装 9 | 10 | 11 | 12 | 请自行[百度](www.baidu.com)解决 13 | 14 | 15 | 16 | supervisor配置文件的名字:django_project.conf 17 | 18 | 19 | 20 | ubuntu下,请使用```ln -s /root/django_project/django_project.conf /ect/supervisor/conf.d/django_project.conf``` 21 | 22 | 23 | 24 | **注意**,其中/root/django_project/django_project.conf替换成自己的配置文件的位置。 25 | 26 | 27 | 28 | # 项目框架以及依赖 29 | 30 | django(主框架) 31 | 32 | channels (实时应用程序) 33 | 34 | Jwt (持久化登录) 35 | 36 | restframework (restful api 框架) 37 | 38 | apscheduler (任务调度) -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/core/__init__.py -------------------------------------------------------------------------------- /core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /core/consumer.py: -------------------------------------------------------------------------------- 1 | from asgiref.sync import async_to_sync 2 | 3 | from h_utils.websocket_extra import WebsocketConsumerExtra 4 | 5 | ''' 6 | {"id":"28","route":"heartbeat","req_data":""} 7 | ''' 8 | 9 | 10 | class IMConsumer(WebsocketConsumerExtra): 11 | room_name = 'im' 12 | 13 | def receive(self, text_data=None, bytes_data=None): 14 | data = self.parse_data(text_data) 15 | if data['route'] == 'heartbeat': 16 | async_to_sync(self.channel_layer.group_send)(self.room_name, { 17 | 'type': data['route'], 18 | 'message': text_data, 19 | }) 20 | else: 21 | async_to_sync(self.channel_layer.group_send)(self.room_name, { 22 | 'type': 'no_route', 23 | }) 24 | -------------------------------------------------------------------------------- /core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models import Manager 3 | 4 | 5 | class CoreManager(Manager): 6 | 7 | def get_queryset(self): 8 | return super(CoreManager, self).get_queryset().filter(is_delete=False) 9 | 10 | 11 | class CoreModel(models.Model): 12 | created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') 13 | updated_time = models.DateTimeField(auto_now=True, verbose_name='修改时间') 14 | is_delete = models.BooleanField(default=False, verbose_name='是否被删除') 15 | objects = CoreManager() 16 | 17 | class Meta: 18 | abstract = True 19 | -------------------------------------------------------------------------------- /core/routing.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from core.consumer import IMConsumer 4 | 5 | websocket_urlpatterns = [ 6 | path('im', IMConsumer), 7 | ] 8 | -------------------------------------------------------------------------------- /db_tool/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | -------------------------------------------------------------------------------- /db_tool/create_admin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from django.contrib.auth.hashers import make_password 5 | 6 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 | sys.path.append(BASE_DIR) 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 9 | 10 | import django 11 | 12 | django.setup() 13 | from users.models import Profile, Balance 14 | from h_utils.generator import gen_uid 15 | 16 | user = Profile.objects.create(uid=gen_uid(), 17 | username='admin', 18 | password=make_password('a12345678'), 19 | is_superuser=True, 20 | is_staff=True) 21 | 22 | Balance.objects.create(user=user, 23 | balance=0.0, 24 | total=0.0) 25 | print('admin数据导入完成') 26 | -------------------------------------------------------------------------------- /db_tool/create_user.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | 12 | from django.contrib.auth.hashers import make_password 13 | from h_utils.generator import gen_uid 14 | from users.models import Profile, Balance 15 | 16 | user = Profile.objects.create(uid=gen_uid(), 17 | phone='15913538383', 18 | password=make_password('huihui123'), 19 | platform='ios', 20 | username='手机用户' + '15913538383', 21 | register_ip='0.0.0.0', 22 | device_id=None) 23 | 24 | Balance.objects.create(user=user, 25 | balance=0.0, 26 | total=0.0, ) 27 | print('user数据导入完成') 28 | -------------------------------------------------------------------------------- /db_tool/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/db_tool/data/__init__.py -------------------------------------------------------------------------------- /db_tool/data/banner_data.py: -------------------------------------------------------------------------------- 1 | banner_data = [ 2 | { 3 | 'title': '节省99%的购机优惠,立刻参加1', 4 | 'image': '/homes/banner/image/apple-iphone-se-press-2_Fda8Na8.jpg', 5 | 'index': 0, 6 | }, 7 | { 8 | 'title': 'iphone居然只要800?赶紧点进来看看吧', 9 | 'image': '/homes/banner/image/apple-iphone-se-press-2_Fda8Na8.jpg', 10 | 'index': 1, 11 | }, 12 | { 13 | 'title': '节省99%的购机优惠,立刻参加3', 14 | 'image': '/homes/banner/image/apple-iphone-se-press-2_Fda8Na8.jpg', 15 | 'index': 2, 16 | }, 17 | { 18 | 'title': '节省99%的购机优惠,立刻参加4', 19 | 'image': '/homes/banner/image/apple-iphone-se-press-2_Fda8Na8.jpg', 20 | 'index': 3, 21 | }, 22 | { 23 | 'title': '节省99%的购机优惠,立刻参加5', 24 | 'image': '/homes/banner/image/apple-iphone-se-press-2_Fda8Na8.jpg', 25 | 'index': 4, 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /db_tool/data/category_data.py: -------------------------------------------------------------------------------- 1 | category_data = [ 2 | { 3 | 'name': '十元专区', 4 | 'image': '/homes/topic/icon/touch_NnclCim.png', 5 | 'index': 0 6 | }, 7 | { 8 | 'name': '手机专区', 9 | 'image': '/homes/topic/icon/touch_NnclCim.png', 10 | 'index': 1 11 | }, 12 | { 13 | 'name': '珠宝配饰', 14 | 'image': '/homes/topic/icon/touch_NnclCim.png', 15 | 'index': 2 16 | }, 17 | { 18 | 'name': '电脑平板', 19 | 'image': '/homes/topic/icon/touch_NnclCim.png', 20 | 'index': 3 21 | }, 22 | { 23 | 'name': '生活家电', 24 | 'image': '/homes/topic/icon/touch_NnclCim.png', 25 | 'index': 4 26 | }, 27 | { 28 | 'name': '数码影音', 29 | 'image': '/homes/topic/icon/touch_NnclCim.png', 30 | 'index': 5 31 | }, 32 | { 33 | 'name': '其他专区', 34 | 'image': '/homes/topic/icon/touch_NnclCim.png', 35 | 'index': 6 36 | }, 37 | { 38 | 'name': '美食天地', 39 | 'image': '/homes/topic/icon/touch_NnclCim.png', 40 | 'index': 7 41 | }, 42 | { 43 | 'name': '运动户外', 44 | 'image': '/homes/topic/icon/touch_NnclCim.png', 45 | 'index': 8 46 | }, 47 | { 48 | 'name': '美妆护肤', 49 | 'image': '/homes/topic/icon/touch_NnclCim.png', 50 | 'index': 9 51 | }, 52 | { 53 | 'name': '家居生活', 54 | 'image': '/homes/topic/icon/touch_NnclCim.png', 55 | 'index': 10 56 | }, 57 | ] 58 | -------------------------------------------------------------------------------- /db_tool/data/goods_data.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from h_utils.generator import gen_goods_id 4 | 5 | goods_data = [ 6 | { 7 | 'goods_id': gen_goods_id(), 8 | 'category': randint(1, 11), 9 | 'name': 'iPhone XS Max', 10 | 'market_price': 9999.99, 11 | 'goods_brief': 'iPhone XS Max', 12 | 'goods_desc': 'iPhone XS Max', 13 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 14 | 'periods': 1, 15 | }, 16 | { 17 | 'goods_id': gen_goods_id(), 18 | 'category': randint(1, 11), 19 | 'name': 'iPhone XS Max', 20 | 'market_price': 9999.99, 21 | 'goods_brief': 'iPhone XS Max', 22 | 'goods_desc': 'iPhone XS Max', 23 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 24 | 'periods': 1, 25 | }, 26 | { 27 | 'goods_id': gen_goods_id(), 28 | 'category': randint(1, 11), 29 | 'name': 'iPhone XS Max', 30 | 'market_price': 9999.99, 31 | 'goods_brief': 'iPhone XS Max', 32 | 'goods_desc': 'iPhone XS Max', 33 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 34 | 'periods': 1, 35 | }, 36 | { 37 | 'goods_id': gen_goods_id(), 38 | 'category': randint(1, 11), 39 | 'name': 'iPhone XS Max', 40 | 'market_price': 9999.99, 41 | 'goods_brief': 'iPhone XS Max', 42 | 'goods_desc': 'iPhone XS Max', 43 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 44 | 'periods': 1, 45 | }, 46 | { 47 | 'goods_id': gen_goods_id(), 48 | 'category': randint(1, 11), 49 | 'name': 'iPhone XS Max', 50 | 'market_price': 9999.99, 51 | 'goods_brief': 'iPhone XS Max', 52 | 'goods_desc': 'iPhone XS Max', 53 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 54 | 'periods': 1, 55 | }, 56 | { 57 | 'goods_id': gen_goods_id(), 58 | 'category': randint(1, 11), 59 | 'name': 'iPhone XS Max', 60 | 'market_price': 9999.99, 61 | 'goods_brief': 'iPhone XS Max', 62 | 'goods_desc': 'iPhone XS Max', 63 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 64 | 'periods': 1, 65 | }, 66 | { 67 | 'goods_id': gen_goods_id(), 68 | 'category': randint(1, 11), 69 | 'name': 'iPhone XS Max', 70 | 'market_price': 9999.99, 71 | 'goods_brief': 'iPhone XS Max', 72 | 'goods_desc': 'iPhone XS Max', 73 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 74 | 'periods': 1, 75 | }, 76 | { 77 | 'goods_id': gen_goods_id(), 78 | 'category': randint(1, 11), 79 | 'name': 'iPhone XS Max', 80 | 'market_price': 9999.99, 81 | 'goods_brief': 'iPhone XS Max', 82 | 'goods_desc': 'iPhone XS Max', 83 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 84 | 'periods': 1, 85 | }, 86 | { 87 | 'goods_id': gen_goods_id(), 88 | 'category': randint(1, 11), 89 | 'name': 'iPhone XS Max', 90 | 'market_price': 9999.99, 91 | 'goods_brief': 'iPhone XS Max', 92 | 'goods_desc': 'iPhone XS Max', 93 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 94 | 'periods': 1, 95 | }, 96 | { 97 | 'goods_id': gen_goods_id(), 98 | 'category': randint(1, 11), 99 | 'name': 'iPhone XS Max', 100 | 'market_price': 9999.99, 101 | 'goods_brief': 'iPhone XS Max', 102 | 'goods_desc': 'iPhone XS Max', 103 | 'goods_front_image': 'goods/front/image/45ab3dd6c35d981b.jpg', 104 | 'periods': 1, 105 | }, 106 | ] 107 | -------------------------------------------------------------------------------- /db_tool/data/goods_image_data.py: -------------------------------------------------------------------------------- 1 | goods_image_data = { 2 | 'index': 0, 3 | 'image': 'goods/detail/image/apple-iphone-se-press-2.jpg', 4 | } 5 | -------------------------------------------------------------------------------- /db_tool/data/msg_data.py: -------------------------------------------------------------------------------- 1 | msg_data = [ 2 | { 3 | 'title': 'DW00004期拍卖公告第四期', 4 | 'msg_type': 2, 5 | 'content': '受委托,于2017年9月11号。闪电拍卖于各大应用商店平台。' 6 | '对以下标的进行无保证金拍卖。手机家电,数码产品等等。' 7 | '详情可以看公告,或者致电于我公司的电话进行咨询。', 8 | 'periods': 1, 9 | }, 10 | ] 11 | -------------------------------------------------------------------------------- /db_tool/data/topic_data.py: -------------------------------------------------------------------------------- 1 | topic_data = [ 2 | { 3 | 'title': '100变200', 4 | 'icon': '/homes/topic/icon/touch_NnclCim.png', 5 | 'index': 0, 6 | 'url': 'www.baidu.com', 7 | }, 8 | { 9 | 'title': '签到', 10 | 'icon': '/homes/topic/icon/touch_NnclCim.png', 11 | 'index': 1, 12 | 'url': 'www.baidu.com', 13 | }, 14 | { 15 | 'title': '充值中心', 16 | 'icon': '/homes/topic/icon/touch_NnclCim.png', 17 | 'index': 2, 18 | 'url': 'www.baidu.com', 19 | }, 20 | { 21 | 'title': '限时秒', 22 | 'icon': '/homes/topic/icon/touch_NnclCim.png', 23 | 'index': 3, 24 | 'url': 'www.baidu.com', 25 | }, 26 | { 27 | 'title': '帮助', 28 | 'icon': '/homes/topic/icon/touch_NnclCim.png', 29 | 'index': 4, 30 | 'url': 'www.baidu.com', 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /db_tool/import_banner_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | 12 | from db_tool.data.banner_data import banner_data 13 | from homes.models import Banner 14 | 15 | for banner in banner_data: 16 | Banner.objects.create(title=banner['title'], 17 | image=banner['image'], 18 | index=banner['index']) 19 | print('banner数据导入完成') 20 | -------------------------------------------------------------------------------- /db_tool/import_category_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | 12 | from db_tool.data.category_data import category_data 13 | from goods.models import Category 14 | 15 | for data in category_data: 16 | category = Category() 17 | category.name = data['name'] 18 | category.image = data['image'] 19 | category.index = data['index'] 20 | category.save() 21 | 22 | print('category数据导入完成') 23 | -------------------------------------------------------------------------------- /db_tool/import_data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | python import_category_data.py 3 | python import_goods_data.py 4 | python import_goods_image.py 5 | python import_banner_data.py 6 | python import_topic_data.py 7 | python import_msg_data.py 8 | python create_admin.py 9 | python create_user.py -------------------------------------------------------------------------------- /db_tool/import_goods_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | 12 | from db_tool.data.goods_data import goods_data 13 | from goods.models import Category, Goods 14 | 15 | for data in goods_data: 16 | goods = Goods() 17 | goods.goods_id = data['goods_id'] 18 | goods.category = Category.objects.filter(pk=data['category']).first() 19 | goods.name = data['name'] 20 | goods.market_price = data['market_price'] 21 | goods.goods_brief = data['goods_brief'] 22 | goods.goods_desc = data['goods_desc'] 23 | goods.goods_front_image = data['goods_front_image'] 24 | goods.periods = data['periods'] 25 | goods.save() 26 | 27 | print('goods数据导入完成') 28 | -------------------------------------------------------------------------------- /db_tool/import_goods_image.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | 12 | from db_tool.data.goods_image_data import goods_image_data 13 | from goods.models import Goods, GoodsImage 14 | 15 | count = [0, 1, 2, 3, 4] 16 | 17 | goods_list = Goods.objects.all() 18 | for goods in goods_list: 19 | for i in count: 20 | GoodsImage.objects.create( 21 | index=goods_image_data['index'], 22 | image=goods_image_data['image'], 23 | goods=goods, 24 | ) 25 | 26 | print('goods_image导入成功') 27 | -------------------------------------------------------------------------------- /db_tool/import_msg_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | 12 | from db_tool.data.msg_data import msg_data 13 | from msg.models import Msg 14 | 15 | for msg in msg_data: 16 | Msg.objects.create( 17 | title=msg['title'], 18 | content=msg['content'], 19 | periods=msg['periods'], 20 | msg_type=msg['msg_type'], 21 | ) 22 | 23 | print('msg数据导入完成') 24 | -------------------------------------------------------------------------------- /db_tool/import_topic_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings") 7 | 8 | import django 9 | 10 | django.setup() 11 | 12 | from db_tool.data.topic_data import topic_data 13 | from homes.models import Topic 14 | 15 | for topic in topic_data: 16 | Topic.objects.create( 17 | title=topic['title'], 18 | icon=topic['icon'], 19 | index=topic['index'], 20 | url=topic['url'] 21 | ) 22 | print('topic数据导入完成') 23 | -------------------------------------------------------------------------------- /django_project.conf: -------------------------------------------------------------------------------- 1 | [program:django_project] 2 | directory=/root/django_project 3 | command=/root/django_project/env/bin/daphne -b 0.0.0.0 -p 5566 django_project.asgi:application 4 | autostart=true 5 | autorestart=true 6 | stdout_logfile=/root/django_project/log/asgi.log 7 | redirect_stderr=true -------------------------------------------------------------------------------- /django_project.nginx: -------------------------------------------------------------------------------- 1 | server { 2 | charset utf-8; 3 | listen 6001; 4 | 5 | location /static { 6 | alias /opt/django_project/nginx/static/; 7 | } 8 | 9 | location /media { 10 | alias /opt/django_project/nginx/media/; 11 | } 12 | 13 | location / { 14 | proxy_set_header X-Real-IP $remote_addr; 15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 | proxy_set_header Host $host; 17 | proxy_pass http://0.0.0.0:5566; 18 | } 19 | } -------------------------------------------------------------------------------- /django_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/django_project/__init__.py -------------------------------------------------------------------------------- /django_project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI entrypoint. Configures Django and then runs the application 3 | defined in the ASGI_APPLICATION setting. 4 | """ 5 | 6 | import os 7 | 8 | import django 9 | from channels.routing import get_default_application 10 | 11 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') 12 | django.setup() 13 | application = get_default_application() 14 | -------------------------------------------------------------------------------- /django_project/routing.py: -------------------------------------------------------------------------------- 1 | from channels.routing import ProtocolTypeRouter, URLRouter 2 | 3 | from core.routing import websocket_urlpatterns 4 | 5 | application = ProtocolTypeRouter({ 6 | # (http->django views is added by default) 7 | 'websocket': URLRouter( 8 | websocket_urlpatterns, 9 | ), 10 | }) 11 | -------------------------------------------------------------------------------- /django_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/ref/settings/ 11 | """ 12 | import datetime 13 | import os 14 | import socket 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | SECRET_KEY = 'ys@zt__pcc&=aj*f9sv3w2^2&@#05ly=o_vej4o%vyl68(y$ci' 19 | 20 | developer = [ 21 | 'luoxhdeMBP', 22 | 'luoxhdeMBP-local', 23 | 'luoxhdeMacBook-Pro.local', 24 | '192.168.0.110', 25 | ] 26 | 27 | 28 | def is_debug(): 29 | if socket.gethostname() in developer: 30 | return True 31 | else: 32 | return False 33 | 34 | 35 | DEBUG = is_debug() 36 | 37 | ALLOWED_HOSTS = ['*'] 38 | 39 | INSTALLED_APPS = [ 40 | 'django.contrib.admin', 41 | 'django.contrib.auth', 42 | 'django.contrib.contenttypes', 43 | 'django.contrib.sessions', 44 | 'django.contrib.messages', 45 | 'django.contrib.staticfiles', 46 | 47 | 'rest_framework', 48 | 'channels', 49 | 'markdownx', 50 | 51 | 'core.apps.CoreConfig', 52 | 'users.apps.UsersConfig', 53 | 'goods.apps.GoodsConfig', 54 | 'homes.apps.HomesConfig', 55 | 'msg.apps.MsgConfig', 56 | 'user_operation.apps.UserOperationConfig', 57 | ] 58 | 59 | MIDDLEWARE = [ 60 | 'django.middleware.security.SecurityMiddleware', 61 | 'django.contrib.sessions.middleware.SessionMiddleware', 62 | 'django.middleware.common.CommonMiddleware', 63 | 64 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 65 | 'django.contrib.messages.middleware.MessageMiddleware', 66 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 67 | ] 68 | 69 | AUTH_USER_MODEL = 'users.Profile' 70 | 71 | ROOT_URLCONF = 'django_project.urls' 72 | 73 | TEMPLATES = [ 74 | { 75 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 76 | 'DIRS': [os.path.join(BASE_DIR, 'templates')] 77 | , 78 | 'APP_DIRS': True, 79 | 'OPTIONS': { 80 | 'context_processors': [ 81 | 'django.template.context_processors.debug', 82 | 'django.template.context_processors.request', 83 | 'django.contrib.auth.context_processors.auth', 84 | 'django.contrib.messages.context_processors.messages', 85 | ], 86 | }, 87 | }, 88 | ] 89 | 90 | WSGI_APPLICATION = 'django_project.wsgi.application' 91 | 92 | ASGI_APPLICATION = 'django_project.routing.application' 93 | 94 | DATABASES = { 95 | 'default': { 96 | 'ENGINE': 'django.db.backends.sqlite3', 97 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 98 | } 99 | } 100 | 101 | AUTH_PASSWORD_VALIDATORS = [ 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 113 | }, 114 | ] 115 | 116 | LANGUAGE_CODE = 'zh-hans' 117 | 118 | TIME_ZONE = 'Asia/Shanghai' 119 | 120 | USE_I18N = True 121 | 122 | USE_L10N = True 123 | 124 | USE_TZ = False 125 | 126 | STATIC_URL = '/static/' 127 | 128 | MEDIA_URL = '/media/' 129 | 130 | if DEBUG: 131 | STATICFILES_DIRS = ( 132 | os.path.join(BASE_DIR, "static"), 133 | ) 134 | MEDIA_ROOT = os.path.join(BASE_DIR, "media") 135 | else: 136 | STATIC_ROOT = '/opt/django_project/nginx/static/' 137 | MEDIA_ROOT = '/opt/django_project/nginx/media/' 138 | 139 | CHANNEL_LAYERS = { 140 | 'default': { 141 | 'BACKEND': 'channels_redis.core.RedisChannelLayer', 142 | 'CONFIG': { 143 | "hosts": [('127.0.0.1', 6379)], 144 | }, 145 | }, 146 | } 147 | 148 | REST_FRAMEWORK = { 149 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 150 | 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 151 | 'rest_framework.authentication.BasicAuthentication', 152 | 'rest_framework.authentication.SessionAuthentication', 153 | ), 154 | 'DEFAULT_THROTTLE_CLASSES': ( 155 | 'rest_framework.throttling.AnonRateThrottle', 156 | 'rest_framework.throttling.UserRateThrottle' 157 | ), 158 | 'DEFAULT_THROTTLE_RATES': { 159 | 'anon': '100000/min', 160 | 'user': '1000/min' 161 | }, 162 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 163 | 'PAGE_SIZE': 10, 164 | } 165 | 166 | JWT_AUTH = { 167 | 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), 168 | 'JWT_ALLOW_REFRESH': True, 169 | } 170 | 171 | CACHES = { 172 | "default": { 173 | "BACKEND": "django_redis.cache.RedisCache", 174 | "LOCATION": "redis://127.0.0.1:6379", 175 | "OPTIONS": { 176 | "CLIENT_CLASS": "django_redis.client.DefaultClient", 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /django_project/urls.py: -------------------------------------------------------------------------------- 1 | """django_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls.static import static 18 | from django.contrib import admin 19 | from django.urls import include, path 20 | 21 | urlpatterns = [ 22 | path('admin/', admin.site.urls), 23 | path('markdownx/', include('markdownx.urls')), 24 | 25 | path('users/', include('users.urls')), 26 | path('goods/', include('goods.urls')), 27 | path('homes/', include('homes.urls')), 28 | path('msg/', include('msg.urls')), 29 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ 30 | + static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS) 31 | -------------------------------------------------------------------------------- /django_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /docs/api_v1.md: -------------------------------------------------------------------------------- 1 | # 竞拍项目API V1接口文档 2 | 3 | 4 | 5 | ## 首页接口 6 | 7 | 接口前缀 8 | 9 | ```python 10 | path('homes/', include('homes.urls')), 11 | ``` 12 | 13 | 接口路径 14 | 15 | ```python 16 | path('index', IndexAPIView.as_view()), # banner 和 topic 17 | path('recommend', RecommendAPIView.as_view()), # 为你推荐 18 | path('guess-you-like', GuessYouLikeAPIView.as_view()), # 猜你喜欢 19 | path('i-am-shooting', IAmShootingAPIView.as_view()), # 我在拍 20 | path('my-collection', MyCollectionAPIView.as_view()), # 我的收藏 21 | ``` 22 | 23 | ### 1.index GET 24 | 25 | 成功请求 26 | 27 | ``` json 28 | { 29 | "code": 200, 30 | "data": { 31 | "gender": 0, 32 | "nick": "", 33 | "oauth_avatar": "", 34 | "phone": "15913538383", 35 | "banner": [ 36 | { 37 | "title": "节省99%的购机优惠,立刻参加", 38 | "image": "/media/homes/banner/image/apple-iphone-se-press-2.jpg", 39 | "index": 0, 40 | "url": null, 41 | "activity": null, 42 | "goods": 1 43 | } 44 | ], 45 | "topic": [ 46 | { 47 | "title": "推荐", 48 | "icon": "/media/homes/topic/icon/touch.png", 49 | "index": 0, 50 | "url": "www.baidu.com", 51 | "activity": "webcontroller", 52 | "category": null 53 | } 54 | ] 55 | } 56 | } 57 | ``` 58 | 59 | ### 2.为你推荐 GET 60 | 61 | 返回最热门的商品 62 | 63 | ```python 64 | Goods.objects.filter(is_hot=True).all() 65 | ``` 66 | 67 | 成功请求 68 | 69 | ``` json 70 | { 71 | "code": 200, 72 | "data": [ 73 | { 74 | "goods_id": "Sxuvm80hRIilw7Jc0nkYzLga5KV1UENn2edkGQIG4cEo9QbpysRf9pDMU5qsjEyi", 75 | "goods_front_image": "/media/goods/front/image", 76 | "now_price": 0 77 | } 78 | ] 79 | } 80 | ``` 81 | 82 | ### 3.猜你喜欢 GET 83 | 84 | 返回最新加入的商品 85 | 86 | ```python 87 | Goods.objects.filter(is_new=True).all() 88 | ``` 89 | 90 | 成功请求 91 | 92 | ``` json 93 | { 94 | "code": 200, 95 | "data": [ 96 | { 97 | "goods_id": "Sxuvm80hRIilw7Jc0nkYzLga5KV1UENn2edkGQIG4cEo9QbpysRf9pDMU5qsjEyi", 98 | "goods_front_image": "/media/goods/front/image", 99 | "now_price": 0 100 | } 101 | ] 102 | } 103 | ``` 104 | 105 | ### 4.我在拍 GET 106 | 107 | 未写 108 | 109 | 110 | 111 | ### 5.我的收藏 GET 112 | 113 | 未写 114 | 115 | 116 | 117 | ## 商品接口 118 | 119 | 接口前缀 120 | 121 | ```python 122 | path('goods/', include('goods.urls')), 123 | ``` 124 | 125 | 接口路径 126 | 127 | ```python 128 | path('list', GoodsListAPIView.as_view()), # 所有商品 129 | path('detail', GoodsDetailAPIView.as_view()), # 某个商品 130 | path('category/detail', GoodsCategoryDetailAPIView.as_view()), # 某个分类下的商品 131 | path('category/list', GoodsListAPIView.as_view()), # 所有分类的商品 132 | path('news', NewsAPIView.as_view()), # 最新动态 133 | ``` 134 | 135 | 136 | 137 | ### 1.所有商品 GET 138 | 139 | 成功请求 140 | 141 | ``` json 142 | { 143 | "code": 200, 144 | "data": [ 145 | { 146 | "category": { 147 | "name": "十元专区", 148 | "image": "/media/category/image/", 149 | "index": 0 150 | }, 151 | "goods_id": "SAXEUtbqhGWvnd2D7zTAFYOPqw6MW2BU9Jn5mVIM4jQ12oICTEBM2lQg7UzY4OXp", 152 | "name": "iPhone XS Max", 153 | "click_num": 0, 154 | "fav_num": 0, 155 | "market_price": 9999.99, 156 | "now_price": 0, 157 | "goods_brief": "iPhone XS Max", 158 | "goods_desc": "iPhone XS Max", 159 | "ship_free": true, 160 | "goods_front_image": "/media/goods/front/image", 161 | "is_new": false, 162 | "is_hot": false, 163 | "is_sell": false, 164 | "periods": 1 165 | }, 166 | { 167 | "category": { 168 | "name": "家居生活", 169 | "image": "/media/category/image/", 170 | "index": 10 171 | }, 172 | "goods_id": "Sxuvm80hRIilw7Jc0nkYzLga5KV1UENn2edkGQIG4cEo9QbpysRf9pDMU5qsjEyi", 173 | "name": "iPhone XS Max", 174 | "click_num": 0, 175 | "fav_num": 0, 176 | "market_price": 9999.99, 177 | "now_price": 0, 178 | "goods_brief": "iPhone XS Max", 179 | "goods_desc": "iPhone XS Max", 180 | "ship_free": true, 181 | "goods_front_image": "/media/goods/front/image", 182 | "is_new": true, 183 | "is_hot": true, 184 | "is_sell": false, 185 | "periods": 1 186 | } 187 | ] 188 | } 189 | ``` 190 | 191 | ### 2.某个商品 GET 192 | 193 | 必填参数 194 | 195 | ```python 196 | goods_id = request.query_params['goods_id'] 197 | ``` 198 | 199 | 成功请求 200 | 201 | ``` json 202 | { 203 | "code": 200, 204 | "data": { 205 | "category": { 206 | "name": "家居生活", 207 | "image": "/media/category/image/", 208 | "index": 10 209 | }, 210 | "goods_id": "Sxuvm80hRIilw7Jc0nkYzLga5KV1UENn2edkGQIG4cEo9QbpysRf9pDMU5qsjEyi", 211 | "name": "iPhone XS Max", 212 | "click_num": 0, 213 | "fav_num": 0, 214 | "market_price": 9999.99, 215 | "now_price": 0, 216 | "goods_brief": "iPhone XS Max", 217 | "goods_desc": "iPhone XS Max", 218 | "ship_free": true, 219 | "goods_front_image": "/media/goods/front/image", 220 | "is_new": true, 221 | "is_hot": true, 222 | "is_sell": false, 223 | "periods": 1, 224 | "goods_images": [] 225 | } 226 | } 227 | ``` 228 | 229 | ### 3.某个分类下的商品 GET 230 | 231 | 必填参数 232 | 233 | ```python 234 | category_id = request.query_params['category_id'] 235 | ``` 236 | 237 | 成功请求 238 | 239 | ``` json 240 | { 241 | "code": 200, 242 | "data": [ 243 | { 244 | "goods_id": "SAXEUtbqhGWvnd2D7zTAFYOPqw6MW2BU9Jn5mVIM4jQ12oICTEBM2lQg7UzY4OXp", 245 | "name": "iPhone XS Max", 246 | "click_num": 0, 247 | "fav_num": 0, 248 | "market_price": 9999.99, 249 | "now_price": 0, 250 | "goods_brief": "iPhone XS Max", 251 | "goods_desc": "iPhone XS Max", 252 | "ship_free": true, 253 | "goods_front_image": "/media/goods/front/image", 254 | "is_new": false, 255 | "is_hot": false, 256 | "is_sell": false, 257 | "periods": 1 258 | }, 259 | { 260 | "goods_id": "IP5U9O9TujKwxV4zVbyQ9EcQry9Jngalb83S2YXo3YezmlfG1Ck1E4KfaS1oxmvl", 261 | "name": "iPhone XS Max", 262 | "click_num": 0, 263 | "fav_num": 0, 264 | "market_price": 9999.99, 265 | "now_price": 0, 266 | "goods_brief": "iPhone XS Max", 267 | "goods_desc": "iPhone XS Max", 268 | "ship_free": true, 269 | "goods_front_image": "/media/goods/front/image", 270 | "is_new": false, 271 | "is_hot": false, 272 | "is_sell": false, 273 | "periods": 1 274 | }, 275 | { 276 | "goods_id": "VEPESQGiZpeLwrHqg2LQaCD2UAVccxeDU3nha9J93cqzbGwk3o1FRguTEse04GEM", 277 | "name": "iPhone XS Max", 278 | "click_num": 0, 279 | "fav_num": 0, 280 | "market_price": 9999.99, 281 | "now_price": 0, 282 | "goods_brief": "iPhone XS Max", 283 | "goods_desc": "iPhone XS Max", 284 | "ship_free": true, 285 | "goods_front_image": "/media/goods/front/image", 286 | "is_new": false, 287 | "is_hot": false, 288 | "is_sell": false, 289 | "periods": 1 290 | } 291 | ] 292 | } 293 | ``` 294 | 295 | ### 4.所有分类的商品 GET 296 | 297 | 成功请求: 298 | 299 | ​ 所有商品 300 | 301 | 302 | 303 | ## 用户接口 304 | 305 | 接口前缀 306 | 307 | ```python 308 | path('users/', include('users.urls')), 309 | ``` 310 | 311 | 接口路径 312 | 313 | ```python 314 | path('phone/login', PhoneLogin.as_view()), # 手机登录 315 | path('phone/register', PhoneRegister.as_view()), # 手机注册 316 | path('info', InfoAPIView.as_view()), # 用户信息 317 | path('token-refresh', refresh_jwt_token), # 刷新token 318 | ``` 319 | 320 | ### 1. 手机登录接口 POST 321 | 322 | 必填参数: 323 | 324 | ```python 325 | phone = request.data['phone'] 326 | password = request.data['password'] 327 | ``` 328 | 329 | 成功请求: 330 | 331 | ``` json 332 | { 333 | "code": 200, 334 | "data": { 335 | "gender": 0, 336 | "nick": "", 337 | "oauth_avatar": "", 338 | "phone": "15913538383", 339 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjozLCJ1c2VybmFtZSI6Ilx1NjI0Ylx1NjczYVx1NzUyOFx1NjIzNzE1OTEzNTM4MzgzIiwiZXhwIjoxNTQxOTIwODkwLCJlbWFpbCI6IiIsIm9yaWdfaWF0IjoxNTQxMzE2MDkwfQ.ncPphcRKO6nCc6I1CUvS_dEq0zEDLDi-APrt2EnsTMk" 340 | }, 341 | "msg": "登录成功" 342 | } 343 | ``` 344 | 345 | ### 2.手机注册接口 POST 346 | 347 | 必填参数: 348 | 349 | ``` python 350 | phone = request.data['phone'] 351 | password = request.data['password'] 352 | code = request.data['code'] # 短信验证码,开发环境填入66666 353 | platform = request.data['platform'] 354 | ``` 355 | 356 | 成功请求: 357 | 358 | ``` json 359 | { 360 | "code": 200, 361 | "data": { 362 | "gender": 0, 363 | "nick": "", 364 | "oauth_avatar": "", 365 | "phone": "15913538384", 366 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJ1c2VybmFtZSI6Ilx1NjI0Ylx1NjczYVx1NzUyOFx1NjIzNzE1OTEzNTM4Mzg0IiwiZXhwIjoxNTQxOTIxMDY2LCJlbWFpbCI6IiIsIm9yaWdfaWF0IjoxNTQxMzE2MjY2fQ.H_7WH3IEk2mH-_OG0QHUKOZdSuv2pC11Ig6GC_u1eJA" 367 | }, 368 | "msg": "注册成功" 369 | } 370 | ``` 371 | 372 | ### 3.用户信息 GET 373 | 374 | 成功请求: 375 | 376 | ``` json 377 | { 378 | "code": 200, 379 | "data": { 380 | "gender": 0, 381 | "nick": "", 382 | "oauth_avatar": "", 383 | "phone": "15913538383" 384 | } 385 | } 386 | ``` 387 | 388 | ### 4.刷新token POST 389 | 390 | 必填参数: 391 | 392 | ``` python 393 | token = request.data['token'] 394 | ``` 395 | 396 | 397 | 398 | 成功请求: 399 | 400 | ``` json 401 | { 402 | "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjozLCJ1c2VybmFtZSI6Ilx1NjI0Ylx1NjczYVx1NzUyOFx1NjIzNzE1OTEzNTM4MzgzIiwiZXhwIjoxNTQxOTIxOTE1LCJlbWFpbCI6IiIsIm9yaWdfaWF0IjoxNTQxMzE2MDkwfQ.bgMaqhfxV_aZS68exvLOts5A60snB8yEtyxPVNTlHr8" 403 | } 404 | ``` 405 | 406 | 407 | 408 | 409 | 410 | ## 用户操作接口 411 | 412 | 413 | 414 | 415 | 416 | ## 消息接口 417 | 418 | -------------------------------------------------------------------------------- /goods/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/goods/__init__.py -------------------------------------------------------------------------------- /goods/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import ModelAdmin, register 2 | from django.db import models 3 | from markdownx.widgets import AdminMarkdownxWidget 4 | 5 | from goods.models import BidRecord, Category, Goods, GoodsImage, RoomDetail 6 | 7 | 8 | @register(Goods) 9 | class GoodsAdmin(ModelAdmin): 10 | formfield_overrides = { 11 | models.TextField: {'widget': AdminMarkdownxWidget}, 12 | } 13 | 14 | 15 | @register(Category) 16 | class CategoryAdmin(ModelAdmin): 17 | pass 18 | 19 | 20 | @register(GoodsImage) 21 | class GoodsImageAdmin(ModelAdmin): 22 | pass 23 | 24 | 25 | @register(BidRecord) 26 | class BidRecordAdmin(ModelAdmin): 27 | pass 28 | 29 | 30 | @register(RoomDetail) 31 | class RoomDetailAdmin(ModelAdmin): 32 | pass 33 | -------------------------------------------------------------------------------- /goods/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class GoodsConfig(AppConfig): 5 | name = 'goods' 6 | verbose_name = '商品管理' 7 | -------------------------------------------------------------------------------- /goods/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | import django.db.models.deletion 4 | import markdownx.models 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='BidRecord', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 20 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 21 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 22 | ('is_out', models.BooleanField(default=True, verbose_name='是否出局')), 23 | ], 24 | options={ 25 | 'verbose_name': '出价记录', 26 | 'verbose_name_plural': '出价记录', 27 | }, 28 | ), 29 | migrations.CreateModel( 30 | name='Category', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 34 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 35 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 36 | ('name', models.CharField(blank=True, max_length=15, null=True, verbose_name='类型名称')), 37 | ('image', models.ImageField(upload_to='category/image/', verbose_name='类型的图片')), 38 | ('index', models.IntegerField(verbose_name='排序')), 39 | ], 40 | options={ 41 | 'verbose_name': '类型', 42 | 'verbose_name_plural': '类型', 43 | }, 44 | ), 45 | migrations.CreateModel( 46 | name='Goods', 47 | fields=[ 48 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 49 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 50 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 51 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 52 | ('goods_id', models.CharField(db_index=True, max_length=64, unique=True, verbose_name='商品id')), 53 | ('goods_sn', models.CharField(blank=True, default='', max_length=50, null=True, verbose_name='商品唯一货号')), 54 | ('name', models.CharField(max_length=100, verbose_name='商品名')), 55 | ('click_num', models.IntegerField(default=0, verbose_name='点击数')), 56 | ('fav_num', models.IntegerField(default=0, verbose_name='收藏数')), 57 | ('market_price', models.FloatField(default=0.0, verbose_name='市场价格')), 58 | ('now_price', models.FloatField(default=0.0, verbose_name='现在价格')), 59 | ('step_price', models.FloatField(default=0.1, verbose_name='每次出价价格')), 60 | ('goods_brief', models.TextField(max_length=500, verbose_name='商品简短描述')), 61 | ('goods_desc', markdownx.models.MarkdownxField(verbose_name='商品描述')), 62 | ('ship_free', models.BooleanField(default=True, verbose_name='是否承担运费')), 63 | ('goods_front_image', models.ImageField(upload_to='goods/front/image', verbose_name='封面图')), 64 | ('is_new', models.BooleanField(default=False, verbose_name='是否新品')), 65 | ('is_hot', models.BooleanField(default=False, verbose_name='是否热拍')), 66 | ('is_sell', models.BooleanField(default=False, verbose_name='是否拍出')), 67 | ('periods', models.IntegerField(verbose_name='期数')), 68 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='goods.Category', 69 | verbose_name='商品类目')), 70 | ], 71 | options={ 72 | 'verbose_name': '商品', 73 | 'verbose_name_plural': '商品', 74 | }, 75 | ), 76 | migrations.CreateModel( 77 | name='GoodsImage', 78 | fields=[ 79 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 80 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 81 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 82 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 83 | ('index', models.IntegerField(verbose_name='图片顺序')), 84 | ('image', models.ImageField(upload_to='goods/detail/image', verbose_name='图片')), 85 | ('goods', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='goods.Goods', 86 | verbose_name='商品')), 87 | ], 88 | options={ 89 | 'verbose_name': '商品图片', 90 | 'verbose_name_plural': '商品图片', 91 | }, 92 | ), 93 | migrations.CreateModel( 94 | name='RoomDetail', 95 | fields=[ 96 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 97 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 98 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 99 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 100 | ('name', models.CharField(max_length=100, verbose_name='房间名字')), 101 | ('onlookers', models.IntegerField(verbose_name='围观人数')), 102 | ('bidders', models.IntegerField(verbose_name='出价人数')), 103 | ('goods', 104 | models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.Goods', verbose_name='商品')), 105 | ], 106 | options={ 107 | 'verbose_name': '房间详情', 108 | 'verbose_name_plural': '房间详情', 109 | }, 110 | ), 111 | migrations.AddField( 112 | model_name='bidrecord', 113 | name='goods', 114 | field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='goods.Goods', 115 | verbose_name='商品'), 116 | ), 117 | ] 118 | -------------------------------------------------------------------------------- /goods/migrations/0002_bidrecord_profile.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [ 12 | ('goods', '0001_initial'), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='bidrecord', 19 | name='profile', 20 | field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL, 21 | verbose_name='出价人'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /goods/migrations/0003_auto_20181110_1759.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-10 17:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('goods', '0002_bidrecord_profile'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name='goods', 14 | name='step_price', 15 | field=models.FloatField(default=1, verbose_name='每次出价价格'), 16 | ), 17 | migrations.AlterField( 18 | model_name='goodsimage', 19 | name='index', 20 | field=models.IntegerField(default=0, verbose_name='图片顺序'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /goods/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/goods/migrations/__init__.py -------------------------------------------------------------------------------- /goods/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from markdownx.models import MarkdownxField 3 | 4 | from core.models import CoreModel 5 | from users.models import Profile 6 | 7 | 8 | class Category(CoreModel): 9 | name = models.CharField(max_length=15, blank=True, null=True, verbose_name='类型名称') 10 | image = models.ImageField(upload_to='category/image/', verbose_name='类型的图片') 11 | index = models.IntegerField(verbose_name='排序') 12 | 13 | class Meta: 14 | verbose_name = '类型' 15 | verbose_name_plural = verbose_name 16 | 17 | def __str__(self): 18 | return self.name 19 | 20 | 21 | class Goods(CoreModel): 22 | goods_id = models.CharField(max_length=64, unique=True, db_index=True, verbose_name='商品id') 23 | category = models.ForeignKey(Category, models.DO_NOTHING, verbose_name="商品类目") 24 | goods_sn = models.CharField(max_length=50, blank=True, null=True, default="", verbose_name="商品唯一货号") 25 | name = models.CharField(max_length=100, verbose_name="商品名") 26 | click_num = models.IntegerField(default=0, verbose_name="点击数") 27 | fav_num = models.IntegerField(default=0, verbose_name="收藏数") 28 | market_price = models.FloatField(default=0.00, verbose_name="市场价格") 29 | now_price = models.FloatField(default=0.00, verbose_name="现在价格") 30 | step_price = models.FloatField(default=1, verbose_name='每次出价价格') 31 | goods_brief = models.TextField(max_length=500, verbose_name="商品简短描述") 32 | goods_desc = MarkdownxField(verbose_name='商品描述') 33 | ship_free = models.BooleanField(default=True, verbose_name="是否承担运费") 34 | goods_front_image = models.ImageField(upload_to="goods/front/image", verbose_name="封面图") 35 | is_new = models.BooleanField(default=False, verbose_name="是否新品") 36 | is_hot = models.BooleanField(default=False, verbose_name="是否热拍") 37 | is_sell = models.BooleanField(default=False, verbose_name="是否拍出") 38 | periods = models.IntegerField(verbose_name='期数') 39 | 40 | class Meta: 41 | verbose_name = '商品' 42 | verbose_name_plural = verbose_name 43 | 44 | def __str__(self): 45 | return self.name 46 | 47 | 48 | class GoodsImage(CoreModel): 49 | """ 50 | 商品轮播图 51 | """ 52 | index = models.IntegerField(default=0, verbose_name='图片顺序') 53 | goods = models.ForeignKey(Goods, models.DO_NOTHING, verbose_name="商品") 54 | image = models.ImageField(upload_to="goods/detail/image", verbose_name="图片") 55 | 56 | class Meta: 57 | verbose_name = '商品图片' 58 | verbose_name_plural = verbose_name 59 | 60 | def __str__(self): 61 | return self.goods.name 62 | 63 | 64 | class RoomDetail(CoreModel): 65 | name = models.CharField(max_length=100, verbose_name='房间名字') 66 | goods = models.ForeignKey(Goods, models.CASCADE, verbose_name='商品') 67 | onlookers = models.IntegerField(verbose_name='围观人数') 68 | bidders = models.IntegerField(verbose_name='出价人数') 69 | 70 | class Meta: 71 | verbose_name = '房间详情' 72 | verbose_name_plural = verbose_name 73 | 74 | def __str__(self): 75 | return self.name 76 | 77 | 78 | class BidRecord(CoreModel): 79 | goods = models.ForeignKey(Goods, models.DO_NOTHING, verbose_name='商品') 80 | profile = models.ForeignKey(Profile, models.DO_NOTHING, verbose_name='出价人') 81 | is_out = models.BooleanField(default=True, verbose_name='是否出局') 82 | 83 | class Meta: 84 | verbose_name = '出价记录' 85 | verbose_name_plural = verbose_name 86 | 87 | def __str__(self): 88 | return '{}---{}'.format(self.goods.name, self.profile.username) 89 | -------------------------------------------------------------------------------- /goods/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from goods.models import Category, Goods, GoodsImage 4 | 5 | 6 | class GoodsImageSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = GoodsImage 9 | exclude = ( 10 | 'id', 11 | 'is_delete', 12 | 'created_time', 13 | 'updated_time', 14 | 'goods' 15 | ) 16 | 17 | 18 | class GoodsCategorySerializer(serializers.ModelSerializer): 19 | class Meta: 20 | model = Goods 21 | exclude = ( 22 | 'id', 23 | 'is_delete', 24 | 'created_time', 25 | 'updated_time', 26 | 'goods_sn', 27 | 'category', 28 | ) 29 | 30 | 31 | class CategorySerializer(serializers.ModelSerializer): 32 | class Meta: 33 | model = Category 34 | exclude = ( 35 | 'id', 36 | 'is_delete', 37 | 'created_time', 38 | 'updated_time', 39 | ) 40 | 41 | 42 | class GoodsDetailSerializer(serializers.ModelSerializer): 43 | category = CategorySerializer() 44 | 45 | class Meta: 46 | model = Goods 47 | exclude = ( 48 | 'id', 49 | 'is_delete', 50 | 'created_time', 51 | 'updated_time', 52 | 'goods_sn', 53 | ) 54 | depth = 1 55 | -------------------------------------------------------------------------------- /goods/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /goods/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from goods.views import BidAPIView, GoodsCategoryDetailAPIView, GoodsDetailAPIView, GoodsListAPIView, NewsAPIView 4 | 5 | urlpatterns = [ 6 | path('list', GoodsListAPIView.as_view()), 7 | path('detail', GoodsDetailAPIView.as_view()), 8 | path('category/detail', GoodsCategoryDetailAPIView.as_view()), 9 | path('category/list', GoodsListAPIView.as_view()), 10 | path('news', NewsAPIView.as_view()), 11 | path('bid', BidAPIView.as_view()), 12 | ] 13 | -------------------------------------------------------------------------------- /goods/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAdminUser, IsAuthenticated 2 | from rest_framework.views import APIView 3 | 4 | from goods.models import Goods, GoodsImage 5 | from goods.serializers import GoodsCategorySerializer, GoodsDetailSerializer, GoodsImageSerializer 6 | from h_utils.errors import ErrBaseParams, ErrBaseNotData, ErrGoodsNotFound, ErrGoodsBidding, ErrBaseServer, ErrGoodsBid 7 | from h_utils.response_extra import success_request, error_request 8 | from h_utils.serializers_extra import serializer_data, serializer_list_data 9 | from h_utils.success import array_success, dict_success, normal_success 10 | from users.models import Balance 11 | 12 | 13 | class GoodsCategoryDetailAPIView(APIView): 14 | 15 | @staticmethod 16 | def get(request): 17 | try: 18 | category_id = request.query_params['category_id'] 19 | except KeyError: 20 | return error_request(ErrBaseParams) 21 | 22 | goods_list = Goods.objects.filter(category_id=int(category_id)) 23 | if not goods_list: 24 | return error_request(ErrBaseNotData) 25 | 26 | return_data = array_success() 27 | return_data['data'] = serializer_list_data(goods_list, GoodsCategorySerializer, request) 28 | return success_request(return_data) 29 | 30 | 31 | class GoodsDetailAPIView(APIView): 32 | 33 | @staticmethod 34 | def get(request): 35 | try: 36 | goods_id = request.query_params['goods_id'] 37 | except KeyError: 38 | return error_request(ErrBaseParams) 39 | 40 | goods = Goods.objects.filter(goods_id=goods_id).first() 41 | 42 | if not goods: 43 | return error_request(ErrBaseNotData) 44 | 45 | goods_image_list = GoodsImage.objects.filter(goods_id=goods.id).all() 46 | return_data = dict_success() 47 | return_data['data'] = serializer_data(goods, GoodsDetailSerializer, request) 48 | return_data['data']['goods_images'] = serializer_list_data(goods_image_list, GoodsImageSerializer, request) 49 | return success_request(return_data) 50 | 51 | 52 | class GoodsListAPIView(APIView): 53 | 54 | @staticmethod 55 | def get(request): 56 | goods_list = Goods.objects.all() 57 | 58 | if not goods_list: 59 | return error_request(ErrGoodsNotFound) 60 | 61 | return_data = array_success() 62 | return_data['data'] = serializer_list_data(goods_list, GoodsDetailSerializer, request) 63 | return success_request(return_data) 64 | 65 | 66 | class NewsAPIView(APIView): 67 | 68 | @staticmethod 69 | def get(request): 70 | pass 71 | 72 | 73 | bid_array = [] 74 | 75 | 76 | class BidAPIView(APIView): 77 | permission_classes = (IsAuthenticated,) or (IsAdminUser,) 78 | 79 | def can_bid(self, goods, balance): 80 | if balance.balance >= goods.step_price: 81 | return True 82 | return False 83 | 84 | def check_bid(self, request): 85 | # 检查是否在支付数组里面 86 | if request.user in bid_array: 87 | return error_request(ErrGoodsBidding) 88 | 89 | # 添加对象到支付数组 90 | bid_array.append(request.user) 91 | 92 | # 从支付数组里面移除对象 93 | def remove_bid(self, request): 94 | bid_array.remove(request.user) 95 | 96 | def post(self, request): 97 | try: 98 | goods_id = request.data['goods_id'] 99 | except KeyError: 100 | return error_request(ErrBaseParams) 101 | 102 | self.check_bid(request) 103 | 104 | goods = Goods.objects.filter(goods_id=goods_id).first() 105 | balance = Balance.objects.filter(user__username=request.user).first() 106 | 107 | if not balance or not goods: 108 | self.remove_bid(request) 109 | return error_request(ErrBaseServer) 110 | 111 | if not self.can_bid(goods, balance): 112 | self.remove_bid(request) 113 | return error_request(ErrGoodsBid) 114 | 115 | balance.balance -= goods.step_price 116 | goods.now_price += 0.1 117 | balance.save() 118 | goods.save() 119 | self.remove_bid(request) 120 | return_data = normal_success() 121 | return_data['msg'] = '出价成功' 122 | return success_request(return_data) 123 | -------------------------------------------------------------------------------- /h_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/h_utils/__init__.py -------------------------------------------------------------------------------- /h_utils/errors.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | /* 0 - 500 请求错误*/ 4 | ErrSysBadRequest = &ServerError{400, "Bad Request"} 5 | ErrSysPageNotFound = &ServerError{404, "Api not Found"} 6 | ErrSysServer = &ServerError{500, "服务器内部错误"} 7 | ErrSysSocket = &ServerError{600, "Socket连接错误"} 8 | 9 | /* 400-500 Base错误 */ 10 | ErrBaseParams = &ServerError{401, "参数错误"} 11 | ErrBaseInputData = &ServerError{402, "数据输入错误"} 12 | ErrBaseDatabase = &ServerError{403, "数据库错误"} 13 | ErrBaseOpenFile = &ServerError{405, "服务器内部错误"} 14 | ErrBaseWriteFile = &ServerError{406, "服务器内部错误"} 15 | ErrBaseIllegalPermission = &ServerError{407, "非法权限"} 16 | ErrBaseParmsFormat = &ServerError{408, "参数传递不符合规范"} 17 | ErrBaseDataNotFound = &ServerError{409, "找不到该数据"} 18 | ErrBaseFrequentRequests = &ServerError{410, "频率超过了限制,请休息片刻在请求"} 19 | ErrBaseUnknown = &ServerError{411, "未知错误"} 20 | 21 | /* 500 -600 用户错误 */ 22 | ErrUserNotLogin = &ServerError{501, "没有登录"} 23 | ErrUserAlreadyExists = &ServerError{502, "用户已存在"} 24 | ErrUserDoesNotExist = &ServerError{503, "用户不存在"} 25 | ErrUserTokenCreate = &ServerError{504, "生成token失败"} 26 | ErrUserNoToken = &ServerError{505, "token not found"} 27 | ErrUserTokenExpired = &ServerError{506, "token过期"} 28 | ErrUserTokenNotValidYet = &ServerError{507, "token无法通过验证"} 29 | ErrUserTokenMalformed = &ServerError{507, "token格式错误"} 30 | ErrUserTokenInvalid = &ServerError{508, "token无效"} 31 | ErrUserPassword = &ServerError{509, "密码错误"} 32 | ErrUserBalance = &ServerError{510, "余额不足"} 33 | ErrUserUnusual = &ServerError{511, "账户异常"} 34 | 35 | // 商城对应的错误码 36 | ErrGoodsUnusual = &ServerError{601, "商品当前价格更新异常"} 37 | ErrGoodsBid = &ServerError{602, "正在出价,请勿重复操作"} 38 | ) 39 | """ 40 | # 0 - 500 请求错误 41 | ErrSysBadRequest = {'code': 400, 'msg': 'Bad Request'} 42 | ErrSysPageNotFound = {'code': 404, 'msg': 'page not found'} 43 | ErrSysApiNotFound = {'code': 404, 'msg': 'api not found'} 44 | 45 | # 500-600 Base错误 46 | ErrBaseServer = {'code': 500, 'msg': '服务器错误'} 47 | ErrBaseParams = {'code': 501, 'msg': '参数错误'} 48 | ErrBaseNotData = {'code': 502, 'msg': '暂无数据'} 49 | ErrBaseIllegalPermission = {'code': 503, 'msg': '非法权限'} 50 | 51 | # 600 - 700 User错误 52 | ErrUserDoesNotExist = {'code': 601, 'msg': "用户不存在"} 53 | ErrUserDoesExist = {'code': 602, 'msg': "用户存在"} 54 | ErrUserPasswordIncorrect = {'code': 603, 'msg': "密码错误"} 55 | ErrUserSamePassword = {'code': 604, 'msg': "新旧密码相同"} 56 | ErrVerificationCodeForResettingThePasswordIsIncorrect = {'code': 605, 'msg': "重置密码的验证码错误"} 57 | ErrUserNotSupportedForLogin = {'code': 606, 'msg': "暂不支持此方式登录"} 58 | ErrUserNotAccessTheValidate = {'code': 607, 'msg': "信息没有通过验证"} 59 | 60 | # 700 - 800 商品错误 61 | ErrGoodsNotFound = {'code': 701, 'msg': '找不到商品'} 62 | ErrGoodsBid = {'code': 702, 'msg': '余额不足'} 63 | ErrGoodsBidding = {'code': 703, 'msg': '正在出价,请勿重复操作'} 64 | -------------------------------------------------------------------------------- /h_utils/errors_handler.py: -------------------------------------------------------------------------------- 1 | from h_utils.response_extra import err_base_server, err_sys_api_not_found 2 | 3 | 4 | def error404(request, exception, template_name=''): 5 | return err_sys_api_not_found() 6 | 7 | 8 | def error500(request, exception, template_name=''): 9 | return err_base_server() 10 | -------------------------------------------------------------------------------- /h_utils/generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | from h_utils.validator import is_goods_id, is_uid 5 | 6 | 7 | def gen_random_string(size=6, chars=string.ascii_letters + string.digits): 8 | return ''.join(random.choice(chars) for _ in range(size)) 9 | 10 | 11 | def gen_uid(): 12 | zero_to_nine = string.digits 13 | one_to_nine = zero_to_nine[1:] 14 | 15 | def _uid(): 16 | first = gen_random_string(1, one_to_nine) 17 | others = gen_random_string(6, zero_to_nine) 18 | return first + others 19 | 20 | while True: 21 | uid = _uid() 22 | if not is_uid(uid): 23 | return uid 24 | 25 | 26 | def gen_goods_id(): 27 | while True: 28 | goods_id = gen_random_string(64) 29 | if not is_goods_id(goods_id): 30 | return goods_id 31 | 32 | 33 | def gen_ip(request): 34 | if request.META.get('HTTP_X_FORWARDED_FOR'): 35 | ip = request.META['HTTP_X_FORWARDED_FOR'] 36 | else: 37 | ip = request.META['REMOTE_ADDR'] 38 | return ip 39 | -------------------------------------------------------------------------------- /h_utils/mutex.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | bid_mutex = threading.Lock() 4 | -------------------------------------------------------------------------------- /h_utils/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | 4 | class IsOwner(BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | return obj.owner == request.user 7 | -------------------------------------------------------------------------------- /h_utils/response_extra.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.response import Response 3 | 4 | 5 | class ResponseExtra: 6 | 7 | @staticmethod 8 | def send_bad_response(e): 9 | return Response(e, status=status.HTTP_400_BAD_REQUEST) 10 | 11 | @staticmethod 12 | def send_ok_response(d): 13 | return Response(d, status=status.HTTP_200_OK) 14 | 15 | 16 | # ========== 17 | # 成功 18 | # ========== 19 | def success_request(data): 20 | return ResponseExtra.send_ok_response(data) 21 | 22 | 23 | # ========== 24 | # 错误 25 | # ========== 26 | def error_request(data): 27 | return ResponseExtra.send_bad_response(data) 28 | 29 | # 30 | # # bad request 31 | # def err_sys_bad_request(): 32 | # return error_request(ErrSysBadRequest) 33 | # 34 | # 35 | # # 找不到页面 36 | # def err_sys_page_not_found(): 37 | # return error_request(ErrSysPageNotFound) 38 | # 39 | # 40 | # # 找不到路由 41 | # def err_sys_api_not_found(): 42 | # return error_request(ErrSysApiNotFound) 43 | # 44 | # 45 | # # 服务器内部错误 46 | # def err_base_server(): 47 | # return error_request(ErrBaseServer) 48 | # 49 | # 50 | # # 参数错误 51 | # def err_base_params(): 52 | # return error_request(ErrBaseParams) 53 | # 54 | # 55 | # # 暂无数据 56 | # def err_base_not_data(): 57 | # return error_request(ErrBaseNotData) 58 | # 59 | # 60 | # # 暂无数据 61 | # def err_base_illegal_permission(): 62 | # return error_request(ErrBaseIllegalPermission) 63 | # 64 | # 65 | # # 用户不存在 66 | # def err_user_does_not_exist(): 67 | # return error_request(ErrUserDoesNotExist) 68 | # 69 | # 70 | # # 用户已经存在 71 | # def err_user_does_exist(): 72 | # return error_request(ErrUserDoesExist) 73 | # 74 | # 75 | # # 密码错误 76 | # def err_user_password_incorrect(): 77 | # return error_request(ErrUserPasswordIncorrect) 78 | # 79 | # 80 | # # 相同密码 81 | # def err_user_same_password(): 82 | # return error_request(ErrUserSamePassword) 83 | # 84 | # 85 | # # 重置密码的验证码错误 86 | # def err_user_reset_password(): 87 | # return error_request(ErrUserResetPassword) 88 | # 89 | # 90 | # # 不支持此方式登录 91 | # def err_user_not_supported_for_login(): 92 | # return error_request(ErrUserNotSupportedForLogin) 93 | # 94 | # 95 | # # 信息没有通过验证 96 | # def err_user_not_access_the_validate(): 97 | # return error_request(ErrUserNotAccessTheValidate) 98 | # 99 | # 100 | # # 暂无商品 101 | # def err_goods_not_found(): 102 | # return error_request(ErrGoodsNotFound) 103 | # 104 | # 105 | # # 余额不足 106 | # def err_goods_bid(): 107 | # return error_request(ErrGoodsBid) 108 | # 109 | # 110 | # # 正在出价 111 | # def err_goods_bidding(): 112 | # return error_request(ErrGoodsBidding) 113 | -------------------------------------------------------------------------------- /h_utils/scheduler_extra.py: -------------------------------------------------------------------------------- 1 | from apscheduler.schedulers.background import BackgroundScheduler 2 | 3 | APS_SCHEDULER_CONFIG = { 4 | 'jobstores': { 5 | 'default': {'type': 'sqlalchemy', 'url': 'sqlite:///job.sqlite3'}, 6 | }, 7 | 'executors': { 8 | 'default': {'type': 'processpool', 'max_workers': 10} 9 | }, 10 | 'job_defaults': { 11 | 'coalesce': True, 12 | 'max_instances': 5, 13 | 'misfire_grace_time': 30 14 | }, 15 | 'timezone': 'Asia/Shanghai' 16 | } 17 | 18 | 19 | class Scheduler: 20 | _scheduler = BackgroundScheduler() 21 | _scheduler.configure(APS_SCHEDULER_CONFIG) 22 | _is_start = False 23 | 24 | def start_work(self): 25 | if not self._is_start: 26 | self._is_start = True 27 | self._scheduler.start() 28 | 29 | def stop_work(self): 30 | if self._is_start: 31 | self._is_start = False 32 | self._scheduler.shutdown() 33 | 34 | def add_job(self, func, time, job_name): 35 | self._scheduler.add_job(func, trigger='interval', seconds=time, id=job_name) 36 | self._scheduler.print_jobs() 37 | 38 | def re_add_job(self, func, time, job_name): 39 | self._scheduler.reschedule_job(job_name, trigger="interval", seconds=time) 40 | self._scheduler.print_jobs() 41 | 42 | def remove_job(self, job_name): 43 | self._scheduler.remove_job(job_name) 44 | self._scheduler.print_jobs() 45 | 46 | 47 | scheduler = Scheduler() 48 | scheduler.start_work() 49 | 50 | """ 51 | {"id":"1","route":"heartbeat","req_data":""} 52 | """ 53 | -------------------------------------------------------------------------------- /h_utils/serializers_extra.py: -------------------------------------------------------------------------------- 1 | foreign_key_set = ('goods', 'category', 'user', 'balance', 'category_id') 2 | 3 | 4 | def serializer_list_data(data, serializer, request=None): 5 | return_data = serializer(data, many=True, context={'request': request}).data 6 | for value in return_data: 7 | for k, v in value.items(): 8 | if value[k] is None: 9 | if k in foreign_key_set: 10 | value[k] = 0 11 | else: 12 | value[k] = '' 13 | return return_data 14 | 15 | 16 | def serializer_data(data, serializer, request=None): 17 | return_data = serializer(data, context={'request': request}).data 18 | for key, value in return_data.items(): 19 | if return_data[key] is None: 20 | if key in foreign_key_set: 21 | return_data[key] = 0 22 | else: 23 | return_data[key] = '' 24 | return return_data 25 | -------------------------------------------------------------------------------- /h_utils/success.py: -------------------------------------------------------------------------------- 1 | def array_success(): 2 | return {'code': 200, 'data': []} 3 | 4 | 5 | def dict_success(): 6 | return {'code': 200, 'data': {}} 7 | 8 | 9 | def normal_success(): 10 | return {'code': 200, 'msg': ''} 11 | -------------------------------------------------------------------------------- /h_utils/validator.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.contrib.auth import get_user_model 4 | 5 | from goods.models import Goods 6 | from users.models import Profile 7 | 8 | User = get_user_model() 9 | 10 | 11 | def is_empty(value): 12 | return value is None 13 | 14 | 15 | def is_phone(phone_number): 16 | if not phone_number or re.match('^1([34578]\d{9})$', phone_number) is None: 17 | return False 18 | return True 19 | 20 | 21 | def is_goods_id(goods_id): 22 | return Goods.objects.filter(goods_id=goods_id).exists() 23 | 24 | 25 | def is_uid(uid): 26 | return Profile.objects.filter(uid=uid).exists() 27 | 28 | 29 | def is_code(code): 30 | return code == '66666' 31 | 32 | 33 | def is_password(password): 34 | if not password or len(password) < 6 or len(password) > 30: 35 | return False 36 | else: 37 | return True 38 | -------------------------------------------------------------------------------- /h_utils/websocket_extra.py: -------------------------------------------------------------------------------- 1 | import json 2 | from json import JSONDecodeError 3 | 4 | from asgiref.sync import async_to_sync 5 | from channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer 6 | 7 | from h_utils.scheduler_extra import scheduler 8 | 9 | 10 | class WebsocketConsumerExtra(WebsocketConsumer): 11 | room_name = 'hall' 12 | text_data_json = {} 13 | bytes_data_json = {} 14 | heartbeat_time_out = 10 15 | 16 | def connect(self): 17 | scheduler.add_job(self.close, self.heartbeat_time_out, self.channel_name) 18 | async_to_sync(self.channel_layer.group_add)( 19 | self.room_name, 20 | self.channel_name 21 | ) 22 | self.accept() 23 | 24 | def no_route(self, event): 25 | self.send(text_data='找不到该路由') 26 | self.close() 27 | 28 | def heartbeat(self, event): 29 | message = event['message'] 30 | scheduler.re_add_job(self.close, self.heartbeat_time_out, self.channel_name) 31 | self.send(text_data=message) 32 | 33 | def parse_data(self, data): 34 | try: 35 | self.text_data_json = dict(json.loads(data)) 36 | except JSONDecodeError: 37 | return {'route': 'no_route'} 38 | except ValueError: 39 | return {'route': 'no_route'} 40 | return self.text_data_json 41 | 42 | def disconnect(self, close_code): 43 | async_to_sync(self.channel_layer.group_discard)( 44 | self.room_name, 45 | self.channel_name 46 | ) 47 | scheduler.remove_job(self.channel_name) 48 | 49 | 50 | class AsyncWebsocketConsumerExtra(AsyncWebsocketConsumer): 51 | room_name = 'hall' 52 | text_data_json = {} 53 | bytes_data_json = {} 54 | 55 | async def connect(self): 56 | scheduler.add_job(self.close, 5, self.channel_name) 57 | await self.channel_layer.group_add( 58 | self.room_name, 59 | self.channel_name 60 | ) 61 | await self.accept() 62 | 63 | async def no_route(self, event): 64 | await self.send(text_data='找不到该路由') 65 | await self.close() 66 | 67 | async def heartbeat(self, event): 68 | message = event['message'] 69 | scheduler.re_add_job(self.close, 5, self.channel_name) 70 | await self.send(text_data=message) 71 | 72 | def parse_data(self, data): 73 | try: 74 | self.text_data_json = dict(json.loads(data)) 75 | except JSONDecodeError: 76 | return {'route': 'no_route'} 77 | return self.text_data_json 78 | 79 | async def disconnect(self, close_code): 80 | await self.channel_layer.group_discard( 81 | self.room_name, 82 | self.channel_name 83 | ) 84 | scheduler.remove_job(self.channel_name) 85 | -------------------------------------------------------------------------------- /homes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/homes/__init__.py -------------------------------------------------------------------------------- /homes/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import ModelAdmin, register 2 | 3 | from homes.models import Banner, Topic 4 | 5 | 6 | @register(Banner) 7 | class BannerAdmin(ModelAdmin): 8 | pass 9 | 10 | 11 | @register(Topic) 12 | class TopicAdmin(ModelAdmin): 13 | pass 14 | -------------------------------------------------------------------------------- /homes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HomesConfig(AppConfig): 5 | name = 'homes' 6 | verbose_name = '首页管理' 7 | -------------------------------------------------------------------------------- /homes/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | initial = True 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Banner', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 18 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 19 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 20 | ('goods_id', models.CharField(blank=True, default='', max_length=64, null=True, verbose_name='商品ID')), 21 | ('title', models.CharField(max_length=20, verbose_name='标题')), 22 | ('image', models.ImageField(upload_to='homes/banner/image', verbose_name='轮播图片')), 23 | ('index', models.IntegerField(default=0, verbose_name='轮播顺序')), 24 | ('url', models.CharField(blank=True, max_length=512, null=True, verbose_name='url')), 25 | ('activity', models.CharField(blank=True, max_length=30, null=True, verbose_name='跳转控制器')), 26 | ], 27 | options={ 28 | 'verbose_name': '首页Banner', 29 | 'verbose_name_plural': '首页Banner', 30 | }, 31 | ), 32 | migrations.CreateModel( 33 | name='Topic', 34 | fields=[ 35 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 36 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 37 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 38 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 39 | ('category_id', models.IntegerField(blank=True, default=0, null=True, verbose_name='类型ID')), 40 | ('title', models.CharField(max_length=20, verbose_name='标题')), 41 | ('icon', models.ImageField(upload_to='homes/topic/icon', verbose_name='图标')), 42 | ('index', models.IntegerField(default=0, verbose_name='排列顺序')), 43 | ('url', models.CharField(blank=True, max_length=512, null=True, verbose_name='url')), 44 | ('activity', models.CharField(blank=True, max_length=30, null=True, verbose_name='跳转控制器')), 45 | ], 46 | options={ 47 | 'verbose_name': '首页Topic', 48 | 'verbose_name_plural': '首页Topic', 49 | }, 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /homes/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/homes/migrations/__init__.py -------------------------------------------------------------------------------- /homes/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from core.models import CoreModel 4 | 5 | 6 | class Banner(CoreModel): 7 | """ 8 | 轮播的商品 9 | """ 10 | goods_id = models.CharField(max_length=64, null=True, blank=True, default='', verbose_name='商品ID') 11 | title = models.CharField(max_length=20, verbose_name='标题') 12 | image = models.ImageField(upload_to='homes/banner/image', verbose_name="轮播图片") 13 | index = models.IntegerField(default=0, verbose_name="轮播顺序") 14 | url = models.CharField(max_length=512, null=True, blank=True, verbose_name='url') 15 | activity = models.CharField(max_length=30, null=True, blank=True, verbose_name="跳转控制器") 16 | 17 | class Meta: 18 | verbose_name = '首页Banner' 19 | verbose_name_plural = verbose_name 20 | 21 | def __str__(self): 22 | return self.title 23 | 24 | 25 | class Topic(CoreModel): 26 | """ 27 | 首页banner图下面的主题 28 | """ 29 | category_id = models.IntegerField(null=True, blank=True, default=0, verbose_name='类型ID') 30 | title = models.CharField(max_length=20, verbose_name='标题') 31 | icon = models.ImageField(upload_to='homes/topic/icon', verbose_name="图标") 32 | index = models.IntegerField(default=0, verbose_name="排列顺序") 33 | url = models.CharField(max_length=512, null=True, blank=True, verbose_name='url') 34 | activity = models.CharField(max_length=30, null=True, blank=True, verbose_name="跳转控制器") 35 | 36 | class Meta: 37 | verbose_name = '首页Topic' 38 | verbose_name_plural = verbose_name 39 | 40 | def __str__(self): 41 | return self.title 42 | -------------------------------------------------------------------------------- /homes/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from goods.models import BidRecord, Goods 4 | from homes.models import Banner, Topic 5 | 6 | 7 | class BannerSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = Banner 10 | fields = ( 11 | 'title', 12 | 'image', 13 | 'index', 14 | 'url', 15 | 'activity', 16 | 'goods_id', 17 | ) 18 | depth = 1 19 | 20 | 21 | class TopicSerializer(serializers.ModelSerializer): 22 | class Meta: 23 | model = Topic 24 | exclude = ( 25 | 'id', 26 | 'is_delete', 27 | 'created_time', 28 | 'updated_time', 29 | ) 30 | 31 | 32 | class RecommendSerializer(serializers.ModelSerializer): 33 | class Meta: 34 | model = Goods 35 | fields = ( 36 | 'name', 37 | 'goods_id', 38 | 'goods_front_image', 39 | 'now_price', 40 | ) 41 | 42 | 43 | class IAmShootingSerializer(serializers.ModelSerializer): 44 | class GoodsSerializer(serializers.ModelSerializer): 45 | class Meta: 46 | model = Goods 47 | fields = ( 48 | 'name', 49 | 'goods_id', 50 | 'goods_front_image', 51 | 'now_price', 52 | ) 53 | 54 | goods = GoodsSerializer() 55 | 56 | class Meta: 57 | model = BidRecord 58 | fields = ( 59 | 'goods', 60 | 'is_out' 61 | ) 62 | depth = 1 63 | 64 | 65 | class MyCollectionSerializer(serializers.ModelSerializer): 66 | class GoodsSerializer(serializers.ModelSerializer): 67 | class Meta: 68 | model = Goods 69 | fields = ( 70 | 'name', 71 | 'goods_id', 72 | 'goods_front_image', 73 | 'now_price', 74 | ) 75 | 76 | goods = GoodsSerializer() 77 | 78 | class Meta: 79 | model = BidRecord 80 | fields = ( 81 | 'goods', 82 | ) 83 | depth = 1 84 | -------------------------------------------------------------------------------- /homes/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /homes/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from homes.views import GuessYouLikeAPIView, IAmShootingAPIView, IndexAPIView, MyCollectionAPIView, RecommendAPIView 4 | 5 | urlpatterns = [ 6 | path('index', IndexAPIView.as_view()), 7 | path('recommend', RecommendAPIView.as_view()), 8 | path('guessYouLike', GuessYouLikeAPIView.as_view()), 9 | path('iAmShooting', IAmShootingAPIView.as_view()), 10 | path('myCollection', MyCollectionAPIView.as_view()), 11 | ] 12 | -------------------------------------------------------------------------------- /homes/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAdminUser, IsAuthenticated 2 | from rest_framework.views import APIView 3 | 4 | from goods.models import BidRecord, Goods 5 | from h_utils.errors import ErrBaseNotData 6 | from h_utils.response_extra import success_request, error_request 7 | from h_utils.serializers_extra import serializer_list_data 8 | from h_utils.success import array_success, dict_success 9 | from homes.models import Banner, Topic 10 | from homes.serializers import (BannerSerializer, 11 | IAmShootingSerializer, 12 | MyCollectionSerializer, 13 | RecommendSerializer, 14 | TopicSerializer) 15 | from user_operation.models import Collection 16 | 17 | 18 | class IndexAPIView(APIView): 19 | 20 | @staticmethod 21 | def get(request): 22 | banner_list = Banner.objects.all() 23 | topic_list = Topic.objects.all() 24 | 25 | if not banner_list and not topic_list: 26 | return error_request(ErrBaseNotData) 27 | 28 | return_data = dict_success() 29 | return_data['data']['banner'] = serializer_list_data(banner_list, BannerSerializer, request) 30 | return_data['data']['topic'] = serializer_list_data(topic_list, TopicSerializer, request) 31 | return success_request(return_data) 32 | 33 | 34 | class RecommendAPIView(APIView): 35 | 36 | @staticmethod 37 | def get(request): 38 | goods_list = Goods.objects.filter(is_hot=True).all() 39 | 40 | if not goods_list: 41 | return error_request(ErrBaseNotData) 42 | 43 | return_data = array_success() 44 | return_data['data'] = serializer_list_data(goods_list, RecommendSerializer, request) 45 | return success_request(return_data) 46 | 47 | 48 | class GuessYouLikeAPIView(APIView): 49 | 50 | @staticmethod 51 | def get(request): 52 | goods_list = Goods.objects.filter(is_new=True).all() 53 | 54 | if not goods_list: 55 | return error_request(ErrBaseNotData) 56 | 57 | return_data = array_success() 58 | return_data['data'] = serializer_list_data(goods_list, RecommendSerializer, request) 59 | return success_request(return_data) 60 | 61 | 62 | class IAmShootingAPIView(APIView): 63 | permission_classes = (IsAuthenticated,) or (IsAdminUser,) 64 | 65 | @staticmethod 66 | def get(request): 67 | br_list = BidRecord.objects.filter(profile__username=request.user).all() 68 | if not br_list: 69 | return error_request(ErrBaseNotData) 70 | return_data = array_success() 71 | return_data['data'] = serializer_list_data(br_list, IAmShootingSerializer, request) 72 | return success_request(return_data) 73 | 74 | 75 | class MyCollectionAPIView(APIView): 76 | permission_classes = (IsAuthenticated,) or (IsAdminUser,) 77 | 78 | @staticmethod 79 | def get(request): 80 | collection = Collection.objects.filter(user__username=request.user).all() 81 | 82 | if not collection: 83 | return error_request(ErrBaseNotData) 84 | 85 | return_data = array_success() 86 | data = serializer_list_data(collection, MyCollectionSerializer, request) 87 | print(data) 88 | return_data['data'] = None 89 | return success_request(return_data) 90 | 91 | @staticmethod 92 | def post(request): 93 | pass 94 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /msg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/msg/__init__.py -------------------------------------------------------------------------------- /msg/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.db import models 3 | from markdownx.widgets import AdminMarkdownxWidget 4 | 5 | from msg.models import Msg, MsgHandler 6 | 7 | 8 | @admin.register(Msg) 9 | class MsgAdmin(admin.ModelAdmin): 10 | formfield_overrides = { 11 | models.TextField: {'widget': AdminMarkdownxWidget}, 12 | } 13 | 14 | 15 | @admin.register(MsgHandler) 16 | class MsgHandler(admin.ModelAdmin): 17 | pass 18 | -------------------------------------------------------------------------------- /msg/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MsgConfig(AppConfig): 5 | name = 'msg' 6 | verbose_name = '消息' 7 | -------------------------------------------------------------------------------- /msg/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | import django.db.models.deletion 4 | import markdownx.models 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Msg', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 20 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 21 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 22 | ('title', models.CharField(max_length=50, verbose_name='标题')), 23 | ('msg_type', models.IntegerField(choices=[(1, '竞拍消息'), (2, '拍卖公告'), (3, '系统消息')], verbose_name='消息类型')), 24 | ('content', markdownx.models.MarkdownxField()), 25 | ('periods', models.IntegerField(verbose_name='公告期数')), 26 | ], 27 | options={ 28 | 'verbose_name': '消息', 29 | 'verbose_name_plural': '消息', 30 | }, 31 | ), 32 | migrations.CreateModel( 33 | name='MsgHandler', 34 | fields=[ 35 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 36 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 37 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 38 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 39 | ( 40 | 'msg', 41 | models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='msg.Msg', verbose_name='消息')), 42 | ], 43 | options={ 44 | 'verbose_name': '消息处理', 45 | 'verbose_name_plural': '消息处理', 46 | }, 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /msg/migrations/0002_auto_20181108_1437.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [ 12 | ('msg', '0001_initial'), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='msghandler', 19 | name='receiver', 20 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, 21 | related_name='receiver', to=settings.AUTH_USER_MODEL, verbose_name='收件人'), 22 | ), 23 | migrations.AddField( 24 | model_name='msghandler', 25 | name='sender', 26 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, 27 | related_name='sender', to=settings.AUTH_USER_MODEL, verbose_name='发送人'), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /msg/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/msg/migrations/__init__.py -------------------------------------------------------------------------------- /msg/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from markdownx.models import MarkdownxField 3 | 4 | from core.models import CoreModel 5 | from users.models import Profile 6 | 7 | 8 | class Msg(CoreModel): 9 | MSG_TYPE = ( 10 | (1, '竞拍消息'), 11 | (2, '拍卖公告'), 12 | (3, '系统消息'), 13 | ) 14 | title = models.CharField(max_length=50, verbose_name='标题') 15 | msg_type = models.IntegerField(choices=MSG_TYPE, verbose_name='消息类型') 16 | content = MarkdownxField() 17 | periods = models.IntegerField(verbose_name='公告期数') 18 | 19 | class Meta: 20 | verbose_name = '消息' 21 | verbose_name_plural = verbose_name 22 | 23 | def __str__(self): 24 | return self.title 25 | 26 | 27 | class MsgHandler(CoreModel): 28 | sender = models.ForeignKey(Profile, models.DO_NOTHING, 29 | null=True, blank=True, 30 | related_name='sender', 31 | verbose_name='发送人') 32 | receiver = models.ForeignKey(Profile, models.DO_NOTHING, 33 | null=True, blank=True, 34 | related_name='receiver', 35 | verbose_name='收件人') 36 | msg = models.ForeignKey(Msg, models.CASCADE, verbose_name='消息') 37 | 38 | class Meta: 39 | verbose_name = '消息处理' 40 | verbose_name_plural = verbose_name 41 | 42 | def __str__(self): 43 | return '{}--{}--{}'.format(self.sender.username, self.receiver.username, self.msg.title) 44 | -------------------------------------------------------------------------------- /msg/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | from msg.models import MsgHandler 4 | 5 | 6 | class IsMsgReceiver(BasePermission): 7 | def has_object_permission(self, request, view, obj): 8 | mh = MsgHandler.objects.filter(msg_id=obj).first() 9 | if not mh: 10 | return False 11 | return mh.receiver == request.user 12 | -------------------------------------------------------------------------------- /msg/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from msg.models import Msg 4 | 5 | 6 | class MsgListSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Msg 9 | fields = ( 10 | 'id', 11 | 'title', 12 | 'msg_type', 13 | ) 14 | 15 | 16 | class MsgSerializer(serializers.ModelSerializer): 17 | class Meta: 18 | model = Msg 19 | fields = ( 20 | 'id', 21 | 'title', 22 | 'msg_type', 23 | 'content', 24 | 'periods', 25 | ) 26 | -------------------------------------------------------------------------------- /msg/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /msg/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from msg.views import MsgAPIView, MsgDetailAPIView 4 | 5 | urlpatterns = [ 6 | path('list', MsgAPIView.as_view()), 7 | path('detail', MsgDetailAPIView.as_view()), 8 | ] 9 | -------------------------------------------------------------------------------- /msg/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAdminUser, IsAuthenticated 2 | from rest_framework.views import APIView 3 | 4 | from h_utils.errors import ErrBaseNotData, ErrBaseParams, ErrBaseIllegalPermission 5 | from h_utils.response_extra import success_request, error_request 6 | from h_utils.success import array_success, dict_success 7 | from msg.models import Msg, MsgHandler 8 | from msg.permissions import IsMsgReceiver 9 | from msg.serializers import MsgListSerializer, MsgSerializer 10 | 11 | 12 | class MsgAPIView(APIView): 13 | permission_classes = (IsAuthenticated,) or (IsAdminUser,) 14 | 15 | @staticmethod 16 | def get(request): 17 | msg_array = [] 18 | msg_list = MsgHandler.objects.filter(receiver__username=request.user.username).all() 19 | 20 | for msg in msg_list: 21 | msg_array.append(Msg.objects.filter(pk=msg.msg_id).first()) 22 | 23 | if not msg_array: 24 | return error_request(ErrBaseNotData) 25 | 26 | return_data = array_success() 27 | return_data['data'] = (MsgListSerializer(value).data for value in msg_array) 28 | return success_request(return_data) 29 | 30 | 31 | class MsgDetailAPIView(APIView): 32 | permission_classes = (IsAuthenticated, IsMsgReceiver) or (IsAdminUser,) 33 | 34 | def get(self, request): 35 | try: 36 | msg_id = request.query_params['msg_id'] 37 | except KeyError: 38 | return error_request(ErrBaseParams) 39 | 40 | msg = Msg.objects.filter(pk=msg_id).first() 41 | 42 | if not msg: 43 | return error_request(ErrBaseNotData) 44 | 45 | if self.check_object_permissions(request, msg_id): 46 | return error_request(ErrBaseIllegalPermission) 47 | 48 | return_data = dict_success() 49 | return_data['data'] = MsgSerializer(msg).data 50 | return success_request(return_data) 51 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | APScheduler==3.5.3 2 | asgiref==2.3.2 3 | async-timeout==3.0.1 4 | attrs==18.2.0 5 | autobahn==18.11.2 6 | Automat==0.7.0 7 | certifi==2018.11.29 8 | channels==2.1.5 9 | chardet==3.0.4 10 | constantly==15.1.0 11 | daphne==2.2.3 12 | Django==2.2.13 13 | django-markdownx==2.0.28 14 | djangorestframework==3.9.0 15 | djangorestframework-jwt==1.11.0 16 | hyperlink==18.0.0 17 | idna==2.7 18 | incremental==17.5.0 19 | Markdown==3.0.1 20 | Pillow==6.2.0 21 | PyHamcrest==1.9.0 22 | PyJWT==1.7.0 23 | pytz==2018.7 24 | requests==2.20.1 25 | six==1.11.0 26 | Twisted==18.9.0 27 | txaio==18.8.1 28 | tzlocal==1.5.1 29 | urllib3==1.24.1 30 | zope.interface==4.6.0 31 | -------------------------------------------------------------------------------- /restart.sh: -------------------------------------------------------------------------------- 1 | supervisorctl restart django_project -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | supervisorctl stop django_project -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/test/__init__.py -------------------------------------------------------------------------------- /test/test_int.py: -------------------------------------------------------------------------------- 1 | a = 'a' 2 | 3 | int(a) 4 | -------------------------------------------------------------------------------- /test/test_json.py: -------------------------------------------------------------------------------- 1 | a = 'qwe' 2 | 3 | print(dict(a)) 4 | -------------------------------------------------------------------------------- /test/test_random_str.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import string 4 | 5 | 6 | def randomString(n): 7 | return (''.join(map(lambda xx: (hex(ord(xx))[2:]), os.urandom(n))))[0:16] 8 | 9 | 10 | def gen_random_string(string_length): 11 | return ''.join(random.sample(string.ascii_letters + string.digits, string_length)) 12 | 13 | 14 | def id_generator(size=6, chars=string.ascii_letters + string.digits): 15 | return ''.join(random.choice(chars) for _ in range(size)) 16 | 17 | 18 | print(id_generator(64)) 19 | -------------------------------------------------------------------------------- /test/test_scheduler.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import datetime 3 | 4 | from apscheduler.schedulers.blocking import BlockingScheduler 5 | 6 | 7 | def aps_test(x): 8 | print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x) 9 | 10 | 11 | scheduler = BlockingScheduler() 12 | scheduler.add_job(func=aps_test, args=('定时任务',), trigger='cron', second='*/5') 13 | scheduler.add_job(func=aps_test, args=('一次性任务',), 14 | next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=12)) 15 | scheduler.add_job(func=aps_test, args=('循环任务',), trigger='interval', seconds=3) 16 | 17 | scheduler.start() 18 | -------------------------------------------------------------------------------- /user_operation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/user_operation/__init__.py -------------------------------------------------------------------------------- /user_operation/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from user_operation.models import Code, Collection 4 | 5 | 6 | @admin.register(Collection) 7 | class CollectionAdmin(admin.ModelAdmin): 8 | pass 9 | 10 | 11 | @admin.register(Code) 12 | class CodeAdmin(admin.ModelAdmin): 13 | pass 14 | -------------------------------------------------------------------------------- /user_operation/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserOperationConfig(AppConfig): 5 | name = 'user_operation' 6 | verbose_name = '用户操作' 7 | -------------------------------------------------------------------------------- /user_operation/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | initial = True 9 | 10 | dependencies = [ 11 | ('goods', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Code', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 20 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 21 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 22 | ('code', models.CharField(max_length=8, verbose_name='验证码')), 23 | ('action', models.IntegerField(choices=[(1, '注册'), (2, '重置密码'), (3, '提现')], verbose_name='动作')), 24 | ], 25 | options={ 26 | 'verbose_name': '验证码', 27 | 'verbose_name_plural': '验证码', 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='Collection', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 35 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 36 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 37 | ('goods', 38 | models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.Goods', verbose_name='商品')), 39 | ], 40 | options={ 41 | 'verbose_name': '收藏', 42 | 'verbose_name_plural': '收藏', 43 | }, 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /user_operation/migrations/0002_auto_20181108_1437.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [ 12 | ('user_operation', '0001_initial'), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='collection', 19 | name='user', 20 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, 21 | verbose_name='用户'), 22 | ), 23 | migrations.AddField( 24 | model_name='code', 25 | name='user', 26 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, 27 | verbose_name='用户'), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /user_operation/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/user_operation/migrations/__init__.py -------------------------------------------------------------------------------- /user_operation/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from core.models import CoreModel 4 | from goods.models import Goods 5 | from users.models import Profile 6 | 7 | 8 | class Collection(CoreModel): 9 | goods = models.ForeignKey(Goods, models.CASCADE, verbose_name='商品') 10 | user = models.ForeignKey(Profile, models.CASCADE, verbose_name='用户') 11 | 12 | class Meta: 13 | verbose_name = '收藏' 14 | verbose_name_plural = verbose_name 15 | 16 | def __str__(self): 17 | return '{}--{}'.format(self.user.username, self.goods.name) 18 | 19 | 20 | class Code(CoreModel): 21 | ACTION = ( 22 | (1, '注册'), 23 | (2, '重置密码'), 24 | (3, '提现'), 25 | ) 26 | code = models.CharField(max_length=8, verbose_name='验证码') 27 | action = models.IntegerField(choices=ACTION, verbose_name='动作') 28 | user = models.ForeignKey(Profile, models.CASCADE, verbose_name='用户') 29 | 30 | class Meta: 31 | verbose_name = '验证码' 32 | verbose_name_plural = verbose_name 33 | 34 | def __str__(self): 35 | return '{}--{}'.format(self.user.username, self.created_time) 36 | -------------------------------------------------------------------------------- /user_operation/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /user_operation/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from user_operation.views import FavoriteProductAPIView 4 | 5 | urlpatterns = [ 6 | path('favoriteProduct', FavoriteProductAPIView.as_view()), 7 | ] 8 | -------------------------------------------------------------------------------- /user_operation/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAdminUser, IsAuthenticated 2 | from rest_framework.views import APIView 3 | 4 | from h_utils.permissions import IsOwner 5 | from h_utils.success import array_success 6 | from user_operation.models import Collection 7 | 8 | 9 | class FavoriteProductAPIView(APIView): 10 | permission_classes = (IsAuthenticated, IsOwner) or (IsAdminUser,) 11 | 12 | @staticmethod 13 | def get(request): 14 | collection_list = Collection.objects.filter(user__username=request.user).all() 15 | return_data = array_success() 16 | return_data['data'] = None 17 | return 18 | 19 | @staticmethod 20 | def post(request): 21 | pass 22 | -------------------------------------------------------------------------------- /users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/users/__init__.py -------------------------------------------------------------------------------- /users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import ModelAdmin, register 2 | 3 | from users.models import Balance, PayAction, Profile 4 | 5 | 6 | @register(Profile) 7 | class ProfileAdmin(ModelAdmin): 8 | pass 9 | 10 | 11 | @register(Balance) 12 | class BalanceAdmin(ModelAdmin): 13 | pass 14 | 15 | 16 | @register(PayAction) 17 | class PayActionAdmin(ModelAdmin): 18 | pass 19 | -------------------------------------------------------------------------------- /users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | verbose_name = '用户管理' 7 | -------------------------------------------------------------------------------- /users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-08 14:37 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | import django.utils.timezone 6 | from django.conf import settings 7 | from django.db import migrations, models 8 | 9 | 10 | class Migration(migrations.Migration): 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0009_alter_user_last_name_max_length'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Profile', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('password', models.CharField(max_length=128, verbose_name='password')), 23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 24 | ('is_superuser', models.BooleanField(default=False, 25 | help_text='Designates that this user has all permissions without explicitly assigning them.', 26 | verbose_name='superuser status')), 27 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, 28 | help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', 29 | max_length=150, unique=True, 30 | validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], 31 | verbose_name='username')), 32 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 33 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 34 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 35 | ('is_staff', models.BooleanField(default=False, 36 | help_text='Designates whether the user can log into this admin site.', 37 | verbose_name='staff status')), 38 | ('is_active', models.BooleanField(default=True, 39 | help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', 40 | verbose_name='active')), 41 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 42 | ('nick', 43 | models.CharField(blank=True, default='', help_text='昵称', max_length=30, null=True, verbose_name='昵称')), 44 | ('birthday', models.DateField(blank=True, help_text='出生年月', null=True, verbose_name='出生年月')), 45 | ('gender', models.IntegerField(choices=[(0, '女'), (1, '男')], default=0, verbose_name='性别')), 46 | ('platform', 47 | models.CharField(choices=[('ios', '苹果用户'), ('android', '安卓设备'), ('core', '未知设备')], default='core', 48 | max_length=20, verbose_name='登录设备类型')), 49 | ('device_id', models.CharField(blank=True, default='', max_length=40, null=True, verbose_name='设备id')), 50 | ('register_ip', 51 | models.CharField(blank=True, default='', max_length=20, null=True, verbose_name='注册时候的IP')), 52 | ('phone', models.CharField(blank=True, default='', max_length=11, null=True, verbose_name='手机号码')), 53 | ('level', 54 | models.CharField(choices=[(0, '普通用户'), (1, '一级用户')], default=0, max_length=2, verbose_name='用户等级')), 55 | ('oauth_openid', models.CharField(default='', max_length=128, verbose_name='第三方openid')), 56 | ('oauth_avatar', models.CharField(default='', max_length=200, verbose_name='第三方头像')), 57 | ('oauth_from', 58 | models.CharField(choices=[('weixin', '微信'), ('weibo', '微博'), ('qq', 'QQ'), ('phone', '手机')], 59 | default='', max_length=10, verbose_name='验证来源')), 60 | ('oauth_userinfo', models.TextField(default='', verbose_name='来自第三方平台的用户额外信息')), 61 | ('oauth_time', models.DateTimeField(blank=True, null=True, verbose_name='绑定第三方帐号时间')), 62 | ('groups', models.ManyToManyField(blank=True, 63 | help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', 64 | related_name='user_set', related_query_name='user', to='auth.Group', 65 | verbose_name='groups')), 66 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', 67 | related_name='user_set', related_query_name='user', 68 | to='auth.Permission', verbose_name='user permissions')), 69 | ], 70 | options={ 71 | 'verbose_name': '用户', 72 | 'verbose_name_plural': '用户', 73 | }, 74 | managers=[ 75 | ('objects', django.contrib.auth.models.UserManager()), 76 | ], 77 | ), 78 | migrations.CreateModel( 79 | name='Balance', 80 | fields=[ 81 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 82 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 83 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 84 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 85 | ('balance', models.FloatField(default=0.0, verbose_name='当前余额')), 86 | ('total', models.FloatField(default=0.0, verbose_name='总充值')), 87 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, 88 | verbose_name='用户')), 89 | ], 90 | options={ 91 | 'verbose_name': '用户余额', 92 | 'verbose_name_plural': '用户余额', 93 | }, 94 | ), 95 | migrations.CreateModel( 96 | name='PayAction', 97 | fields=[ 98 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 99 | ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 100 | ('updated_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), 101 | ('is_delete', models.BooleanField(default=False, verbose_name='是否被删除')), 102 | ('action', models.IntegerField(choices=[(1, '充值'), (2, '出价'), (3, '提现')], verbose_name='操作')), 103 | ('amount', models.IntegerField(verbose_name='具体金额')), 104 | ('balance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Balance', 105 | verbose_name='用于余额')), 106 | ], 107 | options={ 108 | 'verbose_name': '用户支付操作', 109 | 'verbose_name_plural': '用户支付操作', 110 | }, 111 | ), 112 | ] 113 | -------------------------------------------------------------------------------- /users/migrations/0002_auto_20181110_1759.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-10 17:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('users', '0001_initial'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='profile', 14 | name='avatar', 15 | field=models.ImageField(blank=True, null=True, upload_to='users/profile/avatar', verbose_name='用户头像'), 16 | ), 17 | migrations.AddField( 18 | model_name='profile', 19 | name='uid', 20 | field=models.CharField(blank=True, db_index=True, max_length=7, null=True, unique=True, verbose_name='uid'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /users/migrations/0003_profile_is_third.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-11-10 19:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('users', '0002_auto_20181110_1759'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='profile', 14 | name='is_third', 15 | field=models.BooleanField(blank=True, default=False, null=True, verbose_name='是否都第三方登录'), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /users/migrations/0004_auto_20190117_1222.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2019-01-17 12:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('users', '0003_profile_is_third'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name='profile', 14 | name='is_third', 15 | field=models.BooleanField(default=False, verbose_name='是否都第三方登录'), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luoxh8/django_project/115ea947be5f6b464913d479c753011277d18360/users/migrations/__init__.py -------------------------------------------------------------------------------- /users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | 4 | from core.models import CoreModel 5 | 6 | 7 | class Profile(AbstractUser): 8 | LEVEL = ((0, '普通用户'), 9 | (1, '一级用户')) 10 | 11 | PLATFORM = (('ios', '苹果用户'), 12 | ('android', '安卓设备'), 13 | ('core', '未知设备')) 14 | 15 | OAUTH_FORM = (('weixin', '微信'), 16 | ('weibo', '微博'), 17 | ('qq', 'QQ'), 18 | ('phone', '手机')) 19 | 20 | GENDER = ((0, "女"), 21 | (1, "男")) 22 | 23 | uid = models.CharField(max_length=7, blank=True, null=True, 24 | unique=True, db_index=True, verbose_name='uid') 25 | nick = models.CharField(max_length=30, null=True, blank=True, 26 | default='', help_text='昵称', verbose_name='昵称') 27 | birthday = models.DateField( 28 | null=True, blank=True, help_text='出生年月', verbose_name="出生年月") 29 | gender = models.IntegerField(choices=GENDER, default=0, verbose_name="性别") 30 | platform = models.CharField( 31 | max_length=20, choices=PLATFORM, default='core', verbose_name='登录设备类型') 32 | device_id = models.CharField( 33 | max_length=40, null=True, blank=True, default='', verbose_name='设备id') 34 | register_ip = models.CharField( 35 | max_length=20, null=True, blank=True, default='', verbose_name='注册时候的IP') 36 | phone = models.CharField(max_length=11, default='', 37 | null=True, blank=True, verbose_name='手机号码') 38 | level = models.CharField(max_length=2, choices=LEVEL, 39 | default=0, verbose_name='用户等级') 40 | avatar = models.ImageField( 41 | upload_to='users/profile/avatar', null=True, blank=True, verbose_name='用户头像') 42 | 43 | # 第三方登录字段 44 | is_third = models.BooleanField( 45 | default=False, verbose_name='是否都第三方登录') 46 | oauth_openid = models.CharField( 47 | max_length=128, default='', verbose_name='第三方openid') 48 | oauth_avatar = models.CharField( 49 | max_length=200, default='', verbose_name='第三方头像') 50 | oauth_from = models.CharField( 51 | choices=OAUTH_FORM, max_length=10, default='', verbose_name='验证来源') 52 | oauth_userinfo = models.TextField( 53 | default='', verbose_name='来自第三方平台的用户额外信息') 54 | oauth_time = models.DateTimeField( 55 | null=True, blank=True, verbose_name='绑定第三方帐号时间') 56 | 57 | class Meta: 58 | verbose_name = "用户" 59 | verbose_name_plural = verbose_name 60 | 61 | def __str__(self): 62 | return self.username 63 | 64 | 65 | class Balance(CoreModel): 66 | user = models.OneToOneField(Profile, models.CASCADE, verbose_name='用户') 67 | balance = models.FloatField(default=0.00, verbose_name='当前余额') 68 | total = models.FloatField(default=0.00, verbose_name='总充值') 69 | 70 | class Meta: 71 | verbose_name = "用户余额" 72 | verbose_name_plural = verbose_name 73 | 74 | def __str__(self): 75 | return self.user.username 76 | 77 | 78 | class PayAction(CoreModel): 79 | ACTION = ((1, '充值'), 80 | (2, '出价'), 81 | (3, '提现')) 82 | 83 | balance = models.ForeignKey(Balance, models.CASCADE, verbose_name='用于余额') 84 | action = models.IntegerField(choices=ACTION, verbose_name='操作') 85 | amount = models.IntegerField(verbose_name='具体金额') 86 | 87 | class Meta: 88 | verbose_name = "用户支付操作" 89 | verbose_name_plural = verbose_name 90 | 91 | def __str__(self): 92 | return '{action}--{name}--{amount}'.format(action=self.action, name=self.balance.user.username, 93 | amount=self.amount) 94 | -------------------------------------------------------------------------------- /users/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | 4 | class IsInfoSelf(BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | """ 7 | 只在viewset的时候可以起作用 8 | :param request: 请求 9 | :param view: 视图 10 | :param obj: 对象 11 | :return: 12 | """ 13 | return obj.username == request.user 14 | -------------------------------------------------------------------------------- /users/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from users.models import Profile 4 | 5 | 6 | class ProfileSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Profile 9 | fields = ( 10 | 'uid', 11 | 'gender', 12 | 'nick', 13 | 'oauth_avatar', 14 | "phone", 15 | 'avatar', 16 | 'oauth_avatar', 17 | ) 18 | -------------------------------------------------------------------------------- /users/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework_jwt.views import refresh_jwt_token 3 | 4 | from users.views import (ChangePasswordAPIView, 5 | InfoAPIView, 6 | PhoneLogin, 7 | PhoneRegister, 8 | ResetPasswordAPIView, 9 | ThirdLoginAPIView) 10 | 11 | urlpatterns = [ 12 | path('phone/login', PhoneLogin.as_view()), # 手机登录 13 | path('phone/register', PhoneRegister.as_view()), # 手机注册 14 | path('login', ThirdLoginAPIView.as_view()), # 第三方注册 15 | path('changePassword', ChangePasswordAPIView.as_view()), # 修改密码 16 | path('resetPassword', ResetPasswordAPIView.as_view()), # 重置密码 17 | path('info', InfoAPIView.as_view()), # 用户信息 18 | path('tokenRefresh', refresh_jwt_token), # 刷新token 19 | ] 20 | -------------------------------------------------------------------------------- /users/views.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime 3 | 4 | import requests 5 | from django.contrib.auth.hashers import check_password, make_password 6 | from rest_framework.permissions import IsAdminUser, IsAuthenticated 7 | from rest_framework.views import APIView 8 | from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler 9 | 10 | from h_utils.errors import ErrBaseParams, ErrUserDoesExist, ErrUserDoesNotExist, ErrUserPasswordIncorrect, \ 11 | ErrUserNotSupportedForLogin, ErrUserNotAccessTheValidate, ErrUserSamePassword, \ 12 | ErrVerificationCodeForResettingThePasswordIsIncorrect 13 | from h_utils.generator import gen_ip, gen_random_string, gen_uid 14 | from h_utils.response_extra import error_request, success_request 15 | from h_utils.serializers_extra import serializer_data 16 | from h_utils.success import dict_success, normal_success 17 | from h_utils.validator import is_code, is_password, is_phone 18 | from users.models import Balance, Profile 19 | from users.serializers import ProfileSerializer 20 | 21 | 22 | class PhoneRegister(APIView): 23 | 24 | @staticmethod 25 | def post(request): 26 | try: 27 | # get 28 | platform = request.query_params['platform'] 29 | device_id = request.query_params['device_id'] 30 | 31 | # post 32 | phone = request.data['phone'] 33 | password = request.data['password'] 34 | code = request.data['code'] 35 | except KeyError: 36 | return error_request(ErrBaseParams) 37 | 38 | if not is_phone(phone): 39 | return error_request(ErrBaseParams) 40 | 41 | if not is_password(password): 42 | return error_request(ErrBaseParams) 43 | 44 | if not is_code(code): 45 | return error_request(ErrBaseParams) 46 | 47 | exists = Profile.objects.filter(phone=phone).exists() 48 | 49 | if exists: 50 | return error_request(ErrUserDoesExist) 51 | 52 | user = Profile.objects.create(uid=gen_uid(), 53 | phone=phone, 54 | password=make_password(password), 55 | platform=platform, 56 | username='手机用户' + phone, 57 | register_ip=gen_ip(request), 58 | device_id=device_id, ) 59 | 60 | Balance.objects.create( 61 | user=user, 62 | ) 63 | 64 | return_data = dict_success() 65 | return_data['msg'] = '注册成功' 66 | return_data['data'] = serializer_data(user, ProfileSerializer, request) 67 | return_data['data']['token'] = jwt_encode_handler(jwt_payload_handler(user)) 68 | return success_request(return_data) 69 | 70 | 71 | class PhoneLogin(APIView): 72 | 73 | @staticmethod 74 | def post(request): 75 | try: 76 | phone = request.data['phone'] 77 | password = request.data['password'] 78 | except KeyError: 79 | return error_request(ErrBaseParams) 80 | 81 | if not is_phone(phone): 82 | return error_request(ErrBaseParams) 83 | 84 | if not is_password(password): 85 | return error_request(ErrBaseParams) 86 | 87 | user = Profile.objects.filter(phone=phone).first() 88 | 89 | if not user: 90 | return error_request(ErrUserDoesNotExist) 91 | 92 | if not check_password(password, user.password): 93 | return error_request(ErrUserPasswordIncorrect) 94 | 95 | return_data = dict_success() 96 | return_data['msg'] = '登录成功' 97 | return_data['data'] = serializer_data(user, ProfileSerializer, request) 98 | return_data['data']['token'] = jwt_encode_handler(jwt_payload_handler(user)) 99 | return success_request(return_data) 100 | 101 | 102 | class ThirdLoginAPIView(APIView): 103 | login_zone = ['sinaweibo', 'qzone', 'wechat'] 104 | username = None 105 | 106 | def check_in_login_zone(self, oauth_from): 107 | if oauth_from not in self.login_zone: 108 | return False, error_request(ErrUserNotSupportedForLogin) 109 | 110 | def validate_and_get_userinfo(self, oauth_from, access_token, oauth_openid): 111 | if oauth_from == self.login_zone[0]: 112 | ret = requests.get('https://api.weibo.com/2/users/show.json', 113 | params={'access_token': access_token, 114 | 'uid': int(oauth_openid)}) 115 | ret = ret.json() 116 | self.username = '微博用户' + oauth_openid + gen_random_string() 117 | return ret.get('error_code', 0) == 0, ret 118 | elif oauth_from == self.login_zone[1]: 119 | ret = requests.get('https://graph.qq.com/user/get_user_info', 120 | params={'access_token': access_token, 121 | 'openid': oauth_openid, 122 | 'oauth_consumer_key': '1106741162', 123 | 'format': 'json'}) 124 | ret = ret.json() 125 | self.username = 'qq用户' + oauth_openid + gen_random_string() 126 | return ret['ret'] == 0, ret 127 | elif oauth_from == self.login_zone[2]: 128 | ret = requests.get('https://api.weixin.qq.com/sns/userinfo', 129 | params={'access_token': access_token, 130 | 'openid': oauth_openid}) 131 | ret = ret.json() 132 | self.username = '微信用户' + oauth_openid + gen_random_string() 133 | return ret.get('errcode', 0) == 0, ret 134 | 135 | def clear_nick(self, nick): 136 | name_group = [] 137 | for c in nick: 138 | regex = r'^[( )(\u4e00-\u9fa5)(\u0030-\u0039)(\u0041-\u005a)(\u0061-\u007a)(' \ 139 | r'~!@#¥%…&()—“”:?》《·=、】【‘’;、。,!_:`;/,<>})(\-\*\+\|\{\$\^\(\)\?\.\[\])]+$' 140 | if not re.match(regex, c): 141 | name_group.append('*') 142 | name_group.append(c) 143 | nick = ''.join(name_group) 144 | return nick 145 | 146 | def post(self, request): 147 | try: 148 | 149 | # get 150 | platform = request.query_params['platform'] 151 | device_id = request.query_params['device_id'] 152 | 153 | # post 154 | gender = request.data['gender'] 155 | oauth_from = request.data['oauth_from'] 156 | oauth_openid = request.data['open_id'] 157 | access_token = request.data['token'] 158 | nick = request.data['nick'] 159 | avatar = request.data['avatar'] 160 | 161 | gender = int(gender) 162 | 163 | except KeyError: 164 | return error_request(ErrBaseParams) 165 | except ValueError: 166 | return error_request(ErrBaseParams) 167 | 168 | is_in_login_zone, return_data = self.check_in_login_zone(oauth_from) 169 | if not is_in_login_zone: 170 | return return_data 171 | 172 | is_valid, oauth_userinfo = self.validate_and_get_userinfo(oauth_from, access_token, oauth_openid) 173 | 174 | if not is_valid: 175 | return error_request(ErrUserNotAccessTheValidate) 176 | 177 | user = Profile.objects.filter(oauth_openid=oauth_openid).first() 178 | if user: 179 | return_data = dict_success() 180 | return_data['msg'] = '登录成功' 181 | return_data['data'] = serializer_data(user, ProfileSerializer, request) 182 | return_data['data']['token'] = jwt_encode_handler(jwt_payload_handler(user)) 183 | return success_request(return_data) 184 | 185 | user = Profile.objects.create(uid=gen_uid(), 186 | username=self.username, 187 | nick=self.clear_nick(nick), 188 | register_ip=gen_ip(request), 189 | device_id=device_id, 190 | platform=platform, 191 | avatar=avatar, 192 | gender=gender, 193 | oauth_openid=oauth_openid, 194 | oauth_from=oauth_from, 195 | oauth_userinfo=oauth_userinfo, 196 | oauth_time=datetime.now()) 197 | Balance.objects.create( 198 | user=user, 199 | ) 200 | return_data = dict_success() 201 | return_data['msg'] = '登录成功' 202 | return_data['data'] = serializer_data(user, ProfileSerializer, request) 203 | return_data['data']['token'] = jwt_encode_handler(jwt_payload_handler(user)) 204 | return success_request(return_data) 205 | 206 | 207 | class InfoAPIView(APIView): 208 | permission_classes = (IsAuthenticated,) or (IsAdminUser,) 209 | 210 | @staticmethod 211 | def get(request): 212 | user = Profile.objects.filter(username=request.user).first() 213 | 214 | if not user: 215 | return error_request(ErrUserDoesNotExist) 216 | 217 | return_data = dict_success() 218 | return_data['data'] = serializer_data(user, ProfileSerializer, request) 219 | return success_request(return_data) 220 | 221 | 222 | class ChangePasswordAPIView(APIView): 223 | permission_classes = (IsAuthenticated,) or (IsAdminUser,) 224 | 225 | @staticmethod 226 | def post(request): 227 | try: 228 | old_password = request.data['old_password'] 229 | new_password = request.data['new_password'] 230 | except KeyError: 231 | return error_request(ErrBaseParams) 232 | 233 | if not is_password(new_password): 234 | return error_request(ErrBaseParams) 235 | 236 | if old_password == new_password: 237 | return error_request(ErrUserSamePassword) 238 | 239 | user = Profile.objects.filter(username=request.user).first() 240 | 241 | if not check_password(old_password, user.password): 242 | return error_request(ErrUserPasswordIncorrect) 243 | 244 | user.password = make_password(new_password) 245 | user.save() 246 | 247 | return_data = normal_success() 248 | return_data['msg'] = '修改成功' 249 | return success_request(return_data) 250 | 251 | 252 | class ResetPasswordAPIView(APIView): 253 | 254 | @staticmethod 255 | def post(request): 256 | try: 257 | phone = request.data['phone'] 258 | password = request.data['password'] 259 | code = request.data['code'] 260 | except KeyError: 261 | return error_request(ErrBaseParams) 262 | 263 | if not is_code(code): 264 | return error_request(ErrVerificationCodeForResettingThePasswordIsIncorrect) 265 | 266 | user = Profile.objects.filter(phone=phone).first() 267 | user.password = make_password(password) 268 | user.save() 269 | 270 | return_data = normal_success() 271 | return_data['msg'] = '重置成功' 272 | return success_request(return_data) 273 | 274 | 275 | class RechargeAPIView(APIView): 276 | permission_classes = (IsAuthenticated,) or (IsAdminUser,) 277 | 278 | @staticmethod 279 | def post(request): 280 | pass 281 | --------------------------------------------------------------------------------