django
用惯了php语言的web后端,然后尝试一下python的web端。本文记录一下尝试记录。简单记录一个部署启动过程。
感想
django不用自己来写建表的sql,sql其实是从模型中出来的。让我一时难一接受的是:模型之间的关系,它会自动帮你维护。但是,我确很不适应。
比如:多对多的关系,比如:文章跟标签,它会自动帮你创建中间表。article_tag。但是有个问题,如果我想在article_tag还要另另加内容,怎么弄呢?
难道是要我自己建一个article_tag模型吗?自己来维护吗?
本来想做一个简单的,结果发现越来越复杂。
不能说django不好用,是我暂时没有习惯它的机制。
资源
开源的项目
下面是一些用django写的开源项目。
https://gitee.com/lylinux/DjangoBlog.git
https://gitee.com/wenvki/django-idcops.git
https://github.com/twz915/zqxt.git
DjangoBlog
django-idcops.git
后台管理系统,只有一个模块,但是模块的功能还是非常复杂。用到了djangoAdmin功能,但是呢,界面又不是传统那种的界面。
zqxt
自强学堂的官网代码。比较适合新手来学,用到的东西都是中规中矩。
临时笔记
https://www.cnblogs.com/yueyuecoding/articles/13321366.html
使用 django 建模 过程
on_delete=models.CASCADE
误删migrations下的init.py 导致无法创建数据库。原因猜想:可能是因为该模块无法识别,导致无法执行该模块中的创建数据库的内容。虽然init.py本身是个空文件。
另外,因为反复删除Other模块的,导致,创建的数据库总是不对。
limit_choices_to 使用,实在没有搞懂下面说的是什么意思。
http://www.voidcn.com/article/p-wynuqchl-bue.html
原来是翻译过来的
https://www.it1352.com/2047712.html
https://stackoverflow.com/questions/31578559/django-foreignkey-limit-choices-to-a-different-foreignkey-id
apt install sqlite3
搭建第一个例子
以下以ubuntu20.04为例,使用docker的方式,从零开始,快速搭建一个测试环境。(以下为自己测试后的代码)
很多部署方面参照自强学堂网站的配置。
镜像环境
启动一个镜像,后续的所有操作,都在这个镜像里面。
docker run -itd -p 55023:8000 -p 55024:22 -v /tmp/work:/work -w /work ubuntu:20.04 bash
docker exec -it xxxxxx bash
跑通例子
# 更新源,好安装软件
apt update
cp /etc/apt/sources.list{,.bak}
apt install -y curl
# 更新源配置
curl -o /etc/apt/sources.list http://mirrors.cloud.tencent.com/repo/ubuntu20_sources.list
apt update
# python 环境
apt install -y python3.8 python3-pip vim tmux
pip3 config set global.index-url https://mirrors.cloud.tencent.com/pypi/simple
pip3 install requests django uwsgi
# django 环境
django-admin startproject website
cd website/
django-admin startapp demo
#
vi website/settings.py
cat <<EOL
ALLOWED_HOSTS = ['*']
INSTALLED_APPS # 'demo',
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
STATIC_ROOT = BASE_DIR / 'collected_static'
EOL
# 由于设置了时区,执行下面安装,选择亚洲、上海。
# 缺少此步,大概会报:No module named 'tzdata'
apt install tzdata
vi demo/views.py
# 随便增加一个测试的函数
cat <<EOL
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse('hello world')
EOL
vi website/urls.py
cat <<EOL
from demo import views as demo_views
urlpatterns = [
path('admin/', admin.site.urls),
path('demo/', demo_views.index,name='demo_index'),
]
EOL
# 下面是一些常用的启动脚本
cat > run.sh <<EOL
#python3 manage.py runserver 0.0.0.0:8000
uwsgi --http :8000 --chdir /work/website --module website.wsgi --static-map /static=collected_static
EOL
# 启动服务,已经能访问到 demo/ 了
python3 manage.py runserver 0.0.0.0:8000
# 下面配置uwsgi部署 并创建django-admin相关
python3 manage.py
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py createsuperuser
# 收集一下静态文件
python3 manage.py collectstatic
uwsgi --http :8000 --chdir /work/website --module website.wsgi --static-map /static=collected_static
# 后续,配置nginx + uwsgi + python
配置nginx + uwsgi + python
方式1:socket
apt install -y nginx
vi /etc/nginx/sites-enabled/default # 具体配置见后面
nginx -t
nginx
vi /work/website/uwsgi.ini # 具体配置见后面
uwsgi --ini /work/website/uwsgi.ini
# 其他的nginx命令
nginx -s reload/stop
- nginx配置
server {
listen 8000 default_server;
listen [::]:8000 default_server;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
uwsgi_pass unix:///work/website/uwsgi.sock;
include /etc/nginx/uwsgi_params;
}
#location /media {
# alias /path/to/project/media;
#}
location /static {
alias /work/website/collected_static/;
}
}
- uwsgi.ini
[uwsgi]
socket = /work/website/uwsgi.sock
chdir = /work/website
wsgi-file = website/wsgi.py
touch-reload = /work/website/reload
processes = 2
threads = 4
chmod-socket = 664
chown-socket = www-data:www-data
vacuum = true
按如上方式,即可正常的访问到服务,并且提交表单是正常的。
方式2:http
# 修改上面的nginx配置
proxy_pass http://localhost:8001;
include /etc/nginx/proxy_params;
# 启动uwsgi服务
uwsgi --http :8001 --chdir /work/website --module website.wsgi
题外话
如果使用http代理,要导入请求参数,缺少proxy_params,则会出现跨域问题。
socket文件路径、及权限问题
socket文件uwsgi程序会自动生成,按如下的配置,会自动设置好权限。否则,因为权限、路径问题,则无法将代理请求传入到python端。
chown-socket = www-data:www-data
uwsgi命令
# 说明:端口不能被占用
# 如果使用切换用户组、配置静态文件目录等,则可以如下
# module参数,记住,并不是指向某个文件,而是像python导入模块一样,要导入项目的 wsgi模块,不带py结尾
uwsgi --http :8001 --chdir /work/website --module website.wsgi --gid www-data --uid www-data --static-map /static=collected_static
# 使用配置文件
uwsgi --ini /work/website/uwsgi.ini
supervisor
配置守护进程,来启动uwsgi。(如果在容器内跑应用,其实也不一定需要,只需要将该uwsgi放在最后执行,并占住终端即可。但是虚拟机下,可能需要配置它来守护进程。
下面是建立在uwsgi本身已经跑通的情况下。
pip3 install supervisor
# 安装之后,会多出命令: supervisorctl supervisord echo_supervisord_conf
echo_supervisord_conf > /etc/supervisord.conf
# 然后添加后面的配置,
vi /etc/supervisord.conf
#启动
supervisord -c /etc/supervisord.conf
supervisorctl -c /etc/supervisord.conf restart wetsite
supervisorctl -c /etc/supervisord.conf [start|stop|restart] [program-name|all]
首先要确保command本身是可以执行的,不会报错。
然后添加到/etc/supervisord.conf 末尾
[program:wetsite]
command=uwsgi --http :8001 --chdir /work/website --module website.wsgi
directory=/work/website
startsecs=0
stopwaitsecs=0
autostart=true
autorestart=true
gunicorn
使用gunicorn来代替 uwsgi部署,方式差不多。
pip install gunicorn
cd /work/website
gunicorn -w4 -b0.0.0.0:8002 website.wsgi
tzdata自动安装解决
# Dockerfile
echo Asia/Shanghai > /etc/timezone && \
DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata
# 重新选择时间,shell的交互模式
dpkg-reconfigure tzdata
试了下面的,但,好像不起作用
echo "Asia/Shanghai" > /etc/timezone
dpkg-reconfigure -f noninteractive tzdata
正文
视图可以继承、模型也能继承。
目录结构
|-- db.sqlite3
|-- demo
| |-- __init__.py
| |-- admin.py # 后台管理
| |-- apps.py # 应用配置
| |-- migrations
| | |-- __init__.py
| |-- models.py # 模型,可以有多个模型
| |-- tests.py
| `-- views.py # 视图函数
|-- manage.py
|-- run.sh
`-- website
|-- __init__.py
|-- asgi.py
|-- settings.py # 项目配置
|-- urls.py # 项目的所有url
`-- wsgi.py
每个项目,都还可以增加额外的urls,来管理url。
urls.py
3种格式引入
"""
https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
函数形式
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
类形式
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
导入url配置
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from demo import views as demo_views
from blibli import views as blibli_views
urlpatterns = [
path('admin/', admin.site.urls), # 因为url都是以 / 开头,故,省略。
path('', demo_views.index,name='demo_index'),
]
尖括号语法
path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
# 对应的视图函数
views.article_detail(request, year=2003, month=3, slug="building-a-django-site").
路径参数转换
- str 包任意非空字符串,但不包含
/ - int 0或多个数字。
- slug 包含英文字符、数字、下划线、减号。
- uuid
- path 可以包含
/
正则
提前注册。
直接使用。
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
参数传递
老的格式
使用的是 url格式
from django.conf.urls import url, include
from django.contrib import admin
from django.conf import settings
import notifications.urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^idcops', include('idcops.urls')),
url('^inbox/notifications/', include(notifications.urls, namespace='notifications')),
]
静态页面
测试的时候
if settings.DEBUG:
urlpatterns += static.static(
settings.STATIC_URL,
document_root=settings.STATIC_ROOT
)
urlpatterns += static.static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)
views.py
函数式视图
基于类的视图
https://docs.djangoproject.com/zh-hans/4.0/topics/class-based-views/
使用类的继承,来实现复用视图。
URLconf
使用切换视图的模板。
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('about/', TemplateView.as_view(template_name="about.html")),
]
缓存视图。
forms.py
上传表单。
请求参数
def add(request):
a = request.GET['a']
b = request.GET['b']
c = int(a) + int(b)
return HttpResponse(str(c))
def add2(request,a,b):
c = int(a) + int(b)
return HttpResponse(str(c))
# url
path('add/<int:a>/<int:b>/', calc_views.add2, name='add2'),
request.session.get('has_commented', False):
request.session['has_commented'] = True
url(r'^new_add/(\d+)/(\d+)/$', calc_views.add2, name='add2'),
from django.urls import reverse
>>> reverse('add2',args=(2,4))
返回类型
from django.http import HttpResponse
from django.http import JsonResponse
from django.shortcuts import render
模板
# -*- coding: utf-8 -*-
from django.shortcuts import render
def home(request):
string = u"我在自强学堂学习Django,用它来建网站"
return render(request, 'home.html', {'string': string})
{% include 'nav.html' %}
{% block content %}
<div>这里是默认内容,所有继承自这个模板的,如果不覆盖就显示这里的默认内容。</div>
{% endblock %}
{% include 'bottom.html' %}
{% include 'tongji.html' %}
{%for i in cl%}
{{i}}
{%endfor%}
{% for item in List %}
{{ item }}{% if not forloop.last %},{% endif %}
{% endfor %}
forloop.counter 索引从 1 开始算
forloop.counter0 索引从 0 开始算
forloop.revcounter 索引从最大长度到 1
forloop.revcounter0 索引从最大长度到 0
forloop.first 当遍历的元素为第一项时为真
forloop.last 当遍历的元素为最后一项时为真
forloop.parentloop
用在嵌套的 for 循环中,
获取上一层 for 循环的 forloop
[uwsgi]
chdir=/path/to/your/project
module=mysite.wsgi:application
master=True
pidfile=/tmp/project-master.pid
vacuum=True
max-requests=5000
daemonize=/var/log/uwsgi/yourproject.log
示例 ini 配置文件语法:
uwsgi --ini uwsgi.ini
数据库
模型
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
模型之间的关系
https://zhuanlan.zhihu.com/p/466602178
字段类型
查找方式
本来应该是通过文档来查看的,下面是另外一种方式
find . -type d -name "*django*"
grep '[^\s]*Field()' -r .
类型:
models.CharField(max_length=30)
models.IntegerField()
models.TextField()
models.URLField()
FloatField()
DecimalField
DateTimeField
DateField
TimeField
DurationField
BinaryField
UUIDField
关系映射
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
authors = models.ManyToManyField(Author)
字段属性
@total_ordering
class Field(RegisterLookupMixin):
self.name = name
self.verbose_name = verbose_name # May be set by set_attributes_from_name
self._verbose_name = verbose_name # Store original for deconstruction
self.primary_key = primary_key
self.max_length, self._unique = max_length, unique
self.blank, self.null = blank, null
self.remote_field = rel
self.is_relation = self.remote_field is not None
self.default = default
self.editable = editable
self.serialize = serialize
self.unique_for_date = unique_for_date
self.unique_for_month = unique_for_month
self.unique_for_year = unique_for_year
if isinstance(choices, collections.abc.Iterator):
choices = list(choices)
self.choices = choices or []
self.help_text = help_text
self.db_index = db_index
self.db_column = db_column
self._db_tablespace = db_tablespace
self.auto_created = auto_created
更新
python3 manage.py makemigrations
python3 manage.py migrate
增
删
改
查
Person.objects.all()
Person.objects.all()[:10] 切片操作,获取10个人,不支持负索引,切片可以节约内存
Person.objects.get(name=name)
get是用来获取一个对象的,如果需要获取满足条件的一些人,就要用到filter
Person.objects.filter(name=”abc”) # 等于Person.objects.filter(name__exact=”abc”) 名称严格等于 “abc” 的人
Person.objects.filter(name__iexact=”abc”) # 名称为 abc 但是不区分大小写,可以找到 ABC, Abc, aBC,这些都符合条件
Person.objects.filter(name__contains=”abc”) # 名称中包含 “abc”的人
Person.objects.filter(name__icontains=”abc”) #名称中包含 “abc”,且abc不区分大小写
Person.objects.filter(name__regex=”^abc”) # 正则表达式查询
Person.objects.filter(name__iregex=”^abc”) # 正则表达式不区分大小写
filter是找出满足条件的,当然也有排除符合某条件的
Person.objects.exclude(name__contains=”WZ”) # 排除包含 WZ 的Person对象
Person.objects.filter(name__contains=”abc”).exclude(age=23) # 找出名称含有abc, 但是排除年龄是23岁的
shell
https://www.pythonf.cn/read/121861
python manage.py shell
# 使用blog 模块
from blog.models import Category
categories = Category.objects.filter(name__contains='cate') # 获取分类名称中包含"cate"字符串的分类,如:cate_1, cate_2
categories.delete()
command
https://www.cnblogs.com/polly-ling/p/9830060.html
另,参见自强学堂clearcache.py 例子。
第一个demo
cd blibli/
mkdir -p management/commands/
touch management/commands/{__init__,hello}.py
touch management/__init__.py
cat > management/commands/hello.py <<EOL
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = 'demo command.Just say: hello world'
def handle(self, *args, **kwargs):
try:
self.stdout.write('Hello world\n')
except AttributeError:
raise CommandError('Error happened!\n')
EOL
cd ..
python3 manage.py # 已经能看到模块下的命令了
# 删除后其实也能正常运行
rm blibli/management/__init__.py blibli/management/commands/__init__.py
todo
title_description 作用
admin.site.site_header = "后台数据管理系统"
admin.site.site_title = "登录系统后台"
admin.site.index_title = "后台管理"
https://blog.csdn.net/zyh8619990307/article/details/105745889/
on_delete=models.PROTECT,
operator = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
related_name="%(app_label)s_%(class)s_operator",
blank=True, null=True,
verbose_name="修改人", help_text="该对象的修改人"
)
hidden = getattr(settings, 'HIDDEN_CONFIGURE_NAVBAR', True)
admin下有save,跟models下的save区别
效果有啥区别?
def save_model(self, request, obj, form, change):
obj.save()