## Django知识点概述 ### Web应用 ![](./res/web-application.png) 问题1:描述一个Web应用的工作流程。(如上图所示) 问题2:描述项目的物理架构。(上图中补充反向代理服务器、负载均衡服务器、数据库服务器、文件服务器、缓存服务器、防火墙等,每个节点都有可能是多节点构成的集群) 问题3:描述Django项目的工作流程。(如下图所示) ![](./res/django-flowchart.png) ### MVC架构模式 ![](./res/mvc.png) 问题1:为什么要使用MVC架构模式?(模型和视图解耦合) 问题2:MVC架构中每个部分的作用?(如上图所示) ### HTTP请求和响应 #### HTTP请求 HTTP请求 = 请求行+请求头+空行+[消息体] ![](./res/http-request.png) HTTP响应 = 响应行+响应头+空行+消息体 ![](./res/http-response.png) 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. `JsonResponse`(`HttpResponse`的子类型)对象 ```Python class HouseJsonEncoder(JsonEncoder): def default(self, o): # 定义如何将对象转成dict类型并返回这个字典 pass ``` ```Python >>> 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` ```Python 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` ```Python >>> 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} ``` ```Python >>> 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=() >> 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.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,) ]> ``` - `create()` / `update()` / `raw()` ```Python >>> 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') ``` 5. `Q`对象和`F`对象 ```Python >>> from django.db.models import Q >>> Emp.objects.filter( ... Q(name__startswith='张'), ... Q(sal__lte=5000) | Q(comm__gte=1000) ... ) # 查询名字以“张”开头且工资小于等于5000或补贴大于等于1000的员工 ]> ``` ```Python reporter = Reporters.objects.filter(name='Tintin') reporter.update(stories_filed=F('stories_filed') + 1) ``` 6. 原生SQL查询 ```Python 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. 模型管理器 ```Python 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 ```SQL set global transaction isolation level repeatable read; set session transaction isolation level read committed; select @@tx_isolation; ``` - Django中的事务控制。 - 给每个请求绑定事务环境(反模式)。 ```Python ATOMIC_REQUESTS = True ``` - 使用事务装饰器。 ```Python @transaction.non_atomic_requests @transaction.atomic ``` - 使用上下文语法。 ```Python with transaction.atomic(): pass ``` - 关闭自动提交。 ```Python AUTOCOMMIT = False ``` ```Python transaction.commit() transaction.rollback() ``` #### URL配置 1. 可以让部分URL只在调试模式下生效。 ```Python from django.conf import settings urlpatterns = [ ... ] if settings.DEBUG: urlpatterns += [ ... ] ``` 2. 可以使用命名捕获组捕获路径参数。 ```Python url(r'api/code/(?P1[3-9]\d{9})'), path('api/code/'), ``` 3. URL配置不关心请求使用的方法(一个视图函数可以处理不同的请求方式)。 4. 如果使用`url`函数捕获的路径参数都是字符串,`path`函数可以指定路径参数类型。 5. 可以使用`include`函数引入其他URL配置,捕获的参数会向下传递。 6. 在`url`和`path`函数甚至是`include`函数中都可以用字典向视图传入额外的参数,如果参数与捕获的参数同名,则使用字典中的参数。 7. 可以用`reverse`函数实现URL的逆向解析(从名字解析出URL),在模板中也可以用`{% url %}`实现同样的操作。 ```Python path('', views.index, name='index') return redirect(reverse('index')) return redirect('index') ``` ### 模板(View) #### 后端渲染 1. 模板的配置和渲染函数。 ```Python 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', ], }, }, ] ``` ```Python 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. 模板加载器(后面优化部分会讲到)。 - 文件系统加载器 ```Python TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], }] ``` - 应用目录加载器 ```Python 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](https://zh.wikipedia.org/wiki/JSON)(JavaScript Object Notation) | | application/pdf | [PDF](https://zh.wikipedia.org/wiki/PDF)(Portable Document Format) | | audio/mpeg | [MP3](https://zh.wikipedia.org/wiki/MP3)或其他[MPEG](https://zh.wikipedia.org/wiki/MPEG)音频文件 | | audio/vnd.wave | [WAV](https://zh.wikipedia.org/wiki/WAV)音频文件 | | image/gif | [GIF](https://zh.wikipedia.org/wiki/GIF)图像文件 | | image/jpeg | [JPEG](https://zh.wikipedia.org/wiki/JPEG)图像文件 | | image/png | [PNG](https://zh.wikipedia.org/wiki/PNG)图像文件 | | text/html | [HTML](https://zh.wikipedia.org/wiki/HTML)文件 | | text/xml | [XML](https://zh.wikipedia.org/wiki/XML) | | video/mp4 | [MP4](https://zh.wikipedia.org/wiki/MP4)视频文件 | | video/quicktime | [QuickTime](https://zh.wikipedia.org/wiki/QuickTime)视频文件 | 2. 如何处置生成的内容。 ```Python response['content-type'] = 'application/pdf' response['content-disposition'] = 'attachment; filename="xyz.pdf"' ``` > 提醒:URL以及请求和响应中的中文都应该处理成[百分号编码](https://zh.wikipedia.org/zh-hans/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81)。 3. 生成CSV / Excel / PDF。 - 向浏览器传输二进制数据。 ```Python buffer = ByteIO() resp = HttpResponse(content_type='...') resp['Content-Disposition'] = 'attachment;filename="..."' resp.write(buffer.getvalue()) ``` - 大文件的流式处理:`StreamingHttpResponse`。 ```Python 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`。 4. 统计图表。 - [ECharts](http://echarts.baidu.com/)或[Chart.js](https://www.chartjs.org/)。 - 思路:后端只提供JSON格式的数据,前端JavaScript渲染生成图表。 ### 中间件 问题1:中间件背后的设计理念是什么?(分离横切关注功能/拦截过滤器模式) 问题2:中间件有哪些不同的实现方式?(参考下面的代码) 问题4:描述Django内置的中间件及其执行顺序。 推荐阅读:[Django官方文档 - 中间件 - 中间件顺序](https://docs.djangoproject.com/zh-hans/2.0/ref/middleware/#middleware-ordering)。 #### 激活中间件 ```Python 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', ] ``` #### 自定义中间件 ```Python def simple_middleware(get_response): def middleware(request): response = get_response(request) return response return middleware ``` ```Python 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 ``` ```Python 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 - 防范点击劫持攻击 ![](./res/builtin-middlewares.png) ### 表单 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中的`Form`和`ModelForm`有什么作用?(通常不用来生成表单主要用来验证数据) 问题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引擎。 - 基于数据库(默认方式) ```Python INSTALLED_APPS = [ 'django.contrib.sessions', ] ``` - 基于缓存(推荐使用) ```Python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_CACHE_ALIAS = 'default' ``` - 基于文件(基本不考虑) - 基于Cookie(不靠谱) ```Python SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' ``` 3. Cookie相关的配置。 ```Python 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的序列化。 ```Python SESSION_SERIALIZER = django.contrib.sessions.serializers.JSONSerializer ``` - JSONSerializer(默认)- 如果想将自定义的对象放到session中,会遇到“Object of type 'XXX' is not JSON serializable”的问题。 - PickleSerializer(1.6以前的默认,但是因为安全问题不推荐使用,但是只要不去反序列化用户构造的恶意的Payload就行了。关于这种方式的安全漏洞,请参考《[Python Pickle的任意代码执行漏洞实践和Payload构造》](http://www.polaris-lab.com/index.php/archives/178/)。) ### 缓存 #### 配置缓存 ```Python 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' } }, } ``` #### 全站缓存 ```Python 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' ``` #### 视图层缓存 ```Python from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): pass ``` ```Python 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访问缓存。 ```Python >>> from django.core.cache import cache >>> cache.set('my_key', 'hello, world!', 30) >>> cache.get('my_key') >>> cache.clear() ``` ```Python >>> from django.core.cache import caches >>> cache1 = caches['myalias'] >>> cache2 = caches['myalias'] >>> cache1 is cache2 True ``` ### 日志 #### 日志级别 NOTSET < DEBUG < INFO < WARNING < ERROR < FETAL #### 日志配置 ```Python 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, } } } ``` [日志配置官方示例](https://docs.djangoproject.com/zh-hans/2.0/topics/logging/#s-examples)。 #### 日志分析 1. Linux相关命令:head、tail、grep、awk、uniq、sort ```Shell tail -10000 access.log | awk '{print $1}' | uniq -c | sort -r ``` 2. 实时日志文件分析:Python + 正则表达式 + Crontab 3. [《Python日志分析工具》](https://github.com/jkklee/web_log_analyse)。 4. [《集中式日志系统ELK》](https://www.ibm.com/developerworks/cn/opensource/os-cn-elk/index.html)。 - 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的重放攻击》](https://help.aliyun.com/knowledge_detail/50041.html)。 #### 修改配置文件 ```Python INSTALLED_APPS = [ 'rest_framework', ] ``` #### 编写序列化器 ```Python from rest_framework import serializers class ProvinceSerializer(serializers.ModelSerializer): class Meta: model = Province fields = ('prov_id', 'prov_name') ``` #### 最怂的做法 ```Python @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") ``` #### 使用装饰器 ```Python @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) ``` ```Python urlpatterns = [ path('provinces/', views.list_provinces), path('provinces/', views.list_cities_by_prov), ] ``` 问题1:如何让浏览器能够发起DELETE/PUT/PATCH? ```HTML
``` ```Python if request.method == 'POST' and '_method' in request.POST: request.method = request.POST['_method'].upper() ``` ```HTML ``` 问题2:如何解决多个JavaScript库之间某个定义(如$函数)冲突的问题? ```HTML ``` ```HTML ``` 问题3:jQuery对象与原生DOM对象之间如何转换? ```HTML ``` #### 使用类视图 更好的复用代码,不要重“复发明轮子”。 ```Python 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 ``` ```Python urlpatterns = [ path('cities/', views.CityView.as_view()), ] ``` #### 使用ViewSet ```Python class DistrictViewSet(viewsets.ReadOnlyModelViewSet): queryset = District.objects.all() serializer_class = DistrictSerializer ``` ```Python class DistrictViewSet(viewsets.ModelViewSet): queryset = District.objects.all() serializer_class = DistrictSerializer ``` ```Python router = routers.DefaultRouter() router.register('districts', views.DistrictViewSet) urlpatterns += router.urls ``` #### 认证用户身份 1. 利用Django自带的User。 2. 自行对用户及其身份验证的摘要进行处理。 ```Python 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官方文档中文版》](http://docs.jinkan.org/docs/celery/),上面有极为详细的配置和使用指南。 ![](./res/celery.png) Celery是一个本身不提供队列服务,官方推荐使用RabbitMQ或Redis来实现消息队列服务,前者是更好的选择,它对AMQP(高级消息队列协议)做出了非常好的实现。 1. 安装RabbitMQ。 ```Shell docker pull rabbitmq docker run -d -p 5672:5672 --name myrabbit rabbitmq docker container exec -it myrabbit /bin/bash ``` 2. 创建用户、资源以及分配操作权限。 ```Shell rabbitmqctl add_user jackfrued 123456 rabbitmqctl set_user_tags jackfrued administrator rabbitmqctl add_vhost myvhost rabbitmqctl set_permissions -p myvhost jackfrued ".*" ".*" ".*" ``` 3. 创建Celery实例。 ```Python 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:123456@120.77.222.217:5672/myvhost', broker='amqp://jackfrued:123456@120.77.222.217:5672/myvhost' ) # 从默认的配置文件读取配置信息 app.config_from_object('django.conf:settings') # Celery加载所有注册的应用 app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) ``` 4. 启动Celery创建woker。 ```Shell celery -A fang worker -l info & ``` 5. 执行异步任务。 ```Python @app.task def send_email(from, to, cc, subject, content): pass async_result = send_email.delay('', [], [], '', '') async_result.get() ``` 6. 创建定时任务。 ```Python 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执行。 ```Shell celery -A fang beat -l info ``` 8. 检查消息队列状况。 ```Shell rabbitmqctl list_queues -p myvhost ``` ### 安全保护 问题1:什么是跨站脚本攻击,如何防范?(对提交的内容进行消毒) 问题2:什么是跨站身份伪造,如何防范?(使用随机令牌) 问题3:什么是SQL注射攻击,如何防范?(不拼接SQL语句,避免使用单引号) 问题4:什么是点击劫持攻击,如何防范?(不允许`