说明
Django 教程 正在计划编写中,欢迎大家加微信 gairuo123 提供意见、建议、纠错、催更。
Django是一个开放源代码的 Web 应用框架,由Python写成。采用了 MVT 的软件设计模式,即模型 Model,视图 View 和模板 Template。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的。并于2005年7月在 BSD 许可证下发布。这套框架是以比利时的吉普赛爵士吉他手 Django Reinhardt 来命名的。
pip install django # 以 4.0 版本为例
# 数据库支持 PostgreSQL 和 MySql
pip install psycopg2-binary pymysql
# 缓存,图片处理
pip install pymemcache pillow
# 富文本编辑器,markdown,代码样式解析
pip install django-ckeditor django-mdeditor pygments
服务部署可参考:
注: 绿色为你需要写的代码(在一般需求情况下)。
# 查看详细命令列表
python manage.py
# 进入脚本模式
python manage.py shell
# 创建一个项目
django-admin.py startproject project_name
# 创建一个 app
python manage.py startapp app_name
# 同步数据
python manage.py makemigrations # 生成同步信息
python manage.py makemigrations web # 指定 app
python manage.py showmigrations --list # 预同步查看
python manage.py migrate # 同步
python manage.py migrate web # 同步指定 app
python manage.py migrate --database=default-db # 同步指定数据库
# 运行网站
python manage.py runserver
python manage.py runserver 8001
python manage.py runserver 0.0.0.0:8000 # 本机任意 IP,可指定
# 完整脚本
<path>/python <path>/manage.py runserver 8123
# 停止运行
pkill -f runserver
# 或者
ps auxw | grep runserver
kill <pid=3424> # 找到显示的 pid
# 清空数据,只留下表结构
python manage.py flush
# 创建管理员
python manage.py createsuperuser
# 修改用户密码:
python manage.py changepassword username
开发环境与测试环境自适应:
# 在项目原 settings.py (删除),创建以下文档结构:
settings
-- __init__.py
-- common.py # 能用配置
-- dev.py # 开发环境配置
-- pro.py # 生产环境(线上)配置
__init__.py
的内容为:
# 在 mac 和 windows 电脑上为开发环境,生产环境一般为 linux
import socket
from .common import *
host_name_prefix = socket.gethostname().lower()[:3]
if host_name_prefix == 'mac' or host_name_prefix == 'win':
from .dev import *
else:
from .pro import *
配置内容:
# 站点所在目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
# 允许的域名及IP, 线上需要如实填写
ALLOWED_HOSTS = [
'*', # Allow domain and subdomains
# '', # Also allow FQDN and subdomains
]
# 数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'data_db.sqlite3'),
}
}
# 时间格式、语言
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Asia/Shanghai'
DATETIME_FORMAT = 'Y-m-d H:i:s'
USE_I18N = False
USE_L10N = False
USE_TZ = True
# 静态目录
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
os.path.join(BASE_DIR, "file"),
]
# 媒体文件
MEDIA_ROOT = os.path.join(BASE_DIR, "file")
MEDIA_URL = "/file/" # "//xx.xx.com/" dev
# 静态文件
STATIC_URL = "/static/"
在项目中如果需要引用配置变量:
from django.conf import settings
# 使用媒体文件的目录
path_prefix = settings.MEDIA_ROOT
模型结构:
from django.db import models
from django.urls import reverse
import django.utils.timezone as timezone
class Item(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=200, blank=False, )
slug = models.SlugField(max_length=50, unique=True, db_index=True)
class Meta:
db_table = "my_item" # 建议给每个 app 增加统计的前缀
verbose_name = 'Item'
ordering = ('id',)
# 自定义属性方法
def tag_list(self):
return [i for i in str(self.tags).split('#')]
def get_absolute_url(self):
return reverse("item_detail", args=[str(self.slug)])
def __str__(self):
return str(self.title)
字段:
# 主键,自增 ID
id = models.AutoField(primary_key=True)
# slug
slug = models.SlugField(max_length=50, default=default_slug,
unique=True, db_index=True)
# 短正数字
dy = models.SmallIntegerField(blank=True, null=True)
# 整数
qty = models.PositiveSmallIntegerField(blank=True, null=True, )
# 带小数
num = models.DecimalField(blank=True, null=True,
decimal_places=2, max_digits=6)
# 其他数字
qty = models.IntegerField(blank=True, null=True)
qty = models.BigIntegerField(blank=True, null=True)
# 文本
des = models.CharField(max_length=250, blank=True, null=True)
# 长文本
text = models.TextField(blank=True, null=True)
# 单选
gender = models.NullBooleanField(choices=((True, "男"),
(False, "女"),
(None, "")),
default=True)
# 布尔型
published = models.BooleanField(default=True)
# 时间, 默认为创建时间
created = models.DateTimeField('Created', auto_now_add=True,
auto_now=False, editable=False,
null=True)
# 时间, 自动取更新时间
update_time = models.DateTimeField(auto_now=True, null=True)
# 可编辑时间, auto_now/auto_now_add 为 True 不起作用
begin_time = models.DateTimeField(default=timezone.now, editable=True)
# 图片上传
pic = models.ImageField(upload_to='pic/%Y/%m', blank=True, null=True)
# 文件上传
file = models.FileField(upload_to='up/%Y/%m', blank=True, null=True)
# 外键
item = models.ForeignKey("Item", on_delete=models.PROTECT,
blank=True, null=True)
# 外键及选择约束
type = models.ForeignKey("Choice", default=4,
limit_choices_to={'type': 'text_type'},
related_name='+', on_delete=models.PROTECT)
查询结果集:
# filter 方法, 返回所有条件为均为真 and 的结果集
Entry.objects.filter()
# exclude 方法, 返回排除满足所有条件 or 的结果集
Entry.objects.exclude()
# get() 方法, 返回满足条件的唯一结果, 如结果为多个报错
e = Entry.objects.get(id=5)
e.title # 直接取字段值, 下条效果相同
Entry.objects.select_related('title').get(id=5) # 直接返回指定字段
# 不存在的错误对象
from django.core.exceptions import ObjectDoesNotExist
# extra() 灵活调用实现 SQL
Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
q.extra(order_by = ['-is_recent'])
# dates() 获得结果集中的时间列表(字段,精确度), 如 datetime.date(2005, 1, 1)
Entry.objects.dates('pub_date', 'day', order='DESC')
结果集处理:
# 返回空查询结果集
Entry.objects.none()
# 返回当前结果集的副本
Entry.objects.all()
# 将 qs1 和 qs2、qs3 联接组合起来
qs1.union(qs2, qs3)
# 取结果集的交集, 共同的部分
qs1.intersection(qs2, qs3)
# 取结果集的差集, 仅在 qs1 中有的
qs1.difference(qs2, qs3)
# 指定取出信息, 提高性能, 可同时使用
Entry.objects.defer("des", "body") # 除了这两个字段
Person.objects.only("name") # 只取此字段
其他:
# 使用指定的数据库
Entry.objects.using('db2')
# 使用原生 SQL 语句
Person.objects.raw('SELECT * FROM myapp_person')
# 联合使用
Entry.objects.filter().exclude().get()
# 聚合 aggregate()
Blog.objects.aggregate(Count('entry'))
结果集查询方法:
# 迭代
for e in Item.objects.all():
print(e.title)
# 支持切片
my_queryset.all()[:5]
# 转为列表
list(my_queryset.all())
# 是否存在查询结果
my_queryset.filter(pk=entry.pk).exists()
# 获取指定一条结果
my_queryset.first() # 第一个结果
my_queryset.last() # 最后一个结果, 另有 latest() earliest()
# 排序
my_queryset.filter.order_by('-pub_date', 'age') # 负号表示降序
my_queryset.order_by('?') # 随机排序
my_queryset.order_by('blog__name', 'title') # blog__name 中 blog 为外键
my_queryset.order_by('title').order_by('pub_date') # 两次排序
my_queryset.reverse() # 按相反的顺序返回
# 去重
my_queryset.distinct()
# 结果数
my_queryset.count()
# 给定列表对应字段的结果, 默认为主键
my_queryset.in_bulk([1, 2])
my_queryset.in_bulk(['lily', 'tom'], field_name='name')
# 获取时间列表, datetimes() 类似
my_queryset.dates('pub_date', 'day', order='DESC') # 逻辑使用同上文
# exclude 排除
# 与 filter 相同, 取非
P.objects.exclude(name__contains="tlp")
返回数据格式:
# 返回指定字段值, 类 list [(),()]
my_queryset.values_list() # 返回所有字段
my_queryset.values_list('id', flat=True) # 单个字段返回此字段所有值组成的列表
my_queryset.values_list('id', 'title', named=True) # 返回一个 namedtuple
# 返回指定字段值, 类 json [{},{}]
my_queryset.values() # 返回所有字段
my_queryset.values('blog_id') # blog 为外键, 返回 key 为 blog_id
my_queryset.values('id', 'name')
from django.db.models.functions import Lower
my_queryset.values(lower_name=Lower('name')) # 字段名指定, 值转小写
# 返回指定类型数据
# 返回字典 {'user_name': 'lily', ...}
Tweet.objects.values("user_name")
# json 和 xml
from django.core import serializers
data = serializers.serialize("xml", SomeModel.objects.all())
serializers.serialize('json', [book1, book2], indent=2,
use_natural_foreign_keys=True, use_natural_primary_keys=True)
from django.core.serializers import serialize
serialize('json', SomeModel.objects.all(), cls=LazyEncoder)
Q():
# Q(), 对象的复杂查询
# 注: 与字段同时查询时放在前, 不可使用__双下划线
from django.db.models import Q
Model.objects.filter(x=1, y=2) # 字段查询方法
Model.objects.filter(Q(x=1) & Q(y=2)) # AND
Model.objects.filter(Q(x=1, z=4) | Q(y=2)) # OR
Model.objects.filter(~Q(name="cox")) # NOT
F() 表达式:
# F() 表达式, 对象中某列值的操作
# 注: 与字段同时查询时放在前, __双下划线需在一个model里
from django.db.models import F
# 原价加10, 更新数据, 支持单个和多个
E.objects.update(price=F("price")+10)
# 查询语文成绩大于数学成绩减5分的学生
E.objects.filter(chinese__gt=F('math')-5)
E.objects.filter(authors__name=F('blog__name'))
E.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
aggregate:
# aggregate 聚合函数
# 定义 avg, 值为字段 math 的平均数, 返回: {'avg': 27}
from django.db.models import Count, Avg, Sum
from django.db.models import FloatField
P.objects.aggregate(avg=Avg('math'))
# {'price__avg': 43.54}
Book.objects.all().aggregate(Avg('price'))
# {'page_price': 0.4470664529184653}
Book.objects.all().aggregate(page_price=Avg(F('price') / F('pages'),
output_field=FloatField()))
annotate:
# annotate 在 聚合 aggregate 基础上 GROUP BY
# 统计每个班最小的年龄
l = S.objects.all().annotate(min_age=Min("age"))
[(i.class, i.min_age) for i in l]
# Fun 自定义类,继承 Func 类, 处理数据
from django.db.models import Fun
class Lower(Func): # 继承Func类
function = 'LOWER' # 使用类属性指定要使用的方法
pass # 细节得看官方文档
qs.annotate(field_lower=Func(F('field'), function='LOWER'))
# https://docs.djangoproject.com/en/3.0/ref/models/expressions
字段(穿透)查询 lookups:
# 简单匹配
Entry.objects.get(id__exact=14) # 精确匹配 sql =
Blog.objects.get(name__iexact='beatles blog') # 模糊匹配 sql like
E.objects.get(headline__contains='Lennon') # 包含 sql LIKE '%Lennon%'
E.objects.get(headline__icontains='Lennon') # sql ILIKE '%Lennon%'
E.objects.filter(headline__startswith='Lennon') # 开头包含 LIKE 'Lennon%'
E.objects.filter(headline__istartswith='Lennon') # ILIKE 'Lennon%'
E.objects.filter(headline__endswith='Lennon') # 结尾包含 LIKE '%Lennon'
E.objects.filter(headline__iendswith='Lennon') # ILIKE '%Lennon'
E.objects.filter(id__in=[1, 3, 4]) # sql IN (1, 3, 4)
E.objects.filter(headline__in='abc') # IN ('a', 'b', 'c')
# 图片 ImageField 为空的查询
E.objects.filter(picture__exact='')
# 条件, 数据和时间
# gt 大于; gte 大于等于; lt 小于; lte 小于等于;
E.objects.filter(id__gt=4) # 大于 sql id > 4
# 是否为空筛选
Entry.objects.filter(pub_date__isnull=True)
# 正则匹配
Entry.objects.get(title__regex=r'^(An?|The) +')
Entry.objects.get(title__iregex=r'^(an?|the) +')
# 关联外键查询
Blog.objects.filter(entry__authors__name='Lennon')
结果集操作方法:
# 构造一个空的对象实例
Person.objects.none()
# 增加信息
Person.objects.create(first_name="Bruce", last_name="Springsteen")
# 增加信息 2
p = Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)
# 先查询, 无结果创建一条信息, 返回 [obj, created]
Person.objects.get_or_create()
# 批量创建, 批量更新 bulk_update() 略
Entry.objects.bulk_create([
Entry(headline='This is a test'),
Entry(headline='This is only a test'),
])
# 更新, 返回的更新条数
Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
# 更新方法 2
e = Entry.objects.get(id=10)
e.comments_on = False
e.save()
# 有则更新, 无刚创建
Person.update_or_create()
# 删除
b = Blog.objects.get(pk=1)
Entry.objects.filter(blog=b).delete()
# 输出解释信息 explain
print(Blog.objects.filter(title='My Blog').explain(verbose=True))
p.delete() # 删除本数据
P.objects.all().delete() # [危险]全删除
实践技巧:
# 查看实际 SQL
print(queryset.query)
# 随机返回一条信息
Play.objects.filter().order_by('?').first()
# 链式查询: Q 查询 OR, 赋默认值, 多重排序, 取第一条
obj = (Artist
.objects
.filter(Q(bm=now.month, bd=now.day, type__id=5) |
Q(dm=now.month, dd=now.day, type__id=5))
.extra(select={"null_rating": "rating is null"},
order_by=['null_rating', '-rating', 'id'])
.first()
)
from django.utils import timezone
# 当前本地时间
now = timezone.localtime(timezone.now())
now.month # 取年月日等
# 查询几天内内容
ago_3_days = now - timezone.timedelta(days=3)
E.objects.filter(published=1, pub_date__gte=ago_3_days)
# 在两个时间内的内容
E.objects.filter(begin_time__lte=now, end_time__gt=now)
# 将文本解析为时间
from django.utils.dateparse import parse_datetime
time = parse_datetime("2012-02-21 10:28:45")
# 时间范围
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
E.objects.filter(pub_date__range=(start_date, end_date))
# 时间条件
my_queryset.filter(pub_date__date=datetime.date(2005, 1, 1)) # 在指定时间
my_queryset.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) # 时间之后
my_queryset.filter(pub_date__year=2005) # 在某年内
# 支持 year/month/day/week_day/time/hour 等
my_queryset.filter(pub_date__hour__gte=20) # 在时间后
基本视图:
from django.shortcuts import render, get_object_or_404
from web.models import Item
def item_detail(request, slug):
item = Item.objects.get(slug=slug, published=1)
return render(request, 'item.html', {'item': item})
# return render(request, 'i.html',
# {'item': get_object_or_404(Item, slug=slug, published=1)
# })
返回基础内容及跳转:
from django.shortcuts import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/') # 页面需要登录
def item_detail(request, slug):
try:
pass
return HttpResponse('获取成功!') # 返回内容
except:
HttpResponseRedirect('/login/') # 跳转
其他:
# 301 跳转
from django.shortcuts import HttpResponsePermanentRedirect
def photo_item(request, slug):
return HttpResponsePermanentRedirect('/node/{0}'.format(str(slug)))
from django.contrib import admin
from django.urls import include, path, re_path
from web import views
urlpatterns = [
path('', views.index, name='index'),
path('item/<slug:slug>', views.item_detail, name='item_detail'),
path('login/', views_user.login_site, name='login'),
# api
path('api/', include('api._urls')),
# admin
path('admin.noname/', admin.site.urls),
]
配置:
# Caches
CACHES = {
'default-no': { # 不执行, 开发模式下可使用
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'LOCATION': os.path.join(BASE_DIR, 'cache'),
'TIMEOUT': 60 * 60 * 24 * 7, # a week
'OPTIONS': {
'MAX_ENTRIES': 10000
}
},
'default': { # Memcached(需装服务) and pip install pymemcache
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
'TIMEOUT': 60 * 60 * 24 * 7, # a week
},
'file': { # 文件
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': os.path.join(BASE_DIR, 'cache'),
'TIMEOUT': 60 * 60 * 24 * 7, # a week
'OPTIONS': {
'MAX_ENTRIES': 10000
}
},
}
查看缓存:
# cache 为默认缓存引擎
from django.core.cache import cache
# 查询缓存键的值,无则返回 None
cache.get('log-1233')
# data
todo: redis
在视图 views.py 中使用
from django.views.decorators.cache import cache_page
@cache_page(60*10, cache='file') # unit of second, 10min
def index(request):
pass
在路由 urls.py 中使用
from django.views.decorators.cache import cache_page
urlpatterns = [path('node',
cache_page(60*2, cache="default")(node.web),
name='node'),]
写一个缓存装饰器
# todo
基础功能:
# 取模型对象的属性
{{ item.title }}
# 媒体地址
{{ MEDIA_URL }}
# 引用模板
{% include "footer.html" %}
# 给引用模板传入变量
{% include "pub/m_review.html" with type='wiki' cell=wik.slug %}
# 当前时间
{% now "Y" %} # 格式如:2020
# 取其对应外键中的条目
{% for i in teacher.class_set.all %}
流程控制:
# if 语句
{% if item.switch_on %}
开启
{% else %}
关闭
{% endif %}
# for 中的计算
{% if i.type.item == 'var' %} # > < != or and
# for 循环, 处理首个元素
{% for i in item.tag_list %}
{% if forloop.first %}标签:{% endif %}<code>{{ i }}</code>
{% empty %}
no msg.
{% endfor %}
信息过滤处理:
# 按 html 代码输出
{{ i.html|safe }}
# 取 type 的对外显示内容
i.get_type_display }}
# 取外键的属性
{{ i.teacher.name }}
# 切片计算
{% for i in names|slice:":1" %} # 第一个
{% for i in names|slice:":4" %} # 前4个
# 默认值
{{ i.year|default:"2019" }}
# 排序, 注如是对象方法需要加装饰器 @property
{% for i in timeline|dictsort:"time" %} # 正序
{% for i in timeline|dictsortreversed:"time" %} # 降序
# 显示前12个字符,剩余用「...」代替
{{ item.nameCN|truncatechars:12 }}
# 时间格式化
{{ i.pub_time|date:'Y-m-d' }} # 如:2020-01-05
{{ i.pub_time|date:"H:i:s" }} # 如:12:11:04
# 取长度
{{ name|length }} # 返回如:3
# 复合处理
{% for i in teacher.class_set.all|dictsortreversed:"id"|slice:":6" %}
# 格式化为标题
{{ i.name|title }}
# 值减去1, 加为正
{{ evt.ty|add:-1 }}
# 去掉渲染后 html 中的空格
{% spaceless %} code {% endspaceless %}
# 注释
{% comment %} code {% endcomment %} 和 {# code #}
{{ foo|truncatechars:7 }} # 显示部分长度, 用省略号代码
{{ foo|truncatechars_html:7 }} # 针对 html 显示部分长度
分组:
# 将学生名单按年龄分组,同年龄的显示在一起
{% regroup students|dictsortreversed:"time_int" by age as aged %}
<ul>
{% for age in aged %}
<li>{{ age.grouper }}
<ul>
{% for i in age.list %}
<li>{{ i.name }}: {{ i.height }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
# 在 url 中增加路由
from django.urls import path
from django.contrib import admin
urlpatterns = [path('web-admin/', admin.site.urls), ]
# 在 app 目录中创建 admin.py
from django.contrib import admin
from .models import News
class NewsAdmin(admin.ModelAdmin):
list_display = ('id', 'title','tags' ,'url', 'update_time')
raw_id_fields = ('writer', )
list_display_links = ('id', 'title',)
search_fields = ['title', 'writer__name', 'slug']
form = NewsForm # 可定义表单样式
radio_fields = {"type": admin.HORIZONTAL, } # VERTICAL
list_per_page = 30
admin.site.register(News, NewsAdmin)
TODO
推荐使用 django-ckeditor
。
升级 django 3.0 后 ckeditor 无法返回上传文件路径问题的解决:
CKEditor Refused to display 'XXX' in a frame because it set 'X-Frame-Options' to 'deny'.
Django 3.0 需要将 X_FRAME_OPTIONS 的默认值由 SAMEORIGIN 调整为 DENY.
# https://docs.djangoproject.com/en/3.0/ref/clickjacking/
# settings:
MIDDLEWARE = [
...
'django.middleware.clickjacking.XFrameOptionsMiddleware',
...
]
# 给定配置
X_FRAME_OPTIONS = 'SAMEORIGIN'
TODO
TODO
TODO
TODO
支持级数、列表,包括嵌套形式数据,常用场景为文章标签、经纬度等,为 PostgreSQL 所支持, 数据类型如 character varying[]
。
from django.contrib.postgres.fields import ArrayField
# 可用 IntegerField 等作为数组内容,格式如 [1, 3, 5, 7],
tags = ArrayField(models.CharField(max_length=10, null=True), blank=True, null=True)
# 格式如 [[1,3], [2,4]], 限定每个元素里有两个元素(必须)
tags = ArrayField(ArrayField(models.CharField(max_length=10), size=2), null=True)
# 表单, 上例对应。每个元素编辑和显示时可以用 # 号分隔
from django.contrib.postgres.forms import SimpleArrayField
tags = SimpleArrayField(forms.CharField(), delimiter='#', required=False)
tags = SimpleArrayField(SimpleArrayField(forms.CharField()), delimiter='#', required=False)
# 查询
Tags.objects.filter(tags__contains=['a', 'b']) # 包含所有元素
Tags.objects.filter(tags__contained_by=['a', 'b']) # 所有内容为其的子集
Tags.objects.filter(tags__overlap=['a']) # 包含其中一个
Tags.objects.filter(tags__len=1) # 长度为1的
Tags.objects.filter(tags__0='a') # 第一个元素为 a 的
Tags.objects.filter(tags__1__iexact='b')
Tags.objects.filter(tags__0_2__contains=['c']) # 第1和2和元素中包含 c 的
可以存储 json 格式数据,为 PostgreSQL(采用 jsonb 类型)所支持,在新版本后几乎所有的数据均支持 JSONField 字段。
# 旧版本
from django.contrib.postgres.fields import JSONField
# 4.0 以上
from django.db.models import JSONField
data = JSONField() # 在 models 中定义
Dog.objects.filter(data__owner__name='Bob') # 指定 owner.name 值
Dog.objects.filter(data__owner__other_pets__0__name='Fishy') # 第一条数据值
Dog.objects.filter(data__contains={'owner': 'Bob'})
Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
Dog.objects.filter(data__has_key='owner')
Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
Dog.objects.filter(data__values__contains=['collie'])
Dog.objects.filter(data__keys__overlap=['breed', 'toy'])
注:PostgreSQL 支持的 JSONField 字段类型为 jsonb,如果是 json 可能会报以下错误:
json object must be str, bytes or bytearray, not dict
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
pic_list = Picture.objects.filter(photo_type=1).order_by('id')
paginator = Paginator(pic_list, 1) # Show Qty contacts per page
page = request.GET.get('page')
try:
pics = paginator.get_page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
pics = paginator.get_page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
pics = paginator.get_page(1) # (paginator.num_pages)
# 以模板中可以调用, 如 {{ pics.number }}
# pics.has_previous 是否有上一页
# pics.has_next 是否有下一页
# pics.number 当前页序
# pics.paginator.num_pages 总页数
# pics.previous_page_number 上一页页序
# pics.next_page_number 下一页页序
# 设置 cookie, 有效期一年, 单位秒
response = self.get_response(request)
response.set_cookie("uuid", 'cookie值', max_age=365 * 24 * 60 * 60)
# 读取 Cookie
request.COOKIES['uuid'] # 取指定 cookie 值
request.COOKIES.keys() # 取所有 cookie 名
TODO
简单的接口实现:
from django.http import JsonResponse
from music.models import News
def single_object(obj):
return {
'title': obj.title,
'slug': obj.slug,
'type': obj.type,
'files': ['https://pic.gairuo.com/file/' + i for i in obj.file],
'text': obj.text,
}
def news_detail(request):
slug = request.GET['slug']
new = News.objects.get(slug=slug).id
news_data = [single_object_score(obj) for obj in new]
data = {'data': news_data}
return JsonResponse(data, safe=False)
进入交互模型
在开发过程中,我们可以进入交互模式进行代码验证,各大 IDE 都有终端可供使用,也可打开电脑的独立终端工具进行测试。方法为:
# 进入环境
conda activate py39
# 进入项目目录
cd /home/www/webdir
# 查看文件(要有 manage.py)
ls
README.md app1 templates manage.py static ...
# 进入 django 交互模式
python manage.py shell
# 编写 django 代码测试
>>> from app1.models import MyObj
>>> MyObj.objects.all()[:3]
<QuerySet [<MyObj: test1>, <MyObj: test2>, <MyObj: test3>]>
vscode
如果用 vscode ,需要在根目录创建以下文件:
# launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"console": "integratedTerminal",
"args": [
"runserver",
"7000", //配置Django端口
"--noreload",
"--nothreading"
],
"django": true
}
]
}
端口占用
如果 遇到报错端口被占用,说明之前的运行没有退出:
Error: That port is already in use.
可在终端操作:
# 查询端口占用情况
ps aux | grep runserver
# 找到端口号执行,<PID> 是 django 占用的端口
kill -9 <PID>
没有主模块
如果用 PyCharm debug 模式启动失败,显示 can't find '__main__' module”
,可能是 reload 模块的问题,最后在启动时加上 --noreload
参数,就可以正常启动调试。
TODO
更新时间:2022-03-11 18:29:12 标签:网站 python web django