基本配置
# Django三板斧
from django.shortcuts import render, HttpResponse, redirect
1> 返回字符串return HttpResponse('ok')
2> 返回html文件/模版渲染文件return render(request, 'home.html')
3> 重定向
return redirect('http://www.baidu.com')
外链地址
return redirect('/home/')
本地路由
提一嘴: render的本质还是将模版字符串放到HttpResponse中返回回去!
在settings.py中检查应用是否注册、模版路径是否配置
INSTALLED_APPS = [
...
...
'app01.apps.App01Config',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True, # -- 是否在App里查找模板文件!!
...
}
]
# -- Ps:补充说明.
在项目的根目录和app01下分别创建templates文件夹.
根目录的templates通常存放公用的模板文件,以供各个App的模板文件调用,该模式符合代码重复使用的原则
例如HTML中的<head>部分.
app01下的templates是存放当前App所需要使用的模板文件.
'DIRS': [
# -- replace('\\','/')是为了代码兼容win7系统等,跨平台.
os.path.join(BASE_DIR, 'myblog/templates').replace('\\','/'),
os.path.join(BASE_DIR, 'templates').replace('\\','/')
],
'APP_DIRS': True,
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
mysite目录下的url.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
url(r'^home/', views.home),
url(r'^tobaidu/', views.to_baidu),
url(r'^tohome/', views.to_home),
]
2
3
4
5
6
7
8
9
10
11
12
app01目录下的views.py
注意:每一个视图函数都必须有一个形参request.
from django.shortcuts import render, HttpResponse, redirect
def index(request):
print(request) # -- <WSGIRequest: GET '/index/'>
return HttpResponse('ok') # -- 返回字符串
def home(request):
# -- 在templates目录下创建了home.html文件 body标签里写了-- 这是home!!
return render(request, 'home.html') # -- 返回html文件
def to_baidu(request):
return redirect('http://www.baidu.com') # -- 重定向跳转to某个网址
def to_home(request):
return redirect('/home/') # -- 重定向跳转to某个路由
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
http://127.0.0.1:8000/index/
-- 页面显示 ok
http://127.0.0.1:8000/home/
-- 页面显示 这是home!!
http://127.0.0.1:8000/tobaidu/
-- 跳转到百度搜索界面
http://127.0.0.1:8000/tohome/
-- 跳转到/home/路由
# 静态文件配置
我们都会使用动态导入!!这样只需要改STATIC_URL的属性值就行,而不需要改动html文件啦!
何为静态文件? css、js、jq、img、lib、sdk等都是静态文件!
在Django中,我们约定俗成的将html文件放到template目录中,静态文件放到static目录中!
我们以登陆的form表单为例.关键代码如下:
# ▲ mysite目录下的settings.py
# -- 静态文件配置,若想访问静态文件,就得以xxx开头/作为前缀!!
# django看到你以/xxx/为前缀的路径,就晓得你想访问静态文件,没有其它意思.
# 但我们约定俗称配置为 STATIC_URL = '/static/' !!!
STATIC_URL = '/xxx/'
STATICFILES_DIRS = [
# -- 这里的static就是指的根目录下的static文件夹
os.path.join(BASE_DIR, 'static')
# -- 当然可以写多个,依次从上往下找,找到就不找啦.跟环境变量一样,第一个找到了,就不会找第二个啦!
# ...
]
# ▲ mysite目录下的urls.py
urlpatterns = [
...
url(r'^login/', views.login),
]
# ▲ app01目录下的views.py
def login(request):
return render(request, 'login.html')
# ▲ templates目录下的login.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
{# -- ◎ 使用BootCDN提供的免费CDN加速服务#}
{# <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">#}
{# -- ◎ 静态导入#}
{# <link rel="stylesheet" href="/xxx/css/bootstrap.min.css">#}
{# -- ◎ 动态解析 {% load static %}中的static就是 STATIC_URL的值! 这里是'/xxx/' #}
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<form action="">
<p>username:<input type="text" name="username" class="form-control"></p>
<p>password:<input type="text" name="password" class="form-control"></p>
<input type="submit" value="登陆" class="btn btn-success">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
http://127.0.0.1:8000/xxx/css/bootstrap.min.css
http://127.0.0.1:8000/login/
# request对象
1> request.method -- 返回本次的请求方式,返回值是全大写的字符串 GET、POST.
2> request.POST -- 字典
request.POST.get('表单的name属性值') -- 取最后一个
request.POST.getlist('表单的name属性值') -- 可以接受全部数据 eg:多选框
3> request.GET
<!--
action不填默认提交到当前地址,也就是views.py的login函数中!
method不写,默认是get请求,表单里的k-v会拼接到路由中..
input提交按钮不写type="submit",提交不到一点..
post请求到后端,若用户名或者密码错误,后端重新render到登陆页面,页面会刷新,,表单内容清空!
若提交的是button标签按钮,button的type属性值默认是submit,点击即可提交
将button的type设置为button就不会自动提交
-->
<form action="" method="post">
<p>username:<input type="text" name="username" class="form-control"></p>
<p>password:<input type="password" name="password" class="form-control"></p>
<p>
{# ★ 若不给多选框设置value属性,勾选返回的都是on,设置后返回的是设置的值#}
<input type="checkbox" name="hobbies" value="1"> 足球
<input type="checkbox" name="hobbies" value="2"> 篮球
<input type="checkbox" name="hobbies" value="3"> 排球
</p>
<input type="submit" value="登陆" class="btn btn-success">
</form>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
先注释中间件中的csrf验证!!不然post请求通过不了!!
Ps: mysql三大攻击 -- xss、sql注入、csrf
★ 输入网址http://127.0.0.1:8000/login/
, 走的是login函数渲染出模版..
提交表单,表单的action的值为空,提交到当前地址,即http://127.0.0.1:8000/login/
.会再走到login函数.
所以我们需要在login函数中判断请求的方式!!!
当然可以设置action的值为/login_post/
,专门写个路由和函数处理表单的post请求,但没有必要!!
def login(request):
# -- !!应该区分不同的请求(get/post)方式,做不同的事情!
# 1> get请求来的时候,只渲染一个登陆页面!
# 2> post请求来的时候,接收参数!
# -- 那如何区分不同的请求方式呢?
# 通过request.method的值来判断
# 输入网址 http://127.0.0.1:8000/login/,回车,此处打印值 GET <class 'str'>
# 填写表单信息,点击登陆 会再次执行该函数,此处打印值 POST <class 'str'>
print(request.method, type(request.method))
# -- POST
if request.method == "POST":
# <QueryDict: {'username': ['egon'], 'password': ['123'], 'hobbies': ['2', '3']}>
print(request.POST)
# <class 'django.http.request.QueryDict'> -- 简单理解就是字典!
# -- 实验了下,没对应的key,默认返回值跟原生字典一样为None
print(type(request.POST))
# -- 接收用户表单中提交的用户名和密码
username = request.POST.get('username')
password = request.POST.get('password')
# -- 若用get只会接收最后一个;getlist接收多个值!
hobbies = request.POST.getlist('hobbies')
print(username, password, hobbies) # egon 123 ['2', '3']
if username == 'egon' and password == int('123'):
# -- 一系列的处理逻辑
pass
# -- GET
# http://127.0.0.1:8000/login/?name=egon&age=20
# <QueryDict: {'name': ['egon'], 'age': ['20']}>
print(request.GET, type(request.GET))
# <class 'django.http.request.QueryDict'>
print(type(request.GET))
# -- 当然也有request.GET.getlist(..)
name = request.GET.get('name')
age = request.GET.get('age')
print(name, age) # egon 20
return render(request, 'login.html')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# pycharm连接数据库
数据库 -- 加号 -- 数据源 -- mysql -- 弹出页面中的名称那一栏自己随便填...
比如:这里我连接的是数据库中名为youku的数据库,连接时名称填写的是mysite_db!
Ps: 通常不会这么用,看个人.. 一般都在Navicat中操作啦!!
# Django连接mysql
"""
1> 安装mysql,启动mysql服务
2> 可以使用navicat连接mysql服务,连接好后创建一个名为"db"的数据库
3> 在Django配置文件中进行配置
4> 安装相关依赖/第三方组件
"""
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db', # -- 库名
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
'PORT': '3306',
'CHARSET': 'utf8'
}
}
Ps: 查看Django支持哪些数据库,from django.db.backends import mysql 查看源码,那些文件夹名就是支持的数据库.
不够用,需要的是这些之外的数据库?在源码中将那个数据库的引擎放到这些数据库的同级目录下.
postgresql安装的是 pip install psyopg2
oracle安装的是 pip install cx-Oracle
`https://pypi.org/`
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在Django项目启动的时候,会报错: No module named MySQLdb
解决方案: 安装pymysql模块并在app01的__init__.py下添加以下代码.
但很多时候, 也会在 项目根目录/项目名目录/__init__.py下添加一下代码.
import pymysql
# -- 默认用的是mysqldb,改成pymysql
pymysql.install_as_MySQLdb()
2
3
4
# Django ORM
ORM 对象关系映射
作用: 将原生sql语句转成代码操作sql,本质是执行sql语句.
代码 | 数据库 |
---|---|
类 | 表 |
类的属性 | 表里字段对应的值 |
类的实例对象 | 表里的记录 |
# orm创建表
1> 在settings.py中配置好 Django连接mysql中的db数据库 的相关内容,db数据库事先通过navicat建好!
2> ORM的书写位置 -- models.py
# -- app01下的models.py
from django.db import models
# -- 必须继承models.Model!!
class User(models.Model):
# -- id int primary key auto_increment
id = models.AutoField(primary_key=True)
# -- name varchar(64)
# 强调!!CharField的max_length参数必须指定!
name = models.CharField(max_length=64)
# -- password varchar(32)
password = models.CharField(max_length=32)
2
3
4
5
6
7
8
9
10
11
12
13
3> 生成迁移记录与迁移 在终端依次输入命令(manage.py所在路径下)
python3 manage.py makemigrations
-- 会在app01.migrations文件夹下生成0001_initial.py文件
python3 manage.py migrate
-- 运行此命令后, 会读取migrations下0001_initial.py里的配置, 转换成sql语句,在default对应的数据库中生成表
ps: 若有多个app,会依次读取每个app下migrations目录下最新py文件里的配置. 在default对应的数据库中生成表..
说明: app01_user 即app名字_类名小写 是我们自己创建的表,其余是Django每次迁移时默认自动生成的表...
自动生成的表来自settings.py INSTALLED_APPS属性中默认注册的那些app!!! 当前可以在迁移前注释掉,不用它们!
django_migrations
这张表里是数据迁移的记录..
注意: 切勿通过数据库可视化工具修改表结构.. 应该通过修改ORM对应的表,然后进行数据迁移来修改..
不然手动修改表结构, 而ORM表没改, 调用通过ORM创建的表时, 传入的字段不存在, 会报错.
# orm字段增删改查
每次改变models.py中跟数据有关的代码,都需要执行数据库迁移相关的两条命令!!
注意! Django ORM 设置的字段默认不允许为空;用sql写的原生语句默认允许为空!!
▲ 在User类中依次进行以下实验!!
# -- 增加一个age字段,重新执行两条命令
# ★ 生产迁移记录的时候,会让我们设置一个该字段的值(一次性的),作为该表每行数据的age字段的值
age = models.IntegerField()
# -- 当然,我们可以在增加age字段时,设置可为空,或者设置默认值!!!!!!
# age = models.IntegerField(null=True)
# age = models.IntegerField(default=1)
# -- 修改,需要重新执行两条命令
age1 = models.IntegerField()
# -- 删除,直接注释掉,然后重新执行两条命令 生产环境中此操作很危险,慎重!
age1 = models.IntegerField()
2
3
4
5
6
7
8
9
10
11
12
13
14
Ps: 可以简化执行两条命令的操作.
pycharm Tools - Run manage.py Task 在下方弹出的窗口中,执行命令 makemigrations
、migrate
# 补充
# 静态资源
静态资源, 大致可以分为两类:
1> 开发需要的css、js、img -- 根目录下的/static/ ; 已注册的app目录下的/static/.
2> 媒体文件,即用户上传的数据.图片、音频、视频等 -- 根目录下的/media/
static
STATIC_URL = '/xxxx/' # 一般为"/static/"
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
优先在STATICFILES_DIRS中找,没找到就去已注册的app下找static目录!
一般性规则:若是多app,每个app的static目录下应该嵌套一层app名字的目录..
eg:模版中使用
{% load static %}
# Django会自动找到配置文件中的STATIC_URL,自动拼接,相当于src='/xxxx/web/1.png'
<img src='{% static 'web/1.png' %}'>
2
3
4
5
6
7
8
9
10
11
media
"""
以下是查看媒体文件的配置
"""
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('api/', include("apps.api.urls", namespace='x1')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
<img src='/media/aa.png'>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 数据库连接池
官方文档:
https://pypi.org/project/django-db-connection-pool/
每次操作数据库,都要创建连接,发收消息,关闭连接.. 很浪费资源,所以需要数据库连接池.
pip install pymysql
记得写那个猴子补丁.
pip install django-db-connection-pool
DATABASES = {
"default": {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'day04', # 数据库名字
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'POOL_OPTIONS': {
'POOL_SIZE': 10, # 最小,Django运行起来,就与数据库建立10个连接
# 在最小的基础上,不够用,还可以增加10个 即:最大20个.
# So,支持20的并发.再多就得等待.等某个连接用完后交还到连接池中.
'MAX_OVERFLOW': 10,
'RECYCLE': 24 * 60 * 60, # 一个连接可以被重复用多久,超过会重新创建,-1表示永久.
'TIMEOUT':30, # 池中没有连接最多等待的时间.单位s.
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
组件django-db-connection-pool
内部最核心的是一个支持SQLAchemy数据库连接池的组件.
# 数据库读写分离
两个数据库拥有同样的表结构..两数据库实现数据同步. 一数据库用来读,一数据库用来写.
为了配合数据库架构的...
setting文件中数据库的配置如下
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db1',
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
'PORT': '3306',
'CHARSET': 'utf8'
},
'db2': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db2',
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
'PORT': '3306',
'CHARSET': 'utf8'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
一点准备工作
数据库之间的同步,参考资料:
https://zhuanlan.zhihu.com/p/89796383
https://www.bilibili.com/video/BV1Kr4y1i7ru/?p=161
1.准备两台服务器.
192.168.1.2 主/master - 名为db1的数据库 用来写
192.168.2.12 从/slaver - 名为db2的数据库 用来读
可在mysql中创建名为db1和db2的两个数据库来模拟.
2.数据表结构 apps/app01/models.py
from django.db import models
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
3.生成数据库表,执行一下命令
python manage.py makemigrations
python manage.py migrate 等同于 python manage.py migrate --database=default
python manage.py migrate --database=db2
这样的话,在db1、db2两个数据库中都有了相同的表!!
4.总路由
from django.urls import path
from apps.app01 import views
urlpatterns = [
# path('admin/', admin.site.urls),
path('index/', views.index),
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 手动的方式using
"""
视图函数 apps/app01/view.py
"""
from django.shortcuts import HttpResponse
from . import models
def index(request):
# 往主数据库db1里写入数据 默认就是using("default")
models.UserInfo.objects.using("default").create(name="麻瓜")
# 往从数据库db2里读取数据,db1和db2会做配置进行数据同步,同步的慢些的话,是读不到最新数据的.
# 注:Django可不管主从数据库之间数据的同步,数据库之间的同步是运维的活,与开发无关.
res = models.UserInfo.objects.using("db2").all()
print(res)
return HttpResponse("Hello World!")
2
3
4
5
6
7
8
9
10
11
12
13
14
# 基于router来实现
最终效果: 利用ORM语句对数据库进行CURD时, ORM语句不用写using()..
step1: 在项目根目录创建utils目录,在该目录下创建router.py
step2: 往router.py里写入内容
step3: 在setting.py里注册router DATABASE_ROUTERS = ['utils.router.DemoRouter']
"""
utils/router.py
"""
class DemoRouter:
# !!可以添加逻辑判断动态的控制,细粒化的控制某个app的某张表进行读操作时从哪个数据库里读取..
def db_for_read(self, model, **hints):
print(model._meta.app_label) # app01 -- 表明当前ORM语句操作的是哪个app
print(model._meta.model_name) # userinfo -- 表明当前ORM语句操作的是哪个model
"""
想要知道model._meta里还有什么东西,通过.没有提示 有个小技巧.
可以查看model._meta的类型,即它是哪个类的实例化对象,去看该类里面定义了什么.
"""
# app01.userinfo <class 'django.db.models.options.Options'>
# from django.db.models.options import Options 跳转到Options里查看
print(model._meta, type(model._meta))
print(hints)
# return None # 去下一个Router中找
return 'db2' # 返回读的那个数据库
# 写操作同理,也可以细粒化的控制.
def db_for_write(self, model, **hints):
# model._meta
return "default" # 返回写的那个数据库
"""
视图函数 apps/app01/view.py
"""
from django.shortcuts import HttpResponse
from . import models
def index(request):
models.UserInfo.objects.create(name="麻瓜")
res = models.UserInfo.objects.all()
print(res)
return HttpResponse("Hello World!")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 分库
业务比较大, 总共有百来张表, 想要拆分到不同的数据库中
注意!! 一定不要跨数据库进行表关联.. 数据库支持,但Django是不支持的!!! 尽可能的将用关联的表放到同一个数据库中.
# 多个app的情况
假设共100张表, 50张表放到app01; 50张表放到app02中.
表先通过app隔离, 然后将不同的app迁移到不同的数据库中. 比如: app01中的表到db1; app02中的表到db2.
^先来探究下这几个命令:
# 运行该命令后,每个app下migrations目录里会生成配置文件
python manage.py makemigrations
# <将每个>app下migrations目录里生成的配置文件转化为sql语句,都在default对应的数据库中生成表
python manage.py migrate 等同于 python manage.py migrate --database=default
# <只是>将app01这个app下migrations目录里生成的配置文件转化为sql语句,在default对应的数据库中生成表
python manage.py migrate app01 --database=default
2
3
4
5
6
7
准备工作
"""
apps/app01/models.py
"""
from django.db import models
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
"""
apps/app02/models.py
"""
from django.db import models
class Role(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
python manage.py makemigrations
python manage.py migrate app01 --database=default
python manage.py migrate app02 --database=db2
>>: 最终结果,将app01中的表放到了db1数据库中,app02中的表放到了db2中.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
读写操作
注:虽然上面进行了分库操作,但是Django的ORM语句可不管分没分库、读写有无分离
无论在哪个app里,对表进行CURD的ORM语句依旧默认使用的是using("default")
若刚好这个表没有在default对应的数据库中,就会报错..
这里体现在,from apps.app02 import models as m2 m2.Role.objects.all() 无论在哪个app的model里写这行语句都会报错
报错信息:(1146, "Table 'db1.app02_role' doesn't exist")
比较low的解决办法,m2.Role.objects.using("db2").all()
比较好的解决办法,app里的所有表是放到某个数据库里的嘛,所以某app是跟某个数据库一一对应的.
class DemoRouter:
def db_for_read(self, model, **hints):
if model._meta.app_label == "app01": # -- 表明当前ORM语句操作的是哪个app
return "default"
if model._meta.app_label == "app02":
return "db2"
def db_for_write(self, model, **hints):
if model._meta.app_label == "app01":
return "default"
if model._meta.app_label == "app02":
return "db2"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 单个app的情况
我想让userinfo表放到db1; role和goods两张表放到db2.
解决办法: 命令 + router
"""
apps/app01/models.py
"""
from django.db import models
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
class Role(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
class Goods(models.Model):
price = models.CharField(verbose_name="价格", max_length=32)
"""
utils/router.py 记得在settings.py文件中注册
"""
class DemoRouter:
# -- 借助这个函数进行的分库
def allow_migrate(self, db, app_label, model_name=None, **hints):
if db == "db2":
if model_name in ["role","goods"]:
return True
else:
return False
if db == "default":
if model_name in ["userinfo"]:
return True
else:
return False
# 读操作
def db_for_read(self, model, **hints):
if model._meta.model_name in ["role","goods"]:
return "db2"
if model._meta.model_name in ['userinfo']:
return "default"
# 写操作
def db_for_write(self, model, **hints):
if model._meta.model_name in ["role","goods"]:
return "db2"
if model._meta.model_name in ['userinfo']:
return "default"
-- 接着,执行命令
执行命令 python manage.py migrate app01 --database=default
即db为"default",接着遍历app01.models里的所有表,表名为userinfo的在数据库db1中生成
执行命令 python manage.py migrate app01 --database=db2
即db为"db2",接着遍历app01.models里的所有表,表名为"role","goods"的在数据库db2中生成
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54