解决合并冲突,保留develop分支内容

pull/25/head
djq 4 months ago
commit 98fb2e733b

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

@ -4,7 +4,11 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<<<<<<< HEAD
<list default="true" id="bba269c0-1ed7-4666-beba-a3554735c261" name="更改" comment="" />
=======
<list default="true" id="496ba640-abe8-45b2-825d-dbb362f422fe" name="更改" comment="" />
>>>>>>> 3ff39d6e1bc2c76b5a0a9d394a602e712f327368
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@ -13,10 +17,17 @@
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<<<<<<< HEAD
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="34GbnsM5lf8vxTZ8TKf5DRlnLx3" />
=======
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 4
}]]></component>
<component name="ProjectId" id="33yFAzru0bn5YuOQyYRfboyeACH" />
>>>>>>> 3ff39d6e1bc2c76b5a0a9d394a602e712f327368
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
@ -26,11 +37,17 @@
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
<<<<<<< HEAD
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "djq__branch",
"ignore.virus.scanning.warn.message": "true",
"last_opened_file_path": "D:/软件工程方法学/djq",
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
=======
"git-widget-placeholder": "master",
"ignore.virus.scanning.warn.message": "true",
"last_opened_file_path": "D:/软件工程方法学/ccy"
>>>>>>> 3ff39d6e1bc2c76b5a0a9d394a602e712f327368
}
}]]></component>
<component name="SharedIndexes">
@ -42,6 +59,7 @@
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<<<<<<< HEAD
<changelist id="bba269c0-1ed7-4666-beba-a3554735c261" name="更改" comment="" />
<created>1760843025691</created>
<option name="number" value="Default" />
@ -61,4 +79,14 @@
</breakpoints>
</breakpoint-manager>
</component>
=======
<changelist id="496ba640-abe8-45b2-825d-dbb362f422fe" name="更改" comment="" />
<created>1760281265610</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1760281265610</updated>
</task>
<servers />
</component>
>>>>>>> 3ff39d6e1bc2c76b5a0a9d394a602e712f327368
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

@ -0,0 +1 @@
文档内容

@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/software-engineering-methodology-djq-branch(1).iml" filepath="$PROJECT_DIR$/.idea/software-engineering-methodology-djq-branch(1).iml" />
</modules>
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

@ -0,0 +1,42 @@
# 系统和用户目录
Application Data/
Cookies/
Local Settings/
My Documents/
NetHood/
PrintHood/
Recent/
SendTo/
Templates/
「开始」菜单/
AppData/
Contacts/
Desktop/
Documents/
Downloads/
Favorites/
Links/
Music/
NTUSER.*
OneDrive/
Pictures/
Saved Games/
Searches/
Videos/
WPS Cloud Files/
wechat_files/
# 临时文件和IDE配置
.bash_history
.eclipse/
.gitconfig
.idlerc/
.matplotlib/
.p2/
.ssh/
.vscode/
eclipse-workspace/
eclipse/
ntuser.*

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Django.iml" filepath="$PROJECT_DIR$/.idea/Django.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,6 @@
<<<<<<< HEAD
这是我的个人分支说明
=======
# software-engineering-methodology
>>>>>>> f783378e06d6abd4513ad3220bf6f630b2fb7263

@ -0,0 +1,112 @@
from django import forms
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
# Register your models here.
from .models import Article
class ArticleForm(forms.ModelForm):
# body = forms.CharField(widget=AdminPagedownWidget())
class Meta:
model = Article
fields = '__all__'
def makr_article_publish(modeladmin, request, queryset):
queryset.update(status='p')
def draft_article(modeladmin, request, queryset):
queryset.update(status='d')
def close_article_commentstatus(modeladmin, request, queryset):
queryset.update(comment_status='c')
def open_article_commentstatus(modeladmin, request, queryset):
queryset.update(comment_status='o')
makr_article_publish.short_description = _('Publish selected articles')
draft_article.short_description = _('Draft selected articles')
close_article_commentstatus.short_description = _('Close article comments')
open_article_commentstatus.short_description = _('Open article comments')
class ArticlelAdmin(admin.ModelAdmin):
list_per_page = 20
search_fields = ('body', 'title')
form = ArticleForm
list_display = (
'id',
'title',
'author',
'link_to_category',
'creation_time',
'views',
'status',
'type',
'article_order')
list_display_links = ('id', 'title')
list_filter = ('status', 'type', 'category')
filter_horizontal = ('tags',)
exclude = ('creation_time', 'last_modify_time')
view_on_site = True
actions = [
makr_article_publish,
draft_article,
close_article_commentstatus,
open_article_commentstatus]
def link_to_category(self, obj):
info = (obj.category._meta.app_label, obj.category._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
return format_html(u'<a href="%s">%s</a>' % (link, obj.category.name))
link_to_category.short_description = _('category')
def get_form(self, request, obj=None, **kwargs):
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['author'].queryset = get_user_model(
).objects.filter(is_superuser=True)
return form
def save_model(self, request, obj, form, change):
super(ArticlelAdmin, self).save_model(request, obj, form, change)
def get_view_on_site_url(self, obj=None):
if obj:
url = obj.get_full_url()
return url
else:
from djangoblog.utils import get_current_site
site = get_current_site().domain
return site
class TagAdmin(admin.ModelAdmin):
exclude = ('slug', 'last_mod_time', 'creation_time')
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'parent_category', 'index')
exclude = ('slug', 'last_mod_time', 'creation_time')
class LinksAdmin(admin.ModelAdmin):
exclude = ('last_mod_time', 'creation_time')
class SideBarAdmin(admin.ModelAdmin):
list_display = ('name', 'content', 'is_enable', 'sequence')
exclude = ('last_mod_time', 'creation_time')
class BlogSettingsAdmin(admin.ModelAdmin):
pass

@ -0,0 +1,88 @@
# 导入日志模块,用于记录系统运行时的信息和错误
import logging
# 导入Django的时间工具用于获取当前时间
from django.utils import timezone
# 导入自定义工具:缓存工具和获取博客设置的方法
from djangoblog.utils import cache, get_blog_setting
# 导入当前应用的模型:分类和文章模型
from .models import Category, Article
# 创建日志记录器,用于记录当前模块的日志信息
logger = logging.getLogger(__name__)
def seo_processor(requests):
"""
自定义上下文处理器用于在所有模板中全局共享SEO相关的配置和数据
上下文处理器会在每次请求时被调用返回的字典会自动注入到所有模板中
Args:
requests: HttpRequest对象包含当前请求的信息如协议主机等
Returns:
dict: 包含网站配置导航数据等的字典供模板全局使用
"""
# 定义缓存键,用于标识当前处理器的缓存数据
key = 'seo_processor'
# 尝试从缓存中获取数据,减少数据库查询和计算开销
value = cache.get(key)
# 如果缓存中存在数据,直接返回缓存内容
if value:
return value
else:
# 缓存未命中时,记录日志并重新计算数据
logger.info('set processor cache.')
# 获取博客的全局设置(从数据库或其他配置源)
setting = get_blog_setting()
# 构建需要返回给模板的数据集
value = {
# 网站名称(用于页面标题等)
'SITE_NAME': setting.site_name,
# 是否显示谷歌广告
'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,
# 谷歌广告代码
'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,
# 网站SEO描述用于meta标签
'SITE_SEO_DESCRIPTION': setting.site_seo_description,
# 网站描述(用于页面展示)
'SITE_DESCRIPTION': setting.site_description,
# 网站关键词用于meta标签提升SEO
'SITE_KEYWORDS': setting.site_keywords,
# 网站基础URL协议+域名如https://example.com/
'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
# 文章摘要长度(用于列表页显示)
'ARTICLE_SUB_LENGTH': setting.article_sub_length,
# 导航栏显示的分类列表(从数据库查询所有分类)
'nav_category_list': Category.objects.all(),
# 导航栏显示的页面列表(筛选类型为"页面"且状态为"已发布"的文章)
'nav_pages': Article.objects.filter(
type='p', # 'p'表示页面page区别于普通文章article
status='p'), # 'p'表示已发布published
# 是否开启网站评论功能
'OPEN_SITE_COMMENT': setting.open_site_comment,
# 网站备案号
'BEIAN_CODE': setting.beian_code,
# 网站统计代码如百度统计、Google Analytics
'ANALYTICS_CODE': setting.analytics_code,
# 公安备案号
"BEIAN_CODE_GONGAN": setting.gongan_beiancode,
# 是否显示公安备案号
"SHOW_GONGAN_CODE": setting.show_gongan_code,
# 当前年份(用于页脚版权信息等)
"CURRENT_YEAR": timezone.now().year,
# 全局页头代码如自定义CSS、JS
"GLOBAL_HEADER": setting.global_header,
# 全局页脚代码
"GLOBAL_FOOTER": setting.global_footer,
# 评论是否需要审核后才显示
"COMMENT_NEED_REVIEW": setting.comment_need_review,
}
# 将数据存入缓存有效期为10小时60秒*60分*10小时
# 减少重复计算和数据库查询,提升性能
cache.set(key, value, 60 * 60 * 10)
return value

@ -0,0 +1,42 @@
import logging
import time
from ipware import get_client_ip
from user_agents import parse
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
logger = logging.getLogger(__name__)
class OnlineMiddleware(object):
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def __call__(self, request):
''' page render time '''
start_time = time.time()
response = self.get_response(request)
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
ip, _ = get_client_ip(request)
user_agent = parse(http_user_agent)
if not response.streaming:
try:
cast_time = time.time() - start_time
if ELASTICSEARCH_ENABLED:
time_taken = round((cast_time) * 1000, 2)
url = request.path
from django.utils import timezone
ElaspedTimeDocumentManager.create(
url=url,
time_taken=time_taken,
log_datetime=timezone.now(),
useragent=user_agent,
ip=ip)
response.content = response.content.replace(
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
except Exception as e:
logger.error("Error OnlineMiddleware: %s" % e)
return response

@ -0,0 +1,427 @@
import logging
import re
from abc import abstractmethod
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from mdeditor.fields import MDTextField
from uuslug import slugify
from djangoblog.utils import cache_decorator, cache
from djangoblog.utils import get_current_site
logger = logging.getLogger(__name__)
class LinkShowType(models.TextChoices):
# 定义友情链接显示类型的枚举类,分别对应首页、列表页、文章页、所有页面、轮播
I = ('i', _('index'))
L = ('l', _('list'))
P = ('p', _('post'))
A = ('a', _('all'))
S = ('s', _('slide'))
class BaseModel(models.Model):
"""
基础模型类为其他模型提供通用的字段和方法
"""
id = models.AutoField(primary_key=True) # 自增主键
creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
last_modify_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
def save(self, *args, **kwargs):
"""
重写save方法处理slug字段如果模型有slug和title/name字段并调用父类save方法
同时处理仅更新views字段的特殊情况
"""
is_update_views = isinstance(
self,
Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
if is_update_views:
Article.objects.filter(pk=self.pk).update(views=self.views)
else:
# 如果模型有slug字段生成slug基于title或name字段
if 'slug' in self.__dict__:
slug_source = getattr(self, 'title') if 'title' in self.__dict__ else getattr(self, 'name')
setattr(self, 'slug', slugify(slug_source))
super().save(*args, **kwargs)
def get_full_url(self):
"""
获取模型对象的完整URL包含域名
"""
site = get_current_site().domain
url = "https://{site}{path}".format(site=site,
path=self.get_absolute_url())
return url
class Meta:
abstract = True # 抽象模型,不生成数据库表
@abstractmethod
def get_absolute_url(self):
"""
抽象方法子类必须实现用于获取模型对象的绝对URL
"""
pass
class Article(BaseModel):
"""
文章模型类存储文章的相关信息
"""
# 文章状态:草稿、已发布
STATUS_CHOICES = (
('d', _('Draft')),
('p', _('Published')),
)
# 评论状态:开启、关闭
COMMENT_STATUS = (
('o', _('Open')),
('c', _('Close')),
)
# 文章类型:文章、页面
TYPE = (
('a', _('Article')),
('p', _('Page')),
)
title = models.CharField(_('title'), max_length=200, unique=True) # 文章标题,唯一
body = MDTextField(_('body')) # 文章内容使用MDTextField支持markdown
pub_time = models.DateTimeField(
_('publish time'), blank=False, null=False, default=now) # 发布时间
status = models.CharField(
_('status'),
max_length=1,
choices=STATUS_CHOICES,
default='p') # 文章状态
comment_status = models.CharField(
_('comment status'),
max_length=1,
choices=COMMENT_STATUS,
default='o') # 评论状态
type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') # 文章类型
views = models.PositiveIntegerField(_('views'), default=0) # 文章浏览量
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
blank=False,
null=False,
on_delete=models.CASCADE) # 文章作者,外键关联用户模型
article_order = models.IntegerField(
_('order'), blank=False, null=False, default=0) # 文章排序序号
show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) # 是否显示目录
category = models.ForeignKey(
'Category',
verbose_name=_('category'),
on_delete=models.CASCADE,
blank=False,
null=False) # 文章分类外键关联Category模型
tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 文章标签多对多关联Tag模型
def body_to_string(self):
"""将文章内容转换为字符串返回"""
return self.body
def __str__(self):
"""自定义字符串表示,返回文章标题"""
return self.title
class Meta:
ordering = ['-article_order', '-pub_time'] # 排序规则先按article_order降序再按pub_time降序
verbose_name = _('article')
verbose_name_plural = verbose_name
get_latest_by = 'id'
def get_absolute_url(self):
"""获取文章的绝对URL用于生成文章详情页链接"""
return reverse('blog:detailbyid', kwargs={
'article_id': self.id,
'year': self.creation_time.year,
'month': self.creation_time.month,
'day': self.creation_time.day
})
@cache_decorator(60 * 60 * 10)
def get_category_tree(self):
"""
获取文章分类的树形结构包含当前分类及其所有父级分类并缓存
"""
tree = self.category.get_category_tree()
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
return names
def save(self, *args, **kwargs):
"""重写save方法调用父类save方法"""
super().save(*args, **kwargs)
def viewed(self):
"""文章被浏览时浏览量加1并保存"""
self.views += 1
self.save(update_fields=['views'])
def comment_list(self):
"""
获取文章的评论列表优先从缓存获取缓存不存在则查询数据库并缓存
"""
cache_key = 'article_comments_{id}'.format(id=self.id)
value = cache.get(cache_key)
if value:
logger.info('get article comments:{id}'.format(id=self.id))
return value
else:
comments = self.comment_set.filter(is_enable=True).order_by('-id')
cache.set(cache_key, comments, 60 * 100)
logger.info('set article comments:{id}'.format(id=self.id))
return comments
def get_admin_url(self):
"""获取文章在admin后台的编辑URL"""
info = (self._meta.app_label, self._meta.model_name)
return reverse('admin:%s_%s_change' % info, args=(self.pk,))
@cache_decorator(expiration=60 * 100)
def next_article(self):
"""获取下一篇文章id大于当前文章且已发布的第一篇并缓存"""
return Article.objects.filter(
id__gt=self.id, status='p').order_by('id').first()
@cache_decorator(expiration=60 * 100)
def prev_article(self):
"""获取前一篇文章id小于当前文章且已发布的第一篇并缓存"""
return Article.objects.filter(id__lt=self.id, status='p').first()
def get_first_image_url(self):
"""
从文章内容中提取第一张图片的URL
通过正则表达式匹配markdown图片语法中的图片链接
"""
match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
if match:
return match.group(1)
return ""
class Category(BaseModel):
"""
文章分类模型类
"""
name = models.CharField(_('category name'), max_length=30, unique=True) # 分类名称,唯一
parent_category = models.ForeignKey(
'self',
verbose_name=_('parent category'),
blank=True,
null=True,
on_delete=models.CASCADE) # 父分类,自关联
slug = models.SlugField(default='no-slug', max_length=60, blank=True) # 分类的slug用于URL
index = models.IntegerField(default=0, verbose_name=_('index')) # 分类排序序号
class Meta:
ordering = ['-index'] # 按index降序排序
verbose_name = _('category')
verbose_name_plural = verbose_name
def get_absolute_url(self):
"""获取分类的绝对URL用于生成分类页链接"""
return reverse(
'blog:category_detail', kwargs={
'category_name': self.slug})
def __str__(self):
"""自定义字符串表示,返回分类名称"""
return self.name
@cache_decorator(60 * 60 * 10)
def get_category_tree(self):
"""
递归获取分类的树形结构当前分类及其所有父级分类并缓存
"""
categorys = []
def parse(category):
categorys.append(category)
if category.parent_category:
parse(category.parent_category)
parse(self)
return categorys
@cache_decorator(60 * 60 * 10)
def get_sub_categorys(self):
"""
递归获取当前分类的所有子分类包括子分类的子分类等并缓存
"""
categorys = []
all_categorys = Category.objects.all()
def parse(category):
if category not in categorys:
categorys.append(category)
childs = all_categorys.filter(parent_category=category)
for child in childs:
if category not in categorys:
categorys.append(child)
parse(child)
parse(self)
return categorys
class Tag(BaseModel):
"""
文章标签模型类
"""
name = models.CharField(_('tag name'), max_length=30, unique=True) # 标签名称,唯一
slug = models.SlugField(default='no-slug', max_length=60, blank=True) # 标签的slug用于URL
def __str__(self):
"""自定义字符串表示,返回标签名称"""
return self.name
def get_absolute_url(self):
"""获取标签的绝对URL用于生成标签页链接"""
return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
@cache_decorator(60 * 60 * 10)
def get_article_count(self):
"""获取该标签下的文章数量,并缓存"""
return Article.objects.filter(tags__name=self.name).distinct().count()
class Meta:
ordering = ['name'] # 按名称排序
verbose_name = _('tag')
verbose_name_plural = verbose_name
class Links(models.Model):
"""
友情链接模型类
"""
name = models.CharField(_('link name'), max_length=30, unique=True) # 链接名称,唯一
link = models.URLField(_('link')) # 链接URL
sequence = models.IntegerField(_('order'), unique=True) # 排序序号,唯一
is_enable = models.BooleanField(
_('is show'), default=True, blank=False, null=False) # 是否显示
show_type = models.CharField(
_('show type'),
max_length=1,
choices=LinkShowType.choices,
default=LinkShowType.I) # 显示类型关联LinkShowType枚举
creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
class Meta:
ordering = ['sequence'] # 按sequence排序
verbose_name = _('link')
verbose_name_plural = verbose_name
def __str__(self):
"""自定义字符串表示,返回链接名称"""
return self.name
class SideBar(models.Model):
"""
侧边栏模型类用于展示自定义HTML内容
"""
name = models.CharField(_('title'), max_length=100) # 侧边栏标题
content = models.TextField(_('content')) # 侧边栏内容HTML
sequence = models.IntegerField(_('order'), unique=True) # 排序序号,唯一
is_enable = models.BooleanField(_('is enable'), default=True) # 是否启用
creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
class Meta:
ordering = ['sequence'] # 按sequence排序
verbose_name = _('sidebar')
verbose_name_plural = verbose_name
def __str__(self):
"""自定义字符串表示,返回侧边栏标题"""
return self.name
class BlogSettings(models.Model):
"""
博客配置模型类存储网站的各种配置信息
"""
site_name = models.CharField(
_('site name'),
max_length=200,
null=False,
blank=False,
default='') # 网站名称
site_description = models.TextField(
_('site description'),
max_length=1000,
null=False,
blank=False,
default='') # 网站描述
site_seo_description = models.TextField(
_('site seo description'), max_length=1000, null=False, blank=False, default='') # 网站SEO描述
site_keywords = models.TextField(
_('site keywords'),
max_length=1000,
null=False,
blank=False,
default='') # 网站关键词
article_sub_length = models.IntegerField(_('article sub length'), default=300) # 文章摘要长度
sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) # 侧边栏文章数量
sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) # 侧边栏评论数量
article_comment_count = models.IntegerField(_('article comment count'), default=5) # 文章评论数量
show_google_adsense = models.BooleanField(_('show adsense'), default=False) # 是否显示Google广告
google_adsense_codes = models.TextField(
_('adsense code'), max_length=2000, null=True, blank=True, default='') # Google广告代码
open_site_comment = models.BooleanField(_('open site comment'), default=True) # 是否开启网站评论
global_header = models.TextField("公共头部", null=True, blank=True, default='') # 公共头部HTML
global_footer = models.TextField("公共尾部", null=True, blank=True, default='') # 公共尾部HTML
beian_code = models.CharField(
'备案号',
max_length=2000,
null=True,
blank=True,
default='') # 网站备案号
analytics_code = models.TextField(
"网站统计代码",
max_length=1000,
null=False,
blank=False,
default='') # 网站统计代码
show_gongan_code = models.BooleanField(
'是否显示公安备案号', default=False, null=False) # 是否显示公安备案号
gongan_beiancode = models.TextField(
'公安备案号',
max_length=2000,
null=True,
blank=True,
default='') # 公安备案号
comment_need_review = models.BooleanField(
'评论是否需要审核', default=False, null=False) # 评论是否需要审核
class Meta:
verbose_name = _('Website configuration')
verbose_name_plural = verbose_name
def __str__(self):
"""自定义字符串表示,返回网站名称"""
return self.site_name
def clean(self):
"""
模型验证方法确保只能有一个配置实例
如果存在其他配置实例排除当前实例则抛出验证错误
"""
if BlogSettings.objects.exclude(id=self.id).count():
raise ValidationError(_('There can only be one configuration'))
def save(self, *args, **kwargs):
"""
重写save方法保存后清除缓存使配置变更立即生效
"""
super().save(*args, **kwargs)
from djangoblog.utils import cache
cache.clear()

@ -0,0 +1,47 @@
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
def disable_commentstatus(modeladmin, request, queryset):
queryset.update(is_enable=False)
def enable_commentstatus(modeladmin, request, queryset):
queryset.update(is_enable=True)
disable_commentstatus.short_description = _('Disable comments')
enable_commentstatus.short_description = _('Enable comments')
class CommentAdmin(admin.ModelAdmin):
list_per_page = 20
list_display = (
'id',
'body',
'link_to_userinfo',
'link_to_article',
'is_enable',
'creation_time')
list_display_links = ('id', 'body', 'is_enable')
list_filter = ('is_enable',)
exclude = ('creation_time', 'last_modify_time')
actions = [disable_commentstatus, enable_commentstatus]
def link_to_userinfo(self, obj):
info = (obj.author._meta.app_label, obj.author._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
return format_html(
u'<a href="%s">%s</a>' %
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
def link_to_article(self, obj):
info = (obj.article._meta.app_label, obj.article._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
return format_html(
u'<a href="%s">%s</a>' % (link, obj.article.title))
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')

@ -0,0 +1,39 @@
from django.conf import settings
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from blog.models import Article
# Create your models here.
class Comment(models.Model):
body = models.TextField('正文', max_length=300)
creation_time = models.DateTimeField(_('creation time'), default=now)
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
on_delete=models.CASCADE)
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE)
parent_comment = models.ForeignKey(
'self',
verbose_name=_('parent comment'),
blank=True,
null=True,
on_delete=models.CASCADE)
is_enable = models.BooleanField(_('enable'),
default=False, blank=False, null=False)
class Meta:
ordering = ['-id']
verbose_name = _('comment')
verbose_name_plural = verbose_name
get_latest_by = 'id'
def __str__(self):
return self.body

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save