博客
# 站点左侧功能实现
点击左侧的筛选功能,会访问相应的路由地址,在右侧展示对应的文章列表!!!
★ 这个筛选功能涉及的知识点很重要!!
http://127.0.0.1:8000/egon/tag/1
http://127.0.0.1:8000/egon/category/1
http://127.0.0.1:8000/egon/archive/2022-12
urls.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'^register/', views.register), # -- 注册
url(r'^login/', views.login), # -- 登陆
url(r'^get_code/', views.get_code), # -- 获取验证码
url(r'^home/', views.home), # -- 首页
url(r'^logout/', views.logout), # -- 退出登陆
url(r'^set_pwd/', views.set_pwd), # -- 修改密码
# -- 记得测试下,看能不能到这个路由!!
url(r'^(?P<username>\w+)/$', views.site), # -- 个人站点 (有名分组)
# -- 个人站点侧边栏筛选功能!
# url(r'^(?P<username>\w+)/tag/(\d+)', views.site),
# url(r'^(?P<username>\w+)/category/(\d+)', views.site),
# url(r'^(?P<username>\w+)/archive/(\w+)', views.site),
# -- 把上面三个路由合成一个!
url(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)', views.site),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
views.py
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
import hashlib
import random
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO, StringIO
def register(request):...
def get_random():...
def get_code(request):...
def login(request):...
def home(request):...
def logout(request):...
def set_pwd(request):...
def site(request, username, **kwargs):
# -- ▲ 需求:过滤出当前用户所在站点的所有文章!
# 在注册时就已经保证了用户名不重复!
# 方式一:用对象来跨
user_obj = models.UserInfo.objects.filter(username=username).first()
# 需要先判断,若该用户不存在,跳转404页面!
if not user_obj:
return render(request, 'error.html')
article_list = user_obj.site.article_set.all()
# 方式二: 用字段来跨 这行代码也可以达到同样的效果!!
# article_list = models.Article.objects.filter(site__userinfo__username=username).all()
# -- ▲ 个人站点侧边栏筛选功能!
if kwargs:
condition = kwargs['condition'] # tag、category、archive
param = kwargs.get('param') # `标签id`eg:5、`分类id`eg:12、`归档日期`eg:2020-08
if condition == 'tag':
# article_list 这个是article文章的queryset对象!进行进一步的筛选!
# tag是article表中的虚拟字段!
article_list = article_list.filter(tag__pk=param)
elif condition == 'category':
# article_list = article_list.filter(category_id=param) 也行!
article_list = article_list.filter(category__pk=param)
elif condition == 'archive':
year, month = param.split('-')
article_list = article_list.filter(create_time__year=year, create_time__month=month)
# -- ▲ 需求: 查询出 当前用户的站点 下所有标签以及每个标签下的文章数
tagName_articleNum = models.Tag.objects.filter(site__userinfo__username=username) \
.values_list('name', 'pk').annotate(Count('article__pk'))
# print(tagName_articleNum) # <QuerySet [('jason的标签一', 1), ('jason的标签二', 2)]>
# -- ▲ 需求: 查询出 当前用户的站点 下所有分类以及每个分类下的文章数
categoryName_articleNum = models.Category.objects.filter(site__userinfo__username=username) \
.values_list('name', 'pk').annotate(Count('article__pk'))
# print(categoryName_articleNum) # <QuerySet [('jason的分类一', 1), ('jason的分类二', 2)]>
# print(categoryName_articleNum.query)
# -- ▲ 需求: 按照年/月/日 进行日期归档 -- 该需求很普遍!!
# sql: SELECT DATE_FORMAT(create_time,"%Y-%M"),COUNT(id) from app01_article \
# where site_id=1 GROUP BY DATE_FORMAT(create_time,"%Y-%M");
# Ps:或者在创建article表时,除了年月日时分秒的create_time字段,再添加个年月的mouth字段,根据mouth字段来分组,这样有点low!
# Ps:记得注释掉setting.py中 USE_TZ = True 这行代码!
# 否则报错 Database returned an invalid datetime value.
# from django.db.models.functions import TruncMonth
# TruncMonth将原来字段的日期年月日按照月份截取成一个新的虚拟字段以供使用
articleMouth_num = models.Article.objects.filter(site__userinfo__username=username) \
.values_list(TruncMonth('create_time')).annotate(Count('pk'))
# print(articleMouth_num)
return render(request, 'site.html', locals())
"""
根据多个字段进行分组.例如,按活动状态和人员状态分组:
[原生sql]
SELECT
is_active,
is_staff,
COUNT(id) AS total
FROM
auth_user
GROUP BY
is_active,
is_staff
[Django ORM]
(User.objects
.values('is_active', 'is_staff')
.annotate(total=Count('id')))
values('is_active','is_staff'):分组依据
annotate(total=Count('id')):要查询的内容
此查询的结果包括is_active、is_staff和每个组中的用户数!!
(分完组后 能看到的字段只有分组的字段以及聚合的结果.)
SO,若需要根据日期进行归档..
from django.db.models.functions import TruncMonth
TruncMonth将原来字段的日期年月日按照月份截取成一个新的虚拟字段以供使用
分组依据那就写成, values(TruncMonth('create_time'))
"""
1
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
site.html
{% extends 'base.html' %}
{% block css %}
<style>
a:hover {
text-decoration: none;
}
#d1 {
margin-top: 10px;
}
#d1 > span {
margin-left: 10px;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-3">
<div class="panel panel-info">
<div class="panel-heading">
我的标签
</div>
<div class="panel-body">
{% for tag in tagName_articleNum %}
<p>
<a href="/{{ username }}/tag/{{ tag.1 }}">{{ tag.0 }}({{ tag.2 }})</a>
</p>
{% endfor %}
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
我的分类
</div>
<div class="panel-body">
{% for category in categoryName_articleNum %}
<p>
<a href="/{{ username }}/category/{{ category.1 }}">
{{ category.0 }}({{ category.2 }})
</a>
</p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
日期归档
</div>
<div class="panel-body">
{% for mouth in articleMouth_num %}
<p>
<a href="/{{ username }}/archive/{{ mouth.0|date:'Y-m' }}">
{{ mouth.0|date:'Y年m月' }}({{ mouth.1 }})
</a>
</p>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-9">
{# !!!!!! 为了让文章详情页能继承site.html !!! 这样的话文章详情页只需修改这部分内容.#}
{% block content_9 %}
<ul class="media-list">
{% for article in article_list %}
<li class="media">
<div class="media-body">
<h4 class="media-heading"><a href="">{{ article.title }}</a></h4>
<span style="color: gray">{{ article.desc }}</span>
</div>
</li>
<div style="display: flex;justify-content: flex-end;">
<div id="d1">
<span>posted @</span>
<span>{{ article.create_time|date:'Y-m-d H:i:s' }}</span>
<span>{{ article.site.userinfo.username }}</span>
<span>点赞数({{ article.up_num|default:0 }})</span>
<span>评论数({{ article.comment_num|default:0 }})</span>
<span>点踩数({{ article.down_num|default:0 }})</span>
</div>
</div>
<hr>
{% endfor %}
</ul>
{% endblock %}
</div>
</div>
</div>
{% endblock %}
{% block js %}
{% endblock %}
1
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 开放对外访问接口
只是单纯的这样写
avatar = models.FileField(upload_to='static/img/', default='static/img/default.png')
那么上传的图片会到项目根目录下的/static/img文件夹下..
现在在setting.py文件夹下,进行如下的资源文件配置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
那么上传图片后,在项目的根目录下会自动创建/media/static/img/的文件目录!!上传的图片会放到其中!
默认情况,Django中的项目文件是不对外开放的.
So,此时在浏览器输入地址http://127.0.0.1:8000/media/static/img/111.png是访问不到上传的111.png文件的!
若想要外界访问到某一文件,就得开放对外访问的接口.
from django.conf.urls import url
from django.views.static import serve
from BBS import settings
urlpatterns = [
# -- 开放对外访问的接口
# 这里根目录下的media文件夹是我们想对外提供访问的路径
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
此时,地址http://127.0.0.1:8000/media/static/img/111.png就能访问到上传的111.png图片.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 文章内容展示
严谨一点,得到的文章对象必须是该站点的!!避免出现article_id和username不对应的情况!
并准备好点赞点踩的样式..
先给个人站点右侧的文章列表中的每个文章添加一个路由, 修改site.html中一点代码!!
home.html同理
{# -- site.html #}
<li class="media">
<div class="media-body">
<h4 class="media-heading">
{# target="_blank" 点击该链接重新打开一个网页 #}
<a href="/{{ username }}/article/{{ article.pk }}" target="_blank">
{{ article.title }}
</a>
</h4>
<span style="color: gray">{{ article.desc }}</span>
</div>
</li>
{# -- home.html #}
<div class="media-body">
<h4 class="media-heading">
<a href="/{{ article.site.userinfo.username }}/article/{{ article.pk }}">
{{ article.title }}
</a>
</h4>
<span style="color: gray">{{ article.desc }}</span>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
添加文章内容, 网页检查-选中标签-copy element!!
urls.py
from django.conf.urls import url
from django.contrib import admin
from django.views.static import serve
from BBS import settings
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^register/', views.register), # -- 注册
url(r'^login/', views.login), # -- 登陆
url(r'^get_code/', views.get_code), # -- 获取验证码
url(r'^home/', views.home), # -- 首页
url(r'^logout/', views.logout), # -- 退出登陆
url(r'^set_pwd/', views.set_pwd), # -- 修改密码
# -- 记得测试下,看能不能到这个路由!!
url(r'^(?P<username>\w+)/$', views.site), # -- 个人站点 (有名分组)
# -- 个人站点侧边栏筛选功能!
# url(r'^(?P<username>\w+)/tag/(\d+)', views.site),
# url(r'^(?P<username>\w+)/category/(\d+)', views.site),
# url(r'^(?P<username>\w+)/archive/(\w+)', views.site),
# -- 把上面三个路由合成一个!
url(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)', views.site),
# -- 开放对外访问的接口
# 这里根目录下的media文件夹是我们想对外提供访问的路径
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
# -- 文章详情页
url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)', views.article_detail),
]
1
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
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
views.py
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
import hashlib
import random
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO, StringIO
def register(request):...
def get_random():...
def get_code(request):...
def login(request):...
def home(request):...
def logout(request):...
def set_pwd(request):...
def site(request, username, **kwargs):...
def article_detail(request, username, article_id):
# -- 验证站点是否存在、文章是否存在 但凡有一个不存在,跳转404页面
user_obj = models.UserInfo.objects.filter(username=username).first()
# ★ 再严谨一点,得到的文章对象必须是该站点的!!避免出现article_id和username不对应的情况!
# 注意,这里的username不一定是登陆的用户!
article_obj = models.Article.objects.filter(pk=article_id, site__userinfo__username=username).first()
if not user_obj or not article_obj:
return render(request, 'error.html')
# -- 左侧筛选功能的数据还是得再传一遍
tagName_articleNum = models.Tag.objects.filter(site__userinfo__username=username) \
.values_list('name', 'pk').annotate(Count('article__pk'))
categoryName_articleNum = models.Category.objects.filter(site__userinfo__username=username) \
.values_list('name', 'pk').annotate(Count('article__pk'))
articleMouth_num = models.Article.objects.filter(site__userinfo__username=username) \
.values_list(TruncMonth('create_time')).annotate(Count('pk'))
return render(request, 'article_detail.html', locals())
1
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
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
article_detail.py
{% extends 'site.html' %}
{% block content_9 %}
<h1>{{ article_obj.title }}</h1>
{# 点赞点踩的简单样式!!! #}
<div style="display: flex;justify-content: flex-end;">
<div id="d1">
<span>
<input type="button" class="btn btn-success" value="点赞 - {{ article_obj.up_num|default:0 }}">
</span>
<span>
<input type="button" class="btn btn-success" value="点踩 - {{ article_obj.down_num|default:0}}">
</span>
</div>
</div>
<div class="article_content">
{# safe过滤器 忽略掉xss攻击,在模版里加载html标签 #}
{{ article_obj.content|safe }}
</div>
{% endblock %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 点赞点踩功能实现
哪个用户给哪篇文章点了赞或者点了踩.
urls.py
from django.conf.urls import url
from django.contrib import admin
from django.views.static import serve
from BBS import settings
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^register/', views.register), # -- 注册
url(r'^login/', views.login), # -- 登陆
url(r'^get_code/', views.get_code), # -- 获取验证码
url(r'^home/', views.home), # -- 首页
url(r'^logout/', views.logout), # -- 退出登陆
url(r'^set_pwd/', views.set_pwd), # -- 修改密码
# -- 点赞点踩功能
url(r'^up_or_down/$', views.up_or_down),
# -- 个人站点 (有名分组)
url(r'^(?P<username>\w+)/$', views.site),
# -- 个人站点侧边栏筛选功能!
url(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)', views.site),
# -- 开放对外访问的接口
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
# -- 文章详情页
url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)', views.article_detail),
]
1
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
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
views.py
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from app01 import models
from django.db.models import Count, F
from django.db.models.functions import TruncMonth
import json
import hashlib
import random
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO, StringIO
def register(request):...
def get_random():...
def get_code(request):...
def login(request):...
def home(request):...
def logout(request):...
def set_pwd(request):...
def site(request, username, **kwargs):...
def article_detail(request, username, article_id):...
def up_or_down(request):
# -- 该方法只是一个接口!不需要render.
# 实际开发中也很少render,render最常见于Django的混合开发中.
if request.is_ajax(): # -- if request.POST.method == "POST"
back_dic = {'code': 200, 'msg': ''}
is_up = request.POST.get('is_up')
# -- 反序列化,将'true'变为True,'false'变为False
# 用bool是不行的,因为 bool('false')的值为True
is_up = json.loads(is_up)
# print(is_up, type(is_up),bool(is_up)) # false <class 'str'> True
article_id = request.POST.get('article_id')
# -- ▲ 必须登陆
if not request.session.get('username'):
back_dic['code'] = 1015
back_dic['msg'] = "<a href='/login/' style='color: goldenrod'>请先登陆.</a>"
return JsonResponse(back_dic)
# -- ▲ 登陆用户不能点赞点踩自己的文章
article_obj = models.Article.objects.filter(pk=article_id).first() # -- 文章对象
author_name = article_obj.site.userinfo.username # -- 该文章的作者
if request.session.get('username') == author_name:
back_dic['code'] = 1016
back_dic['msg'] = "不能点赞点踩自己的文章!"
return JsonResponse(back_dic)
# -- ▲ 只能点击一次 (感觉是不符合逻辑的,要么点赞要么点踩还不能取消..)
# 通过user_id = article_obj.site.userinfo.pk取到的是当前文章作者的id
user_id = request.session.get('id') # -- 注意!这里应该用当前登陆用户的id
is_click = models.UpdandDown.objects.filter(user_id=user_id, article_id=article_id).first()
if is_click: # -- 存在代表点击过
back_dic['code'] = 1017
back_dic['msg'] = "你已经点过啦!"
return JsonResponse(back_dic)
# -- ★ 入库
# 操作文章表,下面用了两种方法来修改
if is_up:
# -- 判断这个是因为up_num和down_num字段允许为空,那么不填默认为None!!
# 最简单的是改变数据表,给两个字段设计个默认值 default=0!!
# 这里我就不改数据表啦.添加一个判断就好..
if not article_obj.up_num:
models.Article.objects.filter(pk=article_id).update(up_num=1)
else:
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
back_dic['msg'] = "谢谢支持!"
else:
if not article_obj.up_num:
article_obj.down_num = 1
article_obj.save()
else:
article_obj.down_num += 1
back_dic['msg'] = "我继续加油!"
# 操作点赞点踩表
models.UpdandDown.objects.create(user_id=user_id, article_id=article_id, is_up=bool(is_up))
return JsonResponse(back_dic)
1
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
article_detail.py
{% extends 'site.html' %}
{% block content_9 %}
<h1>{{ article_obj.title }}</h1>
<div style="display: flex;justify-content: flex-end;">
<div id="d1">
<span>
<input type="button" class="btn btn-success action up"
value="点赞 - {{ article_obj.up_num|default:0 }}">
</span>
<span>
<input type="button" class="btn btn-success action down"
value="点踩 - {{ article_obj.down_num|default:0 }}">
</span>
<div id="msg" style="margin-left: 10px;margin-top: 5px;"></div>
</div>
</div>
<div class="article_content">
{# safe过滤器 忽略掉xss攻击,在模版里加载html标签 #}
{{ article_obj.content|safe }}
</div>
{% endblock %}
{% block js %}
<script>
// 如何在同一个点击事件中区分是点赞还是点踩?(写两个点击事件有点low)
// 根据当前点击的标签是否具备某一class属性判断.结果返回True或者False.
/* 还有一种方法..
<input type="button" value="点赞" onclick="f('up','{{ article_obj.id }}')">
<input type="button" value="点踩" onclick="f('down','{{ article_obj.id }}')">
function f(is_up,id) {
// -- 通过参数is_up接收的实参是'up'还是'down'来判断是点赞还是点踩!!
pass
}
*/
$('.action').click(function () {
let is_up = $(this).hasClass('up')
// console.log(is_up) // true、false
let article_id = '{{ article_obj.id }}'
let button = $(this)
$.ajax({
url: '/up_or_down/',
type: 'post',
data: {
'is_up': is_up,
'article_id': article_id,
'csrfmiddlewaretoken': '{{ csrf_token }}',
},
success: function (res) {
if (res.code == 200) {
$('#msg').text(res.msg)
// button.attr('value') => 点赞 - 0
let temp0 = button.attr('value').split('-')[0] // 点赞
let temp1 = button.attr('value').split('-')[1] // 0
button.attr('value', temp0 + ' - ' + (parseInt(temp1.trim()) + 1))
} else {
$('#msg').html(res.msg)
}
}
})
})
</script>
{% endblock %}
1
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
55
56
57
58
59
60
61
62
63
64
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
55
56
57
58
59
60
61
62
63
64