创建表结构
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.db import models 2 3 # Create your models here. 4 class Book(models.Model): 5 name = models.CharField(max_length=128) 6 price = models.PositiveSmallIntegerField(null=True) 7 authors = models.ManyToManyField('Author') 8 publisher = models.ForeignKey('Publisher') 9 pub_date = models.DateField() 10 #memo = models.CharField(null=True,max_length=64) 11 12 def __str__(self): 13 return self.name #返回字符串 14 15 class Author(models.Model): 16 name = models.CharField(max_length=32) 17 email = models.EmailField(unique=True) 18 def __str__(self): 19 return self.name 20 21 class Publisher(models.Model): 22 name = models.CharField(max_length=128,unique=True) 23 website = models.URLField(unique=True) 24 25 def __str__(self): 26 return '%s %s' %(self.name,self.website)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 AutoField(Field) 2 - int自增列,必须填入参数 primary_key=True 3 4 BigAutoField(AutoField) 5 - bigint自增列,必须填入参数 primary_key=True 6 7 注:当model中如果没有自增列,则自动会创建一个列名为id的列 8 from django.db import models 9 10 class UserInfo(models.Model): 11 # 自动创建一个列名为id的且为自增的整数列 12 username = models.CharField(max_length=32) 13 14 class Group(models.Model): 15 # 自定义自增列 16 nid = models.AutoField(primary_key=True) 17 name = models.CharField(max_length=32) 18 19 SmallIntegerField(IntegerField): 20 - 小整数 -32768 ~ 32767 21 22 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 23 - 正小整数 0 ~ 32767 24 IntegerField(Field) 25 - 整数列(有符号的) -2147483648 ~ 2147483647 26 27 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 28 - 正整数 0 ~ 2147483647 29 30 BigIntegerField(IntegerField): 31 - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 32 33 自定义无符号整数字段 34 35 class UnsignedIntegerField(models.IntegerField): 36 def db_type(self, connection): 37 return 'integer UNSIGNED' 38 39 PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 40 'AutoField': 'integer AUTO_INCREMENT', 41 'BigAutoField': 'bigint AUTO_INCREMENT', 42 'BinaryField': 'longblob', 43 'BooleanField': 'bool', 44 'CharField': 'varchar(%(max_length)s)', 45 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 46 'DateField': 'date', 47 'DateTimeField': 'datetime', 48 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 49 'DurationField': 'bigint', 50 'FileField': 'varchar(%(max_length)s)', 51 'FilePathField': 'varchar(%(max_length)s)', 52 'FloatField': 'double precision', 53 'IntegerField': 'integer', 54 'BigIntegerField': 'bigint', 55 'IPAddressField': 'char(15)', 56 'GenericIPAddressField': 'char(39)', 57 'NullBooleanField': 'bool', 58 'OneToOneField': 'integer', 59 'PositiveIntegerField': 'integer UNSIGNED', 60 'PositiveSmallIntegerField': 'smallint UNSIGNED', 61 'SlugField': 'varchar(%(max_length)s)', 62 'SmallIntegerField': 'smallint', 63 'TextField': 'longtext', 64 'TimeField': 'time', 65 'UUIDField': 'char(32)', 66 67 BooleanField(Field) 68 - 布尔值类型 69 70 NullBooleanField(Field): 71 - 可以为空的布尔值 72 73 CharField(Field) 74 - 字符类型 75 - 必须提供max_length参数, max_length表示字符长度 76 77 TextField(Field) 78 - 文本类型 79 80 EmailField(CharField): 81 - 字符串类型,Django Admin以及ModelForm中提供验证机制 82 83 IPAddressField(Field) 84 - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 85 86 GenericIPAddressField(Field) 87 - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 88 - 参数: 89 protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" 90 unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" 91 92 URLField(CharField) 93 - 字符串类型,Django Admin以及ModelForm中提供验证 URL 94 95 SlugField(CharField) 96 - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) 97 98 CommaSeparatedIntegerField(CharField) 99 - 字符串类型,格式必须为逗号分割的数字 100 101 UUIDField(Field) 102 - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 103 104 FilePathField(Field) 105 - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 106 - 参数: 107 path, 文件夹路径 108 match=None, 正则匹配 109 recursive=False, 递归下面的文件夹 110 allow_files=True, 允许文件 111 allow_folders=False, 允许文件夹 112 113 FileField(Field) 114 - 字符串,路径保存在数据库,文件上传到指定目录 115 - 参数: 116 upload_to = "" 上传文件的保存路径 117 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage 118 119 ImageField(FileField) 120 - 字符串,路径保存在数据库,文件上传到指定目录 121 - 参数: 122 upload_to = "" 上传文件的保存路径 123 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage 124 width_field=None, 上传图片的高度保存的数据库字段名(字符串) 125 height_field=None 上传图片的宽度保存的数据库字段名(字符串) 126 127 DateTimeField(DateField) 128 - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 129 130 DateField(DateTimeCheckMixin, Field) 131 - 日期格式 YYYY-MM-DD 132 133 TimeField(DateTimeCheckMixin, Field) 134 - 时间格式 HH:MM[:ss[.uuuuuu]] 135 136 DurationField(Field) 137 - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 138 139 FloatField(Field) 140 - 浮点型 141 142 DecimalField(Field) 143 - 10进制小数 144 - 参数: 145 max_digits,小数总长度 146 decimal_places,小数位长度 147 148 BinaryField(Field) 149 - 二进制类型 150 151 字段
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 ForeignKey(ForeignObject) # ForeignObject(RelatedField) 2 to, # 要进行关联的表名 3 to_field=None, # 要关联的表中的字段名称 4 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 5 - models.CASCADE,删除关联数据,与之关联也删除 6 - models.DO_NOTHING,删除关联数据,引发错误IntegrityError 7 - models.PROTECT,删除关联数据,引发错误ProtectedError 8 - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空) 9 - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值) 10 - models.SET,删除关联数据, 11 a. 与之关联的值设置为指定值,设置:models.SET(值) 12 b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象) 13 14 def func(): 15 return 10 16 17 class MyModel(models.Model): 18 user = models.ForeignKey( 19 to="User", 20 to_field="id" 21 on_delete=models.SET(func),) 22 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() 23 related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') 24 limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: 25 # 如: 26 - limit_choices_to={'nid__gt': 5} 27 - limit_choices_to=lambda : {'nid__gt': 5} 28 29 from django.db.models import Q 30 - limit_choices_to=Q(nid__gt=10) 31 - limit_choices_to=Q(nid=8) | Q(nid__gt=10) 32 - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') 33 db_constraint=True # 是否在数据库中创建外键约束 34 parent_link=False # 在Admin中是否显示关联数据 35 36 37 OneToOneField(ForeignKey) 38 to, # 要进行关联的表名 39 to_field=None # 要关联的表中的字段名称 40 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 41 42 ###### 对于一对一 ###### 43 # 1. 一对一其实就是 一对多 + 唯一索引 44 # 2.当两个类之间有继承关系时,默认会创建一个一对一字段 45 # 如下会在A表中额外增加一个c_ptr_id列且唯一: 46 class C(models.Model): 47 nid = models.AutoField(primary_key=True) 48 part = models.CharField(max_length=12) 49 50 class A(C): 51 id = models.AutoField(primary_key=True) 52 code = models.CharField(max_length=1) 53 54 ManyToManyField(RelatedField) 55 to, # 要进行关联的表名 56 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() 57 related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') 58 limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: 59 # 如: 60 - limit_choices_to={'nid__gt': 5} 61 - limit_choices_to=lambda : {'nid__gt': 5} 62 63 from django.db.models import Q 64 - limit_choices_to=Q(nid__gt=10) 65 - limit_choices_to=Q(nid=8) | Q(nid__gt=10) 66 - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') 67 symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段 68 # 做如下操作时,不同的symmetrical会有不同的可选字段 69 models.BB.objects.filter(...) 70 71 # 可选字段有:code, id, m1 72 class BB(models.Model): 73 74 code = models.CharField(max_length=12) 75 m1 = models.ManyToManyField('self',symmetrical=True) 76 77 # 可选字段有: bb, code, id, m1 78 class BB(models.Model): 79 80 code = models.CharField(max_length=12) 81 m1 = models.ManyToManyField('self',symmetrical=False) 82 83 through=None, # 自定义第三张表时,使用字段用于指定关系表 84 through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表 85 from django.db import models 86 87 class Person(models.Model): 88 name = models.CharField(max_length=50) 89 90 class Group(models.Model): 91 name = models.CharField(max_length=128) 92 members = models.ManyToManyField( 93 Person, 94 through='Membership', 95 through_fields=('group', 'person'), 96 ) 97 98 class Membership(models.Model): 99 group = models.ForeignKey(Group, on_delete=models.CASCADE) 100 person = models.ForeignKey(Person, on_delete=models.CASCADE) 101 inviter = models.ForeignKey( 102 Person, 103 on_delete=models.CASCADE, 104 related_name="membership_invites", 105 ) 106 invite_reason = models.CharField(max_length=64) 107 db_constraint=True, # 是否在数据库中创建外键约束 108 db_table=None, # 默认创建第三张表时,数据库中表的名称 109 110 字段以及参数
通过python manage.py shell
from app01 import models
增加数据:
models.Book.objects.create(name='go',price=50,pub_date='2018-09-20',publisher_id=2)
查询:
models.Book.objects.all()
查询的结果是列表,可以通过操作列表来取数
b1 = models.Book.objects.all()[0]
b1.name
manytomany的查询方法
b1.authors.all()
创建作者:
models.Author.objects.create(name='oldboy',email='oldboy@126.com')
将作者添加到书里(前提是必须是创建好的数据,manytomany是不允许创建数据时操作的)
b4 = models.Book.objects.create(name='kanjian',price=199,pub_date='2016-07-21',publisher_id=2)
b4.authors.add(1,2)
b4.save()
select * from app01_book_authors;
这样在这样表里就会有对应关系
b4.authors.remove(1,2) 删除
forienkey的查询方法
b4.publisher.name (写上字段名,在加上关联表的字段名)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 # 增 2 # 3 # models.Tb1.objects.create(c1='xx', c2='oo') 增加一条数据,可以接受字典类型数据 **kwargs 4 5 # obj = models.Tb1(c1='xx', c2='oo') 6 # obj.save() 7 8 # 查 9 # 10 # models.Tb1.objects.get(id=123) # 获取单条数据,不存在则报错(不建议) 11 # models.Tb1.objects.all() # 获取全部 12 # models.Tb1.objects.filter(name='seven') # 获取指定条件的数据 13 14 # 删 15 # 16 # models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据 17 18 # 改 19 # models.Tb1.objects.filter(name='seven').update(gender='0') # 将指定条件的数据更新,均支持 **kwargs 20 # obj = models.Tb1.objects.get(id=1) 21 # obj.c1 = '111' 22 # obj.save() # 修改单条数据 23 24 基本操作
利用双下划线将字段和对应的操作连接起来
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 # 获取个数 2 # 3 # models.Tb1.objects.filter(name='seven').count() 4 5 # 大于,小于 6 # 7 # models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值 8 # models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值 9 # models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值 10 # models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值 11 # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 12 13 # in 14 # 15 # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 16 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in 17 18 # isnull 19 # Entry.objects.filter(pub_date__isnull=True) 20 21 # contains 22 # 23 # models.Tb1.objects.filter(name__contains="ven") 24 # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 25 # models.Tb1.objects.exclude(name__icontains="ven") 26 27 # range 28 # 29 # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and 30 31 # 其他类似 32 # 33 # startswith,istartswith, endswith, iendswith, 34 35 # order by 36 # 37 # models.Tb1.objects.filter(name='seven').order_by('id') # asc 38 # models.Tb1.objects.filter(name='seven').order_by('-id') # desc 39 40 # group by 41 # 42 # from django.db.models import Count, Min, Max, Sum 43 # models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) 44 # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" 45 46 # limit 、offset 47 # 48 # models.Tb1.objects.all()[10:20] 49 50 # regex正则匹配,iregex 不区分大小写 51 # 52 # Entry.objects.get(title__regex=r'^(An?|The) +') 53 # Entry.objects.get(title__iregex=r'^(an?|the) +') 54 55 # date 56 # 57 # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) 58 # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) 59 60 # year 61 # 62 # Entry.objects.filter(pub_date__year=2005) 63 # Entry.objects.filter(pub_date__year__gte=2005) 64 65 # month 66 # 67 # Entry.objects.filter(pub_date__month=12) 68 # Entry.objects.filter(pub_date__month__gte=6) 69 70 # day 71 # 72 # Entry.objects.filter(pub_date__day=3) 73 # Entry.objects.filter(pub_date__day__gte=3) 74 75 # week_day 76 # 77 # Entry.objects.filter(pub_date__week_day=2) 78 # Entry.objects.filter(pub_date__week_day__gte=2) 79 80 # hour 81 # 82 # Event.objects.filter(timestamp__hour=23) 83 # Event.objects.filter(time__hour=5) 84 # Event.objects.filter(timestamp__hour__gte=12) 85 86 # minute 87 # 88 # Event.objects.filter(timestamp__minute=29) 89 # Event.objects.filter(time__minute=46) 90 # Event.objects.filter(timestamp__minute__gte=29) 91 92 # second 93 # 94 # Event.objects.filter(timestamp__second=31) 95 # Event.objects.filter(time__second=2) 96 # Event.objects.filter(timestamp__second__gte=31) 97 98 进阶操作
包含: models.Book.objects.filter(name__contains='go') icontains是忽略大小写
聚合
求书的平均价格
首先导入模块
from django.db.models import Avg Max,Min,Sum,Count
models.Book.objects.all().aggregate(Avg('price'))
统计数量
models.Book.objects.count()
分组聚合
models.Book.objects.values('publisher__name').annotate(Count('id'))
查看出版社各处了几本书,通过书的id可以查看,因为书的id不同
models.Book.objects.values('publisher__name').annotate(Avg('price'))
F语句
数值增加、本表中的两个字段比较
from django.db.models import F
models.Book.objects.update(price=F('price')+10)
models.Book.objects.values('price')
本表中的两个字段比较
entry.object.filter(n_content__gt=F('n_pingbacks'))
将一个字段中的值移到另一个字段
models.Book.objects.update(memo=F('name'))
Q语句
相当于or
from django.db.models import Q
q = Q(pub_date__year=2016) | Q(pub_date__year=2018) 做为条件,将|换做,就表示and
models.Book.objects.filter(q).values('pub_date') 然后查询
注意
如果在已经创建好的表中增加字段时,必须附一个默认值,这样才不会报错
1.memo = models.CharField(null=True,max_length=64) null=True是必须要写的,
2.python manage.py makemigrations
python manage.py migrate
Form表单
以发邮件为例:
前后端都需要验证,前端验证目的是减小后端的验证压力
通过后端来实现,然后传给后端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 urlpatterns = [ 2 url(r'^admin/', admin.site.urls), 3 url(r'^mail$',views.mail) 4 ]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render,HttpResponse 2 from app01.forms import MailSendForm 3 # Create your views here. 4 def mail(request): 5 if request.method == 'POST': 6 form = MailSendForm(request.POST) #接受前端发来的数据 7 if form.is_valid(): #验证表单数据是否合法 8 print('going to send mail....',form.cleaned_data) #清洗数据,后端要求数据,如果前端发过来的数据是字符串,就会尝试变成数字 9 return HttpResponse('ok') 10 else: 11 print('error happend during validation',form.errors) 12 else: 13 form = MailSendForm() 14 return render(request,'mail_send.html',{'form':form})
需要我们自行创建一个前端页面,通过这个页面可实现基本验证,而且也不用再html中也具体的元素,并且要在views中导入
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render,HttpResponse 2 from app01.forms import MailSendForm 3 # Create your views here. 4 def mail(request): 5 if request.method == 'POST': 6 form = MailSendForm(request.POST) #接受前端发来的数据 7 if form.is_valid(): #验证表单数据是否合法 8 print('going to send mail....',form.cleaned_data) #清洗数据,后端要求数据,如果前端发过来的数据是字符串,就会尝试变成数字 9 return HttpResponse('ok') 10 else: 11 print('error happend during validation',form.errors) 12 else: 13 form = MailSendForm() 14 return render(request,'mail_send.html',{'form':form})
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form method="POST"> 9 {{ form.as_p }} 10 <input type="submit" value="提交"> 11 </form> 12 13 </body> 14 </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form method="POST"> 9 {% for item in form %} 10 <div> 11 {{ item.label }} {{ item }} 12 {% if item.errors %} 13 <span style="color: red"> 14 {{ item.errors }} 15 </span> 16 {% endif %} 17 </div> 18 {% endfor %} 19 <input type="submit" value="提交"> 20 </form> 21 22 </body> 23 </html>
forms中德widget类似于小工具,有一些具体的功能。
对一些字段做认证
clean_sender,对那些字段做认证,就写那个字段,此例中对sender做认证
html中的一些as_的功能,可现在html中的一些情景,例如as_p,就是p标签
form其他的事例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django import forms 2 import re 3 from app01 import models 4 FAVORITE_COLORS_CHOICES = ( 5 ('blue','Blue'), 6 ('green','Green'), 7 ('black','Bloack'), 8 ) 9 BIRTH_YEAR_CHOICE = ('1980','1981','1982') 10 11 12 def mobile_validate(value): 13 mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') 14 if not mobile_re.match(str(value)): 15 raise forms.ValidationError('手机号码格式错误') 16 class MailSendForm(forms.Form): 17 sender = forms.EmailField() 18 receiver = forms.EmailField() 19 subject = forms.CharField(max_length=12) 20 content = forms.CharField(widget=forms.Textarea(attrs={'cols':100, 21 'class':'font-color', 22 'style':'background-color:lightgreen' 23 })) 24 birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICE)) #此处的years表示是能选择的项目 25 seat_CHOICES = (('1','First',),('2','Second',)) 26 27 choice_field = forms.ChoiceField(widget=forms.RadioSelect,choices=seat_CHOICES) #choice 表示选择的范围 28 favorite_colors = forms.MultipleChoiceField( 29 widget=forms.CheckboxSelectMultiple, 30 choices=FAVORITE_COLORS_CHOICES, 31 ) 32 33 def clean_sender(self): 34 print('validate sender',self.cleaned_data) 35 if self.cleaned_data.get('sender') != 'alex@126.com': 36 self.add_error('sender','Only alex has right to send email') 37 38 return self.cleaned_data.get('sender') #此处是为了验证后得到干净的数据,业务逻辑用
对多个字段进行验证
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def clean(self): 2 print('clean data:::',self.cleaned_data) 3 sender = self.cleaned_data.get('sender') 4 received = self.cleaned_data.get('received') 5 6 if sender == received: 7 raise forms.ValidationError('发送者和接收者不能相同') #raise全局错误,就不用errors
对手机号进行验证
validator验证插件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django import forms 2 import re 3 from app01 import models 4 FAVORITE_COLORS_CHOICES = ( 5 ('blue','Blue'), 6 ('green','Green'), 7 ('black','Bloack'), 8 ) 9 BIRTH_YEAR_CHOICE = ('1980','1981','1982') 10 11 12 def mobile_validate(value): 13 mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') #通过compile做个模版,然后通过模版匹配。 14 if not mobile_re.match(str(value)): #re只能匹配字符串 15 raise forms.ValidationError('手机号码格式错误') 16 class MailSendForm(forms.Form): 17 sender = forms.EmailField() 18 receiver = forms.EmailField() 19 subject = forms.CharField(max_length=12) 20 content = forms.CharField(widget=forms.Textarea(attrs={'cols':100, 21 'class':'font-color', 22 'style':'background-color:lightgreen' 23 })) 24 birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICE)) #此处的years表示是能选择的项目 25 seat_CHOICES = (('1','First',),('2','Second',)) 26 mobile = forms.IntegerField(validators=[mobile_validate,], 27 error_messages={'required':'手机不能为空'}, 28 widget=forms.TextInput(attrs={'class':'from-control', 29 'placeholder':'手机号码'})) 30 choice_field = forms.ChoiceField(widget=forms.RadioSelect,choices=seat_CHOICES) #choice 表示选择的范围 31 favorite_colors = forms.MultipleChoiceField( 32 widget=forms.CheckboxSelectMultiple, 33 choices=FAVORITE_COLORS_CHOICES, 34 ) 35 36 def clean_sender(self): 37 print('validate sender',self.cleaned_data) 38 if self.cleaned_data.get('sender') != 'alex@126.com': 39 self.add_error('sender','Only alex has right to send email') 40 41 return self.cleaned_data.get('sender') #此处是为了验证后得到干净的数据,业务逻辑用 42 43 def clean(self): 44 print('clean data:::',self.cleaned_data) 45 sender = self.cleaned_data.get('sender') 46 received = self.cleaned_data.get('received') 47 48 if sender == received: 49 raise forms.ValidationError('发送者和接收者不能相同') #raise全局错误,就不用errors 50 51 52 53 class BookForm(forms.ModelForm): 54 class Meta: 55 model = models.Book 56 exclude = ['memo2']
默认只的设置,可以设置多个,用逗号隔开
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def mail(request): 2 if request.method == 'POST': 3 form = MailSendForm(request.POST) 4 if form.is_valid(): #验证表单数据是否合法 5 print('going to send email to .....',form.cleaned_data) 6 else: 7 print('error happend during validation:',form.errors) 8 else: 9 form = MailSendForm(initial={'sender':'sb_alex@126.com','content':'text mail'}) #默认值 10 11 return render(request,'mail_send.html',{'form':form})
form.has_changed():判断表单数据是否被改过。一般和默认值配合使用
修改数据,直接创建一个form,与数据库中的表关联起来。(modelform)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from appo1 import models 2 class BookForm(forms.ModelForm): 3 class Meta: 4 model = models.Book 5 #fields = "__all__" 6 #fields = ['name','price','authors','pub_date','publisher'] 7 exclude = ['memo2',]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from app01 import models 2 def book_mgr(request): 3 4 if request.method == "POST": 5 form = BookForm(data=request.POST) 6 if form.is_valid(): #添加一条数据 7 form.save() 8 form = BookForm() 9 10 else: 11 form = BookForm() 12 return render(request,'book.html',{'form':form})
视图的定义
function based view 只支持post,get (函数)
Class based view
通过出版社反向查询出了多少本书
p1 = models.Publisher.objects.last()
dir(p1)
p1.book_set.all() 就可以反向查出出了多少本书book是表名。只有外键可以反向查询
admin
创建用户
from bbs import models
admin.site.register(models.Article) 如果需要admin管理的话,就需要注册表
python manage.py createsuperuser
migration文件夹中有每次操作数据库的内容。每次操作都有记录。
user = models.ForienKey(User,null=True,default=None) 在表中创建字段,就会出现多个用户对应一个djang用户
user = models.OneToOneField(User,null=True,default=None) 此种方法创建的用户就是与django一一对应
静态文件补充
STATIC_URL = '/static/' 静态文件入口,只要写static,就会默认到STATICFILES_DIRS下找需要的内容。实现前端和后端完全隔离。找到就会返回。如果有重名,第二个就不会返回了。
STATICFILES_DIRS = (
os.path.join(BASE_DIR,'statics'),
os.path.join(BASE_DIR,'static_files'),
)
论坛
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.db import models 2 from django.contrib.auth.models import User 3 4 # Create your models here. 5 6 7 8 class UserProifle(models.Model): 9 user = models.OneToOneField(User,null=True,default=None) #此处要关联django的User表 10 name = models.CharField(max_length=32) 11 12 def __str__(self): 13 return self.name 14 15 class Article(models.Model): 16 """文章表""" 17 title = models.CharField(max_length=128,unique=True) 18 author = models.ForeignKey("UserProifle") 19 category = models.ForeignKey("Category") 20 pub_date = models.DateTimeField(auto_now_add=True,auto_created=True) 21 tags = models.ManyToManyField("Tag", null=True) 22 body = models.TextField(max_length=100000) 23 head_img = models.ImageField(upload_to="uploads") 24 status_choices = ((0,'草稿'),(1,'发布'),(2,'隐藏')) 25 priority = models.SmallIntegerField(default=1000,verbose_name="优先级") 26 27 def __str__(self): 28 return self.title 29 30 class Category(models.Model): 31 """板块""" 32 name = models.CharField(max_length=64,unique=True) 33 set_as_top_menu = models.BooleanField(default=True) 34 35 def __str__(self): 36 return self.name 37 38 39 class Tag(models.Model): 40 """标签表""" 41 name = models.CharField(max_length=64, unique=True) 42 def __str__(self): 43 return self.name 44 45 class Comment(models.Model): 46 """评论""" 47 article = models.ForeignKey("Article") 48 p_node = models.ForeignKey("Comment",null=True,blank=True,related_name="my_child_comments") 49 50 user = models.ForeignKey("UserProifle") 51 date = models.DateTimeField(auto_now_add=True) 52 comment = models.TextField(max_length=1024) 53 54 55 def __str__(self): 56 return self.comment 57 58 class Like(models.Model): 59 """点赞""" 60 article = models.ForeignKey("Article") 61 user = models.ForeignKey("UserProifle") 62 date = models.DateTimeField(auto_now_add=True) 63 64 65 class PrivateMail(models.Model): 66 """私信""" 67 pass
如果想让django管理表,就必须在admin.py中注册
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.contrib import admin 2 from app01 import models 3 # Register your models here. 4 admin.site.register(models.Article) 5 admin.site.register(models.UserProifle) 6 admin.site.register(models.Tag) 7 admin.site.register(models.Comment) 8 admin.site.register(models.Like) 9 admin.site.register(models.PrivateMail) 10 admin.site.register(models.Category)
BootStrap的简单应用
1.导入bootstrap 将bootstrap导入到statics目录下
2.将想要引用的模版另存为,选项中选全部,保存。
3.将html文件保存到到templates 然后将模版中的路径做修改,修改成/static/js 或者其他。
如果路径中涉及到的文件没有的话,再从保存的模版中复制到相应的statics目录下的文件中。(js、css)
注意:通过concole可以查看导入中遇到的错误。
论坛的页面呈现
首先我们要显示模块的展示
如果想要模块的展示,首先我们需要将其中数据库中拿出来,并返回给页面
提取数据:
views:
1 def index(request): 2 categories =models.Category.objects.filter(set_as_top_menu=True) 3 4 return render(request,'index.html',{'caterogies':categories})
在html网页中循环列出
1 {% for category in categories %} 2 3 <li class="active"><a href="#">{{ category.name }}</a></li> 4 {% endfor %}
实现点击单个模块,然后高亮显示,并将每个模块的内容显示(想当于切换一个新页面),这就需要在url中写入地址
url
1 url(r'^category/(\d+)/$', views.category,name="category" ), 2 #这个name是别名,要引用
1 <ul class="nav navbar-nav"> 2 {% for category in categories %} 3 4 <li class=""><a href="{% url 'category' category.id %}">{{ category.name }}</a></li> 5 {% endfor %} 6 </ul>
可以通过id来实现。
1 def category(request,category_id): 2 categories = models.Category.objects.filter(set_as_top_menu=True) 3 4 articles = models.Article.objects.filter(category_id=category_id) 5 6 return render(request,'index.html', {"categories":categories, "articles":articles})
在前段显示
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <div class="container"> 2 3 <div class="starter-template"> 4 <h1>Bootstrap starter template</h1> 5 <p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p> 6 {{ articles }} 7 8 <h2>{{ request.path }}</h2> 9 10 </div> 11 12 </div><!-- /.container -->
通过request.path获取url路径,然后通过这个路径在前端找到,并使其活跃
<script> $(document).ready(function () { $(".navbar-nav a[href='{{ request.path }}']").parent().addClass("active"); });//end doc ready </script>
显示内容
类似于抽屉的排版
页面布局:
整个布局分12份,左边内容占8个栅格
1 <div class="row"> 2 <div class="col-lg-8"> 3 left content 4 </div> 5 <div class="col-lg-4"> 6 right content 7 </div> 8 </div>
左边内容每个文章都包含内容和图片
{% for article in articles %} <div class="row"> <div class="col-lg-10"> <h3>{{ article.title }}</h3> </div> <div class="col-lg-2"> <img src="{{ article.head_img }}" > </div> </div> {% endfor %}
点赞图片的引入
在base.html导入<link href="http://libs.baidu.com/fontawesome/4.0.3/css/font-awesome.css" rel="stylesheet">
然后找到具体图标的html格式,写入html中
1 <div> 2 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 3 </div>
显示图片(默认会传到uploads目录),此时静态文件是无法找到的,把upload加到static中
通过自定义标签实现,去除uploads
在项目里穿件templatetags,在里面创建一个文件bbs_tags
1 from django.template import Library 2 register = Library() 3 @register.simple_tag 4 def truncate_upload_img(img_src): 5 return img_src.name.lstrip('/uploads')
在html中
{%load bbs_tags%}
在img标签中写
<img src="\static\{% truncate_upload_img article.head_img %}" >
点赞、评论图标
1 <div class="row"> 2 <div class="col-md-2"> 3 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 4 </div> 5 <div class="col-md-2"> 6 <i class="fa fa-comment-o" aria-hidden="true">55</i> 7 </div> 8 <hr style="border: 1px dashed darkgray"> 9 10 </div>
文章分割线
1 <div class="row"> 2 <div class="col-lg-10"> 3 <h3>{{ article.title }}</h3> 4 <div class="row"> 5 <div class="col-md-2"> 6 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 7 </div> 8 <div class="col-md-2"> 9 <i class="fa fa-comment-o" aria-hidden="true">55</i> 10 </div> 11 <hr style="border: 1px dashed darkgray"> 12 13 </div> 14 </div>
分页处理
django已经提供了分页功能( Paginator
)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 >>> from django.core.paginator import Paginator 2 >>> objects = ['john', 'paul', 'george', 'ringo'] 3 >>> p = Paginator(objects, 2) 4 5 >>> p.count 6 4 7 >>> p.num_pages 8 2 9 >>> type(p.page_range) # `<type 'rangeiterator'>` in Python 2. 10 <class 'range_iterator'> 11 >>> p.page_range 12 range(1, 3) 13 14 >>> page1 = p.page(1) 15 >>> page1 16 <Page 1 of 2> 17 >>> page1.object_list 18 ['john', 'paul'] 19 20 >>> page2 = p.page(2) 21 >>> page2.object_list 22 ['george', 'ringo'] 23 >>> page2.has_next() 24 False 25 >>> page2.has_previous() 26 True 27 >>> page2.has_other_pages() 28 True 29 >>> page2.next_page_number() 30 Traceback (most recent call last): 31 ... 32 EmptyPage: That page contains no results 33 >>> page2.previous_page_number() 34 1 35 >>> page2.start_index() # The 1-based index of the first item on this page 36 3 37 >>> page2.end_index() # The 1-based index of the last item on this page 38 4 39 40 >>> p.page(0) 41 Traceback (most recent call last): 42 ... 43 EmptyPage: That page number is less than 1 44 >>> p.page(3) 45 Traceback (most recent call last): 46 ... 47 EmptyPage: That page contains no results
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% for contact in contacts %} 2 {# Each "contact" is a Contact model object. #} 3 {{ contact.full_name|upper }}<br /> 4 ... 5 {% endfor %} 6 7 <div class="pagination"> 8 <span class="step-links"> 9 {% if contacts.has_previous %} 10 <a href="?page={{ contacts.previous_page_number }}">previous</a> 11 {% endif %} 12 13 <span class="current"> 14 Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}. 15 </span> 16 17 {% if contacts.has_next %} 18 <a href="?page={{ contacts.next_page_number }}">next</a> 19 {% endif %} 20 </span> 21 </div>
其中p.page_range,对象的一个方法,但是前段页面其实拿到的是
page1 = p.page(1)所以并没有range方法,此时我们需要通过page1.paginator.page_range实现
注:objects.all()其实并没有将数据取回,只有循环的时候才开始取数。
bootstrap有分页图标
页数始终显示固定的数量,以中间页为固定,前后显示三页(循环到页数与当前页做对比,小于等于3显示,其他不显示)
模版中是没有加减运算的,只能通过simple_tag解决
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 {% load bbs_tags %} 3 4 5 {% block body %} 6 <nav class="navbar navbar-inverse navbar-fixed-top"> 7 <div class="container" > 8 <div class="navbar-header"> 9 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> 10 <span class="sr-only">Toggle navigation</span> 11 <span class="icon-bar"></span> 12 <span class="icon-bar"></span> 13 <span class="icon-bar"></span> 14 </button> 15 <a class="navbar-brand" href="http://v3.bootcss.com/examples/starter-template/#">社区</a> 16 </div> 17 <div id="navbar" class="collapse navbar-collapse"> 18 <ul class="nav navbar-nav"> 19 {% for category in categories %} 20 21 <li class=""><a href="{% url 'category' category.id %}">{{ category.name }}</a></li> 22 {% endfor %} 23 </ul> 24 </div><!--/.nav-collapse --> 25 </div> 26 </nav> 27 28 <div class="container" style="background-color: whitesmoke;" > 29 <div class="row"> 30 <div class="col-lg-8"> 31 {% for article in articles %} 32 33 <div class="row"> 34 <div class="col-lg-10"> 35 <h3>{{ article.title }}</h3> 36 <div class="row"> 37 <div class="col-md-2"> 38 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 39 </div> 40 <div class="col-md-2"> 41 <i class="fa fa-comment-o" aria-hidden="true">55</i> 42 </div> 43 <hr style="border: 1px dashed darkgray"> 44 45 </div> 46 </div> 47 <div class="col-lg-2"> 48 <img height="50px" width="50px" src="\static\{% truncate_upload_img article.head_img %}" > 49 </div> 50 </div> 51 {% endfor %} 52 <nav aria-label="..."> 53 <ul class="pagination"> 54 <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 55 {% for page in articles.paginator.page_range %} 56 {% if articles.number == page %} 57 <li class="active"><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li> 58 {% else %} 59 {# <li class=""><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li>#} 60 {% render_paginator_btn articles page %} 61 {% endif %} 62 {% endfor %} 63 </ul> 64 </nav> 65 <p>共{{ articles.paginator.count }}篇文章</p> 66 </div> 67 68 <div class="col-lg-4"> 69 right content 70 </div> 71 </div> 72 </div><!-- /.container --> 73 74 {% endblock %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.template import Library 2 from django.utils.safestring import mark_safe 3 register = Library() 4 @register.simple_tag 5 def truncate_upload_img(img_src): 6 return img_src.name.lstrip('/uploads') 7 8 @register.simple_tag 9 def render_paginator_btn(articles,page): 10 current_page = articles.number 11 if abs(current_page - page) <=3: 12 ele="""<li><a href="?page={page}">{page}</a></li>""".format(page=page) #直接生成一个html 13 return mark_safe(ele) 14 return '' #不满足小于3就返回空格
点击进入,查看内容
继承index.html,将不需要的内容重写。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends 'index.html' %} 2 {% block left-panel-content %} 3 <h1>{{ article.title }}</h1> 4 <p>{{ article.author }}</p> 5 <div class="row"> 6 <div class="col-md-4"> 7 {{ article.pub_date }} 8 </div> 9 <div class="col-md-2"> 10 评论{{ article.comment_set.count }} {# 反向查询,找出文章关联的评论 #} 11 </div> 12 </div> 13 <div > 14 {{ article.body }} 15 </div> 16 17 18 {% endblock %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 # Create your views here. 5 6 7 def index(request): 8 9 10 categories = models.Category.objects.filter(set_as_top_menu=True) 11 return render(request,'index.html', {"categories":categories}) 12 13 14 def category(request,category_id): 15 categories = models.Category.objects.filter(set_as_top_menu=True) 16 articles = models.Article.objects.filter(category_id=category_id) 17 paginator = Paginator(articles, 5) # Show 3 contacts per page 18 19 page = request.GET.get('page') 20 try: 21 objs = paginator.page(page) 22 except PageNotAnInteger: 23 # If page is not an integer, deliver first page. 24 objs = paginator.page(1) 25 except EmptyPage: 26 # If page is out of range (e.g. 9999), deliver last page of results. 27 objs = paginator.page(paginator.num_pages) 28 29 30 return render(request,'index.html', {"categories":categories, "articles":objs}) 31 32 def article_detail(request,article_id): 33 article_obj = models.Article.objects.get(id=article_id) 34 return render(request,'article.html',{'article':article_obj})
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """s15BBS URL Configuration 2 3 The `urlpatterns` list routes URLs to views. For more information please see: 4 https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 Examples: 6 Function views 7 1. Add an import: from my_app import views 8 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 Class-based views 10 1. Add an import: from other_app.views import Home 11 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 Including another URLconf 13 1. Import the include() function: from django.conf.urls import url, include 14 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 """ 16 from django.conf.urls import url 17 from django.contrib import admin 18 19 from bbs import views 20 21 urlpatterns = [ 22 url(r'^admin/', admin.site.urls), 23 url(r'^$', views.index ), 24 url(r'^category/(\d+)/$', views.category,name="category" ), 25 url(r'^article/(\d+)/$', views.article_detail,name="article_detail" ), 26 ]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 {% load bbs_tags %} 3 4 5 {% block body %} 6 <nav class="navbar navbar-inverse navbar-fixed-top"> 7 <div class="container" > 8 <div class="navbar-header"> 9 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> 10 <span class="sr-only">Toggle navigation</span> 11 <span class="icon-bar"></span> 12 <span class="icon-bar"></span> 13 <span class="icon-bar"></span> 14 </button> 15 <a class="navbar-brand" href="http://v3.bootcss.com/examples/starter-template/#">社区</a> 16 </div> 17 <div id="navbar" class="collapse navbar-collapse"> 18 <ul class="nav navbar-nav"> 19 {% for category in categories %} 20 21 <li class=""><a href="{% url 'category' category.id %}">{{ category.name }}</a></li> 22 {% endfor %} 23 </ul> 24 </div><!--/.nav-collapse --> 25 </div> 26 </nav> 27 28 <div class="container" style="background-color: whitesmoke;" > 29 <div class="row"> 30 <div class="col-lg-8"> 31 {% block left-panel-content %} 32 {% for article in articles %} 33 34 <div class="row"> 35 <div class="col-lg-10"> 36 <h3>{{ article.title }}</h3> 37 <div class="row"> 38 <div class="col-md-2"> 39 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 40 </div> 41 <div class="col-md-2"> 42 <i class="fa fa-comment-o" aria-hidden="true">55</i> 43 </div> 44 <hr style="border: 1px dashed darkgray"> 45 46 </div> 47 </div> 48 <div class="col-lg-2"> 49 <img height="50px" width="50px" src="\static\{% truncate_upload_img article.head_img %}" > 50 </div> 51 </div> 52 {% endfor %} 53 <nav aria-label="..."> 54 <ul class="pagination"> 55 <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 56 {% for page in articles.paginator.page_range %} 57 {% if articles.number == page %} 58 <li class="active"><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li> 59 {% else %} 60 {# <li class=""><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li>#} 61 {% render_paginator_btn articles page %} 62 {% endif %} 63 {% endfor %} 64 </ul> 65 </nav> 66 <p>共{{ articles.paginator.count }}篇文章</p> 67 {% endblock %} 68 </div> 69 70 <div class="col-lg-4"> 71 right content 72 </div> 73 </div> 74 </div><!-- /.container --> 75 76 {% endblock %}
文章样式设置(富文本编辑器)
使用ckeditor,这个编辑器支持djaogo(http://ckeditor.com/download)
下载模版,将模版发到piugin目录下,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends 'index.html' %} 2 {% block extra_head_resources %} 3 <script src="/static/plugin/ckeditor/ckeditor.js"></script> 4 {% endblock %} 5 {% block container %} 6 <div style="height: 600px"> 7 <form> 8 <textarea name="editor1" id="article_editor" rows="10" cols="80"> 9 This is my textarea to be replaced with CKEditor. 10 </textarea> 11 12 </form> 13 </div> 14 <script> 15 // Replace the <textarea id="editor1"> with a CKEditor 16 // instance, using default configuration. 17 CKEDITOR.replace( 'article_editor' ); 18 </script> 19 20 {% endblock %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <!-- saved from url=(0048)http://v3.bootcss.com/examples/starter-template/ --> 3 <html lang="zh-CN"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 4 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> 8 <meta name="description" content=""> 9 <meta name="author" content=""> 10 <link rel="icon" href="http://v3.bootcss.com/favicon.ico"> 11 12 <title>1024</title> 13 14 <!-- Bootstrap core CSS --> 15 <link href="/static/css/bootstrap.min.css" rel="stylesheet"> 16 17 <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> 18 <link href="/static/css/ie10-viewport-bug-workaround.css" rel="stylesheet"> 19 20 <!-- Custom styles for this template --> 21 <link href="/static/css/starter-template.css" rel="stylesheet"> 22 23 <!-- Just for debugging purposes. Don't actually copy these 2 lines! --> 24 <!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]--> 25 <script src="/static/js/ie-emulation-modes-warning.js"></script> 26 <link href="http://libs.baidu.com/fontawesome/4.0.3/css/font-awesome.css" rel="stylesheet"> 27 28 {% block extra_head_resources %}{% endblock %} 29 </head> 30 31 <body style="background-color:gainsboro;"> 32 33 {% block body %}{% endblock %} 34 <!-- Bootstrap core JavaScript 35 ================================================== --> 36 <!-- Placed at the end of the document so the pages load faster --> 37 <script src="/static/js/jquery.min.js"></script> 38 <script src="/static/js/bootstrap.min.js"></script> 39 <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> 40 <script src="/static/js/ie10-viewport-bug-workaround.js"></script> 41 42 43 <script> 44 45 $(document).ready(function () { 46 $(".navbar-nav a[href='{{ request.path }}']").parent().addClass("active"); 47 });//end doc ready 48 49 </script> 50 51 {% block bottom-js %}{% endblock %} 52 53 </body></html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 # Create your views here. 5 6 7 def index(request): 8 9 10 categories = models.Category.objects.filter(set_as_top_menu=True) 11 return render(request,'index.html', {"categories":categories}) 12 13 14 def category(request,category_id): 15 categories = models.Category.objects.filter(set_as_top_menu=True) 16 articles = models.Article.objects.filter(category_id=category_id) 17 paginator = Paginator(articles, 5) # Show 3 contacts per page 18 19 page = request.GET.get('page') 20 try: 21 objs = paginator.page(page) 22 except PageNotAnInteger: 23 # If page is not an integer, deliver first page. 24 objs = paginator.page(1) 25 except EmptyPage: 26 # If page is out of range (e.g. 9999), deliver last page of results. 27 objs = paginator.page(paginator.num_pages) 28 29 30 return render(request,'index.html', {"categories":categories, "articles":objs}) 31 32 def article_detail(request,article_id): 33 article_obj = models.Article.objects.get(id=article_id) 34 return render(request,'article.html',{'article':article_obj}) 35 36 def new_article(request): 37 return render(request, 'new_article.html')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """s15BBS URL Configuration 2 3 The `urlpatterns` list routes URLs to views. For more information please see: 4 https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 Examples: 6 Function views 7 1. Add an import: from my_app import views 8 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 Class-based views 10 1. Add an import: from other_app.views import Home 11 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 Including another URLconf 13 1. Import the include() function: from django.conf.urls import url, include 14 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 """ 16 from django.conf.urls import url 17 from django.contrib import admin 18 19 from bbs import views 20 21 urlpatterns = [ 22 url(r'^admin/', admin.site.urls), 23 url(r'^$', views.index ), 24 url(r'^category/(\d+)/$', views.category,name="category" ), 25 url(r'^article/(\d+)/$', views.article_detail,name="article_detail" ), 26 url(r'^new_article/$', views.new_article, name="new_article"), 27 ]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 {% load bbs_tags %} 3 4 5 {% block body %} 6 <nav class="navbar navbar-inverse navbar-fixed-top"> 7 <div class="container" > 8 <div class="navbar-header"> 9 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> 10 <span class="sr-only">Toggle navigation</span> 11 <span class="icon-bar"></span> 12 <span class="icon-bar"></span> 13 <span class="icon-bar"></span> 14 </button> 15 <a class="navbar-brand" href="http://v3.bootcss.com/examples/starter-template/#">社区</a> 16 </div> 17 <div id="navbar" class="collapse navbar-collapse"> 18 <ul class="nav navbar-nav"> 19 {% for category in categories %} 20 21 <li class=""><a href="{% url 'category' category.id %}">{{ category.name }}</a></li> 22 {% endfor %} 23 </ul> 24 </div><!--/.nav-collapse --> 25 </div> 26 </nav> 27 28 <div class="container" style="background-color: whitesmoke;" > 29 {% block container %} 30 <div class="row"> 31 <div class="col-lg-8"> 32 {% block left-panel-content %} 33 {% for article in articles %} 34 35 <div class="row"> 36 <div class="col-lg-10"> 37 <h3>{{ article.title }}</h3> 38 <div class="row"> 39 <div class="col-md-2"> 40 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 41 </div> 42 <div class="col-md-2"> 43 <i class="fa fa-comment-o" aria-hidden="true">55</i> 44 </div> 45 <hr style="border: 1px dashed darkgray"> 46 47 </div> 48 </div> 49 <div class="col-lg-2"> 50 <img height="50px" width="50px" src="\static\{% truncate_upload_img article.head_img %}" > 51 </div> 52 </div> 53 {% endfor %} 54 <nav aria-label="..."> 55 <ul class="pagination"> 56 <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 57 {% for page in articles.paginator.page_range %} 58 {% if articles.number == page %} 59 <li class="active"><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li> 60 {% else %} 61 {# <li class=""><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li>#} 62 {% render_paginator_btn articles page %} 63 {% endif %} 64 {% endfor %} 65 </ul> 66 </nav> 67 <p>共{{ articles.paginator.count }}篇文章</p> 68 {% endblock %} 69 </div> 70 71 <div class="col-lg-4"> 72 right content 73 </div> 74 </div> 75 {% endblock %} 76 </div><!-- /.container --> 77 78 {% endblock %}
接下来,通过富文本编辑创建文章
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 {% load bbs_tags %} 3 4 5 {% block body %} 6 <nav class="navbar navbar-inverse navbar-fixed-top"> 7 <div class="container" > 8 <div class="navbar-header"> 9 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> 10 <span class="sr-only">Toggle navigation</span> 11 <span class="icon-bar"></span> 12 <span class="icon-bar"></span> 13 <span class="icon-bar"></span> 14 </button> 15 <a class="navbar-brand" href="http://v3.bootcss.com/examples/starter-template/#">社区</a> 16 </div> 17 <div id="navbar" class="collapse navbar-collapse"> 18 <ul class="nav navbar-nav"> 19 {% for category in categories %} 20 21 <li class=""><a href="{% url 'category' category.id %}">{{ category.name }}</a></li> 22 {% endfor %} 23 </ul> 24 </div><!--/.nav-collapse --> 25 </div> 26 </nav> 27 28 <div class="container" style="background-color: whitesmoke;" > 29 <div class="row"> 30 <div class="col-lg-8"> 31 {% for article in articles %} 32 33 <div class="row"> 34 <div class="col-lg-10"> 35 <h3>{{ article.title }}</h3> 36 <div class="row"> 37 <div class="col-md-2"> 38 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 39 </div> 40 <div class="col-md-2"> 41 <i class="fa fa-comment-o" aria-hidden="true">55</i> 42 </div> 43 <hr style="border: 1px dashed darkgray"> 44 45 </div> 46 </div> 47 <div class="col-lg-2"> 48 <img height="50px" width="50px" src="\static\{% truncate_upload_img article.head_img %}" > 49 </div> 50 </div> 51 {% endfor %} 52 <nav aria-label="..."> 53 <ul class="pagination"> 54 <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 55 {% for page in articles.paginator.page_range %} 56 {% if articles.number == page %} 57 <li class="active"><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li> 58 {% else %} 59 <li class=""><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li> 60 {% endif %} 61 {% endfor %} 62 </ul> 63 </nav> 64 <p>共{{ articles.paginator.count }}篇文章</p> 65 </div> 66 67 <div class="col-lg-4"> 68 right content 69 </div> 70 </div> 71 </div><!-- /.container --> 72 73 {% endblock %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 # Create your views here. 5 6 7 def index(request): 8 9 10 categories = models.Category.objects.filter(set_as_top_menu=True) 11 return render(request,'index.html', {"categories":categories}) 12 13 14 def category(request,category_id): 15 categories = models.Category.objects.filter(set_as_top_menu=True) 16 articles = models.Article.objects.filter(category_id=category_id) 17 paginator = Paginator(articles, 3) # Show 25 contacts per page 18 19 page = request.GET.get('page') 20 try: 21 objs = paginator.page(page) 22 except PageNotAnInteger: 23 # If page is not an integer, deliver first page. 24 objs = paginator.page(1) 25 except EmptyPage: 26 # If page is out of range (e.g. 9999), deliver last page of results. 27 objs = paginator.page(paginator.num_pages) 28 29 30 return render(request,'index.html', {"categories":categories, "articles":objs})
1.首先先生成一个表单,然后通过表单去创建,这里还是要用户bootstrap的表单,通过modelform实现获取元素
在生成的过程中,引用bootstrap时无法引用的样式,所以要通过modelform来添加样式
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django import forms 2 from bbs import models 3 class ArticleForm(forms.ModelForm): 4 def __new__(cls, *args, **kwargs): #重写new方法, 5 for field_name in cls.base_fields: #cls.base_fields获取所有的元素 6 field = cls.base_fields[field_name] 7 attr_dic = {'class':'form-control'} #给元素加样式 8 field.widget.attrs.update(attr_dic) #更新 9 return forms.ModelForm.__new__(cls) #因为上面重写了new方法,此时需要继承父类的new方法,否则就不会生效了 10 class Meta: 11 model = models.Article 12 fields = '__all__'
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends 'index.html' %} 2 {% block extra_head_resources %} 3 <script src="/static/plugin/ckeditor/ckeditor.js"></script> 4 {% endblock %} 5 {% block container %} 6 <div style="height: 600px"> 7 <form> 8 <textarea name="editor1" id="article_editor" rows="10" cols="80"> 9 This is my textarea to be replaced with CKEditor. 10 </textarea> 11 {% for field in form %} 12 <div class="form-group"> 13 <label class="col-sm-2 control-label">{{ field.name }}</label> 14 <div class="col-sm-10"> 15 {{ field }} 16 </div> 17 </div> 18 {% endfor %} 19 20 </form> 21 </div> 22 <script> 23 // Replace the <textarea id="editor1"> with a CKEditor 24 // instance, using default configuration. 25 CKEDITOR.replace( 'article_editor' ); 26 </script> 27 28 {% endblock %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 from bbs import forms 5 # Create your views here. 6 7 8 def index(request): 9 10 11 categories = models.Category.objects.filter(set_as_top_menu=True) 12 return render(request,'index.html', {"categories":categories}) 13 14 15 def category(request,category_id): 16 categories = models.Category.objects.filter(set_as_top_menu=True) 17 articles = models.Article.objects.filter(category_id=category_id) 18 paginator = Paginator(articles, 5) # Show 3 contacts per page 19 20 page = request.GET.get('page') 21 try: 22 objs = paginator.page(page) 23 except PageNotAnInteger: 24 # If page is not an integer, deliver first page. 25 objs = paginator.page(1) 26 except EmptyPage: 27 # If page is out of range (e.g. 9999), deliver last page of results. 28 objs = paginator.page(paginator.num_pages) 29 30 31 return render(request,'index.html', {"categories":categories, "articles":objs}) 32 33 def article_detail(request,article_id): 34 article_obj = models.Article.objects.get(id=article_id) 35 return render(request,'article.html',{'article':article_obj}) 36 37 def new_article(request): 38 article_from = forms.ArticleForm() 39 return render(request, 'new_article.html',{'form':article_from})
2.接下来我们需要将富文本编辑器放到body中,这就需要对单个元素做修改
富文本编辑器有一个id,我们想办法把这个id加入到body中就可以了,通过增加样式,只需修改forms就可以。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django import forms 2 from bbs import models 3 class ArticleForm(forms.ModelForm): 4 def __new__(cls, *args, **kwargs): #重写new方法, 5 for field_name in cls.base_fields: #cls.base_fields获取所有的元素 6 field = cls.base_fields[field_name] 7 attr_dic = {'class':'form-control'} #给元素加样式 8 if field =='body': #通过判断可以直接更新 9 attr_dic.update({'id':'article_editor'}) 10 field.widget.attrs.update(attr_dic) #更新 11 return forms.ModelForm.__new__(cls) #因为上面重写了new方法,此时需要继承父类的new方法,否则就不会生效了 12 class Meta: 13 model = models.Article 14 fields = '__all__'
还有一种方法是,直接将new_article.html修改成 CKEDITOR.replace( 'id_body' );
提交表单
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends 'index.html' %} 2 {% block extra_head_resources %} 3 <script src="/static/plugin/ckeditor/ckeditor.js"></script> 4 {% endblock %} 5 {% block container %} 6 <div style="height: 600px"> 7 <form method="POST"> 8 {% for field in form %} 9 <div class="form-group"> 10 <label class="col-sm-2 control-label">{{ field.name }}</label> 11 <div class="col-sm-10"> 12 {{ field }} 13 </div> 14 </div> 15 {% endfor %} 16 <input type="submit" value="提交" class="col-lg-offset-5 btn btn-sm btn-success"> 17 </form> 18 </div> 19 <script> 20 // Replace the <textarea id="editor1"> with a CKEditor 21 // instance, using default configuration. 22 CKEDITOR.replace( 'id_body' ); 23 </script> 24 25 {% endblock %}
<input type="submit" value="提交" class="col-lg-offset-5 btn btn-sm btn-success"> offset移动,btn设置大小以及颜色
3.提交数据
注意事项:之前我们把作者和priority隐藏掉,所以在提交时会报错,没有author_id,所以在提交时需要获取author_id,获取方法request.user.
request.POST是一个字典,我们可以把author_id,追加到其中再提交.
这里有个多对多的问题,只有先创建对象,然后才能添加多对多的关系。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render,HttpResponse 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 from bbs import forms 5 # Create your views here. 6 7 8 def index(request): 9 10 11 categories = models.Category.objects.filter(set_as_top_menu=True) 12 return render(request,'index.html', {"categories":categories}) 13 14 15 def category(request,category_id): 16 categories = models.Category.objects.filter(set_as_top_menu=True) 17 articles = models.Article.objects.filter(category_id=category_id) 18 paginator = Paginator(articles, 5) # Show 3 contacts per page 19 20 page = request.GET.get('page') 21 try: 22 objs = paginator.page(page) 23 except PageNotAnInteger: 24 # If page is not an integer, deliver first page. 25 objs = paginator.page(1) 26 except EmptyPage: 27 # If page is out of range (e.g. 9999), deliver last page of results. 28 objs = paginator.page(paginator.num_pages) 29 30 31 return render(request,'index.html', {"categories":categories, "articles":objs}) 32 33 def article_detail(request,article_id): 34 article_obj = models.Article.objects.get(id=article_id) 35 return render(request,'article.html',{'article':article_obj}) 36 37 def new_article(request): 38 if request.method == 'POST': 39 # request.POST['author_id'] =request.user.id 因为forms中已经把author去除了,所以此时加进去也不会处理,直接将数据加入到最后最后验证过的干净数据中 40 article_from = forms.ArticleForm(data=request.POST,files=request.FILES) #后端传文件是必须使用request.FILES 41 if article_from.is_valid(): 42 # article_from.save() 43 article_from.cleaned_data['author_id'] = request.user.id 44 tags = article_from.cleaned_data.pop('tags') 45 obj = models.Article(**article_from.cleaned_data) 46 obj.save() 47 obj.tags.add(*tags) #此处注意这是一个列表,所以需要* 48 obj.save() 49 return HttpResponse("""<h3><a href='/article/%s'>%s</a></h3>""" %(obj.id,obj.title)) 50 51 else: 52 article_from = forms.ArticleForm() 53 return render(request, 'new_article.html',{'form':article_from})
{{ article.body | safe }} safe、marksafe是将html格式全部全换为真正的html文件。
4.返回首页,显示的全部信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """s15BBS URL Configuration 2 3 The `urlpatterns` list routes URLs to views. For more information please see: 4 https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 Examples: 6 Function views 7 1. Add an import: from my_app import views 8 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 Class-based views 10 1. Add an import: from other_app.views import Home 11 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 Including another URLconf 13 1. Import the include() function: from django.conf.urls import url, include 14 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 """ 16 from django.conf.urls import url 17 from django.contrib import admin 18 19 from bbs import views 20 21 urlpatterns = [ 22 url(r'^admin/', admin.site.urls), 23 url(r'^$', views.index ), 24 url(r'^category/(\d+|all)/$', views.category,name="category" ), 25 url(r'^article/(\d+)/$', views.article_detail,name="article_detail" ), 26 url(r'^new_article/$', views.new_article, name="new_article"), 27 ]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render,HttpResponse 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 from bbs import forms 5 # Create your views here. 6 7 8 def index(request): 9 10 11 categories = models.Category.objects.filter(set_as_top_menu=True) 12 return render(request,'index.html', {"categories":categories}) 13 14 15 def category(request,category_id): 16 categories = models.Category.objects.filter(set_as_top_menu=True) 17 if category_id == 'all': 18 articles = models.Article.objects.all() 19 else: 20 articles = models.Article.objects.filter(category_id_=category_id) 21 paginator = Paginator(articles, 5) # Show 3 contacts per page 22 23 page = request.GET.get('page') 24 try: 25 objs = paginator.page(page) 26 except PageNotAnInteger: 27 # If page is not an integer, deliver first page. 28 objs = paginator.page(1) 29 except EmptyPage: 30 # If page is out of range (e.g. 9999), deliver last page of results. 31 objs = paginator.page(paginator.num_pages) 32 33 34 return render(request,'index.html', {"categories":categories, "articles":objs}) 35 36 def article_detail(request,article_id): 37 article_obj = models.Article.objects.get(id=article_id) 38 return render(request,'article.html',{'article':article_obj}) 39 40 def new_article(request): 41 if request.method == 'POST': 42 # request.POST['author_id'] =request.user.id 因为forms中已经把author去除了,所以此时加进去也不会处理,直接将数据加入到最后最后验证过的干净数据中 43 article_from = forms.ArticleForm(data=request.POST,files=request.FILES) #后端传文件是必须使用request.FILES 44 if article_from.is_valid(): 45 # article_from.save() 46 article_from.cleaned_data['author_id'] = request.user.id 47 tags = article_from.cleaned_data.pop('tags') 48 obj = models.Article(**article_from.cleaned_data) 49 obj.save() 50 obj.tags.add(*tags) #此处注意这是一个列表,所以需要* 51 obj.save() 52 return HttpResponse("""<h3><a href='/article/%s'>%s</a></h3>""" %(obj.id,obj.title)) 53 54 else: 55 article_from = forms.ArticleForm() 56 return render(request, 'new_article.html',{'form':article_from})
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 {% load bbs_tags %} 3 4 5 {% block body %} 6 <nav class="navbar navbar-inverse navbar-fixed-top"> 7 <div class="container" > 8 <div class="navbar-header"> 9 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> 10 <span class="sr-only">Toggle navigation</span> 11 <span class="icon-bar"></span> 12 <span class="icon-bar"></span> 13 <span class="icon-bar"></span> 14 </button> 15 <a class="navbar-brand" href="http://v3.bootcss.com/examples/starter-template/#">社区</a> 16 </div> 17 <div id="navbar" class="collapse navbar-collapse"> 18 <ul class="nav navbar-nav"> 19 <li class=""><a href="{% url 'category' 'all' %}">全部</a></li> 20 {% for category in categories %} 21 22 <li class=""><a href="{% url 'category' category.id %}">{{ category.name }}</a></li> 23 {% endfor %} 24 </ul> 25 </div><!--/.nav-collapse --> 26 </div> 27 </nav> 28 29 <div class="container" style="background-color: whitesmoke;" > 30 {% block container %} 31 <div class="row"> 32 <div class="col-lg-8"> 33 {% block left-panel-content %} 34 {% for article in articles %} 35 36 <div class="row"> 37 <div class="col-lg-10"> 38 <h3>{{ article.title }}</h3> 39 <div class="row"> 40 <div class="col-md-2"> 41 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 42 </div> 43 <div class="col-md-2"> 44 <i class="fa fa-comment-o" aria-hidden="true">55</i> 45 </div> 46 <hr style="border: 1px dashed darkgray"> 47 48 </div> 49 </div> 50 <div class="col-lg-2"> 51 <img height="50px" width="50px" src="\static\{% truncate_upload_img article.head_img %}" > 52 </div> 53 </div> 54 {% endfor %} 55 <nav aria-label="..."> 56 <ul class="pagination"> 57 <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li> 58 {% for page in articles.paginator.page_range %} 59 {% if articles.number == page %} 60 <li class="active"><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li> 61 {% else %} 62 {# <li class=""><a href="?page={{ page }}">{{ page }}<span class="sr-only">(current)</span></a></li>#} 63 {% render_paginator_btn articles page %} 64 {% endif %} 65 {% endfor %} 66 </ul> 67 </nav> 68 <p>共{{ articles.paginator.count }}篇文章</p> 69 {% endblock %} 70 </div> 71 72 <div class="col-lg-4"> 73 right content 74 </div> 75 </div> 76 {% endblock %} 77 </div><!-- /.container --> 78 79 {% endblock %}
5.登陆验证
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """s15BBS URL Configuration 2 3 The `urlpatterns` list routes URLs to views. For more information please see: 4 https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 Examples: 6 Function views 7 1. Add an import: from my_app import views 8 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 Class-based views 10 1. Add an import: from other_app.views import Home 11 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 Including another URLconf 13 1. Import the include() function: from django.conf.urls import url, include 14 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 """ 16 from django.conf.urls import url 17 from django.contrib import admin 18 19 from bbs import views 20 21 urlpatterns = [ 22 url(r'^admin/', admin.site.urls), 23 url(r'^$', views.index ), 24 url(r'^category/(\d+|all)/$', views.category,name="category" ), 25 url(r'^article/(\d+)/$', views.article_detail,name="article_detail" ), 26 url(r'^new_article/$', views.new_article,name="new_article" ), 27 url(r'^account/login/$', views.acc_auth, name='acc_auth'), 28 url(r'^account/logout/$', views.acc_logout, name='acc_logout'), 29 ]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render,HttpResponse,redirect 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 from django.contrib.auth import authenticate,logout,login 5 from django.contrib.auth.decorators import login_required 6 7 from bbs import forms 8 # Create your views here. 9 10 11 def index(request): 12 return redirect("/category/all/") 13 14 def acc_logout(request): 15 16 logout(request) 17 18 return redirect("/account/login/") 19 20 def acc_auth(request): 21 errors = {} 22 if request.method == "POST": 23 username = request.POST.get("username") 24 password = request.POST.get("password") 25 user = authenticate(username=username,password=password) 26 print("auth res", user) 27 if user: 28 print("authenticate success") 29 login(request,user) 30 return redirect(request.GET.get("next") or "/") 31 else: 32 errors = {"error":"Wrong username or password!"} 33 34 return render(request,"login.html", errors) 35 36 37 def category(request,category_id): 38 categories = models.Category.objects.filter(set_as_top_menu=True) 39 40 if category_id == "all": 41 articles = models.Article.objects.all().order_by("-id") 42 else: 43 articles = models.Article.objects.filter(category_id=category_id).order_by("-id") 44 45 paginator = Paginator(articles, 5) # Show 25 contacts per page 46 47 page = request.GET.get('page') 48 try: 49 objs = paginator.page(page) 50 except PageNotAnInteger: 51 # If page is not an integer, deliver first page. 52 objs = paginator.page(1) 53 except EmptyPage: 54 # If page is out of range (e.g. 9999), deliver last page of results. 55 objs = paginator.page(paginator.num_pages) 56 57 return render(request,'index.html', {"categories":categories, "articles":objs}) 58 59 60 def article_detail(request,article_id): 61 62 article_obj = models.Article.objects.get(id=article_id) 63 64 65 return render(request,"article.html",{"article":article_obj}) 66 67 @login_required 68 def new_article(request): 69 70 if request.method == "POST": 71 print("request post:",request.POST) 72 print("request files:",request.FILES,request.user ) 73 74 75 article_form = forms.ArticleForm(data=request.POST,files=request.FILES) 76 if article_form.is_valid(): 77 print("formdata", article_form.cleaned_data) 78 article_form.cleaned_data['author_id'] = request.user.id 79 # # #article_form.save() 80 tags = article_form.cleaned_data.pop("tags") 81 obj = models.Article(**article_form.cleaned_data) 82 obj.save() 83 obj.tags.add(*tags) 84 85 obj.save() 86 87 return HttpResponse('''<h3><a href="/article/%s/">%s</a></h3>''' % (obj.id,obj.title) ) 88 89 else: 90 91 article_form = forms.ArticleForm() 92 93 return render(request,"new_article.html",{"form":article_form})
注意:之前一直做的是用户验证,其实并没有登陆,所以我们在认证之后需要登陆,这样才能显示用户登陆。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 3 4 {% block body %} 5 6 <div class="container"> 7 <div class="col-lg-3 col-lg-offset-4"> 8 <form class="form-signin" method="post"> 9 <h2 class="form-signin-heading">1024黄鳝社区</h2> 10 <label for="inputEmail" class="sr-only">Username</label> 11 <input type="text" name="username" class="form-control" placeholder="username" required autofocus> 12 <label for="inputPassword" class="sr-only">Password</label> 13 <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required> 14 15 <span style="color: red">{{ error }}</span> 16 <div class="checkbox"> 17 <label> 18 <input type="checkbox" value="remember-me"> Remember me 19 </label> 20 </div> 21 <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> 22 </form> 23 24 </div> 25 26 27 </div> 28 29 30 {% endblock %}
6.增加发帖功能,如果没有登陆,先登陆再发帖
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """s15BBS URL Configuration 2 3 The `urlpatterns` list routes URLs to views. For more information please see: 4 https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 Examples: 6 Function views 7 1. Add an import: from my_app import views 8 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 Class-based views 10 1. Add an import: from other_app.views import Home 11 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 Including another URLconf 13 1. Import the include() function: from django.conf.urls import url, include 14 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 """ 16 from django.conf.urls import url 17 from django.contrib import admin 18 19 from bbs import views 20 21 urlpatterns = [ 22 url(r'^admin/', admin.site.urls), 23 url(r'^$', views.index ), 24 url(r'^category/(\d+|all)/$', views.category,name="category" ), 25 url(r'^article/(\d+)/$', views.article_detail,name="article_detail" ), 26 url(r'^new_article/$', views.new_article,name="new_article" ), 27 url(r'^account/login/$', views.acc_auth, name='acc_auth'), 28 url(r'^account/logout/$', views.acc_logout, name='acc_logout'), 29 ]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 {% load bbs_tags %} 3 4 {% block body %} 5 <nav class="navbar navbar-inverse navbar-fixed-top"> 6 <div class="container" > 7 <div class="navbar-header"> 8 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> 9 <span class="sr-only">Toggle navigation</span> 10 <span class="icon-bar"></span> 11 <span class="icon-bar"></span> 12 <span class="icon-bar"></span> 13 </button> 14 <a class="navbar-brand" href="http://v3.bootcss.com/examples/starter-template/#">社区</a> 15 </div> 16 <div id="navbar" class="collapse navbar-collapse"> 17 <ul class="nav navbar-nav"> 18 <li class=""><a href="{% url 'category' 'all' %}">全部</a></li> 19 20 {% for category in categories %} 21 22 <li class=""><a href="{% url 'category' category.id %}">{{ category.name }}</a></li> 23 {% endfor %} 24 </ul> 25 26 <ul class="nav navbar-nav navbar-right"> 27 <li class=""><a href="{% url 'new_article' %}">发帖</a></li> 28 {% if request.user.is_authenticated %} 29 30 <li class="dropdown"> 31 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ request.user }} <span class="caret"></span></a> 32 <ul class="dropdown-menu"> 33 <li><a href="{% url 'logout' %}">Logout</a></li> 34 <li><a href="#">Another action</a></li> 35 </ul> 36 </li> 37 {% else %} 38 <li class=""><a href="{% url 'login' %}">登录</a></li> 39 {% endif %} 40 41 42 </ul> 43 44 </div><!--/.nav-collapse --> 45 </div> 46 </nav> 47 48 <div class="container" style="background-color: white;> 49 {% block container %} 50 <div class=" row" > 51 52 <div class="col-lg-8"> 53 {% block left-panel-content %} 54 {% for article in articles %} 55 <div class="row"> 56 57 <div class="col-lg-10"> 58 <h3><a href="{% url 'article_detail' article.id %}">{{ article.title }}</a></h3> 59 60 <div class="row"> 61 <div class="col-md-2" > 62 <i class="fa fa-thumbs-o-up" aria-hidden="true">444</i> 63 </div> 64 <div class="col-md-2"> 65 <i class="fa fa-comment-o" aria-hidden="true">42</i> 66 </div> 67 </div> 68 <hr style="border: 1px dashed darkgray"> 69 </div> 70 <div class="col-lg-2"> 71 <img height="75px" width="75px" src="/static/{% truncate_upload_img article.head_img %}" > 72 </div> 73 74 75 76 </div> 77 {% endfor %} 78 79 80 81 <nav aria-label="..."> 82 <ul class="pagination"> 83 84 {% for page in articles.paginator.page_range %} 85 {% if articles.number == page %} 86 <li class="active"><a href="?page={{ page }}">{{ page }} <span class="sr-only">(current)</span></a></li> 87 {% else %} 88 {# <li class=""><a href="?page={{page}}">{{ page }} <span class="sr-only">(current)</span></a></li>#} 89 90 {% render_paginator_btn articles page %} 91 92 {% endif %} 93 {% endfor %} 94 95 </ul> 96 </nav> 97 <p>共{{ articles.paginator.count }}篇文章</p> 98 {% endblock %} 99 </div> 100 <div class="col-lg-4"> 101 right content 102 </div> 103 104 </div> 105 {% endblock %} 106 </div><!-- /.container --> 107 108 {% endblock %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.shortcuts import render,HttpResponse,redirect 2 from bbs import models 3 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 4 from django.contrib.auth import authenticate,logout,login 5 from django.contrib.auth.decorators import login_required 6 7 from bbs import forms 8 # Create your views here. 9 10 11 def index(request): 12 return redirect("/category/all/") 13 14 def acc_logout(request): 15 16 logout(request) 17 18 return redirect("/account/login/") 19 20 def acc_auth(request): 21 errors = {} 22 if request.method == "POST": 23 username = request.POST.get("username") 24 password = request.POST.get("password") 25 user = authenticate(username=username,password=password) 26 print("auth res", user) 27 if user: 28 print("authenticate success") 29 login(request,user) 30 return redirect(request.GET.get("next") or "/") 31 else: 32 errors = {"error":"Wrong username or password!"} 33 34 return render(request,"login.html", errors) 35 36 37 def category(request,category_id): 38 categories = models.Category.objects.filter(set_as_top_menu=True) 39 40 if category_id == "all": 41 articles = models.Article.objects.all().order_by("-id") 42 else: 43 articles = models.Article.objects.filter(category_id=category_id).order_by("-id") 44 45 paginator = Paginator(articles, 5) # Show 25 contacts per page 46 47 page = request.GET.get('page') 48 try: 49 objs = paginator.page(page) 50 except PageNotAnInteger: 51 # If page is not an integer, deliver first page. 52 objs = paginator.page(1) 53 except EmptyPage: 54 # If page is out of range (e.g. 9999), deliver last page of results. 55 objs = paginator.page(paginator.num_pages) 56 57 return render(request,'index.html', {"categories":categories, "articles":objs}) 58 59 60 def article_detail(request,article_id): 61 62 article_obj = models.Article.objects.get(id=article_id) 63 64 65 return render(request,"article.html",{"article":article_obj}) 66 67 @login_required 68 def new_article(request): 69 70 if request.method == "POST": 71 print("request post:",request.POST) 72 print("request files:",request.FILES,request.user ) 73 74 75 article_form = forms.ArticleForm(data=request.POST,files=request.FILES) 76 if article_form.is_valid(): 77 print("formdata", article_form.cleaned_data) 78 article_form.cleaned_data['author_id'] = request.user.id 79 # # #article_form.save() 80 tags = article_form.cleaned_data.pop("tags") 81 obj = models.Article(**article_form.cleaned_data) 82 obj.save() 83 obj.tags.add(*tags) 84 85 obj.save() 86 87 return HttpResponse('''<h3><a href="/article/%s/">%s</a></h3>''' % (obj.id,obj.title) ) 88 89 else: 90 91 article_form = forms.ArticleForm() 92 93 return render(request,"new_article.html",{"form":article_form})
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends "base.html" %} 2 3 4 {% block body %} 5 6 <div class="container"> 7 <div class="col-lg-3 col-lg-offset-4"> 8 <form class="form-signin" method="post"> 9 <h2 class="form-signin-heading">1024黄鳝社区</h2> 10 <label for="inputEmail" class="sr-only">Username</label> 11 <input type="text" name="username" class="form-control" placeholder="username" required autofocus> 12 <label for="inputPassword" class="sr-only">Password</label> 13 <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required> 14 15 <span style="color: red">{{ error }}</span> 16 <div class="checkbox"> 17 <label> 18 <input type="checkbox" value="remember-me"> Remember me 19 </label> 20 </div> 21 <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> 22 </form> 23 24 </div> 25 26 27 </div> 28 29 30 {% endblock %}
点击发帖时,假如用户没有登陆,会跳转到登陆页面,在url中会出现accounts,在setting中设置成:LOGIN_URL ="/account/login/"
在点击发贴是,如果没有登陆时,就会跳转到登陆页面,在登陆url中,会出现一个next参数,登陆成功时,next会返回到用户登陆之前的位置,所以,在url中要匹配next。
7.评论(树形评论,此处使用字典的深度查询)
之所以使用深度查询,是为了防止遍历时出错,查完一个分支再查另一个.
如果遍历到A3-1,就会到A下查有没有自己的父亲节点,每遍历一个节点都会返回去查到自己的父节点.
第一步:将数据库的评论变为字典形式.
第二步:在前端显示的时候,要以递归的形式循环字典.
第三步:自定义html标签,在后台生成,直接传给前端
补充:字典排序
加入a是一个字典a.items就会变成一个元组,然后通过sort排序
sorted(a.items(),key=lambda x:x[0])
在后台以递归的方式生成html
1 {% extends "index.html" %} 2 {% load bbs_tags %} 3 4 5 {% block left-panel-content %} 6 <h1>{{ article.title }}</h1> 7 <p>{{ article.author }}</p> 8 <div class="row"> 9 <div class="col-md-4"> 10 {{ article.pub_date }} 11 </div> 12 <div class="col-md-2"> 13 评论 {{ article.comment_set.count }} 14 </div> 15 16 17 </div> 18 19 <div style="margin-top: 20px;border: 1px dashed grey;padding: 10px"> 20 21 {{ article.body |safe }} 22 23 </div> 24 <div style="height: 500px;border: 1px dashed red"> 25 {% load_comments article %} 26 27 28 </div> 29 30 31 32 {% endblock %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from django.template import Library 2 from django.utils.safestring import mark_safe 3 4 register = Library() 5 6 @register.simple_tag 7 def truncate_upload_img(img_src): 8 print(dir(img_src)) 9 print(img_src.name) 10 return img_src.name.lstrip("/uploads/") 11 12 13 @register.simple_tag 14 def render_paginator_btn(articles,page): 15 16 current_page = articles.number 17 if abs(current_page - page) <= 5 :#display button 18 ele = """<li ><a href="?page={page}">{page}</a></li>""".format(page=page) 19 return mark_safe(ele) 20 21 return '' 22 def build_comment_tree(comment_dic,obj): 23 """递归的把每个评论放到合适的层级里面""" 24 #首先,要确保顶级不在这个字典中 25 for k,v in comment_dic.items(): 26 if obj.p_node == k: #代表找到了他父亲,把自己加到k下面 27 comment_dic[k][obj] = {} 28 else:#开始进行深度查询 29 build_comment_tree(comment_dic[k],obj) 30 31 def build_comment_html(comment_dic,margin_arg): 32 """循环评论的字典,拼接html""" 33 comment_eles = '' 34 for k,v in comment_dic.items(): 35 comment_eles += """<div style='border:1px dashed block;margin-left:{margin}px'>{user} --- {date} --- {comment}</div>"""\ 36 .format(user=k.user, 37 date=k.date.strftime('%Y-%m-%d %H:%M:%S'), 38 comment=k.comment, 39 margin=margin_arg) 40 if v:#有下一级 41 comment_eles += build_comment_html(comment_dic[k],margin_arg+10) 42 return comment_eles 43 44 @register.simple_tag 45 def load_comments(article_obj): 46 #先把数据库中所有的这边文章评论查出来,转成字典 47 #递归循环字典,生成html评论元素 48 comment_dic = {} 49 #key:父级 50 comment_objs = article_obj.comment_set.all().order_by('date') #评论列表,以时间来排序,这样就不会出现,后评论的在列表的前面 51 for obj in comment_objs: 52 #判断obj有没有p_node,如果没有他自己就是顶级的评论,放到字典的第一层 53 if not obj.p_node: 54 comment_dic[obj] = {} #此处生成空字典是为了他的字评论准备的 55 else: 56 build_comment_tree(comment_dic,obj) 57 58 comment_list = sorted(comment_dic.items(),key=lambda x:x[0].date) 59 comment_html = """""" 60 for commnet_branch in comment_list: 61 margin_arg=0 62 branch_ele = """<div style='border:1px dashed black;margin-left:{margin}px'>{user} --- {date} --- {comment}</div>""".\ 63 format(user=commnet_branch[0].user, 64 date=commnet_branch[0].date.strftime('%Y-%m-%d %H:%M:%S'), 65 comment=commnet_branch[0].comment, 66 margin=margin_arg) 67 comment_html += branch_ele 68 #开始构建他的子集评论 69 comment_html += build_comment_html(commnet_branch[1],margin_arg+10) 70 return mark_safe(comment_html)