Python django project template 一个携带常用模块的django的工程模板,为了快速启动一个工程进行开发,减少配置工程带来的工作量。
clone工程到本地
git clone https://github.com/kagxin/django-project-template.git
安装工程依赖包
pip install requirements.txt
修改settings/local.py 中的的mysql和redis地址
DATABASES = { 'default': { 'NAME': 'django_template', 'ENGINE': 'django.db.backends.mysql', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'TEST': { 'NAME': 'django_template_testdb', 'CHARSET': 'utf8' }, 'CHARSET': 'utf8' } } CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "PASSWORD": "" } }, "token": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/2", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "PASSWORD": "", } } }
执行数据库迁移
python manage.py migrate
启动工程
python manage.py runserver
浏览器访问测试地址
http://localhost:8000/api/v1/demo/
ok
{
"message": "hello world."
}- 环境变量区分站点类型
使用环境变量SITE_TYPE,来区分站点类型 例如使用,export SITE_TYPE=local,启动工程的时候,使用的是settings.py/local.py 中的配置
| SITE_TYPE | 站点类型 |
|---|---|
| local | 本地 (未加环境变量时的默认配置) |
| staging | 测试环境 |
| production | 生产环境 |
| ci | ci环境 |
- 重写drf mixin类的Response使http状态码为固定200, 格式固定为code,message,data三字段
使用 apps.utils.view 中view和viewset,返回的json格式为
{
"code": 0,
"message":"Success",
"data": {}
}- 使用http接口返回数据格式为固定为,code message data 三个字段
/api/v1/demo/article/?page_index=1
{
"code": 0,
"message": "Success",
"data": {
"count": 0,
"next": null,
"previous": null,
"results": []
}
}- 重写exception_handler,使其支持 CustomAPIException,方便raise 自定义异常
在你任何想要的地方终止程序
from rest_framework import serializers from apps.core.exceptions import CustomAPIException from apps.core import response_code from apps.demo.models import * class ReporterSerializer(serializers.ModelSerializer): full_name = serializers.CharField(required=True) def validate_full_name(self, value): if 'admin' in value.lower(): code, message = response_code.ERR_ADMIN_IN_FULLNAME raise CustomAPIException(code=code, message=message) # 终止程序返回ERR_ADMIN_IN_FULLNAME,错误信息 return value class Meta: model = Reporter fields = '__all__'
返回异常的格式
{
"code": 41001,
"message": "admin in fullname.",
"data": {}
}使用示例请见app demo
apps/demo 这个app是一个示例app,使用了apps.utils.view中的view和viewset,使用自定义的异常类
- setting.py DEFAULT_AUTHENTICATION_CLASSES 中使用 apps.utils.exception_handler.exception_handler
REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ( 'rest_framework_filters.backends.RestFrameworkFilterBackend', ), 'EXCEPTION_HANDLER': 'apps.utils.exception_handler.exception_handler', 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'apps.utils.jwt.authentication.CheckTokenChangeAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), 'DATE_FORMAT': '%Y-%m-%d', 'DATE_INPUT_FORMATS': ( '%Y-%m-%d', ISO_8601 ), 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S', 'DATETIME_INPUT_FORMATS': ( '%Y-%m-%d %H:%M:%S', ISO_8601 ) }
- 获取,刷新,校验token接口使用apps.utils.jwt.views 中的三个对应接口
from django.urls import path, include from apps.utils.jwt.views import obtain_jwt_check_token_change, refresh_jwt_check_token_change,\ verify_jwt_check_token_change urlpatterns = [ path('token/', obtain_jwt_check_token_change), path('token/refresh/', refresh_jwt_check_token_change), path('token/verify/', verify_jwt_check_token_change), ]
- ok 这时如果token改变则接口会返回
{
"code": 40006,
"message": "Token has been changed.",
"data": {}
}自定义user在account.model.UserPrfile, 可以对其删减字段重新migrate
from django.contrib.auth.models import AbstractUser from django.db import models class UserProfile(AbstractUser): """ 用户 """ name = models.CharField(max_length=30, null=True, blank=True, verbose_name="姓名") birthday = models.DateField(null=True, blank=True, verbose_name="出生年月") gender = models.CharField(max_length=6, choices=(("male", u"男"), ("female", "女")), default="female", verbose_name="性别", blank=True) mobile = models.CharField(null=True, blank=True, max_length=11, verbose_name="电话") image = models.ImageField(blank=True, null=True, upload_to="image/%Y/%m/%d") class Meta: verbose_name = "用户" verbose_name_plural = verbose_name def __str__(self): return self.username
添加aliyun oss上传storage django-aliyun-oss2-storage
配置文件中把对应的配置添加完成之后,可以到admin使用account UserPrfile中的image字段测试 使用时,补全配置参数并解开DEFAULT_FILE_STORAGE的注释
## oss ACCESS_KEY_ID = "****" ACCESS_KEY_SECRET = "****" END_POINT = "oss-cn-shanghai.aliyuncs.com" BUCKET_NAME = "****" ALIYUN_OSS_CNAME = "" # 自定义域名,如果不需要可以不填写 BUCKET_ACL_TYPE = "private" # private, public-read, public-read-write # mediafile将自动上传 DEFAULT_FILE_STORAGE = 'aliyun_oss2_storage.backends.AliyunMediaStorage' # staticfile将自动上传 # STATICFILES_STORAGE = 'aliyun_oss2_storage.backends.AliyunStaticStorage'
重写get_paginated_response,使response格式满足code,message,data。 修改paginate_queryset,当页面大于最后一页时(或无效页面),返回最后一页数据
from rest_framework.pagination import PageNumberPagination from apps.utils.response import simple_response from django.core.paginator import InvalidPage class DefaultResultsSetPagination(PageNumberPagination): page_size = 20 #默认单页面20个 page_size_query_param = 'page_size' #可通过该参数自己设置每页多少个 page_query_param = "page_index" #t通过该参数选择多少页 max_page_size = 100 #通过url修改参数 最大单页面100个 def get_paginated_response(self, data): reponse_data = super().get_paginated_response(data).data return simple_response(data=reponse_data) def paginate_queryset(self, queryset, request, view=None): """ Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view. """ page_size = self.get_page_size(request) if not page_size: return None paginator = self.django_paginator_class(queryset, page_size) page_number = request.query_params.get(self.page_query_param, 1) if page_number in self.last_page_strings: page_number = paginator.num_pages try: self.page = paginator.page(page_number) except InvalidPage: # 无效的页码返回最后一页数据 self.page = paginator.page(paginator.num_pages) if paginator.num_pages > 1 and self.template is not None: # The browsable API should display pagination controls. self.display_page_controls = True self.request = request return list(self.page)
请求响应的示例
/api/v1/demo/article/?page_size=1&page_index=1
{
"code": 0,
"message": "Success",
"data": {
"count": 0,
"next": null,
"previous": null,
"results": []
}
}在工程目录
scripts/目录下添加新的 .py 文件, .py中要包含run()函数作为入口
- run 测试script
python manage.py runscript sayhello
apps/utils/middleware/request_log_middleware.py 日志输出在logs/request_info.log中
MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] + [ 'apps.utils.middleware.request_log_middleware.RequestLogMiddleware' ]
celery worker
celery -A settings worker -l info
celery beat
celery -A settings beat -l info
celery flower
使用 celery flower 作为监控程序的时候,要使用rabbitmq作为broker
CELERY_BROKER_URL = 'amqp://root:root@localhost:5672//'
启动 flower
celery flower --broker=amqp://root:root@localhost:5672// --broker_api=amqp://root:root@localhost:15672/api/
- build 镜像,并启动
docker-compose up
- 执行迁移,添加admin用户,收集静态文件
docker exec -it django_template_app sh -c "cd /home/docker/code/ && python3 manage.py migrate && python3 manage.py collectstatic && python3 manage.py createsuperuser"
- ok 访问7000端口
- 在线示例 username: admin password: admin123