diff --git a/src/DjangoBlog/djangoblog/__init__.py b/src/DjangoBlog/djangoblog/__init__.py index 1e205f4..4592301 100644 --- a/src/DjangoBlog/djangoblog/__init__.py +++ b/src/DjangoBlog/djangoblog/__init__.py @@ -1 +1,2 @@ +# szy:此文件用于将当前目录识别为一个Python包 default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/src/DjangoBlog/djangoblog/admin_site.py b/src/DjangoBlog/djangoblog/admin_site.py index f120405..7f1194e 100644 --- a/src/DjangoBlog/djangoblog/admin_site.py +++ b/src/DjangoBlog/djangoblog/admin_site.py @@ -1,3 +1,4 @@ +# szy:功能描述:自定义Django后台管理站点,并注册各个模型 from django.contrib.admin import AdminSite from django.contrib.admin.models import LogEntry from django.contrib.sites.admin import SiteAdmin @@ -16,14 +17,16 @@ from owntracks.models import * from servermanager.admin import * from servermanager.models import * - +# szy:自定义Django后台管理站点,并注册各个模型 class DjangoBlogAdminSite(AdminSite): site_header = 'djangoblog administration' site_title = 'djangoblog site admin' + # szy:初始化管理站点,设置站点名称 def __init__(self, name='admin'): super().__init__(name) + # szy:检查用户权限,是否为超级管理员 def has_permission(self, request): return request.user.is_superuser @@ -37,7 +40,7 @@ class DjangoBlogAdminSite(AdminSite): # ] # return urls + my_urls - +# szy:注册各个模型到后台管理 admin_site = DjangoBlogAdminSite(name='admin') admin_site.register(Article, ArticlelAdmin) diff --git a/src/DjangoBlog/djangoblog/apps.py b/src/DjangoBlog/djangoblog/apps.py index d29e318..469dbdd 100644 --- a/src/DjangoBlog/djangoblog/apps.py +++ b/src/DjangoBlog/djangoblog/apps.py @@ -1,9 +1,11 @@ from django.apps import AppConfig +# szy:Django应用配置类,用于加载插件 class DjangoblogAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'djangoblog' + # szy:应用准备时加载插件 def ready(self): super().ready() # Import and load plugins here diff --git a/src/DjangoBlog/djangoblog/blog_signals.py b/src/DjangoBlog/djangoblog/blog_signals.py index 393f441..fa381a9 100644 --- a/src/DjangoBlog/djangoblog/blog_signals.py +++ b/src/DjangoBlog/djangoblog/blog_signals.py @@ -1,3 +1,4 @@ +# szy:定义Django信号并处理相关业务逻辑 import _thread import logging @@ -22,7 +23,7 @@ oauth_user_login_signal = django.dispatch.Signal(['id']) send_email_signal = django.dispatch.Signal( ['emailto', 'title', 'content']) - +# szy:处理发送邮件的信号 @receiver(send_email_signal) def send_email_signal_handler(sender, **kwargs): emailto = kwargs['emailto'] @@ -50,7 +51,7 @@ def send_email_signal_handler(sender, **kwargs): log.send_result = False log.save() - +# szy:处理OAuth用户登录信号 @receiver(oauth_user_login_signal) def oauth_user_login_signal_handler(sender, **kwargs): id = kwargs['id'] diff --git a/src/DjangoBlog/djangoblog/elasticsearch_backend.py b/src/DjangoBlog/djangoblog/elasticsearch_backend.py index 4afe498..fbc92b4 100644 --- a/src/DjangoBlog/djangoblog/elasticsearch_backend.py +++ b/src/DjangoBlog/djangoblog/elasticsearch_backend.py @@ -10,7 +10,7 @@ from blog.models import Article logger = logging.getLogger(__name__) - +# szy:定义Elasticsearch后端,处理索引和查询 class ElasticSearchBackend(BaseSearchBackend): def __init__(self, connection_alias, **connection_options): super( @@ -21,38 +21,46 @@ class ElasticSearchBackend(BaseSearchBackend): self.manager = ArticleDocumentManager() self.include_spelling = True + # szy:获取要索引的模型数据 def _get_models(self, iterable): models = iterable if iterable and iterable[0] else Article.objects.all() docs = self.manager.convert_to_doc(models) return docs + # szy:创建索引 def _create(self, models): self.manager.create_index() docs = self._get_models(models) self.manager.rebuild(docs) + # szy:删除索引 def _delete(self, models): for m in models: m.delete() return True + # szy:重组索引 def _rebuild(self, models): models = models if models else Article.objects.all() docs = self.manager.convert_to_doc(models) self.manager.update_docs(docs) + # szy:更新索引 def update(self, index, iterable, commit=True): models = self._get_models(iterable) self.manager.update_docs(models) + # szy:移除索引 def remove(self, obj_or_string): models = self._get_models([obj_or_string]) self._delete(models) + # szy:清空索引 def clear(self, models=None, commit=True): self.remove(None) + # szy:获取搜索建议词 @staticmethod def get_suggestion(query: str) -> str: """获取推荐词, 如果没有找到添加原搜索词""" @@ -71,6 +79,7 @@ class ElasticSearchBackend(BaseSearchBackend): return ' '.join(keywords) + # szy:执行搜索并返回结果 @log_query def search(self, query_string, **kwargs): logger.info('search query_string:' + query_string) @@ -84,10 +93,13 @@ class ElasticSearchBackend(BaseSearchBackend): else: suggestion = query_string + + # szy:构建查询条件,匹配标题或正文,设置最小匹配度 q = Q('bool', should=[Q('match', body=suggestion), Q('match', title=suggestion)], minimum_should_match="70%") + # szy:执行搜索查询,过滤已发布的状态和文章类型 search = ArticleDocument.search() \ .query('bool', filter=[q]) \ .filter('term', status='p') \ @@ -97,6 +109,8 @@ class ElasticSearchBackend(BaseSearchBackend): results = search.execute() hits = results['hits'].total raw_results = [] + + # szy:处理搜索结果,构建SearchResult对象 for raw_result in results['hits']['hits']: app_label = 'blog' model_name = 'Article' @@ -112,6 +126,8 @@ class ElasticSearchBackend(BaseSearchBackend): **additional_fields) raw_results.append(result) facets = {} + + # szy:设置拼写建议,如果查询词与建议词不同则返回建议词 spelling_suggestion = None if query_string == suggestion else suggestion return { @@ -121,7 +137,7 @@ class ElasticSearchBackend(BaseSearchBackend): 'spelling_suggestion': spelling_suggestion, } - +# szy:定义Elasticsearch查询类 class ElasticSearchQuery(BaseSearchQuery): def _convert_datetime(self, date): if hasattr(date, 'hour'): @@ -129,6 +145,7 @@ class ElasticSearchQuery(BaseSearchQuery): else: return force_str(date.strftime('%Y%m%d000000')) + # szy:清理查询片段,处理保留字和特殊字符 def clean(self, query_fragment): """ Provides a mechanism for sanitizing user input before presenting the @@ -154,30 +171,35 @@ class ElasticSearchQuery(BaseSearchQuery): return ' '.join(cleaned_words) + + # szy:构建查询片段 def build_query_fragment(self, field, filter_type, value): return value.query_string + # szy:获取查询结果数量 def get_count(self): results = self.get_results() return len(results) if results else 0 + # szy:获取拼写建议 def get_spelling_suggestion(self, preferred_query=None): return self._spelling_suggestion + # szy:构建查询参数 def build_params(self, spelling_query=None): kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) return kwargs - +# szy:定义Elasticsearch模型搜索表单 class ElasticSearchModelSearchForm(ModelSearchForm): - + # szy:重写搜索方法,支持建议搜索功能 def search(self): # 是否建议搜索 self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" sqs = super().search() return sqs - +# szy:定义Elasticsearch搜索引擎 class ElasticSearchEngine(BaseEngine): backend = ElasticSearchBackend query = ElasticSearchQuery diff --git a/src/DjangoBlog/djangoblog/feeds.py b/src/DjangoBlog/djangoblog/feeds.py index 8c4e851..2675951 100644 --- a/src/DjangoBlog/djangoblog/feeds.py +++ b/src/DjangoBlog/djangoblog/feeds.py @@ -6,35 +6,44 @@ from django.utils.feedgenerator import Rss201rev2Feed from blog.models import Article from djangoblog.utils import CommonMarkdown - +# szy:定义Django博客的RSS订阅类 class DjangoBlogFeed(Feed): feed_type = Rss201rev2Feed + # szy:设置Feed的描述、标题和链接 description = '大巧无工,重剑无锋.' title = "且听风吟 大巧无工,重剑无锋. " link = "/feed/" + # szy:获取作者名称(博客主昵称) def author_name(self): return get_user_model().objects.first().nickname + # szy:获取作者链接地址 def author_link(self): return get_user_model().objects.first().get_absolute_url() + # szy:获取要包含在Feed中的文章项 def items(self): return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] + # szy:获取文章项的标题 def item_title(self, item): return item.title + # szy:获取文章项的描述(使用Markdown渲染正文) def item_description(self, item): return CommonMarkdown.get_markdown(item.body) + # szy:设置Feed的版权信息 def feed_copyright(self): now = timezone.now() return "Copyright© {year} 且听风吟".format(year=now.year) + # szy:获取文章项的链接地址 def item_link(self, item): return item.get_absolute_url() + # szy:获取文章项的全局唯一标识符 def item_guid(self, item): return diff --git a/src/DjangoBlog/djangoblog/logentryadmin.py b/src/DjangoBlog/djangoblog/logentryadmin.py index 2f6a535..3b14550 100644 --- a/src/DjangoBlog/djangoblog/logentryadmin.py +++ b/src/DjangoBlog/djangoblog/logentryadmin.py @@ -7,21 +7,26 @@ from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ - +# szy:定义日志条目管理类 class LogEntryAdmin(admin.ModelAdmin): + # szy:设置列表页过滤器字段 list_filter = [ 'content_type' ] + # szy:设置搜索字段 search_fields = [ 'object_repr', 'change_message' ] + # szy:设置列表页可点击链接的字段 list_display_links = [ 'action_time', 'get_change_message', ] + + # szy:设置列表页显示的字段 list_display = [ 'action_time', 'user_link', @@ -30,18 +35,22 @@ class LogEntryAdmin(admin.ModelAdmin): 'get_change_message', ] + # szy:禁用添加权限 def has_add_permission(self, request): return False + # szy:设置修改权限,仅超级用户或有特定权限的用户可以查看 def has_change_permission(self, request, obj=None): return ( request.user.is_superuser or request.user.has_perm('admin.change_logentry') ) and request.method != 'POST' + # szy:禁用删除权限 def has_delete_permission(self, request, obj=None): return False + # szy:创建对象链接,如果是删除操作则不生成链接 def object_link(self, obj): object_link = escape(obj.object_repr) content_type = obj.content_type @@ -62,6 +71,7 @@ class LogEntryAdmin(admin.ModelAdmin): object_link.admin_order_field = 'object_repr' object_link.short_description = _('object') + # szy:创建用户链接,链接到用户编辑页面 def user_link(self, obj): content_type = ContentType.objects.get_for_model(type(obj.user)) user_link = escape(force_str(obj.user)) @@ -80,10 +90,12 @@ class LogEntryAdmin(admin.ModelAdmin): user_link.admin_order_field = 'user' user_link.short_description = _('user') + # szy:优化查询集,预取关联的内容类型数据 def get_queryset(self, request): queryset = super(LogEntryAdmin, self).get_queryset(request) return queryset.prefetch_related('content_type') + # szy:重写获取actions的方法,移除删除选中项的action def get_actions(self, request): actions = super(LogEntryAdmin, self).get_actions(request) if 'delete_selected' in actions: diff --git a/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py b/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py index 2b4be5c..63cbbe6 100644 --- a/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py +++ b/src/DjangoBlog/djangoblog/plugin_manage/base_plugin.py @@ -2,19 +2,21 @@ import logging logger = logging.getLogger(__name__) - +# szy:基础插件类,所有插件都应该继承此类 class BasePlugin: - # 插件元数据 + # szy:插件元数据定义 PLUGIN_NAME = None PLUGIN_DESCRIPTION = None PLUGIN_VERSION = None + # szy:插件初始化方法 def __init__(self): if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") self.init_plugin() self.register_hooks() + # szy:插件初始化逻辑,子类可重写实现特定初始化 def init_plugin(self): """ 插件初始化逻辑 @@ -22,6 +24,7 @@ class BasePlugin: """ logger.info(f'{self.PLUGIN_NAME} initialized.') + # szy:注册插件钩子,子类可重写实现特定钩子注册 def register_hooks(self): """ 注册插件钩子 @@ -29,6 +32,7 @@ class BasePlugin: """ pass + # szy:获取插件信息 def get_plugin_info(self): """ 获取插件信息 diff --git a/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py index 6685b7c..ccc9813 100644 --- a/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py +++ b/src/DjangoBlog/djangoblog/plugin_manage/hook_constants.py @@ -1,7 +1,10 @@ +# szy:文章详情加载事件钩子名称 ARTICLE_DETAIL_LOAD = 'article_detail_load' +# szy:文章创建事件钩子名称 ARTICLE_CREATE = 'article_create' +# szy:文章更新事件钩子名称 ARTICLE_UPDATE = 'article_update' +# szy:文章删除事件钩子名称 ARTICLE_DELETE = 'article_delete' - -ARTICLE_CONTENT_HOOK_NAME = "the_content" - +# szy:文章内容处理钩子名称 +ARTICLE_CONTENT_HOOK_NAME = "the_content" \ No newline at end of file diff --git a/src/DjangoBlog/djangoblog/plugin_manage/hooks.py b/src/DjangoBlog/djangoblog/plugin_manage/hooks.py index d712540..49ab083 100644 --- a/src/DjangoBlog/djangoblog/plugin_manage/hooks.py +++ b/src/DjangoBlog/djangoblog/plugin_manage/hooks.py @@ -1,7 +1,7 @@ import logging logger = logging.getLogger(__name__) - +# szy:全局钩子字典,存储所有注册的钩子 _hooks = {} @@ -34,11 +34,15 @@ def apply_filters(hook_name: str, value, *args, **kwargs): 执行一个 Filter Hook。 它会把 value 依次传递给所有注册的回调函数进行处理。 """ + # szy:检查钩子是否存在,存在则依次应用所有过滤器 if hook_name in _hooks: logger.debug(f"Applying filter hook '{hook_name}'") for callback in _hooks[hook_name]: try: + # szy:将当前值传递给回调函数进行处理,并更新值 value = callback(value, *args, **kwargs) except Exception as e: + # szy:捕获并记录过滤器执行过程中的异常 logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) + # szy:返回处理后的最终值 return value diff --git a/src/DjangoBlog/djangoblog/plugin_manage/loader.py b/src/DjangoBlog/djangoblog/plugin_manage/loader.py index 12e824b..a732c75 100644 --- a/src/DjangoBlog/djangoblog/plugin_manage/loader.py +++ b/src/DjangoBlog/djangoblog/plugin_manage/loader.py @@ -9,11 +9,16 @@ def load_plugins(): Dynamically loads and initializes plugins from the 'plugins' directory. This function is intended to be called when the Django app registry is ready. """ + # szy:遍历settings中配置的激活插件列表 for plugin_name in settings.ACTIVE_PLUGINS: + # szy:构建插件路径 plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) + # szy:检查插件目录是否存在且包含plugin.py文件 if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): try: + # szy:动态导入插件模块 __import__(f'plugins.{plugin_name}.plugin') logger.info(f"Successfully loaded plugin: {plugin_name}") except ImportError as e: + # szy:记录插件导入失败的错误信息 logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) \ No newline at end of file diff --git a/src/DjangoBlog/djangoblog/settings.py b/src/DjangoBlog/djangoblog/settings.py index d076bb6..50f46b8 100644 --- a/src/DjangoBlog/djangoblog/settings.py +++ b/src/DjangoBlog/djangoblog/settings.py @@ -15,12 +15,12 @@ from pathlib import Path from django.utils.translation import gettext_lazy as _ - +# szy:将环境变量转换为布尔值 def env_to_bool(env, default): str_val = os.environ.get(env) return default if str_val is None else str_val == 'True' - +# szy:构建项目基础路径 # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -41,7 +41,7 @@ ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] CSRF_TRUSTED_ORIGINS = ['http://example.com'] # Application definition - +# szy:已安装的应用列表 INSTALLED_APPS = [ # 'django.contrib.admin', 'django.contrib.admin.apps.SimpleAdminConfig', @@ -64,6 +64,7 @@ INSTALLED_APPS = [ 'djangoblog' ] +# szy:中间件配置 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', @@ -83,6 +84,7 @@ MIDDLEWARE = [ ROOT_URLCONF = 'djangoblog.urls' +# szy:模板配置 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -106,6 +108,7 @@ WSGI_APPLICATION = 'djangoblog.wsgi.application' # https://docs.djangoproject.com/en/1.10/ref/settings/#databases +# szy:MySQL数据库配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', @@ -119,6 +122,7 @@ DATABASES = { 'charset': 'utf8mb4'}, }} +# szy:密码验证配置 # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators @@ -137,23 +141,26 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +# szy:国际化配置 - 支持的语言 LANGUAGES = ( ('en', _('English')), ('zh-hans', _('Simplified Chinese')), ('zh-hant', _('Traditional Chinese')), ) +# szy:本地化文件路径 LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale'), ) - +# szy:默认语言代码 LANGUAGE_CODE = 'zh-hans' +# szy:时区设置 TIME_ZONE = 'Asia/Shanghai' - +# szy:启用国际化 USE_I18N = True - +# szy:启用本地化 USE_L10N = True - +# szy:使用时区支持 USE_TZ = False # Static files (CSS, JavaScript, Images) @@ -180,6 +187,7 @@ STATICFILES = os.path.join(BASE_DIR, 'static') AUTH_USER_MODEL = 'accounts.BlogUser' LOGIN_URL = '/login/' +# szy:时间和日期格式 TIME_FORMAT = '%Y-%m-%d %H:%M:%S' DATE_TIME_FORMAT = '%Y-%m-%d' @@ -188,11 +196,13 @@ BOOTSTRAP_COLOR_TYPES = [ 'default', 'primary', 'success', 'info', 'warning', 'danger' ] +# szy:分页设置 # paginate PAGINATE_BY = 10 # http cache timeout CACHE_CONTROL_MAX_AGE = 2592000 # cache setting +# szy:缓存配置 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -208,11 +218,12 @@ if os.environ.get("DJANGO_REDIS_URL"): 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}', } } - +# szy:站点ID SITE_ID = 1 BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \ or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' +# szy:邮件配置 # Email: EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) @@ -229,6 +240,7 @@ ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')] WXADMIN = os.environ.get( 'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7' +# szy:日志配置 LOG_PATH = os.path.join(BASE_DIR, 'logs') if not os.path.exists(LOG_PATH): os.makedirs(LOG_PATH, exist_ok=True) @@ -300,6 +312,7 @@ STATICFILES_FINDERS = ( # other 'compressor.finders.CompressorFinder', ) +# szy:启用压缩 COMPRESS_ENABLED = True # COMPRESS_OFFLINE = True @@ -314,6 +327,7 @@ COMPRESS_JS_FILTERS = [ 'compressor.filters.jsmin.JSMinFilter' ] +# szy:媒体文件配置 MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') MEDIA_URL = '/media/' X_FRAME_OPTIONS = 'SAMEORIGIN' @@ -332,6 +346,7 @@ if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): }, } +# szy:插件系统配置 # Plugin System PLUGINS_DIR = BASE_DIR / 'plugins' ACTIVE_PLUGINS = [ diff --git a/src/DjangoBlog/djangoblog/sitemap.py b/src/DjangoBlog/djangoblog/sitemap.py index 8b7d446..bb2ed3b 100644 --- a/src/DjangoBlog/djangoblog/sitemap.py +++ b/src/DjangoBlog/djangoblog/sitemap.py @@ -3,57 +3,72 @@ from django.urls import reverse from blog.models import Article, Category, Tag - +# szy:静态视图站点地图类 class StaticViewSitemap(Sitemap): priority = 0.5 changefreq = 'daily' + # szy:定义包含在站点地图中的项 def items(self): return ['blog:index', ] + # szy:获取项的URL位置 def location(self, item): return reverse(item) - +# szy:文章站点地图类 class ArticleSiteMap(Sitemap): + # szy:设置文章更新频率和优先级 changefreq = "monthly" priority = "0.6" + # szy:获取所有已发布的文章 def items(self): return Article.objects.filter(status='p') + # szy:获取文章的最后修改时间 def lastmod(self, obj): return obj.last_modify_time - +# szy:分类站点地图类 class CategorySiteMap(Sitemap): + # szy:设置分类更新频率和优先级 changefreq = "Weekly" priority = "0.6" + # szy:获取所有分类 def items(self): return Category.objects.all() + # szy:获取分类的最后修改时间 def lastmod(self, obj): return obj.last_modify_time - +# szy:标签站点地图类 class TagSiteMap(Sitemap): + # szy:设置标签更新频率和优先级 changefreq = "Weekly" priority = "0.3" + # szy:获取所有标签 def items(self): return Tag.objects.all() + # szy:获取标签的最后修改时间 def lastmod(self, obj): return obj.last_modify_time +# szy:用户站点地图类 class UserSiteMap(Sitemap): + # szy:设置用户更新频率和优先级 changefreq = "Weekly" priority = "0.3" + # szy:获取所有有文章的作者(去重) def items(self): return list(set(map(lambda x: x.author, Article.objects.all()))) + # szy:获取用户的注册时间 def lastmod(self, obj): return obj.date_joined diff --git a/src/DjangoBlog/djangoblog/spider_notify.py b/src/DjangoBlog/djangoblog/spider_notify.py index 7b909e9..c2a8864 100644 --- a/src/DjangoBlog/djangoblog/spider_notify.py +++ b/src/DjangoBlog/djangoblog/spider_notify.py @@ -6,16 +6,21 @@ from django.conf import settings logger = logging.getLogger(__name__) +# szy:蜘蛛爬虫通知类,用于向搜索引擎推送URL class SpiderNotify(): @staticmethod def baidu_notify(urls): try: + # szy:将URL列表转换为换行分隔的字符串 data = '\n'.join(urls) result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) + # szy:记录推送结果日志 logger.info(result.text) except Exception as e: + # szy:记录推送异常日志 logger.error(e) + # szy:推送单个URL的便捷方法 @staticmethod def notify(url): SpiderNotify.baidu_notify(url) diff --git a/src/DjangoBlog/djangoblog/tests.py b/src/DjangoBlog/djangoblog/tests.py index 01237d9..9bb0876 100644 --- a/src/DjangoBlog/djangoblog/tests.py +++ b/src/DjangoBlog/djangoblog/tests.py @@ -3,13 +3,18 @@ from django.test import TestCase from djangoblog.utils import * +# szy:DjangoBlog测试类,用于测试工具函数 class DjangoBlogTest(TestCase): + # szy:测试初始化设置 def setUp(self): pass + # szy:测试工具函数功能 def test_utils(self): + # szy:测试SHA256加密函数 md5 = get_sha256('test') self.assertIsNotNone(md5) + # szy:测试Markdown解析函数 c = CommonMarkdown.get_markdown(''' # Title1 @@ -24,6 +29,8 @@ class DjangoBlogTest(TestCase): ''') self.assertIsNotNone(c) + + # szy:测试字典转URL参数字符串函数 d = { 'd': 'key1', 'd2': 'key2' diff --git a/src/DjangoBlog/djangoblog/urls.py b/src/DjangoBlog/djangoblog/urls.py index 4aae58a..d414ff4 100644 --- a/src/DjangoBlog/djangoblog/urls.py +++ b/src/DjangoBlog/djangoblog/urls.py @@ -27,6 +27,7 @@ from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm from djangoblog.feeds import DjangoBlogFeed from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap +# szy:定义站点地图配置 sitemaps = { 'blog': ArticleSiteMap, @@ -36,29 +37,45 @@ sitemaps = { 'static': StaticViewSitemap } +# szy:定义错误处理视图 handler404 = 'blog.views.page_not_found_view' handler500 = 'blog.views.server_error_view' handle403 = 'blog.views.permission_denied_view' +# szy:基础URL模式配置 urlpatterns = [ path('i18n/', include('django.conf.urls.i18n')), ] +# szy:国际化URL模式配置 urlpatterns += i18n_patterns( + # szy:管理员后台URL re_path(r'^admin/', admin_site.urls), + # szy:博客应用URL re_path(r'', include('blog.urls', namespace='blog')), + # szy:Markdown编辑器URL re_path(r'mdeditor/', include('mdeditor.urls')), + # szy:评论系统URL re_path(r'', include('comments.urls', namespace='comment')), + # szy:账户管理URL re_path(r'', include('accounts.urls', namespace='account')), + # szy:OAuth认证URL re_path(r'', include('oauth.urls', namespace='oauth')), + # szy:站点地图URL re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'), + # szy:Feed订阅URL re_path(r'^feed/$', DjangoBlogFeed()), + # szy:RSS订阅URL re_path(r'^rss/$', DjangoBlogFeed()), + # szy:搜索功能URL,使用ElasticSearch视图和表单 re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), name='search'), + # szy:服务器管理URL re_path(r'', include('servermanager.urls', namespace='servermanager')), + # szy:位置追踪URL re_path(r'', include('owntracks.urls', namespace='owntracks')) , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +# szy:调试模式下添加媒体文件服务 if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/src/DjangoBlog/djangoblog/utils.py b/src/DjangoBlog/djangoblog/utils.py index 57f63dc..f2c9a3a 100644 --- a/src/DjangoBlog/djangoblog/utils.py +++ b/src/DjangoBlog/djangoblog/utils.py @@ -19,18 +19,18 @@ from django.templatetags.static import static logger = logging.getLogger(__name__) - +# szy:获取最大文章ID和评论ID def get_max_articleid_commentid(): from blog.models import Article from comments.models import Comment return (Article.objects.latest().pk, Comment.objects.latest().pk) - +# szy:计算字符串的SHA256哈希值 def get_sha256(str): m = sha256(str.encode('utf-8')) return m.hexdigest() - +# szy:缓存装饰器,用于函数结果缓存 def cache_decorator(expiration=3 * 60): def wrapper(func): def news(*args, **kwargs): @@ -67,6 +67,7 @@ def cache_decorator(expiration=3 * 60): return wrapper +# szy:刷新视图缓存 def expire_view_cache(path, servername, serverport, key_prefix=None): ''' 刷新视图缓存 @@ -91,14 +92,15 @@ def expire_view_cache(path, servername, serverport, key_prefix=None): return True return False - +# szy:获取当前站点信息(带缓存) @cache_decorator() def get_current_site(): site = Site.objects.get_current() return site - +# szy:通用Markdown处理类 class CommonMarkdown: + # szy:转换Markdown文本为HTML @staticmethod def _convert_markdown(value): md = markdown.Markdown( @@ -113,17 +115,21 @@ class CommonMarkdown: toc = md.toc return body, toc + + # szy:获取带目录的Markdown内容 @staticmethod def get_markdown_with_toc(value): body, toc = CommonMarkdown._convert_markdown(value) return body, toc + # szy:获取Markdown内容 @staticmethod def get_markdown(value): body, toc = CommonMarkdown._convert_markdown(value) return body +# szy:发送邮件函数 def send_email(emailto, title, content): from djangoblog.blog_signals import send_email_signal send_email_signal.send( @@ -132,12 +138,13 @@ def send_email(emailto, title, content): title=title, content=content) - +# szy:生成6位随机数字验证码 def generate_code() -> str: """生成随机数验证码""" return ''.join(random.sample(string.digits, 6)) +# szy:将字典转换为URL参数字符串 def parse_dict_to_url(dict): from urllib.parse import quote url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) @@ -145,6 +152,7 @@ def parse_dict_to_url(dict): return url +# szy:获取博客设置 def get_blog_setting(): value = cache.get('get_blog_setting') if value: @@ -173,6 +181,7 @@ def get_blog_setting(): return value +# szy:保存用户头像到本地 def save_user_avatar(url): ''' 保存用户头像 @@ -201,6 +210,7 @@ def save_user_avatar(url): return static('blog/img/avatar.png') +# szy:删除侧边栏缓存 def delete_sidebar_cache(): from blog.models import LinkShowType keys = ["sidebar" + x for x in LinkShowType.values] @@ -209,12 +219,14 @@ def delete_sidebar_cache(): cache.delete(k) +# szy:删除视图缓存 def delete_view_cache(prefix, keys): from django.core.cache.utils import make_template_fragment_key key = make_template_fragment_key(prefix, keys) cache.delete(key) +# szy:获取资源URL def get_resource_url(): if settings.STATIC_URL: return settings.STATIC_URL @@ -227,6 +239,6 @@ ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'l 'h2', 'p'] ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} - +# szy:清洗HTML内容,移除不安全的标签和属性 def sanitize_html(html): return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) diff --git a/src/DjangoBlog/djangoblog/whoosh_cn_backend.py b/src/DjangoBlog/djangoblog/whoosh_cn_backend.py index 04e3f7f..88f1770 100644 --- a/src/DjangoBlog/djangoblog/whoosh_cn_backend.py +++ b/src/DjangoBlog/djangoblog/whoosh_cn_backend.py @@ -40,6 +40,7 @@ except ImportError: raise MissingDependency( "The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.") +# szy:处理最低版本要求 # Handle minimum requirement. if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0): raise MissingDependency( @@ -47,12 +48,13 @@ if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0): # Bubble up the correct error. +# szy:日期时间正则表达式,用于解析日期字符串 DATETIME_REGEX = re.compile( '^(?P\d{4})-(?P\d{2})-(?P\d{2})T(?P\d{2}):(?P\d{2}):(?P\d{2})(\.\d{3,6}Z?)?$') LOCALS = threading.local() LOCALS.RAM_STORE = None - +# szy:自定义Whoosh HTML格式化器,用于保持跨后端一致的高亮结果 class WhooshHtmlFormatter(HtmlFormatter): """ This is a HtmlFormatter simpler than the whoosh.HtmlFormatter. @@ -62,6 +64,7 @@ class WhooshHtmlFormatter(HtmlFormatter): template = '<%(tag)s>%(t)s' +# szy:Whoosh搜索后端主类 class WhooshSearchBackend(BaseSearchBackend): # Word reserved by Whoosh for special use. RESERVED_WORDS = ( @@ -79,6 +82,7 @@ class WhooshSearchBackend(BaseSearchBackend): '[', ']', '^', '"', '~', '*', '?', ':', '.', ) + # szy:初始化Whoosh后端 def __init__(self, connection_alias, **connection_options): super( WhooshSearchBackend, @@ -103,6 +107,7 @@ class WhooshSearchBackend(BaseSearchBackend): self.log = logging.getLogger('haystack') + # szy:设置Whoosh索引和配置 def setup(self): """ Defers loading until needed. @@ -110,6 +115,7 @@ class WhooshSearchBackend(BaseSearchBackend): from haystack import connections new_index = False + # szy:确保索引目录存在,如果不存在则创建 # Make sure the index is there. if self.use_file_storage and not os.path.exists(self.path): os.makedirs(self.path) @@ -120,6 +126,7 @@ class WhooshSearchBackend(BaseSearchBackend): "The path to your Whoosh index '%s' is not writable for the current user/group." % self.path) + # szy:根据配置选择文件存储或内存存储 if self.use_file_storage: self.storage = FileStorage(self.path) else: @@ -134,6 +141,7 @@ class WhooshSearchBackend(BaseSearchBackend): connections[self.connection_alias].get_unified_index().all_searchfields()) self.parser = QueryParser(self.content_field_name, schema=self.schema) + # szy:创建或打开索引 if new_index is True: self.index = self.storage.create_index(self.schema) else: @@ -144,6 +152,7 @@ class WhooshSearchBackend(BaseSearchBackend): self.setup_complete = True + # szy:构建Whoosh schema,定义字段类型 def build_schema(self, fields): schema_fields = { ID: WHOOSH_ID(stored=True, unique=True), @@ -199,6 +208,7 @@ class WhooshSearchBackend(BaseSearchBackend): return (content_field_name, Schema(**schema_fields)) + # szy:更新索引 def update(self, index, iterable, commit=True): if not self.setup_complete: self.setup() @@ -206,6 +216,7 @@ class WhooshSearchBackend(BaseSearchBackend): self.index = self.index.refresh() writer = AsyncWriter(self.index) + # szy:遍历对象并更新索引 for obj in iterable: try: doc = index.full_prepare(obj) @@ -244,6 +255,7 @@ class WhooshSearchBackend(BaseSearchBackend): # otherwise. writer.commit() + # szy:从索引中移除对象 def remove(self, obj_or_string, commit=True): if not self.setup_complete: self.setup() @@ -266,6 +278,7 @@ class WhooshSearchBackend(BaseSearchBackend): e, exc_info=True) + # szy:清空索引 def clear(self, models=None, commit=True): if not self.setup_complete: self.setup() @@ -303,6 +316,8 @@ class WhooshSearchBackend(BaseSearchBackend): self.log.error( "Failed to clear Whoosh index: %s", e, exc_info=True) + + # szy:删除整个索引 def delete_index(self): # Per the Whoosh mailing list, if wiping out everything from the index, # it's much more efficient to simply delete the index files. @@ -311,9 +326,11 @@ class WhooshSearchBackend(BaseSearchBackend): elif not self.use_file_storage: self.storage.clean() + # szy:重新创建所有内容 # Recreate everything. self.setup() + # szy:优化索引 def optimize(self): if not self.setup_complete: self.setup() @@ -321,12 +338,14 @@ class WhooshSearchBackend(BaseSearchBackend): self.index = self.index.refresh() self.index.optimize() + # szy:计算分页信息 def calculate_page(self, start_offset=0, end_offset=None): # Prevent against Whoosh throwing an error. Requires an end_offset # greater than 0. if end_offset is not None and end_offset <= 0: end_offset = 1 + # szy:确定页码 # Determine the page. page_num = 0 @@ -345,6 +364,8 @@ class WhooshSearchBackend(BaseSearchBackend): page_num += 1 return page_num, page_length + + # szy:执行搜索查询 @log_query def search( self, @@ -388,6 +409,8 @@ class WhooshSearchBackend(BaseSearchBackend): reverse = False + + # szy:处理排序 if sort_by is not None: # Determine if we need to reverse the results and if Whoosh can # handle what it's being asked to sort by. Reversing is an @@ -560,6 +583,8 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': spelling_suggestion, } + + # szy:实现"更多类似此结果"功能 def more_like_this( self, model_instance, @@ -675,6 +700,8 @@ class WhooshSearchBackend(BaseSearchBackend): return results + + # szy:处理原始搜索结果,转换为Haystack格式 def _process_results( self, raw_page, @@ -767,6 +794,8 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': spelling_suggestion, } + + # szy:创建拼写建议 def create_spelling_suggestion(self, query_string): spelling_suggestion = None reader = self.index.reader() @@ -819,6 +848,8 @@ class WhooshSearchBackend(BaseSearchBackend): value = force_str(value) return value + + # szy:将Whoosh值转换为Python原生值 def _to_python(self, value): """ Converts values from Whoosh to native Python values. @@ -870,6 +901,7 @@ class WhooshSearchBackend(BaseSearchBackend): return value +# szy:Whoosh搜索查询类 class WhooshSearchQuery(BaseSearchQuery): def _convert_datetime(self, date): if hasattr(date, 'hour'): @@ -877,6 +909,8 @@ class WhooshSearchQuery(BaseSearchQuery): else: return force_str(date.strftime('%Y%m%d000000')) + + # szy:清理查询片段,处理保留字和特殊字符 def clean(self, query_fragment): """ Provides a mechanism for sanitizing user input before presenting the @@ -902,6 +936,8 @@ class WhooshSearchQuery(BaseSearchQuery): return ' '.join(cleaned_words) + + # szy:构建查询片段 def build_query_fragment(self, field, filter_type, value): from haystack import connections query_frag = '' @@ -1039,6 +1075,7 @@ class WhooshSearchQuery(BaseSearchQuery): # value = self.backend._from_python(value) +# szy:Whoosh搜索引擎类 class WhooshEngine(BaseEngine): backend = WhooshSearchBackend query = WhooshSearchQuery diff --git a/src/DjangoBlog/djangoblog/wsgi.py b/src/DjangoBlog/djangoblog/wsgi.py index 2295efd..6795850 100644 --- a/src/DjangoBlog/djangoblog/wsgi.py +++ b/src/DjangoBlog/djangoblog/wsgi.py @@ -11,6 +11,8 @@ import os from django.core.wsgi import get_wsgi_application +# szy:设置Django的默认设置模块环境变量 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") +# szy:获取WSGI应用实例,用于服务器部署 application = get_wsgi_application()