I. Functional Requirement Analysis
1. function
-
Rotation chart
-
List of Recommended Articles
-
Article Label Navigation
-
Article list
-
paging
2. Model Design
Based on the functional analysis, we need the following table
1. Table and field analysis
-
Articles Category Table
-
Article table
-
Commentary Table of Articles
-
List of Recommended Articles
-
Wheel chart
2. Model Definition
Define a class model to extract common fields
# stay utils Under the directory, create a models.py File, in which a class model is defined from django.db import models class BaseModel(models.Model): """ //Base class, common field """ create_time = models.DateTimeField('Creation time', auto_now_add=True) update_time = models.DateTimeField('Update time', auto_now=True) is_delete = models.BooleanField('Logical deletion', default=False) class Meta: # Abstract class for inheritance, not created during migration abstract = True
Define other models
# stay news Directory models.py The following data model is defined in the file from django.db import models from utils.models import BaseModel class Tag(BaseModel): """ //Articles Classification Label Model """ name = models.CharField('Tag name', max_length=64, help_text='Tag name') class Meta: ordering = ['-update_time', '-id'] # sort db_table = "tb_tag" # Specify the database table name verbose_name = "Article Tags" # stay admin Names displayed on the site verbose_name_plural = verbose_name # The plural name displayed def __str__(self): return self.name class News(BaseModel): """ //Article model """ title = models.CharField('Title', max_length=150, help_text='Title') digest = models.CharField('abstract', max_length=200, help_text='abstract') content = models.TextField('content', help_text='content') clicks = models.IntegerField('Clicks', default=0, help_text='Clicks') image_url = models.URLField('picture url', default='', help_text='picture url') tag = models.ForeignKey('Tag', on_delete=models.SET_NULL, null=True) author = models.ForeignKey('user.User', on_delete=models.SET_NULL, null=True) class Meta: ordering = ['-update_time', '-id'] # sort db_table = "tb_news" # Specify the database table name verbose_name = "Journalism" # stay admin Names displayed on the site verbose_name_plural = verbose_name # The plural name displayed def __str__(self): return self.title class Comments(BaseModel): """ //Comment model """ content = models.TextField('content', help_text='content') author = models.ForeignKey('user.User', on_delete=models.SET_NULL, null=True) news = models.ForeignKey('News', on_delete=models.CASCADE) class Meta: ordering = ['-update_time', '-id'] # sort db_table = "tb_comments" # Specify the database table name verbose_name = "comment" # stay admin Names displayed on the site verbose_name_plural = verbose_name # The plural name displayed def __str__(self): return '<comment{}>'.format(self.id) class HotNews(BaseModel): """ //List of Recommended Articles """ news = models.OneToOneField('News', on_delete=models.CASCADE) priority = models.IntegerField('priority', help_text='priority') class Meta: ordering = ['-update_time', '-id'] # sort db_table = "tb_hotnews" # Specify the database table name verbose_name = "Hot news" # stay admin Names displayed on the site verbose_name_plural = verbose_name # The plural name displayed def __str__(self): return '<Hot news{}>'.format(self.id) class Banner(BaseModel): """ //Rotation chart """ image_url = models.URLField('Rotation chart url', help_text='Rotation chart url') priority = models.IntegerField('priority', help_text='priority') news = models.OneToOneField('News', on_delete=models.CASCADE) class Meta: ordering = ['priority', '-update_time', '-id'] # sort db_table = "tb_banner" # Specify the database table name verbose_name = "Rotation chart" # stay admin Names displayed on the site verbose_name_plural = verbose_name # The plural name displayed def __str__(self): return '<Rotation chart{}>'.format(self.id)
3. Article Label Navigation Function
1. Interface design
-
Interface Description:
Category Explain Request method GET url definition / Parameter format No parameter -
Return result
Return to the news page and render directly in the template
2. Backend code
# stay news/views.py The following views are defined in the file from django.shortcuts import render from .models import Tag def index(request): """ //News Home View :param request: :return: """ tags = Tag.objects.only('id', 'name').filter(is_delete=False) return render(request, 'news/index.html', context={ 'tags': tags })
Import tag test data, or add data directly to the tb_tag table with Navicat software (see the contents in the quotation marks below, totaling 6), because the reason of the previous section setting must be the same as my content, which will be used later.
# insert news tag data INSERT INTO tb_tag(name, create_time, update_time, is_delete) values ('Python Basics', now(), now(), 0), ('Python senior', now(), now(), 0), ('Python function', now(), now(), 0), ('PythonGUI', now(), now(), 0), ('Linux Course', now(), now(), 0), ('Python frame', now(), now(), 0);
3. Front-end code
# Modify the news-nav code in templates/news/index.html, such as: <!-- news-nav start--> <nav class="news-nav"> <ul class="clearfix"> <li class="active"><a href="javascript:void(0)">Latest information</a></li> {% for tag in tags %} <li><a href="javascript:void(0)" data-id="{{ tag.id }}">{{ tag.name }}</a> </li> {% endfor %} </ul> </nav> <!-- news-nav end -->
4. News List Function
1. Business process analysis
-
Determine whether the front-end pass label classification id is empty, integer, and beyond the scope
-
Determine whether the number of pages of the current article passed by the front end is empty, integer, or beyond the scope
2. Interface design
Interface Description:
Category | Explain |
---|---|
Request method | GET |
url definition | /news/ |
Parameter format | Query parameters |
Description of parameters:
Parameter name | type | Is it necessary? | describe |
---|---|---|---|
tag | integer | yes | Label classification id |
page | integer | yes | Current Page Number of Articles |
Returns the result:
{ "errno": "0", "errmsg": "", "data": { "total_pages": 61, "news": [ { 'id': 'xxx', "digest": "stay python use import perhaps from...import perhaps from...import...as...To import the corresponding modules, functions and usage methods and C Linguistic include Header files are similar. In fact, it is the introduction....", "title": "import Method Introducing Module Details", "author": "python", "image_url": "/media/jichujiaochen.jpeg", "tag_name": "Python Basics", "update_time": "2018 December 17, 14:48" }, { 'id': 'xxx' "digest": "If you were one php Programmer, you are right about php Functions are well understood( PS: The stationmaster used to be one php Programmers), but now for work or other reasons to learn python,however p...", "title": "For once phper Programmers recommend a learning website", "author": "python", "image_url": "/media/jichujiaochen.jpeg", "tag_name": "Python Basics", "update_time": "2018 December 17, 14:48" } ] } }
3. Backend code
Create a media folder in the project root directory to store news photos and upload files.
# stay settings.py Add in the file # Media file configuration MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
django provides static file service in debugging mode. In order to be able to return media files in media, the following configuration needs to be done in root urls.py
# At root urls.py Medium plus static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('', include('news.urls')), path('', include('verification.urls')), path('user/', include('user.urls')) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Import test data. In order to test the import of data, make sure that the table names are consistent.
# Import test data in the xshell, upload tb_news_20181217.sql file to the virtual machine through rz command in the xshell Mysql-u username-p-D database name < tb_news_20181217.sql
Documents I have placed in my file bar (name is sql packet. rar After decompression, there are four files: tag table, news table, popular news table and broadcast chart.
View code
import logging from django.shortcuts import render from django.views import View from django.core.paginator import Paginator from django.db.models import F from .models import Tag, News from . import constants from utils.json_res import json_response logger = logging.getLogger('django') def index(request): """ //News Home View url: / :param request: :return: """ tags = Tag.objects.only('id', 'name').filter(is_delete=False) return render(request, 'news/index.html', context={ 'tags': tags }) class NewsListView(View): """ //News List View url: /news/ args: tag, page """ def get(self, request): # 1.Get parameters try: tag_id = int(request.GET.get('tag', 0)) except Exception as e: logger.error('Label error:\n{}'.format(e)) tag_id = 0 try: page = int(request.GET.get('page', 0)) except Exception as e: logger.error('Page number error:\n{}'.format(e)) page = 1 # Use only The object is returned, so it needs to be processed iteratively when passed to the front end # news_queryset = News.objects.select_related('tag', 'author').only( # 'title', 'digest', 'image_url', 'update_time', 'tag__name', 'author__username') # 2.Get the query set (values Returns a dictionary.,only The object returned is the object) ############## important start ################# # queryset:Inertia does not go to the database # structure,Section,filter,Usually query sets do not really go to the database when passing # When,Go to the database.: # 1.iteration # 2.Section,As long as you don't jump to look it up, you won't look it up in the database.,Only when there is a slice of data in the middle will the database be queried.,When cutting out an element # 3.Care will be printed on the console # 4.Serialization Cache,Save the contents of the query set to redis,Memory # 5.Use len()Method Gets Length,count # 6.Use list()Method transformation type # 7.bool,To judge whether it is empty or not # QuerySet Caching # When Not Cached # Execute only part of the query set # Simple Printing without Changing Cars:Q = News.objects.all() print(Q[1]) # Not cached ############## important end ################# news_queryset = News.objects.values('id', 'title', 'digest', 'image_url', 'update_time').annotate( tag_name=F('tag__name'), author=F('author__username')) # 3.filter # if tag_id: # news = news_queryset.fileter(is_delete=False, tag_id=tag_id) # else: # news = news_queryset.fileter(is_delete=False) news = news_queryset.filter(is_delete=False, tag_id=tag_id) or news_queryset.filter(is_delete=False) # 4.paging paginator = Paginator(news, constants.PER_PAGE_NEWS_COUNT) # See the explanation below. # Get the current page data get_page Fault tolerant news_info = paginator.get_page(page) # 5.Return data data = { 'total_pages': paginator.num_pages, # How many pages are divided? 'news':list(news_info) } return json_response(data=data)
Paginator method:
Paginator.get_page (number) This method is a new 2.0 method.
Returns that Page has a given index object starting from 1, and also handles out-of-range and invalid page numbers. If the page number is not a number, it returns to the first page. If the page number is negative or larger, return to the last page.
# 2.Paging parsing paginator = Paginator(docs, 5) # Page every five copies of content # 2.2 Get the previous paragraph and send it. page The value of this query parameter page = paginator.get_page(request.GET.get('page',1)) # request.GET.get('page',1) Obtain url Parameters, 127.0.0.1:8000/?page=<value> # Illegal values return 1 to be empty and 1 to be 127..0.0.1:8000/?page=asdsa # Get current(Page number)The required list of articles is equivalent to a container
Because json does not support datetime-type data by default, a custom json encoder (both to serialize news time)
# stay utils/json_res.py Add custom to the file json Encoder so that it can be serialized datetime data type import json import datetime from django.http import JsonResponse from .res_code import Code # json Encoder # Custom serializer 1, processing time fields(Older versions will be wrong.) #class MyJSONEncoder(json.JSONEncoder): # def default(self, o): # if isinstance(o, datetime.datetime): # return o.astimezone().strftime('%Y-%m-%d %H:%M:%S') # Conversion to local time # Custom serializer 2 to process time fields(2.1.10django Edition DjangoJSONEncoder Can help us realize automatically) class MyJSONEncoder(DjangoJSONEncoder): def default(self, o): if isinstance(o, datetime.datetime): return o.astimezone().strftime('%Y-%m-%d %H:%M:%S') # Conversion to local time def json_response(errno=Code.OK, errmsg='', data=None, kwargs=None): json_dict = { 'errno': errno, 'errmsg': errmsg, 'data': data } if kwargs and isinstance(kwargs, dict) : json_dict.update(kwargs) return JsonResponse(json_dict, encoder=MyJSONEncoder)
Define constants
# stay news Directory constants.py The following constants are defined: # Number of news per page PER_PAGE_NEWS_COUNT = 5
Routing configuration
from django.urls import path from . import views # url Namespace app_name = 'news' urlpatterns = [ path('', views.index, name='index'), # Name this route index path('news/', views.NewsListView.as_view(), name='news_list') ]
4. Front-end code
Front-end html page code modification
<!-- news-contain start empty ul Content in --> <div class="news-contain"> <ul class="news-list"> </ul> </div> <!-- news-contain end -->
js code
// static/js/news/index.js $(function () { // News list let $newNavLi = $('.news-nav ul li'); // Label li let iPage = 1; // Default page 1 let iTotalPage = 1; // The default total number of pages is 1 let iCurrentTagId = 0; // The default classification label is 0 let bIsLoadData = true; // Are you loading data into the background? fn_load_content(); // Click on the Classification Label $newNavLi.click(function () { // Click on the category label to add a label to the clicked label active Of class attribute // And remove the sibling elements active Of class attribute $(this).addClass('active').siblings('li').removeClass('active'); // Get the binding in data-id Attribute tag_id let iClickTagId = $(this).children('a').attr('data-id'); if (iClickTagId !== iCurrentTagId){ iCurrentTagId = iClickTagId; // Record current classification id // Reset Paging Parameters iPage = 1; iTotalPage = 1; fn_load_content() } }); // Page scroll loading $(window).scroll(function () { // Browser window height let showHeigtht = $(window).height(); // Whole page height let pageHeight = $(document).height(); //How far a page can scroll let canScrollHeight = pageHeight - showHeigtht; // How many pages scroll, and the whole thing changes in real time as the pages scroll let nowScroll = $(document).scrollTop(); if ((canScrollHeight - nowScroll) < 100){ if(!bIsLoadData){ bIsLoadData = true; //Judge the number of pages, update the news, load less than the total if(iPage < iTotalPage){ iPage += 1; fn_load_content(); }else { message.showInfo('All loaded, no more content!'); $('a.btn-more').html('All loaded, no more content!') } } } }); // Getting news list data from the back end function fn_load_content() { $.ajax({ url: '/news/', type: 'GET', data:{ tag: iCurrentTagId, page: iPage }, dataType: 'json', success: function (res) { if(res.errno === '0'){ iTotalPage = res.data.total_pages; if(iPage === 1){ // The first page is empty. $('.news-list').html('') } res.data.news.forEach(function (one_news) { let content = ` <li class="news-item"> <a href="https://www.shiguangkey.com/course/2432" class="news-thumbnail" target="_blank"> <img src="${one_news.image_url}" alt="${one_news.title}" title="${one_news.title}"> </a> <div class="news-content"> <h4 class="news-title"><a href="#">${one_news.title}</a> </h4> <p class="news-details">${one_news.digest}</p> <div class="news-other"> <span class="news-type">${one_news.tag_name}</span> <span class="news-time">${one_news.update_time}</span> <span class="news-author">${one_news.author}</span> </div> </div> </li>`; $('.news-list').append(content); }); // $('.news-list').append($('<a href="javascript:void(0);" class="btn-more">Scroll Load More</a>')); //When the data is loaded, set the data variable being loaded to false,Indicates that no data is currently loaded bIsLoadData = false; $('a.btn-more').html('Scroll Load More') }else { // Load failure, print error message message.showError(res.errmsg) } }, error: function () { message.showError('Server timeout, please try again!') } }); } });
V. Rotary Planting Function
1. Interface design
-
Interface Description:
Category | Explain |
---|---|
Request method | GET |
url definition | /news/banners/ |
Parameter format | No parameter |
Returns the result:
{ "errno": "0", "errmsg": "OK", "data": { "banners": [ { 'image_url': '/media/jichujiaochen.jpeg', 'news_id': 221, 'news_title': "python Fast sorting algorithm" }, { "image_url": "/media/python_advanced.jpg", "news_id": 707, "news_title": "Python Unpacking operation of sequence and mapping" } ] } }
2. Backend code
View code
# stay news Directory views.py Create the following view in class NewsBannerView(View): """ //Rotary Graph View url:/news/banners/ """ def get(self, request): banners = Banner.objects.values('image_url', 'news_id').annotate( news_title=F('news__title') ).filter(is_delete=False)[:constants.SHOW_BANNER_COUNT] data = { 'banners': list(banners) } return json_response(data=data)
Define constants
# stay news Directory constants.py The following constants are defined # banner Number of pages displayed SHOW_BANNER_COUNT = 6
Route
# news Directory urls.py The following routing is defined: from django.urls import path from . import views # url Namespace app_name = 'news' urlpatterns = [ path('', views.index, name='index'), # Name this route index path('news/', views.NewsListView.as_view(), name='news_list'), path('news/banners/', views.NewsBannerView.as_view(), name='news_banner') ]
3. Front-end code
html code
<!-- modify templates/news/index.html in banner Part of the code is as follows --> <!-- banner start --> <div class="banner"> <ul class="pic"> <!--Fade in and fade out banner--> </ul> <a href="javascript:void(0);" class="btn prev"> <i class="PyWhich py-arrow-left"></i></a> <a href="javascript:void(0);" class="btn next"> <i class="PyWhich py-arrow-right"></i></a> <ul class="tab"> <!-- The number of buttons must be the same as the picture. --> </ul> </div> <!-- banner end -->
js code
<!-- static/js/news/index.js --> $(function () { // News list let $newNavLi = $('.news-nav ul li'); // Label li let iPage = 1; // Default page 1 let iTotalPage = 1; // The default total number of pages is 1 let iCurrentTagId = 0; // The default classification label is 0 let bIsLoadData = true; // Are you loading data into the background? fn_load_content(); // Click on the Classification Label $newNavLi.click(function () { // Click on the category label to add a label to the clicked label active Of class attribute // And remove the sibling elements active Of class attribute $(this).addClass('active').siblings('li').removeClass('active'); // Get the binding in data-id Attribute tag_id let iClickTagId = $(this).children('a').attr('data-id'); if (iClickTagId !== iCurrentTagId){ iCurrentTagId = iClickTagId; // Record current classification id // Reset Paging Parameters iPage = 1; iTotalPage = 1; fn_load_content() } }); // Page scroll loading $(window).scroll(function () { // Browser window height let showHeigtht = $(window).height(); // Whole page height let pageHeight = $(document).height(); //How far a page can scroll let canScrollHeight = pageHeight - showHeigtht; // How many pages scroll, and the whole thing changes in real time as the pages scroll let nowScroll = $(document).scrollTop(); if ((canScrollHeight - nowScroll) < 100){ if(!bIsLoadData){ bIsLoadData = true; //Judge the number of pages, update the news, load less than the total if(iPage < iTotalPage){ iPage += 1; fn_load_content(); }else { message.showInfo('All loaded, no more content!'); $('a.btn-more').html('All loaded, no more content!') } } } }); // Getting news list data from the back end function fn_load_content() { $.ajax({ url: '/news/', type: 'GET', data:{ tag: iCurrentTagId, page: iPage }, dataType: 'json', success: function (res) { if(res.errno === '0'){ iTotalPage = res.data.total_pages; if(iPage === 1){ // The first page is empty. $('.news-list').html('') } res.data.news.forEach(function (one_news) { let content = ` <li class="news-item"> <a href="https://www.shiguangkey.com/course/2432" class="news-thumbnail" target="_blank"> <img src="${one_news.image_url}" alt="${one_news.title}" title="${one_news.title}"> </a> <div class="news-content"> <h4 class="news-title"><a href="#">${one_news.title}</a> </h4> <p class="news-details">${one_news.digest}</p> <div class="news-other"> <span class="news-type">${one_news.tag_name}</span> <span class="news-time">${one_news.update_time}</span> <span class="news-author">${one_news.author}</span> </div> </div> </li>`; $('.news-list').append(content); }); // $('.news-list').append($('<a href="javascript:void(0);" class="btn-more">Scroll Load More</a>')); //When the data is loaded, set the data variable being loaded to false,Indicates that no data is currently loaded bIsLoadData = false; $('a.btn-more').html('Scroll Load More') }else { // Load failure, print error message message.showError(res.errmsg) } }, error: function () { message.showError('Server timeout, please try again!') } }) } // Function of News Rotary Map // 1.Loading Rotary Map Data function fn_load_banner() // 2.Click the navigation button to switch this Words must be written function()No use.()=> // 3.Previous page,next page // 4.Automatic switching // 5.Mouse slides into pause to play automatically fn_load_banner(); // Load first banner let $banner = $('.banner'); // banner container div let $picLi = $('.banner .pic li'); // picture li Label let $pre = $('.banner .prev'); // Last one let $next = $('.banner .next'); // Next piece let $tabLi = $('.banner .tab li'); // Button let index = 0; // Current index // Navigation dot $tabLi.click(function () { index = $(this).index(); $(this).addClass('active').siblings('li').removeClass('active'); $picLi.eq(index).fadeIn(1500).siblings('li').fadeOut(1500); }); // Click to switch to the previous one $pre.click(()=> { index --; if(index<0){ index = $tabLi.length - 1 // Last piece } $tabLi.eq(index).addClass('active').siblings('li').removeClass('active'); $picLi.eq(index).fadeIn(1500).siblings('li').fadeOut(1500); }); // Click to switch to the next one $next.click(()=>{ auto(); }); // The picture slides forward function auto() { index ++; index %= $tabLi.length; $tabLi.eq(index).addClass('active').siblings('li').removeClass('active'); $picLi.eq(index).fadeIn(1500).siblings('li').fadeOut(1500) } // timer let timer = setInterval(auto, 2500); $banner.hover( ()=>{ clearInterval(timer) }, ()=>{ timer = setInterval(auto, 2500); } ); // Define Back-End Acquisition banner Of ajax data function fn_load_banner() { $ .ajax({ url: '/news/banners/', type: 'GET', async: false, // Synchronized execution, the following code dependencies banner Loading dataType: "json", }) .done( (res)=> { if(res.errno === '0'){ let content = ''; let tab_content = ''; res.data.banners.forEach( (one_banner, index) =>{ if(index === 0){ // Page 1 plus active attribute content = `<li style="display:block;"><a href="/news/${one_banner.news_id}/"> <img src="${one_banner.image_url}" alt="${one_banner.news_title}"></a></li>`; tab_content = '<li class="active"></li>'; }else { content = `<li><a href="/news/${one_banner.news_id}/"><img src="${one_banner.image_url}" alt="${one_banner.news_title}"></a></li>`; tab_content = '<li></li>'; } $('.pic').append(content); $('.tab').append(tab_content) }) }else { message.showError(res.errmsg) } }) .fail(()=>{ message.showError('Server timeout, please try again!') }) } });
Recommended News
1. Interface design
-
Interface Description:
Category | Explain |
---|---|
Request method | GET |
url definition | / |
Parameter format | No parameter |
2. Return content
Return to the news page and render directly in the template
2. Backend code
View code
# modify news/views.py Medium index view def index(request): """ //News Home View url: / :param request: :return: """ tags = Tag.objects.only('id', 'name').filter(is_delete=False) hot_news = HotNews.objects.select_related('news').only('news__title', 'news__image_url', 'news_id').filter( is_delete=False ).order_by('priority', '-news__clicks')[:constants.SHOW_HOTNEWS_COUNT] return render(request, 'news/index.html', context={ 'tags': tags, 'hot_news': hot_news })
Define constants
# stay news/constants.py Define the following constants # Show the number of hot news items SHOW_HOTNEWS_COUNT = 3
3. Front-end code
html code
<!-- modify templates/news/index.html --> <ul class="recommend-news"> {% for item in hot_news %} <li> <a href="https://www.shiguangkey.com/course/2432" target="_blank"> <div class="recommend-thumbnail"> <img src="{{ item.news.image_url }}" alt="title"> </div> <p class="info">{{ item.news.title }}</p> </a> </li> {% endfor %} </ul> <!-- recommend-news end -->