From 3a3fdd2ec012bfcdf8a6e27d4c54a975d3ef61b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=81=92=E7=A5=BA?= <1581408258@qq.com> Date: Sun, 9 Nov 2025 18:19:45 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 5 ++ .idea/DjangoBlog-master.iml | 35 +++++++++++ .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/misc.xml | 7 +++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 7 +++ src/blog/models.py | 63 +++++++++++-------- 7 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/DjangoBlog-master.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/DjangoBlog-master.iml b/.idea/DjangoBlog-master.iml new file mode 100644 index 0000000..d2ab4f7 --- /dev/null +++ b/.idea/DjangoBlog-master.iml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4c5155d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5494c00 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..8306744 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/blog/models.py b/src/blog/models.py index 083788b..5a179a5 100644 --- a/src/blog/models.py +++ b/src/blog/models.py @@ -16,27 +16,28 @@ from djangoblog.utils import get_current_site logger = logging.getLogger(__name__) - +#zhq: 链接显示类型选择类 - 定义链接在不同页面的显示方式 class LinkShowType(models.TextChoices): - I = ('i', _('index')) - L = ('l', _('list')) - P = ('p', _('post')) - A = ('a', _('all')) - S = ('s', _('slide')) - + I = ('i', _('index'))#zhq: 首页显示 + L = ('l', _('list'))#zhq: 列表页显示 + P = ('p', _('post'))#zhq: 文章页显示 + A = ('a', _('all'))#zhq: 所有页面显示 + S = ('s', _('slide'))#zhq: 幻灯片显示 +#zhq: 基础模型抽象类 - 所有模型的基类,包含公共字段和方法 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): + def save(self, *args, **kwargs):#zhq: 特殊处理文章浏览量的更新,避免触发其他字段的保存逻辑 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: + # zhq: 自动生成slug字段,用于SEO友好的URL if 'slug' in self.__dict__: slug = getattr( self, 'title') if 'title' in self.__dict__ else getattr( @@ -45,6 +46,7 @@ class BaseModel(models.Model): super().save(*args, **kwargs) def get_full_url(self): + # zhq: 获取对象的完整URL地址 site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) @@ -55,9 +57,10 @@ class BaseModel(models.Model): @abstractmethod def get_absolute_url(self): + # zhq: 抽象方法,子类必须实现获取绝对URL的方法 pass - +#zhq: 文章模型 - 博客的核心内容模型 class Article(BaseModel): """文章""" STATUS_CHOICES = ( @@ -112,7 +115,7 @@ class Article(BaseModel): return self.title class Meta: - ordering = ['-article_order', '-pub_time'] + ordering = ['-article_order', '-pub_time']#zhq: 按排序和发布时间降序排列 verbose_name = _('article') verbose_name_plural = verbose_name get_latest_by = 'id' @@ -127,6 +130,7 @@ class Article(BaseModel): @cache_decorator(60 * 60 * 10) def get_category_tree(self): + # zhq: 获取文章所属分类的完整树形结构 tree = self.category.get_category_tree() names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) @@ -136,10 +140,12 @@ class Article(BaseModel): super().save(*args, **kwargs) def viewed(self): + # zhq: 增加文章浏览量 self.views += 1 self.save(update_fields=['views']) def comment_list(self): + # zhq: 获取文章的评论列表,带缓存功能 cache_key = 'article_comments_{id}'.format(id=self.id) value = cache.get(cache_key) if value: @@ -152,6 +158,7 @@ class Article(BaseModel): return comments def get_admin_url(self): + # zhq: 获取文章在Admin后台的编辑链接 info = (self._meta.app_label, self._meta.model_name) return reverse('admin:%s_%s_change' % info, args=(self.pk,)) @@ -176,7 +183,7 @@ class Article(BaseModel): return match.group(1) return "" - +#zhq: 分类模型 - 支持多级分类结构 class Category(BaseModel): """文章分类""" name = models.CharField(_('category name'), max_length=30, unique=True) @@ -185,7 +192,7 @@ class Category(BaseModel): verbose_name=_('parent category'), blank=True, null=True, - on_delete=models.CASCADE) + on_delete=models.CASCADE) #zhq: 自关联,支持多级分类 slug = models.SlugField(default='no-slug', max_length=60, blank=True) index = models.IntegerField(default=0, verbose_name=_('index')) @@ -239,7 +246,7 @@ class Category(BaseModel): parse(self) return categorys - +#zhq: 标签模型 - 简单的标签管理 class Tag(BaseModel): """文章标签""" name = models.CharField(_('tag name'), max_length=30, unique=True) @@ -253,6 +260,7 @@ class Tag(BaseModel): @cache_decorator(60 * 60 * 10) def get_article_count(self): + # zhq: 获取该标签下的文章数量 return Article.objects.filter(tags__name=self.name).distinct().count() class Meta: @@ -260,32 +268,32 @@ class Tag(BaseModel): verbose_name = _('tag') verbose_name_plural = verbose_name - +#zhq: 友情链接模型 class Links(models.Model): """友情链接""" name = models.CharField(_('link name'), max_length=30, unique=True) link = models.URLField(_('link')) - sequence = models.IntegerField(_('order'), unique=True) + sequence = models.IntegerField(_('order'), unique=True) #zhq: 链接显示顺序 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) + default=LinkShowType.I) #zhq: 链接显示类型 creation_time = models.DateTimeField(_('creation time'), default=now) last_mod_time = models.DateTimeField(_('modify time'), default=now) class Meta: - ordering = ['sequence'] + ordering = ['sequence'] #zhq: 按顺序排列 verbose_name = _('link') verbose_name_plural = verbose_name def __str__(self): return self.name - +#zhq: 侧边栏模型 - 支持自定义HTML内容 class SideBar(models.Model): """侧边栏,可以展示一些html内容""" name = models.CharField(_('title'), max_length=100) @@ -303,7 +311,7 @@ class SideBar(models.Model): def __str__(self): return self.name - +#zhq: 博客设置模型 - 单例模式,存储全局配置 class BlogSettings(models.Model): """blog的配置""" site_name = models.CharField( @@ -326,11 +334,11 @@ class BlogSettings(models.Model): 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) + article_sub_length = models.IntegerField(_('article sub length'), default=300) #zhq: 文章摘要长度 + sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) #zhq: 侧边栏文章数量 + sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) #zhq: 侧边栏评论数量 + article_comment_count = models.IntegerField(_('article comment count'), default=5) #zhq: 文章页评论数量 + show_google_adsense = models.BooleanField(_('show adsense'), default=False) #zhq:是否显示Google广告 google_adsense_codes = models.TextField( _('adsense code'), max_length=2000, null=True, blank=True, default='') open_site_comment = models.BooleanField(_('open site comment'), default=True) @@ -341,13 +349,13 @@ class BlogSettings(models.Model): max_length=2000, null=True, blank=True, - default='') + default='') #zhq: ICP备案号 analytics_code = models.TextField( "网站统计代码", max_length=1000, null=False, blank=False, - default='') + default='') #zhq: 网站统计代码 show_gongan_code = models.BooleanField( '是否显示公安备案号', default=False, null=False) gongan_beiancode = models.TextField( @@ -357,7 +365,7 @@ class BlogSettings(models.Model): blank=True, default='') comment_need_review = models.BooleanField( - '评论是否需要审核', default=False, null=False) + '评论是否需要审核', default=False, null=False) #zhq: 评论审核开关 class Meta: verbose_name = _('Website configuration') @@ -367,6 +375,7 @@ class BlogSettings(models.Model): return self.site_name def clean(self): + # zhq: 确保配置表只有一条记录(单例模式) if BlogSettings.objects.exclude(id=self.id).count(): raise ValidationError(_('There can only be one configuration')) -- 2.34.1 From 2feca00d393b437acf2eedfb9d811cad6bf26130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=81=92=E7=A5=BA?= <1581408258@qq.com> Date: Sun, 9 Nov 2025 18:22:16 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 5 --- .idea/DjangoBlog-master.iml | 35 ------------------- .../inspectionProfiles/profiles_settings.xml | 6 ---- .idea/misc.xml | 7 ---- .idea/modules.xml | 8 ----- .idea/vcs.xml | 7 ---- 6 files changed, 68 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/DjangoBlog-master.iml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 10b731c..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ diff --git a/.idea/DjangoBlog-master.iml b/.idea/DjangoBlog-master.iml deleted file mode 100644 index d2ab4f7..0000000 --- a/.idea/DjangoBlog-master.iml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4c5155d..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 5494c00..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 8306744..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file -- 2.34.1 From 30c981662eaeb7b4a1bdaa365f9365f7260d779c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=81=92=E7=A5=BA?= <1581408258@qq.com> Date: Sun, 9 Nov 2025 19:54:09 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/DjangoBlog-master.iml | 7 ++ .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/misc.xml | 7 ++ .idea/vcs.xml | 6 ++ .idea/workspace.xml | 102 ++++++++++++++++++ src/blog/admin.py | 29 ++--- src/blog/apps.py | 4 +- src/blog/context_processors.py | 45 ++++---- src/blog/forms.py | 7 +- src/blog/middleware.py | 10 +- src/blog/urls.py | 15 ++- src/blog/views.py | 38 ++++--- 12 files changed, 220 insertions(+), 56 deletions(-) create mode 100644 .idea/DjangoBlog-master.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/DjangoBlog-master.iml b/.idea/DjangoBlog-master.iml new file mode 100644 index 0000000..ec63674 --- /dev/null +++ b/.idea/DjangoBlog-master.iml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4c5155d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..375e579 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + { + "customColor": "", + "associatedIndex": 5 +} + + + + { + "keyToString": { + "ModuleVcsDetector.initialDetectionPerformed": "true", + "Python.manage.executor": "Run", + "Python.models.executor": "Run", + "Python.settings.executor": "Run", + "RunOnceActivity.OpenDjangoStructureViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "git-widget-placeholder": "zhq__branch", + "vue.rearranger.settings.migration": "true" + } +} + + + + + + + + + + + + + + + 1762680639610 + + + + + + + + + \ No newline at end of file diff --git a/src/blog/admin.py b/src/blog/admin.py index 46c3420..706d4e3 100644 --- a/src/blog/admin.py +++ b/src/blog/admin.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _ # Register your models here. from .models import Article - +#zhq: 文章表单类 - 可用于定制文章编辑表单 class ArticleForm(forms.ModelForm): # body = forms.CharField(widget=AdminPagedownWidget()) @@ -16,29 +16,29 @@ class ArticleForm(forms.ModelForm): model = Article fields = '__all__' - +#zhq: Admin动作函数 - 批量发布文章 def makr_article_publish(modeladmin, request, queryset): queryset.update(status='p') - +#zhq: Admin动作函数 - 批量设为草稿 def draft_article(modeladmin, request, queryset): queryset.update(status='d') - +#zhq: Admin动作函数 - 批量关闭评论 def close_article_commentstatus(modeladmin, request, queryset): queryset.update(comment_status='c') - +#zhq: Admin动作函数 - 批量开启评论 def open_article_commentstatus(modeladmin, request, queryset): queryset.update(comment_status='o') - +#zhq: 设置动作的描述信息 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') - +#zhq: 文章管理类 - 定制文章在Admin中的显示和行为 class ArticlelAdmin(admin.ModelAdmin): list_per_page = 20 search_fields = ('body', 'title') @@ -62,9 +62,10 @@ class ArticlelAdmin(admin.ModelAdmin): makr_article_publish, draft_article, close_article_commentstatus, - open_article_commentstatus] + open_article_commentstatus] #zhq: 注册批量动作 def link_to_category(self, obj): + # zhq: 自定义方法,生成分类的管理后台链接 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'%s' % (link, obj.category.name)) @@ -72,6 +73,7 @@ class ArticlelAdmin(admin.ModelAdmin): link_to_category.short_description = _('category') def get_form(self, request, obj=None, **kwargs): + # zhq: 限制作者字段只能选择超级用户 form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) form.base_fields['author'].queryset = get_user_model( ).objects.filter(is_superuser=True) @@ -81,6 +83,7 @@ class ArticlelAdmin(admin.ModelAdmin): super(ArticlelAdmin, self).save_model(request, obj, form, change) def get_view_on_site_url(self, obj=None): + # zhq: 获取文章在前台的访问链接 if obj: url = obj.get_full_url() return url @@ -89,24 +92,24 @@ class ArticlelAdmin(admin.ModelAdmin): site = get_current_site().domain return site - +#zhq: 标签管理类 - 简化管理界面 class TagAdmin(admin.ModelAdmin): exclude = ('slug', 'last_mod_time', 'creation_time') - +#zhq: 分类管理类 - 显示父级分类信息 class CategoryAdmin(admin.ModelAdmin): list_display = ('name', 'parent_category', 'index') exclude = ('slug', 'last_mod_time', 'creation_time') - +#zhq: 友情链接管理类 class LinksAdmin(admin.ModelAdmin): exclude = ('last_mod_time', 'creation_time') - +#zhq: 侧边栏管理类 class SideBarAdmin(admin.ModelAdmin): list_display = ('name', 'content', 'is_enable', 'sequence') exclude = ('last_mod_time', 'creation_time') - +#zhq: 博客设置管理类 - 使用默认管理界面 class BlogSettingsAdmin(admin.ModelAdmin): pass diff --git a/src/blog/apps.py b/src/blog/apps.py index 7930587..fbe236c 100644 --- a/src/blog/apps.py +++ b/src/blog/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig - +#zhq: 博客应用配置类 - 继承自Django的AppConfig基类 class BlogConfig(AppConfig): - name = 'blog' + name = 'blog' #zhq: 应用名称,对应INSTALLED_APPS中的名称 diff --git a/src/blog/context_processors.py b/src/blog/context_processors.py index 73e3088..6b24a31 100644 --- a/src/blog/context_processors.py +++ b/src/blog/context_processors.py @@ -7,7 +7,7 @@ from .models import Category, Article logger = logging.getLogger(__name__) - +#zhq: SEO上下文处理器 - 为所有模板提供SEO相关变量和导航数据 def seo_processor(requests): key = 'seo_processor' value = cache.get(key) @@ -15,29 +15,32 @@ def seo_processor(requests): return value else: logger.info('set processor cache.') + # zhq: 获取博客全局设置 setting = get_blog_setting() + # zhq: 构建上下文数据字典 value = { - 'SITE_NAME': setting.site_name, - 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, - 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, - 'SITE_SEO_DESCRIPTION': setting.site_seo_description, - 'SITE_DESCRIPTION': setting.site_description, - 'SITE_KEYWORDS': setting.site_keywords, - 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/', - 'ARTICLE_SUB_LENGTH': setting.article_sub_length, - 'nav_category_list': Category.objects.all(), + 'SITE_NAME': setting.site_name,#zhq: 网站名称 + 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,#zhq: 是否显示Google广告 + 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,#zhq: Google广告代码 + 'SITE_SEO_DESCRIPTION': setting.site_seo_description,#zhq: SEO描述 + 'SITE_DESCRIPTION': setting.site_description,#zhq: 网站描述 + 'SITE_KEYWORDS': setting.site_keywords,#zhq: 网站关键词 + 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',#zhq: 网站基础URL + 'ARTICLE_SUB_LENGTH': setting.article_sub_length,#zhq: 文章摘要长度 + 'nav_category_list': Category.objects.all(),#zhq: 导航分类列表 'nav_pages': Article.objects.filter( - type='p', - status='p'), - 'OPEN_SITE_COMMENT': setting.open_site_comment, - 'BEIAN_CODE': setting.beian_code, - 'ANALYTICS_CODE': setting.analytics_code, - "BEIAN_CODE_GONGAN": setting.gongan_beiancode, - "SHOW_GONGAN_CODE": setting.show_gongan_code, - "CURRENT_YEAR": timezone.now().year, - "GLOBAL_HEADER": setting.global_header, - "GLOBAL_FOOTER": setting.global_footer, - "COMMENT_NEED_REVIEW": setting.comment_need_review, + type='p',#zhq: 页面类型 + status='p'),#zhq: 发布状态 + 'OPEN_SITE_COMMENT': setting.open_site_comment,#zhq: 是否开启全站评论 + 'BEIAN_CODE': setting.beian_code,#zhq: ICP备案号 + 'ANALYTICS_CODE': setting.analytics_code,#zhq: 网站统计代码 + "BEIAN_CODE_GONGAN": setting.gongan_beiancode,#zhq: 公安备案号 + "SHOW_GONGAN_CODE": setting.show_gongan_code,#zhq: 是否显示公安备案号 + "CURRENT_YEAR": timezone.now().year,#zhq: 当前年份 + "GLOBAL_HEADER": setting.global_header,#zhq: 全局头部HTML + "GLOBAL_FOOTER": setting.global_footer,#zhq: 全局尾部HTML + "COMMENT_NEED_REVIEW": setting.comment_need_review,#zhq: 评论是否需要审核 } + # zhq: 缓存上下文数据10小时,提高性能 cache.set(key, value, 60 * 60 * 10) return value diff --git a/src/blog/forms.py b/src/blog/forms.py index 715be76..7d703a1 100644 --- a/src/blog/forms.py +++ b/src/blog/forms.py @@ -5,15 +5,18 @@ from haystack.forms import SearchForm logger = logging.getLogger(__name__) - +#zhq: 博客搜索表单类 - 继承自Haystack的SearchForm class BlogSearchForm(SearchForm): querydata = forms.CharField(required=True) def search(self): + #zhq: 执行搜索操作,调用父类的search方法 datas = super(BlogSearchForm, self).search() + # zhq: 如果表单验证失败,返回空结果 if not self.is_valid(): return self.no_query_found() - + + # zhq: 记录搜索关键词到日志 if self.cleaned_data['querydata']: logger.info(self.cleaned_data['querydata']) return datas diff --git a/src/blog/middleware.py b/src/blog/middleware.py index 94dd70c..8ad1e54 100644 --- a/src/blog/middleware.py +++ b/src/blog/middleware.py @@ -9,6 +9,7 @@ from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager logger = logging.getLogger(__name__) +#zhq: 在线中间件类 - 用于监控页面加载时间和用户访问信息 class OnlineMiddleware(object): def __init__(self, get_response=None): self.get_response = get_response @@ -16,16 +17,20 @@ class OnlineMiddleware(object): def __call__(self, request): ''' page render time ''' + # zhq: 记录请求开始时间 start_time = time.time() response = self.get_response(request) + #zhq: 获取客户端IP地址和User-Agent信息 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: + #zhq: 计算页面渲染时间 cast_time = time.time() - start_time + # zhq: 如果启用了Elasticsearch,记录性能数据 if ELASTICSEARCH_ENABLED: - time_taken = round((cast_time) * 1000, 2) + time_taken = round((cast_time) * 1000, 2) #zhq: 转换为毫秒并保留2位小数 url = request.path from django.utils import timezone ElaspedTimeDocumentManager.create( @@ -34,8 +39,9 @@ class OnlineMiddleware(object): log_datetime=timezone.now(), useragent=user_agent, ip=ip) + # zhq: 在响应内容中替换加载时间占位符 response.content = response.content.replace( - b'', str.encode(str(cast_time)[:5])) + b'', str.encode(str(cast_time)[:5])) #zhq: 保留前5位字符 except Exception as e: logger.error("Error OnlineMiddleware: %s" % e) diff --git a/src/blog/urls.py b/src/blog/urls.py index adf2703..e2f5cc1 100644 --- a/src/blog/urls.py +++ b/src/blog/urls.py @@ -3,58 +3,71 @@ from django.views.decorators.cache import cache_page from . import views -app_name = "blog" +app_name = "blog" #zhq: 应用命名空间,用于URL反向解析 urlpatterns = [ +#zhq: 首页路由 - 显示文章列表 path( r'', views.IndexView.as_view(), name='index'), +#zhq: 首页分页路由 - 支持分页浏览 path( r'page//', views.IndexView.as_view(), name='index_page'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( r'article////.html', views.ArticleDetailView.as_view(), name='detailbyid'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( r'category/.html', views.CategoryDetailView.as_view(), name='category_detail'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( r'category//.html', views.CategoryDetailView.as_view(), name='category_detail_page'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( r'author/.html', views.AuthorDetailView.as_view(), name='author_detail'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( r'author//.html', views.AuthorDetailView.as_view(), name='author_detail_page'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( r'tag/.html', views.TagDetailView.as_view(), name='tag_detail'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( r'tag//.html', views.TagDetailView.as_view(), name='tag_detail_page'), +#zhq: 文章详情页路由 - 包含年月日信息用于SEO优化 path( 'archives.html', cache_page( 60 * 60)( views.ArchivesView.as_view()), name='archives'), +#zhq: 友情链接页路由 - 显示所有启用的友情链接 path( 'links.html', views.LinkListView.as_view(), name='links'), +#zhq: 友情链接页路由 - 显示所有启用的友情链接 path( r'upload', views.fileupload, name='upload'), +# zhq: 缓存清理路由 - 手动清理系统缓存 path( r'clean', views.clean_cache_view, diff --git a/src/blog/views.py b/src/blog/views.py index d5dc7ec..9df9f7b 100644 --- a/src/blog/views.py +++ b/src/blog/views.py @@ -23,7 +23,7 @@ from djangoblog.utils import cache, get_blog_setting, get_sha256 logger = logging.getLogger(__name__) - +#zhq: 文章列表基类视图 - 提供通用的列表功能和缓存机制 class ArticleListView(ListView): # template_name属性用于指定使用哪个模板进行渲染 template_name = 'blog/article_index.html' @@ -33,7 +33,7 @@ class ArticleListView(ListView): # 页面类型,分类目录或标签列表等 page_type = '' - paginate_by = settings.PAGINATE_BY + paginate_by = settings.PAGINATE_BY #zhq: 从配置获取分页大小 page_kwarg = 'page' link_type = LinkShowType.L @@ -42,6 +42,7 @@ class ArticleListView(ListView): @property def page_number(self): + # zhq: 获取当前页码,支持URL参数和GET参数 page_kwarg = self.page_kwarg page = self.kwargs.get( page_kwarg) or self.request.GET.get(page_kwarg) or 1 @@ -88,7 +89,7 @@ class ArticleListView(ListView): kwargs['linktype'] = self.link_type return super(ArticleListView, self).get_context_data(**kwargs) - + #zhq: 获取当前页码,支持URL参数和GET参数 class IndexView(ArticleListView): ''' 首页 @@ -97,6 +98,7 @@ class IndexView(ArticleListView): link_type = LinkShowType.I def get_queryset_data(self): + # zhq: 获取已发布的普通文章 article_list = Article.objects.filter(type='a', status='p') return article_list @@ -104,7 +106,7 @@ class IndexView(ArticleListView): cache_key = 'index_{page}'.format(page=self.page_number) return cache_key - +#zhq: 文章详情页视图 - 显示单篇文章内容和评论 class ArticleDetailView(DetailView): ''' 文章详情页面 @@ -117,6 +119,7 @@ class ArticleDetailView(DetailView): def get_context_data(self, **kwargs): comment_form = CommentForm() + # zhq: 获取文章评论并进行分页处理 article_comments = self.object.comment_list() parent_comments = article_comments.filter(parent_comment=None) blog_setting = get_blog_setting() @@ -135,6 +138,7 @@ class ArticleDetailView(DetailView): next_page = p_comments.next_page_number() if p_comments.has_next() else None prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None + # zhq: 获取文章评论并进行分页处理 if next_page: kwargs[ 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container' @@ -160,7 +164,7 @@ class ArticleDetailView(DetailView): return context - +#zhq: 分类详情页视图 - 显示指定分类下的文章 class CategoryDetailView(ArticleListView): ''' 分类目录列表 @@ -173,6 +177,7 @@ class CategoryDetailView(ArticleListView): categoryname = category.name self.categoryname = categoryname + # zhq: 获取分类及其所有子分类 categorynames = list( map(lambda c: c.name, category.get_sub_categorys())) article_list = Article.objects.filter( @@ -199,7 +204,7 @@ class CategoryDetailView(ArticleListView): kwargs['tag_name'] = categoryname return super(CategoryDetailView, self).get_context_data(**kwargs) - +#zhq: 作者详情页视图 - 显示指定作者的文章 class AuthorDetailView(ArticleListView): ''' 作者详情页 @@ -225,7 +230,7 @@ class AuthorDetailView(ArticleListView): kwargs['tag_name'] = author_name return super(AuthorDetailView, self).get_context_data(**kwargs) - +#zhq: 标签详情页视图 - 显示指定标签的文章 class TagDetailView(ArticleListView): ''' 标签列表页面 @@ -257,7 +262,7 @@ class TagDetailView(ArticleListView): kwargs['tag_name'] = tag_name return super(TagDetailView, self).get_context_data(**kwargs) - +#zhq: 文章归档页视图 - 显示所有文章的按时间归档 class ArchivesView(ArticleListView): ''' 文章归档页面 @@ -274,7 +279,7 @@ class ArchivesView(ArticleListView): cache_key = 'archives' return cache_key - +#zhq: 友情链接页视图 class LinkListView(ListView): model = Links template_name = 'blog/links_list.html' @@ -282,7 +287,7 @@ class LinkListView(ListView): def get_queryset(self): return Links.objects.filter(is_enable=True) - +#zhq: Elasticsearch搜索视图 class EsSearchView(SearchView): def get_context(self): paginator, page = self.build_page() @@ -299,7 +304,7 @@ class EsSearchView(SearchView): return context - +#zhq: 文件上传视图 - 支持图片和其他文件上传 @csrf_exempt def fileupload(request): """ @@ -308,6 +313,7 @@ def fileupload(request): :return: """ if request.method == 'POST': + # zhq: 验证上传签名,确保安全性 sign = request.GET.get('sign', None) if not sign: return HttpResponseForbidden() @@ -322,6 +328,7 @@ def fileupload(request): base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr) if not os.path.exists(base_dir): os.makedirs(base_dir) + # zhq: 使用UUID生成唯一文件名,避免冲突 savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}")) if not savepath.startswith(base_dir): return HttpResponse("only for post") @@ -329,6 +336,7 @@ def fileupload(request): for chunk in request.FILES[filename].chunks(): wfile.write(chunk) if isimage: + # zhq: 对图片进行压缩优化 from PIL import Image image = Image.open(savepath) image.save(savepath, quality=20, optimize=True) @@ -339,7 +347,7 @@ def fileupload(request): else: return HttpResponse("only for post") - +#zhq: 404错误页面视图 def page_not_found_view( request, exception, @@ -353,7 +361,7 @@ def page_not_found_view( 'statuscode': '404'}, status=404) - +#zhq: 500服务器错误页面视图 def server_error_view(request, template_name='blog/error_page.html'): return render(request, template_name, @@ -361,7 +369,7 @@ def server_error_view(request, template_name='blog/error_page.html'): 'statuscode': '500'}, status=500) - +#zhq: 403权限拒绝页面视图 def permission_denied_view( request, exception, @@ -373,7 +381,7 @@ def permission_denied_view( 'message': _('Sorry, you do not have permission to access this page?'), 'statuscode': '403'}, status=403) - +zhq: 清理缓存视图 def clean_cache_view(request): cache.clear() return HttpResponse('ok') -- 2.34.1 From 5e64c4155ea2ade2237f2ab19539b41dbd039cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=81=92=E7=A5=BA?= <1581408258@qq.com> Date: Sun, 9 Nov 2025 19:56:04 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/DjangoBlog-master.iml | 7 -- .../inspectionProfiles/profiles_settings.xml | 6 -- .idea/misc.xml | 7 -- .idea/vcs.xml | 6 -- .idea/workspace.xml | 102 ------------------ 5 files changed, 128 deletions(-) delete mode 100644 .idea/DjangoBlog-master.iml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.idea/DjangoBlog-master.iml b/.idea/DjangoBlog-master.iml deleted file mode 100644 index ec63674..0000000 --- a/.idea/DjangoBlog-master.iml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4c5155d..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 375e579..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - { - "customColor": "", - "associatedIndex": 5 -} - - - - { - "keyToString": { - "ModuleVcsDetector.initialDetectionPerformed": "true", - "Python.manage.executor": "Run", - "Python.models.executor": "Run", - "Python.settings.executor": "Run", - "RunOnceActivity.OpenDjangoStructureViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "git-widget-placeholder": "zhq__branch", - "vue.rearranger.settings.migration": "true" - } -} - - - - - - - - - - - - - - - 1762680639610 - - - - - - - - - \ No newline at end of file -- 2.34.1