Django其他tips

时间:Nov. 19, 2019 分类:

目录:

支持跨域

由于js的同源策略,询问后端是否允许进行跨域访问,采用Middle中间件支持

from django.utils.deprecation import MiddlewareMixin

class MyCors(MiddlewareMixin):
    def process_response(self, request, response):
        response["Access-Control-Allow-Origin"] = "*"
        if request.method == "OPTIONS":
            response["Access-Control-Allow-Headers"] = "Content-Type"
            response["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE"
        return response

然后在setting的MIDDLEWARE中配置即可

或者

安装django-cors-headers

pip install django-cors-headers

配置settings.py

INSTALLED_APPS = [
    ......
    'corsheaders',
    ......
]

MIDDLEWARE = [
    ......
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ......
]

CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True

Admin支持上传图片

setting中配置

# Media配置
MEDIA_URL = "media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

model的字段配置

course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='课程的图片')

url

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/course/', include("course.urls")),
    # media路径配置
    url(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT})
]

ContentType

有以下两个表,水果,食物

class Fruit(models.Model):
    """
    id      name
    1       西瓜
    2       香蕉
    """
    name = models.CharField(max_length=32)


class Food(models.Model):
    """
    id      name
    1       旺仔小馒头
    2       卫龙辣条
    """
    name = models.CharField(max_length=32)

如果设置一个优惠券的表,关联这两个表,如果为每个表都都单独建一列的话就会造成数据表臃肿

class Coupon(models.Model):
    """
    id      title           food_id     fruit_id
    1       西瓜八折            1         null
    2       卫龙买一送一      null          1
    """
    title = models.CharField(max_length=32)
    food = models.ForeignKey(to="Food")
    fruit = models.ForeignKey(to="Fruit")

再用一个表来存一下

class Coupon(models.Model):
    title = models.CharField(max_length=32)
    table = models.ForeignKey(to="Mytables")
    object_id = models.IntegerField()


class MyTables(models.Model):
    """
    id      table_name
    1       Food
    2       Fruit
    """
    table_name = models.CharField(max_length=32)

table存储表名,object存储在对应表的id

django提供了ContentType

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation


class Fruit(models.Model):
    name = models.CharField(max_length=32)
    coupons = GenericRelation(to="Coupons")


class Food(models.Model):
    name = models.CharField(max_length=32)
    coupons = GenericRelation(to="Coupons")


class Coupon(models.Model):
    title = models.CharField(max_length=32)
    content_type = models.ForeignKey(to=ContentType)
    object_id = models.IntegerField()
    content_object = GenericForeignKey("content_type", "object_id")

使用

from .models import Book, Food, Fruit, Coupon
from django.contrib.contenttypes.models import ContentType


class TestView(APIView):
    def get(self, request):
        food_obj = Food.objects.filter(id=2).first()
        Coupon.objects.create(title="卫龙买一送一", content_object=food_obj)
        # 也可以写为
        # Coupon.objects.create(title="卫龙买一送一", content_type_id=9, object_id=2)
        # 这个content_type_id是在djangocontent

        # 对象绑定所有优惠券
        print(food_obj.conpons.all())
        # 查询优惠券绑定对象
        coupon_obj = Coupon.objects.filter(id=1).first()
        print(coupon_obj.content_object.name)

也可以

from .models import Book, Food, Fruit, Coupon
from django.contrib.contenttypes.models import ContentType


class TestView(APIView):
    def get(self, request):
        content_type_obj = ContentType.objects.filter(model="food").first()
        model_class = content_type_obj.model_class()
        print(content_type_obj.id)
        print(model_class)

重置django管理员密码

$ python manage.py shell
Python 2.7.5 (default, Nov  6 2016, 00:28:07) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user =User.objects.get(username='why')
>>> user.set_password('123456')
>>> user.save()

django事务

在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务

atomic提供两种方案实现事务

  • 装饰器:将整个视图作为一个事务
  • with:选择性将某些操作作为一个事务

装饰器方式

from django.db import transaction

@transaction.atomic
def viewfunc(request):
  # 这些代码会在一个事务中执行
  ...

with方式

from django.db import transaction

def viewfunc(request):
  # 这部分代码不在事务中,会被 Django 自动提交
  ......

  with transaction.atomic():
      # 这部分代码会在事务中执行

基本使用

from django.db import transaction

# 创建保存点
save_id = transaction.savepoint()

# 回滚到保存点
transaction.savepoint_rollback(save_id)

# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)

示例

class OrderCommitView(LoginRequiredJSONMixin, View):
    """订单提交"""

    def post(self, request):
        """保存订单信息和订单商品信息"""
        # 获取当前保存订单时需要的信息
        ......

        # 显式的开启一个事务
        with transaction.atomic():
            # 创建事务保存点
            save_id = transaction.savepoint()

            # 暴力回滚
            try:
                # 保存订单基本信息 OrderInfo(一)
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,
                    total_amount=Decimal('0'),
                    freight=Decimal('10.00'),
                    pay_method=pay_method,
                    status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else
                    OrderInfo.ORDER_STATUS_ENUM['UNSEND']
                )

                # 从redis读取购物车中被勾选的商品信息
                redis_conn = get_redis_connection('carts')
                redis_cart = redis_conn.hgetall('carts_%s' % user.id)
                selected = redis_conn.smembers('selected_%s' % user.id)
                carts = {}
                for sku_id in selected:
                    carts[int(sku_id)] = int(redis_cart[sku_id])
                sku_ids = carts.keys()

                # 遍历购物车中被勾选的商品信息
                for sku_id in sku_ids:
                    # 查询SKU信息
                    sku = SKU.objects.get(id=sku_id)
                    # 判断SKU库存
                    sku_count = carts[sku.id]
                    if sku_count > sku.stock:
                        # 出错就回滚
                        transaction.savepoint_rollback(save_id)
                        return http.JsonResponse({
                                  'code': RETCODE.STOCKERR, 
                                  'errmsg': '库存不足'})

                    # SKU减少库存,增加销量
                    sku.stock -= sku_count
                    sku.sales += sku_count
                    sku.save()

                    # 修改SPU销量
                    sku.goods.sales += sku_count
                    sku.goods.save()

                    # 保存订单商品信息 OrderGoods(多)
                    OrderGoods.objects.create(
                        order=order,
                        sku=sku,
                        count=sku_count,
                        price=sku.price,
                    )

                    # 保存商品订单中总价和总数量
                    order.total_count += sku_count
                    order.total_amount += (sku_count * sku.price)

                # 添加邮费和保存订单信息
                order.total_amount += order.freight
                order.save()
            except Exception as e:
                logger.error(e)
                transaction.savepoint_rollback(save_id)
                return http.JsonResponse({
                         'code': RETCODE.DBERR, 
                         'errmsg': '下单失败'})

            # 提交订单成功,显式的提交一次事务
            transaction.savepoint_commit(save_id)

        # 清除购物车中已结算的商品
        pl = redis_conn.pipeline()
        pl.hdel('carts_%s' % user.id, *selected)
        pl.srem('selected_%s' % user.id, *selected)
        pl.execute()

        # 响应提交订单结果
        return http.JsonResponse({'code': RETCODE.OK, 
                                  'errmsg': '下单成功', 
                                  'order_id': order.order_id}

django使用celery

项目/__init__

from .celery_config import app as celery_app
__all__ = ['celery_app'] 

项目/celery_config配置celery

import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gaea.settings')
app = Celery('gaea')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

项目/setting配置celery依赖配置

CELERY_BROKER_URL = 'redis://localhost'
CELERY_RESULT_BACKEND = 'redis://localhost'

tasks/tasks写一些异步任务

from celery import shared_task
import os
import requests
import time

@shared_task
def shell(command):
     pass

@shared_task
def callback_task(record_id):
     pass

日期范围搜索

大于某个时间: gt

now = datetime.datetime.now()
start = now – datetime.timedelta(hours=23, minutes=59, seconds=59)
yourobject.objects .filter(youdatetimcolumn__gt=start)

同理:

  • 大于等于某个时间:gte
  • 小于:lt
  • 小于等于:lte

查询时间段range

start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))

查询某年:year

Entry.objects.filter(pub_date__year=2005)

同理

  • 某月:month
  • 某天day:

可以配合Q进行查询

django日志

django日志设置

import logging

# 设置日志目录
LOGGING_DIR = "/home/work/logs/"
if not os.path.exists(LOGGING_DIR):
    os.mkdir(LOGGING_DIR)

# LOGGING
LOGGING  ={
    'version':1,
    'disable_existing_loggers':False,
    'formatters':{ #格式化
        'file':{
            'format':'[%(asctime)s][%(levelname)s][%(filename)s %(lineno)d] %(message)s',
            'datefmt':'%Y-%m-%d %H:%M:%S'
                },
        'console':{
            'format':'[%(asctime)s][%(levelname)s][%(pathname)s %(lineno)d] %(message)s',
            'datefmt':'%Y-%m-%d %H:%M:%S'
                }
    },
    'handlers':{ #处理器
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
            'formatter':'console'
        },
        'file':{
            'level':'INFO',
            'class':'logging.handlers.TimedRotatingFileHandler',
            'formatter':'file',
            'filename': os.path.join(LOGGING_DIR, 'gaea.log')
        }

    },
    'loggers':{ #记录器
        'django':{
            # 'handlers':['console','file'],
            'handlers':['file'],
            'level':'INFO',
            'propagate':False
        }

    }
}

django日志使用

import logging
logger = logging.getLogger('django')


logger.info("msg")

token

INSTALLED_APPS = [
    # 添加
    'rest_framework.authtoken',
]


REST_FRAMEWORK = {
    # 添加
    'DEFAULT_AUTHENTICATION_CLASSES': (                      
        'rest_framework.authentication.TokenAuthentication', 
        'rest_framework.authentication.SessionAuthentication',                  
    ),                                                                          
    #'DEFAULT_PERMISSION_CLASSES': (                                            
    #    'rest_framework.permissions.IsAuthenticated',                          
    #),                          
}
# 初始化token表
python3 manage.py makemigrations authtoken
python3 manage.py migrate
# 创建用户
python3 manage.py createsuperuser
# 获取token
python3 manage.py shell
from rest_framework.authtoken.models import Token
from accounts.models import User
u = User.objects.filter(username='admin').first()
t = Token.objects.create(user=u)
t.key

编码问题

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128) 在python2中,运行encode(‘utf-8’)时报此错误,是因为python2中,默认字符串编码为ascii,与Unicode冲突

python3中不会出现此问题,是因为python3中将字符串默认编码为Unicode。

python2中,在程序前加入三行代码,便可解决

import sys

reload(sys)

sys.setdefaultencoding('utf8')