登陆注册
BBS页面的搭建使用的是bootstrap! 在真实的办公中,是不会使用这个技术的,jquery都使用的很少..
所以不必纠结于前端的实现,了解即可.. 需要熟练掌握的是后端代码的书写!!
# BBS数据表分析
记住一点! 做任何项目,先设计表!! 设计出合理的表意味着项目完成了一半(´▽`).
博客园的功能实现很简单,但一旦用户量很大,那就不简单啦,一个首页就够我们玩的啦,现在还体验不到.(つД`)ノ
1.用户表 - 扩展auth_user表
-- phone (手机号可以为空)
-- avatar
-- create_time
---- 用户表:站点表 = 1:1
2.站点表
https://www.cnblogs.com/linhaifeng
https://www.cnblogs.com/liuqingzheng
输入一个网址,打开一个博客,这叫做站点表!!linhaifeng、liuqingzheng这叫作站点名称!每个站点的CSS不一样.
-- 站点名称
-- 站点标题
-- 站点样式(存储css路径)
3.标签表
注意: 分类与标签不一样!eg:一个班级的同学可以按照男女、好看程度等进行分类;对某一个人的描述,相当于给此人打上了标签.
-- 标签名
---- 站点表:标签表 = 1:n
4.分类表
公司里可能有现成的分类树算法: 逐级分类.
-- 分类名
---- 站点表:分类表 = 1:n
5.文章表(BBS项目中最核心的一张表)
文章最末尾显示的有点赞数、点踩数、评论数
如何实现呢?初次展示文章页面和用户点击点赞点踩时,去点赞点踩表里查?使用缓存?
但要明确一点,每次查库不大好,会拖慢该页面的打开时间!!
So,当每次有人点赞点踩时,不仅写入点赞点踩表,文章表的点赞数也要加1,这是一个优化策略!!
-- 文章标题
-- 摘要
-- 内容
-- 发布时间
-- 点赞数
-- 点踩数
-- 评论数
---- 站点表:文章表 = 1:n
---- 分类表:文章表 = 1:n
---- 标签表:文章表 = n:n (半自动)
6.点赞点踩表
谁给哪篇文章点赞了还是点踩了(相当于用户表:文章表=n:n,纯手动的创建了第三张表)
---- user 外键
---- article 外键
-- updown(bool值)
7.评论表
谁给哪篇文章什么时间评论了什么内容
注意:一级评论,二级评论,三级评论.. !!无限级分类!!(自己与自己关联)
---- user 外键
---- article 外键
-- content
-- create_time
---- pid (自关联 值为评论表的id值,意味着该评论是哪条评论的子评论.不一定有子评论,所以该字段允许为空!)
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
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
# BBS表的创建
建表先建基础字段!!
创建数据库,将DATABASES配置成mysql;
书写扩展表的配置项AUTH_USER_MODEL;
配置静态文件路径STATICFILES_DIRS;
默认用的是mysqldb,将其改成pymysql..
进行数据库迁移的两条命令!
表一旦建立成功,大吉大利,今晚吃鸡Hhh.
Ps: 将某些外键允许为空,是便于测试.. 可以迁移更改,也可以直接在navicat中设计表字段更改.
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.CharField(max_length=32, null=True, blank=True, verbose_name='手机号')
# -- 只要给avatar字段传递文件对象,图片就会上传到指定文件夹!
# 参数upload_to写存储图片的路径!
# 参数default表明不上传头像的话使用默认图片!
avatar = models.FileField(upload_to='static/img/', default='static/img/default.png', verbose_name='头像')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间') # -- 自动写入当前时间!
site = models.OneToOneField(to='Site', null=True)
class Site(models.Model):
site_name = models.CharField(max_length=64, verbose_name='站点名称')
site_title = models.CharField(max_length=64, verbose_name='站点标题')
site_css = models.CharField(max_length=128, verbose_name='站点样式路径')
def __str__(self):
return self.site_name
class Tag(models.Model):
name = models.CharField(max_length=64, verbose_name='标签名')
site = models.ForeignKey(to='Site')
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=64, verbose_name='分类名')
site = models.ForeignKey(to='Site')
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=256, verbose_name='文章标题')
desc = models.CharField(max_length=256, verbose_name='简介')
content = models.TextField(verbose_name='文章内容')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
# -- 这三个字段是优化字段!
up_num = models.IntegerField(verbose_name='点赞数', null=True, blank=True)
down_num = models.IntegerField(verbose_name='点踩数', null=True, blank=True)
comment_num = models.IntegerField(verbose_name='评论数', null=True, blank=True)
# -- 外键关系
site = models.ForeignKey(to='Site', null=True)
category = models.ForeignKey(to='Category', null=True)
tag = models.ManyToManyField(to='Tag',
through='Article2Tag',
through_fields=('article', 'tag'))
def __str__(self):
return self.title
class Article2Tag(models.Model):
article = models.ForeignKey(to="Article", null=True)
tag = models.ForeignKey(to="Tag", null=True)
class UpdandDown(models.Model):
user = models.ForeignKey(to="UserInfo")
article = models.ForeignKey(to="Article")
is_up = models.BooleanField() # -- 存True/False,但表中的表现是1/0
class Comment(models.Model):
user = models.ForeignKey(to="UserInfo")
article = models.ForeignKey(to="Article")
content = models.CharField(max_length=256, verbose_name='评论内容')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
# -- 自关联
# 这里可以 to='Comment'
parent = models.ForeignKey(to='self', null=True, verbose_name='父id')
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
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
# 注册页面的搭建
借助文件阅读器实现在页面上实时展示上传的头像图片!!
三把斧再现(⁎⁍̴̛ᴗ⁍̴̛⁎)
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),
]
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
views.py
from django.shortcuts import render
def register(request):
return render(request, 'register.html')
1
2
3
4
2
3
4
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>Title</title>
</head>
<body>
{# div.container-fluid > div.row > div#}
<div class="container-fluid">
<div class="row">
<h1 class="text-center">注册页面</h1>
<div class="col-md-4 col-md-offset-4">
<form action="">
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" class="form-control">
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" class="form-control">
</div>
<div class="form-group">
<label for="re_password">确认密码:</label>
<input type="password" id="re_password" class="form-control">
</div>
<div class="form-group">
<label for="my_file">
上传头像:
<img src="/static/img/default.png" width="80" alt="" id="my_img">
</label>
{# display:none !!#}
<input type="file" id="my_file" style="display: none">
</div>
<input type="button" class="btn btn-success pull-right" value="注册">
</form>
</div>
</div>
</div>
<script>
$('#my_file').change(function () {
// 借助于JS的文件阅读器(了解)
// step1:拿到文件阅读器对象
let myFileReader = new FileReader()
// step2:拿到文件对象
let myFileObj = $('#my_file')[0].files[0] // 死死的记住
// step3:把文件对象给文件阅读器来读取数据
// 该操作是异步的!使用onload加载完毕后再执行其它操作.
myFileReader.readAsDataURL(myFileObj)
myFileReader.onload = function () {
console.log(myFileReader.result)
// 将读出的图片数据作为img的src属性的值,该值并不是路径,因为该图片并没有上传到后端!
// 当然可以ajax上传图片,然后将src改为后端的路径,不推荐这样做!!
$('#my_img').attr('src', myFileReader.result)
}
})
</script>
</body>
</html>
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
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
# 注册功能的实现
用户名是否存在只能在后端进行校验!!
register.html
使用了layer组件!!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
{% load static %}
<script src="{% static 'layer-v3.5.1/layer/layer.js' %}"></script>
<title>Title</title>
</head>
<body>
{# div.container-fluid > div.row > div#}
<div class="container-fluid" ...>
<script>
// ★ 实现上传的头像图片在页面的实时展示
$('#my_file').change(function () {...})
// ★ 实现注册功能
$('.btn').click(function () {
// step1:实例化FormData
let myFormDataObj = new FormData();
// step2:获取表单数据
let username = $('#username').val();
let password = $('#password').val();
let re_password = $('#re_password').val();
// 在前端进行表单数据的验证!(前端的验证弱不禁风,Hhh)
if (!username) {
layer.msg('用户名必须填写!')
return
}
if (!password) {
layer.msg('密码必须填写!')
return
}
if (!re_password) {
layer.msg('确认密码必须填写!')
return
}
if (password !== re_password) {
layer.msg('两次密码必须一致!')
return
}
// step3:添加普通数据
myFormDataObj.append('username', username)
myFormDataObj.append('password', password)
myFormDataObj.append('re_password', re_password)
// csrf验证
myFormDataObj.append('csrfmiddlewaretoken', '{{ csrf_token }}')
// step4:添加文件对象
myFormDataObj.append('avatar', $('#my_file')[0].files[0])
// step5:发送ajax请求
$.ajax({
url: '',
type: 'post',
data: myFormDataObj,
contentType: false,
processData: false,
success: function (res) {
{# 没有达到预定的效果,如何调试? 打印后端返回的结果 res、typeof res、res.url#}
if (res.code == 200) {
layer.msg(res.msg, {icon: 1}, function () {
// 注册成功跳转指定页面
location.href = res.url
})
} else {
layer.msg(res.msg, {icon: 2})
}
}
})
})
</script>
</body>
</html>
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
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
views.py
from django.shortcuts import render
from django.http import JsonResponse
from app01 import models
import hashlib
def register(request):
# ★ 粗略的分,分为4步:接收操作、验证参数、写逻辑、返回数据格式
if request.method == "POST":
back_dic = {
'code': 200, # -- 200响应状态码
'msg': '注册成功!'
}
username = request.POST.get('username')
password = request.POST.get('password')
re_password = request.POST.get('re_password')
avatar = request.FILES.get('avatar') # -- avatar是一个文件对象,若前端不传,结果为None
# -- 在后端进行表单参数的验证
if not username:
back_dic['code'] = 1001 # -- 1001业务状态码
back_dic['msg'] = '用户名不能为空!'
return JsonResponse(back_dic)
if not password:
back_dic['code'] = 1002
back_dic['msg'] = '密码不能为空!'
return JsonResponse(back_dic)
if not re_password:
back_dic['code'] = 1003
back_dic['msg'] = '确认密码不能为空!'
return JsonResponse(back_dic)
if password != re_password:
back_dic['code'] = 1004
back_dic['msg'] = '两次密码必须一致!'
return JsonResponse(back_dic)
# -- 验证用户名是否注册
res = models.UserInfo.objects.filter(username=username).first()
if res:
back_dic['code'] = 1005
back_dic['msg'] = '用户名已经存在!'
return JsonResponse(back_dic)
# -- 入库
# -- 密码用md5算法加密,为了提高安全性可以加盐!!secret的值写到setting配置文件中
# password += settings.secret
# 每个用户注册的盐都是一样的,还可以玩的更花,每个用户的 盐/一随机字符串 都不一样!
m = hashlib.md5()
m.update(password.encode('utf-8'))
password = m.hexdigest()
dic = {
'username': username,
'password': password,
}
if avatar:
dic['avatar'] = avatar # -- 不上传返回的是None,若上传,覆盖默认的路径值
# models.UserInfo.objects.create_user(**dic) # -- Auth模块的加密
models.UserInfo.objects.create(**dic) # -- 自个儿用hash算法对密码进行加密
back_dic['url'] = '/login/' # -- 注册成功后跳转登陆页面!
return JsonResponse(back_dic)
return render(request, 'register.html')
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
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
# 登陆页面的搭建
登陆页面里图片验证码的实现了解即可,不必纠结.. 真实的工作中不会用它!
三把斧再现(⁎⁍̴̛ᴗ⁍̴̛⁎)
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), # -- 获取验证码
]
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
views.py
特别注意, 导入字体文件的工作目录是Django的启动文件manage.py所在的目录,而不是views.py所在的目录!!!
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from app01 import models
import hashlib
import random
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO, StringIO
def register(request):...
def login(request):
return render(request, 'login.html')
def get_random():
# -- 返回RGB的颜色!
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
def get_code(request):
img_obj = Image.new('RGB', (430, 35), get_random())
img_draw = ImageDraw.Draw(img_obj) # -- 产生一个画笔对象
# -- 下载免费字体的网址:http://www.zhaozi.cn/s/freefont/
img_font = ImageFont.truetype('static/font/hello.ttf', 30) # -- 字体样式 大小
# -- 五位数的随机验证码
code = ''
for i in range(5):
random_upper = chr(random.randint(65, 90)) # -- A-Z
random_lower = chr(random.randint(97, 122)) # -- a-z
random_int = str(random.randint(0, 9)) # -- 0-9
# -- random.choice从上面三个里面任意选择一个
tmp = random.choice([random_lower, random_upper, random_int])
# -- 将产生的随机字符串写入到图片上
# Q1:为什么一个个写而不是生成好了之后再写?
# A1:因为一个个写能够控制每个字体的间隙,而生成好之后再写的话,间隙就没法控制了!
# Q2:为什么要把验证码写到图片上呢?在页面上直接展示不行吗?
# A2:为了安全,可以提高防范机器人的概率!
img_draw.text((i * 60 + 60, -2), tmp, get_random(), img_font)
# -- 拼接随机字符串
code += tmp
print('验证码:', code)
# -- 随机验证码在登陆的视图函数里面需要用到,要比对,所以要找地方存起来并且其他视图函数也能拿到
# cookie/session是一个选择,redis缓存数据库也行,但存到mysql中不推荐.
request.session['code'] = code
io_obj = BytesIO()
img_obj.save(io_obj, 'png')
return HttpResponse(io_obj.getvalue())
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
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
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
{% load static %}
<script src="{% static 'layer-v3.5.1/layer/layer.js' %}"></script>
<title>Title</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1 class="text-center">登陆页面</h1>
<div class="col-md-4 col-md-offset-4">
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" class="form-control">
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" class="form-control">
</div>
<div class="form-group">
<label for="code">验证码:</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="code" class="form-control">
</div>
<div class="col-md-6">
{# /get_code/会请求后端这个接口! #}
<img src="/get_code/" alt="" width="208" height="33">
</div>
</div>
</div>
<input type="button" class="btn btn-success pull-right" value="登陆">
</div>
</div>
</div>
</body>
</html>
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
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
# 登陆功能的实现
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
{% load static %}
<script src="{% static 'layer-v3.5.1/layer/layer.js' %}"></script>
<title>Title</title>
</head>
<body>
<div class="container-fluid" ...>
<script>
$('.btn').click(function () {
let username = $("#username").val()
let password = $("#password").val()
let code = $("#code").val()
if (!username) {
layer.msg('用户名必须填写!')
return
}
if (!password) {
layer.msg('密码必须填写!')
return
}
if (!code) {
layer.msg('验证码必须填写!')
return
}
// 发动Ajax请求
$.ajax({
url: '',
type: 'post',
data: {
'username': username,
'password': password,
'code': code,
'csrfmiddlewaretoken': '{{ csrf_token }}',
},
success: function (res) {
if (res.code == 200) {
layer.msg(res.msg, {icon: 1}, function () {
location.href = res.url
})
} else {
layer.msg(res.msg, {icon: 2})
}
}
})
})
</script>
</body>
</html>
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
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
views.py
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from app01 import models
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):
# -- 判断请求方式,接收参数,验证参数,处理数据
if request.method == "POST":
back_dic = {
'code': 200,
'msg': '登陆成功'
}
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
if not username:
back_dic['code'] = 1001
back_dic['msg'] = '用户名不能为空!'
return JsonResponse(back_dic)
if not password:
back_dic['code'] = 1002
back_dic['msg'] = '密码不能为空!'
return JsonResponse(back_dic)
# -- 验证码是否正确?
if request.session.get('code') != code:
back_dic['code'] = 1006s
back_dic['msg'] = '验证码错误!'
return JsonResponse(back_dic)
# -- 用户名和密码是否匹配
m = hashlib.md5()
m.update(password.encode('utf-8'))
password = m.hexdigest()
# Ps:这里最好用户名和密码一起查,不用确切的告知是用户名不存在还是密码错误,降低撞库的风险.
res = models.UserInfo.objects.filter(username=username, password=password).first()
if not res:
back_dic['code'] = 1007
back_dic['msg'] = '用户名或密码不正确!'
# -- 用session保存用户信息
# 当业务逻辑复杂的时候,会保存用户多个或所有的信息作为session
# request.session['info'] = res -- 这样就可取出session/用户对象,使用.点语法!
request.session['username'] = username
request.session['id'] = res.id # -- 其它地方就可以通过session取登陆用户id啦!!
back_dic['url'] = '/home/'
return JsonResponse(back_dic)
return render(request, 'login.html')
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
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