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/

使用类的继承,来实现复用视图。

generic-views-of-objects

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

  1. Person.objects.all()

  2. Person.objects.all()[:10] 切片操作,获取10个人,不支持负索引,切片可以节约内存

  3. Person.objects.get(name=name)

get是用来获取一个对象的,如果需要获取满足条件的一些人,就要用到filter

  1. Person.objects.filter(name=”abc”) # 等于Person.objects.filter(name__exact=”abc”) 名称严格等于 “abc” 的人

  2. Person.objects.filter(name__iexact=”abc”) # 名称为 abc 但是不区分大小写,可以找到 ABC, Abc, aBC,这些都符合条件

  1. Person.objects.filter(name__contains=”abc”) # 名称中包含 “abc”的人

  2. Person.objects.filter(name__icontains=”abc”) #名称中包含 “abc”,且abc不区分大小写

  1. Person.objects.filter(name__regex=”^abc”) # 正则表达式查询

  2. Person.objects.filter(name__iregex=”^abc”) # 正则表达式不区分大小写

filter是找出满足条件的,当然也有排除符合某条件的

  1. Person.objects.exclude(name__contains=”WZ”) # 排除包含 WZ 的Person对象

  2. 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()