django 线上教育平台开发记录
1、环境搭建
2、新建项目
1)、首先通过 django-admin 新建一个项目,(例如项目名为mxonline)
django-admin startproject mxonline
运行后会出现一个 mxonline的文件夹,这就是整个项目的文件夹,文件夹下的文件结构为:
2)、进入到 mxonline下的settings.py 文件配置数据库,我自己是这样配置的
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': "mxonline", 'USER':'root', 'PASSWORD':'147896321', 'HOST':'localhost', 'PORT':'3306', } }
然后通过 migrate命令在数据库中会生成初始的数据表
python manage.py makemigrations
python manage.py migrate
注意:如果makemigrations出错,一般都是数据库配置错误,请仔细检查
3、第一个django页面
运行django
python manage.py runserver
然后在浏览器打开 http://127.0.0.1:8000/,就可以看到我么第一个django页面了
4、app的model设计
1)新建一个叫users的app,新建完成后,会出现一个和mxonline同级的users文件夹,这就是我们的users 的app
python manage.py startapp users
2)设计user的model(users/model.py)
# -*- coding: utf-8 -*-
from django.db import models from django.contrib.auth.models import AbstractUser from django.utils.encoding import python_2_unicode_compatible python_2_unicode_compatible class UserProfile(AbstractUser): nickname = models.CharField(max_length=16, verbose_name=u'昵称', default="") birday = models.DateField(verbose_name=u'生日', null=True, blank=True) gender = models.CharField(choices=((‘male’,u'男'),('female',u'女')), default="female") address = models.CharField(max_length=64, default="") mobile = models.CharField(max_length=11, null=True, blank=True) image = mdoels.ImageField(upload_to="image/%Y/%m", default = u'image/default.png', max_length=64) class = Meta: verbose_name = "用户信息" verbose_name_plural = verbose_name def __str__(self): return self.username
#other models
...
3)注册 app,并设置 AUTH_USER_MODEL
在settings.py中的 INSTALLED_APP中添加 users
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users', ]
AUTH_USER_MODEL = "users.UserProfile"
4)因为models中用到了 ImageField,需要安装pillow
进入虚拟环境,安装pillow
pip install pillow
5)同步user表到数据库
python manage.py makemigrations
python manage.py migrate
5、如何将多个app放到一个文件夹apps(apps所在位置为项目根目录)下:
在settings.py文件中,insert apps路径即可
首先
import sys
然后在 BASE_DIR 后面添加apps
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
6、创建后台admin的用户
python manage.py createsuperuser admin
7、settings.py相关设置
LANGUAGE_CODE = 'zh-hans' #修改django后台为中文 TIME_ZONE = 'Asia/Shanghai' #修改时区 USE_TZ = False #本地时间,True为国际时间
8、xadmin(django最好使用1.9.8的版本,pip install django=1.9.8,当然自动安装很简单)
1)settings.py修改
pip install xadmin
修改settings.py中的配置
将 xadmin 和 crispy_forms(注意这里的-改为_) 加入到INSTALLED_APPS中
2)项目urls.py的修改
# from django.contrib import admin import xadmin urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^xadmin/', xadmin.site.urls), ]
9、手动安装 xadmin
1)、下载xadmin源码,xadmin的源码是保存在github上的,在github首页进行搜索,然后下载到本地
2)、新建extra_apps(第三方app)文件夹(与apps文件夹同级),并在该文件夹下新建__init__.py文件(标识为一个可被调用的包)
然后打开xadmin的源码,将里面的xadmin目录复制到extra_apps文件夹下
3)、最后在settings.py文件中将extra_apps添加到项目路径
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))
注意:本人在手动安装后运行项目时,出现了下面的错误
from future.utils import iteritems ImportError: No module named future.utils
通过网上查询,在github上下载了future相关代码
然后复制到项目对应虚拟环境的目录下
我自己对应的目录为:C:\Users\Administrator\Envs\mxonline\Lib\site-packages\
再次运行项目,又出现ImportError: No module named six 的错误,然后可以通过pip install six的方式解决
最后, 同步xadmin相关数据表到数据库
python manage.py makemigrations
python manage.py migrate
项目终于成功启动!!!
10、django邮箱验证登录的实现方式
views.py文件中,在登录函数上面重写CustomBackend类:
from django.contrib.auth.backends import ModelBackend from django.db.models import Q from .models import UserProfile class CustomBackend(ModelBackend): def authenticate(self, username=None, password=None, **kwargs): try: user = UserProfile.objects.get(Q(username=username)|Q(email=username)) if user.check_password(password): return user except Exception as e: return None
然后在settings.py中添加:
AUTHENTICATION_BACKENDS = ( 'users.views.CustomBackend', )
11、Django验证码 django-simple-captcha 的使用
1)、安装和配置
通过pip install 快捷安装
pip install django-simple-captcha
将captcha
添加到settings.py文件中的 INSTALLED_APPS中
同步响应的配置到数据库中(同步后数据库会新建一个名为captcha_captchastore的数据表)
python manage.py migrate
最后在项目的urls.py文件中添加:
urlpatterns += patterns('', url(r'^captcha/', include('captcha.urls')), )
2)、添加到表单并在视图函数或试图类验证
添加到表单
class RegisterForm(forms.Form): password = forms.CharField(required=True, min_length=6) email = forms.EmailField() captcha = captcha = CaptchaField(error_messages={"invalid":u"验证码错误"})
模板中添加以显示出验证码
<div class="form-group marb8 captcha1 {% if register_form.errors.captcha %}errorput{% endif %}"> <label>验 证 码</label> {{ register_form.captcha }} </div>
验证:
class RegisterView(View):
def get(self, request):
register_form = RegisterForm()
return render(request, 'register.html',{'register_form':register_form})
def post(self, request):
register_form = RegisterForm(request.POST)
if register_form.is_valid():
...
12、邮箱验证
1)、首先配置settings.py
EMAIL_HOST = 'smtp.163.com' EMAIL_PORT = 25 EMAIL_HOST_USER = "abc@163.com" EMAIL_HOST_PASSWORD = '123456' EMAIL_USE_TLS = False EMAIL_FROM = "abc@163.com"
2)、在app的同级目录中新建一个utils的文件夹(记得新建一个__init__.py文件,将文件夹标记为可引用的包),在utils下新建email_send.py文件用于通过邮箱发送验证信息
email_send.py
# -*- coding: utf-8 -*- from random import Random from django.core.mail import send_mail from users.models import EmailVerfyRecord from mxonline.settings import EMAIL_FROM def random_str(randomlength=8): str = '' chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' length = len(chars) - 1 random = Random() for i in range(randomlength): str += chars[random.randint(0, length)] return str def send_email_verification_code(email, send_type="register"): email_record = EmailVerfyRecord() #生成一个16位数字的验证码 code = random_str(16) email_record.code = code email_record.email = email email_record.send_type = send_type email_record.save() if send_type == 'register': email_title = "暮雪在线网注册激活链接" email_body = '请点击链接激活账号:http://127.0.0.1:8000/active/{0}'.format(code) send_status = send_mail(email_title, email_body, EMAIL_FROM, [email]) if send_status: pass
3)、在注册的处理函数中调用email_send.py中的 send_email_verification_code()函数,以发送验证信息到新用户的注册邮箱
class RegisterView(View): def get(self, request): ... def post(self, request): register_form = RegisterForm(request.POST) if register_form.is_valid(): username = request.POST.get("email", "") ...
user = UserProfile()
user.username = username
user.email = username
user.password = make_password(password)
user.is_active = False #注册的时候先将该字段设置为false,通过邮箱验证后再设置为True
user.save()
send_email_verification_code(username, 'register') return render(request, 'login.html') else: return render(request, 'register.html', {'register_form':register_form})
4)、可以看到我们发送给新用户的访问链接中是通过active/ 加上一个16位的随机码来访问的,所以需要在urls.py 和 views.py中添加相应的激活处理
urls.py
urlpatterns += url(r'^active/(?P<active_code>[a-zA-Z0-9]{16})/$', ActiveUserView.as_view(), name='active_user')
views.py
class ActiveUserView(View): def get(self, request, active_code): all_record = EmailVerfyRecord.objects.filter(code=active_code) for record in all_record: email = record.email user = UserProfile.objects.get(email=email) user.is_active = True user.save() return render(request, 'login.html')
13、模板继承:
注意:模板继承中的 static 标签,即使父模板中load了一次,子模板中还是要load
子模板中记得要重新加载static标签 {% load staticfiles %}
14、关于上传图片 media相关的设置
例如某个model 配置了 image这个字段,用于设置用户头像,是这么定义的:
image = models.ImageField(upload_to='org/%Y%m', verbose_name=u'logo')
upload_to指明了上传的路径,然后还需要在settings.py文件中,TEMPLATES添加media的配置,并设置MEDIA_URL 和 MEDIA_ROOT
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, "templates")], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django.core.context_processors.media', ], }, }, ] ... MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
最后在项目的urls.py文件中做如下设置
from organization.views import OrgView from mxonline.settings import MEDIA_ROOT urlpatterns += url(r'^media/(?P<path>.*)$', serve, {"document_root":MEDIA_ROOT})
完成!
15、jQuery提示parsererror错误的解决办法
原因:从服务端返回的json不合法,比如这样的形式,{'name':'xxxx'}。必须是双引号,{"name":"xxxx"}
这是后端返回给页面的部分代码:json文本部分使用了单引号!
class AddUserAskView(View): def post(relf, request): userask_form = UserAskForm(request.POST) if userask_form.is_valid(): user_ask = userask_form.save(commit=True) #UserAskForm为一个ModelForm类,相较于Form类,它可以直接提交表单数据到数据库 return HttpResponse(“{‘status’:'success'}”, content_type='application/json') else: return ...
ajax代码如下
<script> $(function(){ $('#jsStayBtn').on('click', function(){ $.ajax({ cache: false, type: "POST", url:"{% url 'org:add_ask' %}", data:$('#jsStayForm').serialize(), async: true, success: function(data) { if(data.status == 'success'){ $('#jsStayForm')[0].reset(); alert("提交成功") }else if(data.status == 'fail'){ $('#jsCompanyTips').html(data.msg) } }, error: function(){ alert(arguments[1]); } }); }); }) </script>
结果success函数一直不执行,最后添加了error函数后,提示parse error的异常
最后发现问题,HttpResponse() 中的json文本,一定要用双引号
return HttpResponse('{"status":"success"}', content_type='application/json')
16、404页面配置
1、设置settings.py 文件中 DEBUG 为 false
DEBUG = False
2、将DEBUG模式为True的时候,django可以根据设置的STATICFILES_DIRS 取到的静态文件,当设置为false后,这种访问静态文件的方式将会失效(网站部署的时候,静态文件都是通过第三方【apache、nginx等】来代理的。),为了能正常访问静态文件,可以通过配置static的访问配置url和访问函数。
1)首先在settings.py文件中 ,配置STATIC_ROOT
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
2)在项目文件的urls.py文件中,添加static的urls配置
from mxonline.settings import STATIC_ROOT url(r'^static/(?P<path>.*)$', serve, {"document_root":STATIC_ROOT}),
3)在views.py文件中添加404页面的处理函数
#全局404处理函数 def page_not_found(request): response = render_to_response('404.html',{}) response.status_code = 404 return response
4)最后在配置url的 urls.py文件中添加404页面的全局配置
handler404 = 'users.views.page_not_found'
17、xadmin 使用的图标来自第三方库 awesome
18、xadmin 中使用ueditor(DjangoEditor源码使用文档)
1、安装
pip install DjangoUeditor
2、在settings.py文件中,添加DjangoUeditor 到INSTALL_APPS里面
3、urls.py文件中配置url
url(r'^ueditor/',include('DjangoUeditor.urls' )),
4、models.py 中使用,并在xadmin注册的将其添加到 style_fields
models.py
from DjangoUeditor.models import UEditorField class Blog(models.Model): Name=models.CharField(,max_length=100,blank=True) detail=UEditorField(u'内容 ',width=600, height=300, toolbars="full", imagePath="", filePath="", upload_settings={"imageMaxSize":1204000}, settings={},command=None,event_handler=myEventHander(),blank=True)
adminx.py
class CourseAdmin(object): ...#指定在编辑状态不显示的字段 exclude = ['fav_nums'] #添加课程时,可以直接添加章节, Lesson 和 CourseResource 均有外键指向Course inlines = [LessonInline, CourseResourceInline] style_fields = {'detail':'ueditor'}
5、xadmin下的plugins文件夹中添加ueditor.py文件,然后在__init__.py中的 PLUGINS里面添加 'ueditor'
ueditor.py文件内容为:
import xadmin from django.db.models import TextField from xadmin.views import BaseAdminPlugin, ModelFormAdminView, DetailAdminView from DjangoUeditor.models import UEditorField from DjangoUeditor.widgets import UEditorWidget from django.conf import settings class XadminUEditorWidget(UEditorWidget): def __init__(self,**kwargs): self.ueditor_options=kwargs self.Media.js = None super(XadminUEditorWidget,self).__init__(kwargs) class UeditorPlugin(BaseAdminPlugin): def get_field_style(self, attrs, db_field, style, **kwargs): if style == 'ueditor': if isinstance(db_field, UEditorField): widget = db_field.formfield().widget param = {} param.update(widget.ueditor_settings) param.update(widget.attrs) return {'widget': XadminUEditorWidget(**param)} if isinstance(db_field, TextField): return {'widget': XadminUEditorWidget} return attrs def block_extrahead(self, context, nodes): js = '<script type="text/javascript" src="%s"></script>' % (settings.STATIC_URL + "media/ueditor/ueditor.config.js") js += '<script type="text/javascript" src="%s"></script>' % (settings.STATIC_URL + "media/ueditor/ueditor.all.min.js") nodes.append(js) xadmin.site.register_plugin(UeditorPlugin, DetailAdminView) xadmin.site.register_plugin(UeditorPlugin, ModelFormAdminView)
__init__.py
PLUGINS = ( ... 'sortablelist', 'ueditor', )
6、最后需要在展示富文本的地方关掉自动转义
{% autoescape off %}
{{ course.detail }}
{% endautoescape %}
19、xadmin插件开发之 excel导入
1、xadmin/plugins中新建excel.py文件
excel.py
# -*- coding: utf-8 -*- import xadmin from xadmin.views import BaseAdminPlugin, ListAdminView from django.template import loader #excel导入功能 class ListImportExcelPlugin(BaseAdminPlugin): import_excel = False def init_request(self, *args, **kwargs): return bool(self.import_excel) #返回True才会加载插件 def block_top_toolbar(self, context, nodes): #显示自己的html nodes.append(loader.render_to_string('xadmin/excel/model_list.top_toolbar.import.html', context_instance=context)) xadmin.site.register_plugin(ListImportExcelPlugin, ListAdminView)
2、配置对应的html模板文件
在xadmin/templates 下新建excel文件夹,然后新建一个html文件,命名为
model_list.top_toolbar.import.html,
{% load i18n %} <div class="btn-group export"> <a class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown" href="#"> <i class="icon-share"></i> 导入 <span class="caret"></span> </a> <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel"> <li><a data-toggle="modal" data-target="#export-modal-import-excel"><i class="icon-circle-arrow-down"></i> 导入 Excel</a></li> </ul> <script> function fileChange(target){ //检测上传文件的类型 var imgName = document.all.submit_upload.value; var ext,idx; if (imgName == ''){ document.all.submit_upload_b.disabled=true; alert("请选择需要上传的 xls 文件!"); return; } else { idx = imgName.lastIndexOf("."); if (idx != -1){ ext = imgName.substr(idx+1).toUpperCase(); ext = ext.toLowerCase( ); {# alert("ext="+ext);#} if (ext != 'xls' && ext != 'xlsx'){ document.all.submit_upload_b.disabled=true; alert("只能上传 .xls 类型的文件!"); return; } } else { document.all.submit_upload_b.disabled=true; alert("只能上传 .xls 类型的文件!"); return; } } } </script> <div id="export-modal-import-excel" class="modal fade"> <div class="modal-dialog"> <div class="modal-content"> <form method="post" action="" enctype="multipart/form-data"> {% csrf_token %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title">导入 Excel</h4> </div> <div class="modal-body"> <input type="file" onchange="fileChange(this)" name="excel" id="submit_upload"> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button> <button class="btn btn-success" type="submit" id="submit_upload_b"><i class="icon-share"></i> 导入</button> </div> </form> </div><!-- /.modal-content --> </div><!-- /.modal-dalog --> </div><!-- /.modal --> </div>
3、在plugins/__init__.py文件中添加excel(处理excel导入的文件名称)
PLUGINS = ( ... 'ueditor', 'excel', )
4、在adminx.py文件中对应model注册的时候,设置 import_excel 并重写post()方法以将上传的excel文件解析,最后保存到数据库对应的表中
这是我的xadmin.py文件中添加了了导入功能的Course的处理
xlrd第三方库可以通过 pip install xlrd来安装
import xlrd from .models import Course ... class CourseAdmin(object): #Course 中的方法 get_lesson_num 也可以放在展示列表中 list_display = ['name','desc','detail','degree','learn_time','students','fav_nums','image','click_nums','get_lesson_num','go_to','add_time'] search_fields = ['name','desc','detail','degree', 'students','fav_nums','image','click_nums'] list_filter = ['name','desc','detail','degree','learn_time','students','fav_nums','image','click_nums','add_time'] #根据点击排序 ordering = ['-click_nums'] #指定只读(不可修改)的字段 readonly_fields = ['click_nums'] #可直接在列表页编辑的字段 list_editable = ['degree', 'desc'] #指定在编辑状态不显示的字段 exclude = ['fav_nums'] #添加课程时,可以直接添加章节, Lesson 和 CourseResource 均有外键指向Course inlines = [LessonInline, CourseResourceInline] style_fields = {'detail':'ueditor'} #import_excel位True,excel导入,会覆盖插件中(plugins/excel.py)import_excel的默认值 import_excel = True #将导入的excel文件内容存入数据库的course表中 def post(self, request, *args, **kwargs): if 'excel' in request.FILES: wb = xlrd.open_workbook(filename=None, file_contents=request.FILES['excel'].read()) table = wb.sheets()[0] row = table.nrows sql_list = [] org_id_list = [] for i in xrange(1,row): col = table.row_values(i) sql = Course( degree=col[0], learn_time=col[1], detail=col[2], desc=col[3], students=col[4], fav_nums=col[5], name=col[6], image=col[7], click_nums=col[8], course_org_id=col[10], category=col[11], tag=col[12], teacher_id=col[13], learn_what=col[14], need_know=col[15], notice=col[16], is_banner=col[17], ) sql_list.append(sql) org_id_list.append(col[10]) Course.objects.bulk_create(sql_list) #更新excel文件中机构包含的课程数 for id in org_id_list: org = CourseOrg.objects.get(id=int(id)) org_course_nums = org.course_set.all().count() org.course_nums = org_course_nums org.save() return super(CourseAdmin, self).post(request, args, kwargs) #页面定时刷新,在页面选择 # refresh_times = [3,5] #页面展示的数据过滤(只展示符合条件的数据) def queryset(self): course_ = super(CourseAdmin, self).queryset() course_ = course_.filter(is_banner=False) return course_ #保存课程时统计课程机构的课程数 def save_models(self): obj = self.new_obj #新建一个课程对象 obj.save() if obj.course_org is not None: course_org = obj.course_org course_org.course_nums = Course.objects.filter(course_org=course_org).count() course_org.save() ...
20、csrf_token 在html 和 js 中写法不一样
1、js中 当做模板变量 应该用 {{ csrf_token }}
<script type="text/javascript"> function add_fav(current_elem, fav_id, fav_type){ $.ajax({ cache: false, type: "POST", url:"{% url 'org:add_fav' %}", data:{'fav_id':fav_id, 'fav_type':fav_type}, async: true, beforeSend:function(xhr, settings){ xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}"); }, success: function(data) { ... }, }); } $('#jsLeftBtn').on('click', function(){ add_fav($(this), {{ course.id }}, 1); }); $('#jsRightBtn').on('click', function(){ add_fav($(this), {{ course.course_org.id }}, 2); }); </script>
2、html中当做模板标签,应该用 {% csrf_token %}
<form action="/login/" method="post" autocomplete="off">
<div class="form-group marb20 {% if login_form.errors.username %}errorput{% endif %}">
<label>用 户 名</label>
<input name="username" id="account_l" type="text" placeholder="手机号/邮箱" />
</div>
...
<input class="btn btn-green" id="jsLoginBtn" type="submit" value="立即登录 > " />
<input type='hidden' name='csrfmiddlewaretoken' value='5I2SlleZJOMUX9QbwYLUIAOshdrdpRcy' />
{% csrf_token %}
</form>