Django+xadmin实现教育网站系统

环境配置与项目介绍

在这里插入图片描述
在这里插入图片描述

Django升级2.0+ 注意的点

在这里插入图片描述

app管理

在这里插入图片描述

数据库设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注册model

  • django如果每个app注册后,需要admin显示,则在app/admin.py中注册,xadmin则为adminx.py
# course/adminx.py

import xadmin

from .models import Course, Lesson, Video, CourseResource,BannerCourse
from organization.models import CourseOrg

class LessonInline(object):
    model = Lesson
    extra = 0


class CourseResourceInline(object):
    model = CourseResource
    extra = 0


# Course的admin管理器
class CourseAdmin(object):
    '''课程'''

    list_display = [ 'name','desc','detail','degree','learn_times','students','get_zj_nums','go_to']   #显示的字段
    search_fields = ['name', 'desc', 'detail', 'degree', 'students']             #搜索
    list_filter = [ 'name','desc','detail','degree','learn_times','students']    #过滤
    model_icon = 'fa fa-book'            #图标
    ordering = ['-click_nums']           #排序
    readonly_fields = ['click_nums']     #只读字段
    exclude = ['fav_nums']               #不显示的字段
    # list_editable = ['degree','desc']
    # refresh_times = [3,5]                #自动刷新(里面是秒数范围)
    inlines = [LessonInline,CourseResourceInline]    #增加章节和课程资源
    style_fields = {"detail": "ueditor"}

    def queryset(self):
        # 重载queryset方法,来过滤出我们想要的数据的
        qs = super(CourseAdmin, self).queryset()
        # 只显示is_banner=True的课程
        qs = qs.filter(is_banner=False)
        return qs

    def save_models(self):
        # 在保存课程的时候统计课程机构的课程数
        # obj实际是一个course对象
        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()


class BannerCourseAdmin(object):
    '''轮播课程'''

    list_display = [ 'name','desc','detail','degree','learn_times','students']
    search_fields = ['name', 'desc', 'detail', 'degree', 'students']
    list_filter = [ 'name','desc','detail','degree','learn_times','students']
    model_icon = 'fa fa-book'
    ordering = ['-click_nums']
    readonly_fields = ['click_nums']
    exclude = ['fav_nums']
    inlines = [LessonInline,CourseResourceInline]

    def queryset(self):
        #重载queryset方法,来过滤出我们想要的数据的
        qs = super(BannerCourseAdmin, self).queryset()
        #只显示is_banner=True的课程
        qs = qs.filter(is_banner=True)
        return qs

class LessonAdmin(object):
    '''章节'''

    list_display = ['course', 'name', 'add_time']
    search_fields = ['course', 'name']
    #这里course__name是根据课程名称过滤
    list_filter = ['course__name', 'name', 'add_time']


class VideoAdmin(object):
    '''视频'''

    list_display = ['lesson', 'name', 'add_time']
    search_fields = ['lesson', 'name']
    list_filter = ['lesson', 'name', 'add_time']


class CourseResourceAdmin(object):
    '''课程资源'''

    list_display = ['course', 'name', 'download', 'add_time']
    search_fields = ['course', 'name', 'download']
    list_filter = ['course__name', 'name', 'download', 'add_time']


# 将管理器与model进行注册关联
xadmin.site.register(Course, CourseAdmin)
xadmin.site.register(BannerCourse, BannerCourseAdmin)
xadmin.site.register(Lesson, LessonAdmin)
xadmin.site.register(Video, VideoAdmin)
xadmin.site.register(CourseResource, CourseResourceAdmin)

注册app目录

在这里插入图片描述

5 xadmin搭建后台管理系统

5-3 users app 的model注册

  • 一般在admin中对model进行注册与显示的逻辑更改
    在这里插入图片描述

  • 显示字段 list_display = (‘action_time’, ‘user’, ‘ip_addr’, ‘str’, ‘link’)

  • 筛选 list_filter = [‘user’, ‘action_time’]

  • 搜索 search_fields = [‘ip_addr’, ‘message’]

  • 图标 model_icon = ‘fa fa-cog’

在这里插入图片描述

在这里插入图片描述

5-5 xadmin全局配置

# users/adminx.py

import xadmin

from .models import EmailVerifyRecord,Banner
from xadmin import views


# 创建xadmin的最基本管理器配置,并与view绑定
class BaseSetting(object):
    # 开启主题功能
    enable_themes = True
    use_bootswatch = True

# 全局修改,固定写法
class GlobalSettings(object):
    # 修改title
    site_title = 'imooc后台管理界面'
    # 修改footer
    site_footer = '科比的公司'
    # 收起菜单 将每个数据库的表收起
    menu_style = 'accordion'


#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
    # 显示的列
    list_display = ['code', 'email', 'send_type', 'send_time']
    # 搜索的字段
    search_fields = ['code', 'email', 'send_type']
    # 过滤
    list_filter = ['code', 'email', 'send_type', 'send_time']


class BannerAdmin(object):
    list_display = ['title', 'image', 'url','index', 'add_time']
    search_fields = ['title', 'image', 'url','index']
    list_filter = ['title', 'image', 'url','index', 'add_time']


xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)
xadmin.site.register(Banner,BannerAdmin)

# 将基本配置管理与view绑定
xadmin.site.register(views.BaseAdminView,BaseSetting)

# 将title和footer信息进行注册
xadmin.site.register(views.CommAdminView,GlobalSettings)

app.py 定义app后台名称

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将app.py 的配置 注册到init.py中

第6章 用户注册功能实现

6.1 登录表单验证

from django import forms
from captcha.fields import CaptchaField
class LoginForm(forms.Form):
    '''登录验证表单'''

    username = forms.CharField(required=True)
    password = forms.CharField(required=True,min_length=5)
class LoginView(View):
    '''用户登录'''

    def get(self,request):
        return render(request, 'login.html')

    def post(self,request):
        # 实例化
        login_form = LoginForm(request.POST)
        if login_form.is_valid():
            # 获取用户提交的用户名和密码
            user_name = request.POST.get('username', None)
            pass_word = request.POST.get('password', None)
            # 成功返回user对象,失败None
            user = authenticate(username=user_name, password=pass_word)
            # 如果不是null说明验证成功
            if user is not None:
                if user.is_active:
                    # 只有注册激活才能登录
                    login(request, user)
                    return HttpResponseRedirect(reverse('index'))
                else:
                    return render(request, 'login.html', {'msg': '用户名或密码错误', 'login_form': login_form})
            # 只有当用户名或密码不存在时,才返回错误信息到前端
            else:
                return render(request, 'login.html', {'msg': '用户名或密码错误','login_form':login_form})

        # form.is_valid()已经判断不合法了,所以这里不需要再返回错误信息到前端了
        else:
            return render(request,'login.html',{'login_form':login_form})

6-6 session和cookie自动登录机制

在这里插入图片描述

设置redis Djangosession
这里采用默认的mysql保存

用户注册

from django.contrib.auth.hashers import make_password

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():
            user_name = request.POST.get('email', None)
            # 如果用户已存在,则提示错误信息
            if UserProfile.objects.filter(email = user_name):
                return render(request, 'register.html', {'register_form':register_form,'msg': '用户已存在'})

            pass_word = request.POST.get('password', None)
            # 实例化一个user_profile对象
            user_profile = UserProfile()
            user_profile.username = user_name
            user_profile.email = user_name
            user_profile.is_active = False
            # 对保存到数据库的密码加密
            user_profile.password = make_password(pass_word)
            user_profile.save()
            send_register_eamil(user_name,'register')
            return render(request,'login.html')
        else:
            return render(request,'register.html',{'register_form':register_form})

发送邮件

# apps/utils/email_send.py

from random import Random
from django.core.mail import send_mail

from users.models import EmailVerifyRecord
from RcOnline.settings import EMAIL_FROM

# 生成随机字符串
def random_str(random_length=8):
    str = ''
    # 生成字符串的可选字符串
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
    length = len(chars) - 1
    random = Random()
    for i in range(random_length):
        str += chars[random.randint(0, length)]
    return str

# 发送注册邮件
def send_register_eamil(email, send_type="register"):
    # 发送之前先保存到数据库,到时候查询链接是否存在
    # 实例化一个EmailVerifyRecord对象
    email_record = EmailVerifyRecord()
    # 生成随机的code放入链接
    if send_type == 'update_email':
        code = random_str(4)
    else:
        code = random_str(16)
    email_record.code = code
    email_record.email = email
    email_record.send_type = send_type

    email_record.save()

    # 定义邮件内容:
    email_title = ""
    email_body = ""

    if send_type == "register":
        email_title = "NBA注册激活链接"
        email_body = "请点击下面的链接激活你的账号: http://127.0.0.1:8000/active/{0}".format(code)

        # 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,从哪里发,接受者list
        send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
        # 如果发送成功
        if send_status:
            pass

    elif send_type == "forget":
        email_title = "NBA找回密码链接"
        email_body = "请点击下面的链接找回你的密码: http://127.0.0.1:8000/reset/{0}".format(code)

        # 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,从哪里发,接受者list
        send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
        # 如果发送成功
        if send_status:
            pass

    elif send_type == "update_email":
        email_title = "NBA邮箱修改验证码"
        email_body = "你的邮箱验证码为{0}".format(code)

        # 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,从哪里发,接受者list
        send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
        # 如果发送成功
        if send_status:
            pass

settings

EMAIL_HOST = "smtp.qq.com"  # SMTP服务器主机
EMAIL_PORT = 25             # 端口
EMAIL_HOST_USER = ""       # 邮箱地址
EMAIL_HOST_PASSWORD = ""    # 授权码
EMAIL_USE_TLS = False
EMAIL_FROM = ""            # 邮箱地址

激活用户

# 激活用户
class ActiveUserView(View):
    def get(self, request, active_code):
        # 查询邮箱验证记录是否存在
        all_record = EmailVerifyRecord.objects.filter(code = active_code)

        if all_record:
            for record in all_record:
                # 获取到对应的邮箱
                email = record.email
                # 查找到邮箱对应的user
                user = UserProfile.objects.get(email=email)
                user.is_active = True
                user.save()
         # 验证码不对的时候跳转到激活失败页面
        else:
            return render(request,'active_fail.html')
        # 激活成功跳转到登录页面
        return render(request, "login.html", )

第7章 课程机构功能实现

7-3 课程机构列表页数据展示

Django 定义本地上传图片的路径

  • 在settings.py定义

    # 上传图片
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR,'media')
    
  • 在root目录下设置media文件夹

  • 在models.py中定义文件路径

    class CourseOrg(models.Model):
    ORG_CHOICES = (
        ("pxjg", u"培训机构"),
        ("gx", u"高校"),
        ("gr", u"个人"),
    )
    name = models.CharField('机构名称',max_length=50)
    desc = models.TextField('机构描述')
    category = models.CharField(max_length=20, choices=ORG_CHOICES, verbose_name=u"机构类别", default="pxjg")
    click_nums = models.IntegerField('点击数',default=0)
    tag = models.CharField('机构标签',max_length=10,default='全国知名')
    fav_nums = models.IntegerField('收藏数',default=0)
    students = models.IntegerField("学习人数",default=0)
    course_nums = models.IntegerField("课程数",default=0)
    image = models.ImageField('logo',upload_to='org/%Y/%m',max_length=100)
    address = models.CharField('机构地址',max_length=150,)
    city = models.ForeignKey(CityDict,verbose_name='所在城市',on_delete=models.CASCADE)
    add_time = models.DateTimeField(default=datetime.now)
    
    • 目录结构,(其实应该将文件上传到云 or 改成时间戳的名称存储)
      在这里插入图片描述

渲染模板(使用后端配置)

  • 1.settings.py

    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',
                     #添加图片处理器,为了在课程列表中前面加上MEDIA_URL
                    'django.template.context_processors.media',
                ],
            },
        },
    ]
    
  • 2.设置图片路径,使用{{ MEDIA_URL }}{{ course_org.image }}

     {% for course_org in all_orgs.object_list %}
     <dl class="des difdes">
    
           <dt>
               <a href="{% url 'org:org_home' course_org.id %}">
    
                   <img width="200" height="120" class="scrollLoading"
                        data-url="{{ MEDIA_URL }}{{ course_org.image }}"/>
    
               </a>
           </dt>
           <dd>
               <div class="clearfix">
                   <a href="org-detail-homepage.html">
                       <h1>{{ course_org.name }}</h1>
                       <div class="pic fl">
    
                           <img src="{% static 'images/authentication.png' %}"/>
    
                           <img src="{% static 'images/gold.png' %}"/>
    
                       </div>
                   </a>
               </div>
               <ul class="cont">
                   <li class="first"><p class="pic9">课程数:<span>1</span></p>
                       <p class="c7">学习人数:<span>1000</span></p></li>
                   <li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
                   <li class="pic10" style="padding-left:18px;">经典课程:
    
                       <a href="/diary/19/">c语言基础入门</a>
    
                       <a href="/diary/16/">数据库基础</a>
    
                   </li>
               </ul>
           </dd>
           <div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
       </dl>
    {% endfor %}
    

分页 pure_pagination

pure_pagination 对Django的分页paginate进行封装, 在settings.py的INSTALLED_APPS设置pure_pagination

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'course',
    'operation',
    'organization',
    'users',

    'xadmin',
    'crispy_forms',

    'captcha',
    'pure_pagination',
    'DjangoUeditor',
]
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger

class OrgView(View):
    '''课程机构'''

    def get(self, request):
        # 所有课程机构
        all_orgs = CourseOrg.objects.all()

        # 所有城市
        all_citys = CityDict.objects.all()

        # 机构搜索功能
        search_keywords = request.GET.get('keywords', '')
        if search_keywords:
            # 在name字段进行操作,做like语句的操作。i代表不区分大小写
            # or操作使用Q
            all_orgs = all_orgs.filter(Q(name__icontains=search_keywords) | Q(desc__icontains=search_keywords))
        # 城市筛选
        city_id = request.GET.get('city','')
        if city_id:
            all_orgs = all_orgs.filter(city_id=int(city_id))

        # 类别筛选
        category = request.GET.get('ct','')
        if category:
            all_orgs = all_orgs.filter(category=category)

 
        # 对课程机构进行分页
        # 尝试获取前台get请求传递过来的page参数
        # 如果是不合法的配置参数默认返回第一页
        try:
            page = request.GET.get('page', 1)
        except PageNotAnInteger:
            page = 1
        # 这里指从allorg中取五个出来,每页显示2个,可以在settings中设置分页显示数量
        p = Paginator(all_orgs, 2, request=request)
        orgs = p.page(page)

        return render(request, "org-list.html", {
            "all_orgs": orgs,
            "all_citys": all_citys,
            "org_nums": org_nums,
            'city_id':city_id,
            "category": category,
            'hot_orgs':hot_orgs,
            'sort':sort,
        })
   <div class="pageturn">
     <ul class="pagelist">
         {% if all_courses.has_previous %}
             <li class="long"><a href="?{{ all_courses.previous_page_number.querystring }}">上一页</a></li>
         {% endif %}

         {% for page in all_courses.pages %}
             {% if page %}
                 {% ifequal page all_courses.number %}
                     <li class="active"><a href="?{{ page.querystring }}">{{ page }}</a></li>
                 {% else %}
                     <li><a href="?{{ page.querystring }}" class="page">{{ page }}</a></li>
                 {% endifequal %}
             {% else %}
                 <li class="none"><a href="">...</a></li>
             {% endif %}
         {% endfor %}
         {% if all_courses.has_next %}
             <li class="long"><a href="?{{ all_courses.next_page_number.querystring }}">下一页</a></li>
         {% endif %}
     </ul>
 </div>

7-7 Django modelform提交我要学习咨询

# organization/forms.py

import re
from django import forms
from operation.models import UserAsk


class UserAskForm(forms.ModelForm):

    class Meta:
        model = UserAsk
        fields = ['name', 'mobile', 'course_name']

    def clean_mobile(self):
        """
        验证手机号码是否合法
        """
        mobile = self.cleaned_data['mobile']
        REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|176\d{8}$"
        p = re.compile(REGEX_MOBILE)
        if p.match(mobile):
            return mobile
        else:
            raise forms.ValidationError(u"手机号码非法", code="mobile_invalid")
class AddUserAskView(View):
    """
    用户添加咨询
    """
    def post(self, request):
        userask_form = UserAskForm(request.POST)
        if userask_form.is_valid():
            user_ask = userask_form.save(commit=True)
            # 如果保存成功,返回json字符串,后面content type是告诉浏览器返回的数据类型
            return HttpResponse('{"status":"success"}', content_type='application/json')
        else:
            # 如果保存失败,返回json字符串,并将form的报错信息通过msg传递到前端
            return HttpResponse('{"status":"fail", "msg":"添加出错"}', content_type='application/json')

7-9 机构详情展示

Django orm反向查找 course_org.course_set.all()[:4],利用课程机构查找课程

class OrgHomeView(View):
    '''机构首页'''

    def get(self,request,org_id):
        current_page = 'home'
        # 根据id找到课程机构
        course_org = CourseOrg.objects.get(id=int(org_id))
        course_org.click_nums += 1
        course_org.save()
        # 判断收藏状态
        has_fav = False
        if request.user.is_authenticated:
            if UserFavorite.objects.filter(user=request.user, fav_id=course_org.id, fav_type=2):
                has_fav = True
        # 反向查询到课程机构的所有课程和老师
        all_courses = course_org.course_set.all()[:4]
        all_teacher = course_org.teacher_set.all()[:2]
        return render(request,'org-detail-homepage.html',{
            'course_org':course_org,
            'all_courses':all_courses,
            'all_teacher':all_teacher,
            'current_page':current_page,
            'has_fav':has_fav,
        })

BUG

集成DjangoUeditor时,出现错误,错误提示为:

from widgets import UEditorWidget,AdminUEditorWidget
ImportError: No module named ‘widgets’

经查发现,DjangoUeditor是基于Python 2.7的,对Python3的支持有问题。导致widgets.py文件出错,不能import。解决方法为修改widgets.py或者采用网上修改好的版本

参考
https://blog.csdn.net/zhch1979/article/details/104684122/

https://blog.csdn.net/weixin_43063753/article/details/87466738

猜你喜欢

转载自blog.csdn.net/weixin_43746433/article/details/106632575