第15章 常见问题
django创建表失败
Django 创建数据库表失败 ,输入python manage.py migrate 不能新建表
解决方法:
如果 app/migrations/ 存在的话, 删除 app/migrations/下文件,除了__init__.py
# 重新生成表迁移文件
python manage.py makeigrations app_name
# 迁移到数据库
python manage.py migrate
migrate时报错1
# 环境:
# python3、django2、mysql5.7
WARNINGS:
?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it. See: https://docs.djangoprojec
t.com/en/dev/ref/databases/#mysql-sql-mode
解决方案
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_test',
'USER':'root',
'PASSWORD':'',
'HOST':'localhost',
'PORT':'3306',
#下面是新加入的
'OPTIONS':{
'init_command':"SET sql_mode='STRICT_TRANS_TABLES'",
'charset':'utf8mb4',
},
}
}
migrate时报错2
报错提示
Django在执行python manage.py makemigrations的时候提示异常:
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency user.0001_initial on database 'default'
原因:Django中有一个原生的User模型类,admin的模型依赖这个模型类,由于前面一个应用中的模型类User继承了AbstractUser类,所以提示这个错误。
解决方案:
删除数据库中 除了auth_user的其他表,然后重新来一次。
参考: https://my.oschina.net/u/1446823/blog/861712
django初始化DB
一个相当常见的问题:“如何为app提供初始化数据?”,或者是:“如何在代码通过syncdb运行时,同时运行某些代码?”
django提供了多种方法来实现这个功能。
- 使用原生SQL
- 使用 fixtures
- 使用 post_syncdb 信号
1. 使用原生sql
建立 app/sql/models_name.sql
文件,写入insert就可以了,当然也可以写其它语句。
可以使用 manage.py的sqlcustom命令查看一个app是否提供了初始化SQL
2. 使用fixtures
fixtures是一种上新的提供初始化数据的方法,并且被django的测试框架服务业处理单元测试的测试数据。区别与原生SQL方式的是,使用fixtures你可以提供一个被 django的serialization序列器能误置的序列化文件,它会被读取并自动转换成对应的model,然后保存进数据库。
建立初始化文件
建立 fixtures 文件,可以手工创建;如果数据已经有一些数据,也可以使用命令自动创建manage.py dumpdata
,文件类似如下:
[
{
"model": "myapp.person",
"pk": 1,
"fields": {
"first_name": "John",
"last_name": "Lennon"
}
},
{
"model": "myapp.person",
"pk": 2,
"fields": {
"first_name": "Paul",
"last_name": "McCartney"
}
}
]
加载初始化数据
使用命令
manae.py loaddata <fixturename>
如果你创建了一个名为 app/fixture/initial_data.[json/xml/yaml]
的fixtrue,在你每次运行migrate命令时 fixture都会被加载。这非常方便,但是要注意:记住数据在你每次运行migrate命令后都会被刷新。
django在哪里寻找 fixtrue文件
app/fixtures/
FIXTURE_DIRS=[dir1, dir2]
3. 使用 post_syncdb信号
第三种,最复杂最强大的方法,是使用Django内部分发器提供的post_syncdb
信号。你可能还不了解这个东西,分发器是一种方法,Django的许多程序,包括你自己的程序,可以用它来通知部分某个事件,只需要发送一个信号。post_syncdb
是个Django内建的信号,当一个app的数据表被使用syncdb创建时被发送。
要使用这种方法,你需要在你的app目录下创建一个名为management.py的文件,添加下面的代码:
from django.dispatch import dispatcher
from django.db.models.signals import post_syncdb
from myapp import models as myapp
用你自己的app的名字替换最后一行的内容。比如我有一个app名为blog,那么这一行应该是这样:
from blog import models as blog_app
然后定义一个普通的Python函数,里面是你希望在安装时被执行的代码。一旦该函数被注册到分发器上(一会就能看到了),它就会在syncdb创建完数据库以后立刻被执行,所以这个函数可以实现你想要的任何功能,包括修改数据表,添加额外功能,或者使用Django的model API插入数据。
最后,把你的函数注册到分发器,使它监听post_syncdb信号。dispatcher.connect()方法包括三个参数:
- 你的函数名,必须是一个函数,而不是一个字符串
- 信号的名字,这里是post_syncdb
- 发送者,这里应该是你前面导入的那个app
继续blog的例子,你可以定义一个函数setup_blog(),并像这样来注册它:
dispatcher.connect(setup_blog, signal=post_syncdb, sender=blog_app)
执行syncdb时,Django会在你的app目录寻找management.py文件并导入,此时dispatcher.connect被执行并注册你的函数,当你的表被创建后,post_syncdb信号会发给你的application,分发器会确保你的函数会被调用。
Django的认证应用使用了这种方法来自动创建超级管理员,sites应用使用这种方法创建默认的站点。
如果你希望你的程序在任何其它的app被安装时做些什么,你可以忽略发送者参数,那么每次syncdb被调用安装了一个程序的时候你的代码都会被执行。如果你打算这么做,那么你需要确保你的函数接收另外的几个可选参数(分发器会正确的传递它们):
- app参数,可以传递给get_models()以便取得刚刚被安装的app里面的model
- created_models,是一个list,包含了刚刚实际被创建了表的model
有几个Django内置的应用使用了这个技巧:
- 认证应用在每个model被安装时为它们创建Permission对象
- contenttypes框架在每个model被安装时为它们创建ContentType对象
正确使用 User Model
1. 确定 user model
我们推荐一下方式来确定某一django项目使用的user model:
# 使用默认User model时
>>> from django.contrib.auth import get_user_model
>>> get_user_model()
<class 'django.contrib.auth.models.User'>
# 使用自定义User model时
>>> from django.contrib.auth import get_user_model
>>> get_user_model()
<class 'xxx.models.UserProfile'>
2. 使用settings.AUTH_USER_MODEL
自从django 1.5之后, 用户可以自定义User model了, 如果需要外键使用user model, 官方推荐的方法如下:
在settings中设置AUTH_USER_MODEL
# settings.py
# 格式为 "<django_app名>.<model名>"
AUTH_USER_MODEL = "myapp.NewUser"
在models中使用
# models.py
from django.conf import settings
from django.db import models
class Article(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=255)
注意: 不在要外键中使用 get_user_model()
3. 自定义 user model
方法1: 扩展 AbstractUser类
如果你对django自带的User model感到满意, 又希望额外的field的话, 你可以扩展AbstractUser类:
# myapp/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class NewUser(AbstractUser):
new_field = models.CharField(max_length=100)
不要忘了在settings.py中设置:
AUTH_USER_MODEL = "myapp.NewUser"
方法2: 扩展 AbstractBaseUser类
AbstractBaseUser中只含有3个field: password, last_login和is_active. 如果你对django user model默认的first_name, last_name不满意, 或者只想保留默认的密码储存方式, 则可以选择这一方式.
方法3: 使用OneToOneField
如果你想建立一个第三方模块发布在PyPi上, 这一模块需要根据用户储存每个用户的额外信息. 或者我们的django项目中希望不同的用户拥有不同的field, 有些用户则需要不同field的组合, 且我们使用了方法1或方法2:
# profiles/models.py
from django.conf import settings
from django.db import models
from flavors.models import Flavor
class EasterProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
favorite_ice_cream = models.ForeignKey(Flavor, null=True, blank=True)
class ScooperProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
scoops_scooped = models.IntergerField(default=0)
class InventorProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
flavors_invented = models.ManyToManyField(Flavor, null=True, blank=True)
使用以上方法, 我们可以使用user.easterprofile.favorite_ice_cream获取相应的profile.
使用这一方法的坏处可能就是增加了代码的复杂性.
用户模型使用多个 USERNAME_FIELD
用户模型
class MyUser(AbstractBaseUser):
username = models.CharField(unique=True,max_length=30)
email = models.EmailField(unique=True,max_length=75)
is_staff = models.IntegerField(default=False)
is_active = models.IntegerField(default=False)
date_joined = models.DateTimeField(default=None)
# Use default usermanager
objects = UserManager()
USERNAME_FIELD = 'email'
有没有办法指定多个USERNAME_FIELD?是[‘email’,’username’]这样的,用户可以通过电子邮件和用户名登录?
USERNAME_FIELD设置不支持列表。您可以创建一个自定义身份验证后端,尝试在“电子邮件”或“用户名”字段中查找用户。
from django.db.models import Q
class UsernameOrEmailBackend(object):
def authenticate(self, username=None, password=None, **kwargs):
try:
# Try to fetch the user by searching the username or email field
user = MyUser.objects.get(Q(username=username)|Q(email=username))
if user.check_password(password):
return user
except MyUser.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
MyUser().set_password(password)
然后,在您的身份验证后端settings.py设置AUTHENTICATION_BACKENDS中:
AUTHENTICATION_BACKENDS = ('path.to.UsernameOrEmailBackend,)\
请注意,此解决方案并不完美。例如,密码重置只适用于您USERNAME_FIELD设置中指定的字段。
解决方案:
可以通过实现自己的电子邮件身份验证后端来实现
- 在settings.py中替换自定义的用户模型
- 编写自定义身份验证后端的逻辑
- 在设置中指定自定义身份验证后端
# settings.py
AUTH_USER_MODEL = 'myapp.MyUser
# 编写自定义身份验证后端的逻辑 <https://docs.djangoproject.com/zh-hans/2.0/topics/auth/customizing/>
# 编写后端需要至少实现两个方法:
# 1. get_user(user_id)
# 2. authenticate(request, **credentials)
# 以及其它一系列可选的权限相关的方法:ref:`authorization methods<authorization_methods> `.
# - get_user 方法只接受一个参数``user_id``,user_id 有可能是 用户名、数据库 ID 或者其它任何值(该值必须是用户对象的主键),该方法返回一个用户对象。
# - authenticate``方法接受 ``request 参数和 credentials 关键字参数,大多数情况下,该方法类似于下面的代码:
from django.contrib.auth import get_user_model
from django.contrib.auth.models import check_password
class MyEmailBackend(object):
"""
Custom Email Backend to perform authentication via email
"""
def authenticate(self, username=None, password=None):
my_user_model = get_user_model()
try:
user = my_user_model.objects.get(email=username)
if user.check_password(password):
return user # return user on valid credentials
except my_user_model.DoesNotExist:
return None # return None if custom user model does not exist
except:
return None # return None in case of other exceptions
def get_user(self, user_id):
my_user_model = get_user_model()
try:
return my_user_model.objects.get(pk=user_id)
except my_user_model.DoesNotExist:
return None
# 在设置中指定自定义身份验证后端
# 编写自定义身份验证后端后,在AUTHENTICATION_BACKENDS设置中指定此身份验证后端。
# AUTHENTICATION_BACKENDS包含要使用的身份验证后端列表。Django尝试对其所有身份验证后端进行身份验证。如果第一个身份验证方法失败,Django会尝试第二个身份验证方法,依此类推,直到尝试了所有后端。
AUTHENTICATION_BACKENDS = (
'my_app.backends.MyEmailBackend', # our custom authentication backend
'django.contrib.auth.backends.ModelBackend' # fallback to default authentication backend if first fails
)
如果身份验证MyEmailBackend失败,即用户无法通过身份验证email,那么我们将使用Django的默认身份验证ModelBackend,该身份验证将尝试通过模型username字段进行身份验证MyUser。
Changed in Django 1.11:
request 参数已经添加到 authenticate(),Django 2.1 将会移除不接受它的后端。
wsgi nginx 连接到上游时拒绝
日志代码提示如下:
2018/05/30 02:04:17 [crit] 10336#0: *20 connect() to 127.0.0.1:3031 failed (13: Permission denied) while connecting to upstream, client: 192.168.0.62, server: 192.168.0.147, request: "GET / HTTP/1.1", upstream: "uwsgi://127.0.0.1:3031", host: "192.168.0.147"
ubuntu18.04 安装pip
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
$ python get-pip.py
$ apt install python3-distutils
ubuntu18.04 安装Developer Tools
# Developer Tools
$ sudo apt-get install build-essential git
# Python Development
$ sudo apt-get install python3-all-dev
## virtualenv wrapper installation
sudo pip install virtualenvwrapper
echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bashrc
# Create Virtualenv (env location is ~/.virtualenvs)
mkvirtualenv <env_name>
# select virtualenv
workon <env_name>
跨域访问CORS
CORS
Cross-Origin Resource Sharing
问题描述
在使用javascript进行ajax访问的时候,出现如下错误
register:1 Failed to load http://192.168.0.120/api/v1/user/register/: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:5000' is therefore not allowed access.
简述:
Access-Control-Allow-Origin 是 HTML5添加听新功能,基本上,这是一个http的header,用在返回资源的时候,指定这个资源可以被哪些网域跨域访问。
比如说,你的图片都在res.com这个域下,如果在返回的头中没有设置 Access-Control-Allow-Origin,那么别的域是不能外链你的图片的。
解决方法就是,在资源的头中,加入Access-Control-Allow-Origin指定你授权的域名。无所谓就指定是星号,任何域名都可以访问我的资源
出错原因:
javascript处于安全考虑,不允许跨域访问.
协议 | 子域名 | 主域名 | 端口号 | 请求资源地址 |
---|---|---|---|---|
http:// | www | abc.com | 8080 | scripts/jquery.js |
- 当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域
- 不同域之间相互请求资源,就算做“跨域”
解决方法:
- 修改视图函数,允许其他域通过Ajax请求数据
todo_list = [
{"id": "1", "content": "吃饭"},
{"id": "2", "content": "吃饭"},
]
class Query(View):
@staticmethod
def get(request):
response = JsonResponse(todo_list, safe=False)
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
response["Access-Control-Max-Age"] = "1000"
response["Access-Control-Allow-Headers"] = "*"
return response
@staticmethod
def post(request):
print(request.POST)
return HttpResponse()
- 使用中间件
django-cors-headers
官方pypi页:<https://pypi.org/project/django-cors-headers/
参考文档: < https://www.jianshu.com/p/4de99dddf961>
https://enable-cors.org/server_nginx.html
nginx配置跨域
关键字 Access-Control-Allow-Origin
参考地址:https://segmentfault.com/a/1190000012550346
问题描述:
客户端访问服务器时出现403跨域错误提示,No 'Access-Control-Allow-Origin' header is present on the requested resource
解决方案:
需要在nginx的配置文件中配置
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
配置解释
- Access-Control-Allow-Origin:
*
服务器可以接受所有的请求源origin,即接受所有跨域请求 - Access-Control-Allow-Headers:防止出现以下错误
Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
- Access-Control-Allow-Methods:防止出现以下错误
Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
- OPTIONS返回204:防止为了处理在发送POST请求时nginx依然拒绝访问的错误
CROS,全称跨域资源共享 cross-origin resource sharing,它的请求就是为了解决跨域请求的。
跨域资源共享(CORS)标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。
规范要求:
对那些可能对服务器数据产生副作用的HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求) ,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。
服务器确认允许之后,才发起实际的 HTTP 请求。
在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
CORS规定,Content-Type不属于以下MIME类型的,都属于预检请求:
application/x-www-form-urlencoded
multipart/form-data
text/plain
多值跨域
错误提示
Failed to load http://13.75.199.130/api/v1/user/login/: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. Origin 'http://www.wowoxo.org:5100' is therefore not allowed access.
Cross-Origin Read Blocking (CORB) blocked cross-origin response http://13.75.199.130/api/v1/user/login/ with MIME type application/json. See https://www.chromestatus.com/feature/5629709824032768 for more details.
错误信息很好理解,就是说 Access-Control-Allow-Origin 有两个值,但是浏览器只准许有一个值,所以报错。
nginx http https 重定向
方法一
使用http站点index文件重定向
http站点配置
# index.html
<html>
<meta http-equiv="refresh" content="0;url=https://docs.lvrui.io/">
</html>
nginx配置
server {
listen 80;
server_name docs.lvrui.io;
location / {
# 将 index.html 文件放到下面的目录下, http自动跳转到https
root /var/www/html/;
}
}
server {
listen 443 ssl;
server_name docs.lvrui.io;
index index.html index.htm;
access_log /var/log/nginx/docs.log main;
ssl on;
ssl_certificate /etc/ssl/docs.20150509.cn.crt;
ssl_certificate_key /etc/ssl/docs.20150509.cn.key;
error_page 404 /404.html;
location / {
root /var/www/html/docs;
}
}
方法二
nginx超时配置
nginx超时提示
nginx 504 Gateway Time-out
分析
Nginx 504 Gateway Time-out的含义是所请求的网关没有请求到,简单来说就是没有请求到可以执行的PHP-CGI
通常以下几种情况会导致这个问题:
1.程序在处理大量的数据,或者有死循环之类的问题
2.创建数据库之类的连接因为某些原因连接不上,然后没有超时失败的机制,导致一直在创建连接
3.程序中有一些http请求,这些请求执行时间过长,导致超时
修改设置
# 1.尝试修改 gninx中代理的超时设置
proxy_connect_timeout 75s;
proxy_read_timeout 300s;
# 2. 必须修改 gunicorn进程配置中的超时设置
-t INT, --timeout INT
--timeout 300
proxy转发超时
语法
Syntax: proxy_connect_timeout time; # max 75s
Default: proxy_connect_timeout 60s;
Context: http, server, location
说明
后端服务器连接的超时时间_发起握手等候响应超时时间
该指令设置与upstream server的连接超时时间,有必要记住,这个超时不能超过75秒。
这个不是等待后端返回页面的时间,那是由proxy_read_timeout声明的。如果你的upstream服务器起来了,但是hanging住了(例如,没有足够的线程处理请求,所以把你的请求放到请求池里稍后处理),那么这个声明是没有用的,因为与upstream服务器的连接已经建立了。
proxy_read_timeout
语法
Syntax: proxy_read_timeout time;
Default: proxy_read_timeout 60s;
Context: http, server, location
说明
连接成功后等候后端服务器响应时间其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间)
该指令设置与代理服务器的读超时时间。它决定了nginx会等待多长时间来获得请求的响应。这个时间不是获得整个response的时间,而是两次reading操作的时间。(??什么是两次reading操作的时间)
proxy_send_timeout
语法
Syntax: proxy_send_timeout time;
Default: proxy_send_timeout 60s;
Context: http, server, location
说明
后端服务器数据回传时间_就是在规定时间之内后端服务器必须传完所有的数据
这个指定设置了发送请求给upstream服务器的超时时间。超时设置不是为了整个发送期间,而是在两次write操作期间。如果超时后,upstream没有收到新的数据,nginx会关闭连接
nginx gzip
开启网站的 gzip 压缩功能,通常可以高达70%,也就是说,如果你的网页有30K,压缩之后就变成9K, 对于大部分网站,显然可以明显提高浏览速度(注:需要浏览器支持)。
##
# Gzip Settings
##
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 5;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;
git查找删除文件
which commit deleted a file
git log -1 -- [file path]
gitignore规则无效
在.gitignore
文件里定义的规则无效,
原因:如果已经提交了文件到 git 服务器,那么后续在.gitignore
文件里定义规则忽略已经提交的文件无法生效。
解决办法
git rm -r --cached .
git add .
git commit -m "we really don't want Git to track this anymore!"
git clone 所有分支
默认 git clone ,仅clone远端仓库的主分支
$ git clone git://example.com/myproject
$ cd myproject
$ git branch
* master
查看远端仓库的所有分支
$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/develop
remotes/origin/feature
remotes/origin/feature-im
remotes/origin/master
remotes/origin/newbranch
$ git branch -r # 查看远程分支
克隆需要分支
$ git checkout -b develop remotes/origin/develop
$ git pull
$ root@server 04:12:04 /srv/futurestar_tcpserver |develop ✓| → git branch -a
* develop
master
remotes/origin/HEAD -> origin/master
remotes/origin/develop
remotes/origin/master
参考:
https://www.cnblogs.com/kuyuecs/p/7111749.html
http://lichen-blog.logdown.com/posts/2135873
mysql连接超时
项目后台日志报错
OperationalError: (2006, 'MySQL server has gone away')
这种行为的原因是持久连接数据库,这是在Django 1.6中引入的。
为了防止连接超时错误,你应该设置CONN_MAX_AGE
在settings.py
以值小于wait_timeout
在MySQL配置(my.cnf
)。在这种情况下,Django检测到连接需要在MySQL抛出之前重新打开。MySQL 5.7的默认值是28800秒。
# settings.py
# 文档:https://docs.djangoproject.com/en/2.0/ref/settings/#conn-max-age
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'CONN_MAX_AGE': 3600,
<other params here>
}
}
mysql配置
# 文档:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout
wait_timeout = 28800
这通常是一个mysql配置问题。您要为网站调整的设置特别是:
- max_allowed_packet:http://dev.mysql.com/doc/refman/5.7/en/packet-too-large.html
- wait_timeout:http://dev.mysql.com/doc/refman/5.7/en/gone-away.html
nginx
2018/08/29 17:49:39 [error] 11876#0: *542 open() "/srv/weme_http_net/server-status" failed (2: No such file or directory), client: t: 42.129.99.95, se, server: r: xxxx.net, re, request: "GET /server-status HTTP/1.1", host: ": "www.xxxx.net""
相关介绍:
https://easyengine.io/tutorials/nginx/status-page/
https://blog.longwin.com.tw/2011/05/nginx-status-set-2011/
https://kovyrin.net/2006/04/29/monitoring-nginx-with-rrdtool/