Django框架 - 19 Django框架总结

Django框架总结

Web应用

Django框架 - 19 Django框架总结_第1张图片

问题1:描述一个Web应用的工作流程。(如上图所示)

问题2:描述项目的物理架构。(上图中补充反向代理服务器、负载均衡服务器、数据库服务器、文件服务器、缓存服务器、防火墙等,每个节点都有可能是多节点构成的集群)

问题3:描述Django项目的工作流程。(如下图所示)
Django框架 - 19 Django框架总结_第2张图片

MVC架构模式

Django框架 - 19 Django框架总结_第3张图片

问题1:为什么要使用MVC架构模式?(模型和视图解耦合)

问题2:MVC架构中每个部分的作用?(如上图所示)

HTTP请求和响应

HTTP请求

HTTP请求 = 请求行+请求头+空行+[消息体]
Django框架 - 19 Django框架总结_第4张图片

HTTP响应 = 响应行+响应头+空行+消息体
Django框架 - 19 Django框架总结_第5张图片

  1. HTTPRequest对象的属性和方法:
    • method
    • path / get_full_path()
    • scheme / is_secure() / get_host() / get_port()
    • META / COOKIES
    • GET / POST / FILES
    • get_signed_cookie()
    • is_ajax()
    • body / content_type / encoding
  2. 中间件添加的属性:

    • session / user / site
  3. HttpResponse对象的属性和方法:
    • set_cookie() / set_signed_cookie() / delete_cookie()
    • __setitem__ / __getitem__ / __delitem__
    • charset / content / status_code
  4. JsonResponseHttpResponse的子类型)对象

    
    class HouseJsonEncoder(JsonEncoder):
    
        def default(self, o):
            # 定义如何将对象转成dict类型并返回这个字典
            pass
    
    >>> from django.http import JsonResponse
    >>> response = JsonResponse({'foo': 'bar'})
    >>> response.content
    
    >>> response = JsonResponse([1, 2, 3], safe=False)
    >>> response = JsonResponse(house, encoder=HouseJsonEncoder)
    
    >>> response = HttpResponse('')
    >>> response['cotent-type'] = 'application/pdf';
    >>> response['content-disposition'] = 'inline; filename="xyz.pdf"'
    >>> response['content-disposition'] = 'attachment; filename="xyz.pdf"'
    >>> response.set_signed_cookie('', '', salt='')
    >>> response.status_code = 200

数据模型(Model)

问题1:关系型数据库表的设计应该注意哪些问题?(范式理论)

问题2:关系型数据库中数据完整性指的是什么?(实体完整性/参照完整性/域完整性)

问题3:ORM是什么以及解决了什么问题?(对象模型-关系模型双向转换)

  1. Field及其子类的属性:

    • 通用选项:
      • db_column / db_tablespace
      • null / blank / default
      • primary_key
      • db_index / unqiue
      • choices / help_text / error_message / editable / hidden
    • 其他选项:
      • CharField: max_length
      • DateField: auto_now / auto_now_add
      • DecimalField: max_digits / decimal_places
      • FileField: storage / upload_to
      • ImageField: height_field / width_field
  2. ForeignKey的属性:

    • 重要属性:

      • db_constraint(提升性能或者数据分片的情况可能需要设置为False)

      • on_delete

      • CASCADE:级联删除。

      • PROTECT:抛出ProtectedError异常,阻止删除引用的对象。

      • SET_NULL:把外键设置为null,当null属性被设置为True时才能这么做。
      • SET_DEFAULT:把外键设置为默认值,提供了默认值才能这么做。

      • related_name

      
      class Dept(models.Model):
         pass
      
      
      class Emp(models.Model):
         dept = models.ForeignKey(related_name='+', ...)
      
      
      Dept.objects.get(no=10).emp_set.all()
      Emp.objects.filter(dept__no=10)
    • 其他属性:

      • to_field / limit_choices_to / swappable
  3. Model的属性和方法

    • objects/ pk
    • save() / delete()
    • from_db() / get_XXX_display() / clean() / full_clean()
  4. QuerySet的方法

    • get() / all() / values()

    • count() / order_by() / exists() / reverse()

    • filter() / exclude()

      • exact / iexact:精确匹配/忽略大小写的精确匹配查询

      • contains / icontains / startswith / istartswith / endswith / iendswith:基于like的模糊查询

      • in:集合运算

      • gt / gte / lt / lte:大于/大于等于/小于/小于等于关系运算

      • range:指定范围查询(SQL中的between…and…

      • year / month / day / week_day / hour / minute / second:查询时间日期

      • isnull:查询空值(True)或非空值(False

      • search:基于全文索引的全文检索

      • regex / iregex:基于正则表达式的模糊匹配查询

      • aggregate() / annotate()

      • Avg / Count / Sum / Max / Min

      
      >>> from django.db.models import Avg
      >>> Emp.objects.aggregate(avg_sal=Avg('sal'))
      (0.001) SELECT AVG(`TbEmp`.`sal`) AS `avg_sal` FROM `TbEmp`; args=()
      {'avg_sal': 3521.4286}
      
      >>> Emp.objects.values('dept').annotate(total=Count('dept'))
      (0.001) SELECT `TbEmp`.`dno`, COUNT(`TbEmp`.`dno`) AS `total` FROM `TbEmp` GROUP BY `TbEmp`.`dno` ORDER BY NULL LIMIT 21; args=()
      <QuerySet [{'dept': 10, 'total': 4}, {'dept': 20, 'total': 7}, {'dept': 30, 'total': 3}]
    • first() / last()

    • only() / defer()

      
      >>> Emp.objects.filter(pk=7800).only('name', 'sal')
      (0.001) SELECT `TbEmp`.`empno`, `TbEmp`.`ename`, `TbEmp`.`sal` FROM `TbEmp` WHERE `TbEmp`.`empno` = 7800 LIMIT 21; args=(7800,)
       Emp object (7800)>]>
      >>> Emp.objects.filter(pk=7800).defer('name', 'sal')
      (0.001) SELECT `TbEmp`.`empno`, `TbEmp`.`job`, `TbEmp`.`mgr`, `TbEmp`.`comm`, `TbEmp`.`dno` FROM `TbEmp` WHERE `TbEmp`.`empno` = 7800 LIMIT 21; args=(7800,)
       Emp object (7800)>]>
    • create() / update() / raw()

      
      >>> Emp.objects.filter(dept__no=20).update(sal=F('sal') + 100)
      (0.011) UPDATE `TbEmp` SET `sal` = (`TbEmp`.`sal` + 100) WHERE `TbEmp`.`dno` = 20; args=(100, 20)
      >>>
      >>> Emp.objects.raw('select empno, ename, job from TbEmp where dno=10')
       select empno, ename, job from TbEmp where dno=10>
  5. Q对象和F对象

    
    >>> from django.db.models import Q
    >>> Emp.objects.filter(
    ...     Q(name__startswith='张'),
    ...     Q(sal__lte=5000) | Q(comm__gte=1000)
    ... ) # 查询名字以“张”开头且工资小于等于5000或补贴大于等于1000的员工
    ]>
    
    reporter = Reporters.objects.filter(name='Tintin')
    reporter.update(stories_filed=F('stories_filed') + 1)
  6. 原生SQL查询

    
    from django.db import connection
    
    with connection.cursor() as cursor:
       cursor.execute("UPDATE TbEmp SET sal=sal+10 WHERE dno=30")
       cursor.execute("SELECT ename, job FROM TbEmp WHERE dno=10")
       row = cursor.fetchall()
  7. 模型管理器

    
    class BookManager(models.Manager):
    
       def title_count(self, keyword):
           return self.filter(title__icontains=keyword).count()

视图函数(Controller)

如何设计视图函数

  1. 用户的每个操作对应一个视图函数。

  2. 每个视图函数构成一个事务边界。

    • 事务的概念。

    • 事务的ACID特性。

    • 事务隔离级别。

      Read Uncommitted < Read Committed < Repeatable Read < Serializable

      
      set global transaction isolation level repeatable read;
      set session transaction isolation level read committed;
      
      
      select @@tx_isolation;
    • Django中的事务控制。

      • 给每个请求绑定事务环境(反模式)。
      
      ATOMIC_REQUESTS = True
      • 使用事务装饰器。
      
      @transaction.non_atomic_requests
      @transaction.atomic
      • 使用上下文语法。
      
      with transaction.atomic():
         pass
      • 关闭自动提交。
      
      AUTOCOMMIT = False
      
      transaction.commit()
      transaction.rollback()

URL配置

  1. 可以让部分URL只在调试模式下生效。

    
    from django.conf import settings
    
    urlpatterns = [
       ...
    ]
    
    if settings.DEBUG:
       urlpatterns += [ ... ]
  2. 可以使用命名捕获组捕获路径参数。

    
    url(r'api/code/(?P1[3-9]\d{9})'),
    path('api/code/'),
  3. URL配置不关心请求使用的方法(一个视图函数可以处理不同的请求方式)。

  4. 如果使用url函数捕获的路径参数都是字符串,path函数可以指定路径参数类型。

  5. 可以使用include函数引入其他URL配置,捕获的参数会向下传递。

  6. urlpath函数甚至是include函数中都可以用字典向视图传入额外的参数,如果参数与捕获的参数同名,则使用字典中的参数。

  7. 可以用reverse函数实现URL的逆向解析(从名字解析出URL),在模板中也可以用{% url %}实现同样的操作。

    
    path('', views.index, name='index')
    
    return redirect(reverse('index'))
    return redirect('index')

模板(View)

后端渲染

  1. 模板的配置和渲染函数。

    
    TEMPLATES = [
       {
           'BACKEND': 'django.template.backends.django.DjangoTemplates',
           'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
           'APP_DIRS': True,
           'OPTIONS': {
               'context_processors': [
                   'django.template.context_processors.debug',
                   'django.template.context_processors.request',
                   'django.contrib.auth.context_processors.auth',
                   'django.contrib.messages.context_processors.messages',
               ],
           },
       },
    ]
    
    resp = render(request, 'foo.html', {'foo': 'bar'})
  2. 模板遇到变量名的查找顺序。

    • 字典查找(如:foo['bar']
    • 属性查找(如:foo.bar
    • 方法调用(如:foo.bar()
      • 方法不能有必须传值的参数
      • 在模板中不能够给方法传参
      • 如果方法的alters_data被设置为True则不能调用该方法(避免误操作的风险),模型对象动态生成的delete()save()方法都设定了alters_data = True
    • 列表索引查找(如:foo[0]
  3. 模板标签的使用。

    • {% if %} / {% else %} / {% endif %}
    • {% for %} / {% endfor %}
    • {% ifequal %} / {% endifequal %} / {% ifnotequal %} / {% endifnotequal %}
    • {# comment #} / {% comment %} / {% endcomment %}
  4. 过滤器的使用。

    • lower / upper / first / last / truncatewords / date/ time / length / pluralize / center / ljust / rjust / cut / urlencode / default_if_none / filesizeformat / join / slice / slugify
  5. 模板的包含和继承。

    • {% include %} / {% block %}
    • {% extends %}
  6. 模板加载器(后面优化部分会讲到)。

    • 文件系统加载器

      
      TEMPLATES = [{
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'DIRS': [os.path.join(BASE_DIR, 'templates')],
      }]
    • 应用目录加载器

      
      TEMPLATES = [{
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'APP_DIRS': True,
      }]

前端渲染

  1. 前端模板引擎:Handlebars / Mustache。
  2. 前端MV*框架。
    • MVC - AngularJS
    • MVVM - Vue.js

其他视图

  1. MIME类型。
Content-Type 说明
application/json JSON(JavaScript Object Notation)
application/pdf PDF(Portable Document Format)
audio/mpeg MP3或其他MPEG音频文件
audio/vnd.wave WAV音频文件
image/gif GIF图像文件
image/jpeg JPEG图像文件
image/png PNG图像文件
text/html HTML文件
text/xml XML
video/mp4 MP4视频文件
video/quicktime QuickTime视频文件

2. 如何处置生成的内容。

“`Python

response[‘content-type’] = ‘application/pdf’
response[‘content-disposition’] = ‘attachment; filename=”xyz.pdf”’
“`

提醒:URL以及请求和响应中的中文都应该处理成百分号编码。

  1. 生成CSV / Excel / PDF。

    • 向浏览器传输二进制数据。

      
      buffer = ByteIO()
      
      resp = HttpResponse(content_type='...')
      resp['Content-Disposition'] = 'attachment;filename="..."'
      resp.write(buffer.getvalue())
    • 大文件的流式处理:StreamingHttpResponse

      
      class EchoBuffer(object):
      
       def write(self, value):
           return value
      
      
      def some_streaming_csv_view(request):
       rows = (["Row {}".format(idx), str(idx)] for idx in range(65536))    
       writer = csv.writer(EchoBuffer())
       resp = StreamingHttpResponse((writer.writerow(row) for row in rows), 
                                    content_type="text/csv")
       resp['Content-Disposition'] = 'attachment; filename="data.csv"'
       return resp
    • 生成PDF:需要安装reportlab

    • 生成Excel:需要安装openpyxl

  2. 统计图表。

    • ECharts或Chart.js。
    • 思路:后端只提供JSON格式的数据,前端JavaScript渲染生成图表。

中间件

问题1:中间件背后的设计理念是什么?(分离横切关注功能/拦截过滤器模式)

问题2:中间件有哪些不同的实现方式?(参考下面的代码)

问题4:描述Django内置的中间件及其执行顺序。

推荐阅读:Django官方文档 - 中间件 - 中间件顺序。

激活中间件


MIDDLEWARE = [
    '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',
    'common.middlewares.block_sms_middleware',
]

自定义中间件


def simple_middleware(get_response):

    def middleware(request):

        response = get_response(request)

        return response

    return middleware

class MyMiddleware(object):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        response = self.get_response(request)

        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        response = view_func(*view_args, **view_kwargs)
        return response

class MyMiddleware(object):

    def __init__(self):
        pass

    def process_request(request):
        pass

    def process_view(request, view_func, view_args, view_kwargs):
        pass

    def process_template_response(request, response):
        pass

    def process_response(request, response):
        pass

    def process_exception(request, exception):
        pass

内置中间件

  1. CommonMiddleware
    • DISALLOWED_USER_AGENTS
    • APPEND_SLASH
    • USE_ETAG
  2. SecurityMiddleware
    • SECURE_HSTS_SECONDS
    • SECURE_HSTS_INCLUDE_SUBDOMAINS
    • SECURE_CONTENT_TYPE_NOSNIFF
    • SECURE_BROWSER_XSS_FILTER
    • SECURE_SSL_REDIRECT
    • SECURE_REDIRECT_EXEMPT
  3. SessionMiddleware
  4. CsrfViewMiddleware - 防范跨栈身份伪造。
  5. XFrameOptionsMiddleware - 防范点击劫持攻击

表单

  1. 用法:通常不要用来生成页面上的表单控件(耦合度太高不容易定制),主要用来验证数据。
  2. Form的属性和方法:
    • is_valid() / is_multipart()
    • errors / fields / is_bound / changed_data / cleaned_data
    • add_error() / has_errors() / non_field_errors()
    • clean()
  3. Form.errors的方法:
    • as_data() / as_json() / get_json_data()

问题1:Django中的FormModelForm有什么作用?(通常不用来生成表单主要用来验证数据)

问题2:表单上传文件时应该注意哪些问题?(表单的设置、多文件上传、图片预览、Ajax上传文件、上传后的文件如何存储)

Cookie和Session

问题1:使用Cookie能解决什么问题?(用户跟踪,解决HTTP协议无状态问题)

  1. URL重写
  2. 隐藏域(隐式表单域)
  3. Cookie

问题2:Cookie和Session之间关系是什么?(Session的标识会通过Cookie记录)

Session的配置

  1. Session对应的中间件:django.contrib.sessions.middleware.SessionMiddleware

  2. Session引擎。

    • 基于数据库(默认方式)

      
      INSTALLED_APPS = [
       'django.contrib.sessions',
      ]
    • 基于缓存(推荐使用)

      
      SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
      SESSION_CACHE_ALIAS = 'default'
    • 基于文件(基本不考虑)

    • 基于Cookie(不靠谱)

      
      SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
  3. Cookie相关的配置。

    
    SESSION_COOKIE_NAME = 'djang_session_id'
    SESSION_COOKIE_AGE = 1209600
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False 
    SESSION_SAVE_EVERY_REQUEST = False
    SESSION_COOKIE_HTTPONLY = True
  4. session的属性和方法。

    • session_key / session_data / expire_date
    • __getitem__ / __setitem__ / __delitem__ / __contains__
    • set_expiry() / get_expiry_age() / get_expiry_date()
    • flush()
    • set_test_cookie() / test_cookie_worked() / delete_test_cookie()
  5. session的序列化。

    
    SESSION_SERIALIZER = django.contrib.sessions.serializers.JSONSerializer
    • JSONSerializer(默认)- 如果想将自定义的对象放到session中,会遇到“Object of type ‘XXX’ is not JSON serializable”的问题。

    • PickleSerializer(1.6以前的默认,但是因为安全问题不推荐使用,但是只要不去反序列化用户构造的恶意的Payload就行了。关于这种方式的安全漏洞,请参考《Python Pickle的任意代码执行漏洞实践和Payload构造》。)

缓存

配置缓存


CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': [
            'redis://120.77.222.217:6379/0',
            'redis://120.77.222.217:6380/0',
            'redis://120.77.222.217:6381/0',
            # 'redis://120.77.222.217:6382/0',
        ],
        # 'KEY_PREFIX': 'fang',
        # 'TIMEOUT': 120,
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 100,
            },
            'PASSWORD': '1qaz2wsx',
            # 'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor'
        }
    },
}

全站缓存


MIDDLEWARE_CLASSES = [
    'django.middleware.cache.UpdateCacheMiddleware',
    ...
    'django.middleware.common.CommonMiddleware',
    ...
    'django.middleware.cache.FetchFromCacheMiddleware',
]

CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 300
CACHE_MIDDLEWARE_KEY_PREFIX = 'djang:cache'

视图层缓存


from django.views.decorators.cache import cache_page


@cache_page(60 * 15)
def my_view(request):
    pass

from django.views.decorators.cache import cache_page

urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]

其他内容

  1. 模板片段缓存。

    • {% load cache %}
    • {% cache %} / {% endcache %}
  2. 使用底层API访问缓存。

    
    >>> from django.core.cache import cache
    >>> cache.set('my_key', 'hello, world!', 30)
    >>> cache.get('my_key')
    >>> cache.clear()
    
    >>> from django.core.cache import caches
    >>> cache1 = caches['myalias']
    >>> cache2 = caches['myalias']
    >>> cache1 is cache2
    True

日志

日志级别

NOTSET < DEBUG < INFO < WARNING < ERROR < FETAL

日志配置


LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'simple': {
            'format': '%(asctime)s %(module)s.%(funcName)s: %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        },
        'verbose': {
            'format': '%(asctime)s %(levelname)s [%(process)d-%(threadName)s] '
                      '%(module)s.%(funcName)s line %(lineno)d: %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        }
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
        },
        'inf': {
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': 'info.log',
            'when': 'W0',
            'backupCount': 12,
            'formatter': 'simple',
            'level': 'INFO',
        },
        'err': {
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': 'error.log',
            'when': 'D',
            'backupCount': 31,
            'formatter': 'verbose',
            'level': 'WARNING',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': 'DEBUG',
        },
        'inform': {
            'handlers': ['inf'],
            'level': 'DEBUG',
            'propagate': True,
        },
        'error': {
            'handlers': ['err'],
            'level': 'DEBUG',
            'propagate': True,
        }
    }
}

日志配置官方示例。

日志分析

  1. Linux相关命令:head、tail、grep、awk、uniq、sort

    
    tail -10000 access.log | awk '{print $1}' | uniq -c | sort -r
  2. 实时日志文件分析:Python + 正则表达式 + Crontab

  3. 《Python日志分析工具》。

  4. 《集中式日志系统ELK》。

    • ElasticSearch
    • Logstash
    • Kibana

RESTful

问题1:RESTful架构到底解决了什么问题?(URL具有自描述性、资源表述与视图的解耦和、互操作性利用构建微服务以及集成第三方系统、无状态性提高水平扩展能力)

问题2:项目在使用RESTful架构时有没有遇到一些问题或隐患?(对资源访问的限制、资源从属关系检查、避免泄露业务信息、防范可能的攻击)

补充:下面的几个和安全性相关的响应头在前面讲中间件的时候提到过的。

  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • X-XSS-Protection: 1; mode=block;
  • Strict­-Transport-­Security: max-age=31536000;

问题3:如何保护API中的敏感信息以及防范重放攻击?(摘要和令牌)

推荐阅读:《如何有效防止API的重放攻击》。

修改配置文件


INSTALLED_APPS = [

    'rest_framework',

]

编写序列化器


from rest_framework import serializers


class ProvinceSerializer(serializers.ModelSerializer):

    class Meta:
        model = Province
        fields = ('prov_id', 'prov_name')

最怂的做法


@csrf_exempt
def list_provinces(request):
    if request.method == 'GET':
        serializer = ProvinceSerializer(Province.objects.all(), many=True)
    elif request.method = 'POST':
        serializer = ProvinceSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
    return HttpResponse(json.dumps(serializer.data), 
                        content_type="application/json; charset=utf-8")

使用装饰器


@api_view(['GET', 'POST'])
def list_provinces(request):
    if request.method == 'GET':
        serializer = ProvinceSerializer(Province.objects.all(), many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = ProvinceSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(['GET'])
def list_cities_by_prov(request, prov_id):
    serializer = CitySerializer(City.objects.filter(prov__prov_id=prov_id), many=True)
    return Response(serializer.data)

urlpatterns = [
    path('provinces/', views.list_provinces),
    path('provinces/', views.list_cities_by_prov),
]

问题1:如何让浏览器能够发起DELETE/PUT/PATCH?


method="post"> <input type="hidden" name="_method" value="delete"> form>

if request.method == 'POST' and '_method' in request.POST:
    request.method = request.POST['_method'].upper()

<script>
    $.ajax({
        'url': '/api/provinces',
        'type': 'put',
        'data': {},
        'dataType': 'json',
        'success': function(json) {
            // Web = 标签(内容) + CSS(显示) + JS(行为)
            // JavaScript = ES + BOM + DOM
            // DOM操作实现页面的局部刷新
        },
        'error': function() {}
    });
    $.getJSON('/api/provinces', function(json) {
        // DOM操作实现页面的局部刷新
    });
script>

问题2:如何解决多个JavaScript库之间某个定义(如$函数)冲突的问题?


<script src="js/jquery.js">script>
<script src="js/abc.js">script>
<script>
    // $已经被后加载的JavaScript库占用了
    // 但是可以直接用绑定在window对象上的jQuery去代替$
    jQuery(function() {
        jQuery('#okBtn').on('click', function() {});
    });
script>

<script src="js/abc.min.js">script>
<script src="js/jquery.min.js">script>
<script>
    // 将$让出给其他的JavaScript库使用
    jQuery.noConflict();
    jQuery(function() {
        jQuery('#okBtn').on('click', function() {});
    });
script>

问题3:jQuery对象与原生DOM对象之间如何转换?


<button id="okBtn">点我button>
<script src="js/jquery.min.js">script>
<script>
    var btn = document.getElementById('okBtn'); // 原生JavaScript对象
    // $(btn) --> jQuery --> 拥有更多的属性和方法而且没有浏览器兼容性问题
    var $btn = $('#okBtn');    // jQuery对象
    // $btn[0] / $btn.get(0) --> JavaScript --> 自己处理浏览器兼容性问题
    $btn.on('click', function() {});
script>

使用类视图

更好的复用代码,不要重“复发明轮子”。


class CityView(APIView):

    def get(self, request, pk, format=None):
        try:
            serializer = CitySerializer(City.objects.get(pk=pk))
            return Response(serializer.data)
        except City.DoesNotExist:
            raise Http404

    def put(self, request, pk, format=None):
        try:
            city = City.objects.get(pk=pk)
            serializer = CitySerializer(city, data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        except City.DoesNotExist:
            raise Http404

    def delete(self, request, pk, format=None):
        try:
            city = City.objects.get(pk=pk)
            city.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
        except City.DoesNotExist:
            raise Http404

urlpatterns = [
    path('cities/', views.CityView.as_view()),
]

使用ViewSet


class DistrictViewSet(viewsets.ReadOnlyModelViewSet):

    queryset = District.objects.all()
    serializer_class = DistrictSerializer

class DistrictViewSet(viewsets.ModelViewSet):

    queryset = District.objects.all()
    serializer_class = DistrictSerializer

router = routers.DefaultRouter()
router.register('districts', views.DistrictViewSet)

urlpatterns += router.urls

认证用户身份

  1. 利用Django自带的User。

  2. 自行对用户及其身份验证的摘要进行处理。

    
    sha1_proto = hashlib.sha1()
    
    
    def check_user_sign(get_response):
    
       def middleware(request):
           if request.path.startswith('/api'):
               data = request.GET if request.method == 'GET' else request.data
               try:
                   user_id = data['user_id']
                   user_token = cache.get(f'fang:user:{user_id}')
                   user_sign = data['user_sign']
                   hasher = sha1_proto.copy()
                   hasher.update(f'{user_id}:{user_token}'.encode('utf-8'))
                   if hasher.hexdigest() == user_sign:
                       return get_response(request)
               except KeyError:
                   pass
               return JsonResponse({'msg': '身份验证失败拒绝访问'})
           else:
               return get_response(request)
    
       return middleware
  3. 请求的时效性问题。(请求中再加上一个随机的令牌)

其他问题

问题1:如何设计一套权限系统?(RBAC或ACL)

  1. RBAC - 基于角色的访问控制(用户-角色-权限,都是多对多关系)。
  2. ACL - 访问控制列表(每个用户绑定自己的访问白名单)。

问题2:如何实现异步任务和定时任务?(Celery)

问题3:如何解决JavaScript跨域获取数据的问题?(django-cors-headers)

问题4:网站图片(水印、剪裁)和视频(截图、水印、转码)是如何处理的?(云存储、FFmpeg)

问题5:网站如何架设(静态资源)文件系统?

Celery的应用

Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。它是一个专注于实时处理的任务队列,同时也支持任务调度。

推荐阅读:《Celery官方文档中文版》,上面有极为详细的配置和使用指南。

Celery是一个本身不提供队列服务,官方推荐使用RabbitMQ或Redis来实现消息队列服务,前者是更好的选择,它对AMQP(高级消息队列协议)做出了非常好的实现。

  1. 安装RabbitMQ。

    
    docker pull rabbitmq
    docker run -d -p 5672:5672 --name myrabbit rabbitmq
    docker container exec -it myrabbit /bin/bash
  2. 创建用户、资源以及分配操作权限。

    
    rabbitmqctl add_user jackfrued 123456
    rabbitmqctl set_user_tags jackfrued administrator
    rabbitmqctl add_vhost myvhost
    rabbitmqctl set_permissions -p myvhost jackfrued ".*" ".*" ".*"
  3. 创建Celery实例。

    
    project_name = 'fang'
    project_settings = '%s.settings' % project_name
    
    
    # 注册环境变量
    
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', project_settings)
    
    app = celery.Celery(
       project_name, 
       backend='amqp://jackfrued:[email protected]:5672/myvhost', 
       broker='amqp://jackfrued:[email protected]:5672/myvhost'
    )
    
    
    # 从默认的配置文件读取配置信息
    
    app.config_from_object('django.conf:settings')
    
    
    # Celery加载所有注册的应用
    
    app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
  4. 启动Celery创建woker。

    
    celery -A fang worker -l info &
  5. 执行异步任务。

    
    @app.task
    def send_email(from, to, cc, subject, content):
       pass
    
    
    async_result = send_email.delay('', [], [], '', '')
    async_result.get()
  6. 创建定时任务。

    
    from celery.schedules import crontab
    from celery.task import periodic_task
    
    
    @periodic_task(run_every=crontab('*', '12,18'))
    def print_dummy_info():
       print('你妈喊你回家吃饭啦')
  7. 检查定时任务并交给worker执行。

    
    celery -A fang beat -l info
  8. 检查消息队列状况。

    
    rabbitmqctl list_queues -p myvhost

安全保护

问题1:什么是跨站脚本攻击,如何防范?(对提交的内容进行消毒)

问题2:什么是跨站身份伪造,如何防范?(使用随机令牌)

问题3:什么是SQL注射攻击,如何防范?(不拼接SQL语句,避免使用单引号)

问题4:什么是点击劫持攻击,如何防范?(不允许