diff --git a/src/DjangoBlog/blog/models.py b/src/DjangoBlog/blog/models.py index 083788b..70b5020 100644 --- a/src/DjangoBlog/blog/models.py +++ b/src/DjangoBlog/blog/models.py @@ -18,6 +18,10 @@ logger = logging.getLogger(__name__) class LinkShowType(models.TextChoices): + """ + 链接显示类型选择 + 定义友情链接在网站中的显示位置 + """ I = ('i', _('index')) L = ('l', _('list')) P = ('p', _('post')) @@ -26,11 +30,19 @@ class LinkShowType(models.TextChoices): 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): + """ + 重写保存方法 + 处理文章浏览量更新和自动生成slug + """ is_update_views = isinstance( self, Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] @@ -45,6 +57,7 @@ class BaseModel(models.Model): 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()) @@ -55,11 +68,15 @@ class BaseModel(models.Model): @abstractmethod def get_absolute_url(self): + """抽象方法:获取对象的绝对URL""" pass class Article(BaseModel): - """文章""" + """ + 文章模型 + 博客系统的核心模型,存储所有文章内容 + """ STATUS_CHOICES = ( ('d', _('Draft')), ('p', _('Published')), @@ -106,9 +123,11 @@ class Article(BaseModel): tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) def body_to_string(self): + """将文章内容转换为字符串""" return self.body def __str__(self): + """对象的字符串表示""" return self.title class Meta: @@ -118,6 +137,7 @@ class Article(BaseModel): get_latest_by = 'id' def get_absolute_url(self): + """获取文章的绝对URL""" return reverse('blog:detailbyid', kwargs={ 'article_id': self.id, 'year': self.creation_time.year, @@ -127,19 +147,23 @@ class Article(BaseModel): @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): + """保存文章""" super().save(*args, **kwargs) def viewed(self): + """增加文章浏览量""" 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: @@ -152,24 +176,25 @@ class Article(BaseModel): 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): - # 下一篇 + """获取下一篇文章""" return Article.objects.filter( id__gt=self.id, status='p').order_by('id').first() @cache_decorator(expiration=60 * 100) def prev_article(self): - # 前一篇 + """获取上一篇文章""" return Article.objects.filter(id__lt=self.id, status='p').first() def get_first_image_url(self): """ - Get the first image url from article.body. - :return: + 从文章内容中提取第一张图片的URL + 用于文章列表的缩略图显示 """ match = re.search(r'!\[.*?\]\((.+?)\)', self.body) if match: @@ -178,7 +203,10 @@ class Article(BaseModel): class Category(BaseModel): - """文章分类""" + """ + 文章分类模型 + 用于组织和管理博客文章的类别,支持多级分类结构 + """ name = models.CharField(_('category name'), max_length=30, unique=True) parent_category = models.ForeignKey( 'self', @@ -190,23 +218,38 @@ class Category(BaseModel): index = models.IntegerField(default=0, verbose_name=_('index')) class Meta: + """ + 分类模型的元数据配置 + """ ordering = ['-index'] verbose_name = _('category') verbose_name_plural = verbose_name def get_absolute_url(self): + """ + 获取分类的绝对URL地址 + 用于在模板中生成分类页面的链接 + Returns: + str: 分类详情页的URL + """ return reverse( 'blog:category_detail', kwargs={ 'category_name': self.slug}) def __str__(self): + """ + 对象的字符串表示 + 在admin后台和shell中显示的分类名称 + Returns: + str: 分类名称 + """ return self.name @cache_decorator(60 * 60 * 10) def get_category_tree(self): """ 递归获得分类目录的父级 - :return: + 返回从当前分类到根分类的路径 """ categorys = [] @@ -222,7 +265,7 @@ class Category(BaseModel): def get_sub_categorys(self): """ 获得当前分类目录所有子集 - :return: + 返回所有子分类的列表 """ categorys = [] all_categorys = Category.objects.all() @@ -241,7 +284,7 @@ class Category(BaseModel): class Tag(BaseModel): - """文章标签""" + """文章标签模型""" name = models.CharField(_('tag name'), max_length=30, unique=True) slug = models.SlugField(default='no-slug', max_length=60, blank=True) @@ -249,10 +292,12 @@ class Tag(BaseModel): 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: @@ -305,7 +350,7 @@ class SideBar(models.Model): class BlogSettings(models.Model): - """blog的配置""" + """博客全局配置模型""" site_name = models.CharField( _('site name'), max_length=200, @@ -367,10 +412,12 @@ class BlogSettings(models.Model): 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): + """保存配置并清除缓存""" super().save(*args, **kwargs) from djangoblog.utils import cache cache.clear() diff --git a/src/DjangoBlog/blog/views.py b/src/DjangoBlog/blog/views.py index d5dc7ec..ab81b63 100644 --- a/src/DjangoBlog/blog/views.py +++ b/src/DjangoBlog/blog/views.py @@ -25,6 +25,10 @@ logger = logging.getLogger(__name__) class ArticleListView(ListView): + """ + 文章列表基类视图 + 提供通用的文章列表功能和缓存机制 + """ # template_name属性用于指定使用哪个模板进行渲染 template_name = 'blog/article_index.html' @@ -38,10 +42,12 @@ class ArticleListView(ListView): link_type = LinkShowType.L def get_view_cache_key(self): + """获取视图缓存键 - 需要子类实现""" return self.request.get['pages'] @property def page_number(self): + """获取当前页码""" page_kwarg = self.page_kwarg page = self.kwargs.get( page_kwarg) or self.request.GET.get(page_kwarg) or 1 @@ -49,22 +55,26 @@ class ArticleListView(ListView): def get_queryset_cache_key(self): """ - 子类重写.获得queryset的缓存key + 获取查询集缓存键 + 子类必须重写此方法 """ raise NotImplementedError() def get_queryset_data(self): """ - 子类重写.获取queryset的数据 + 获取查询集数据 + 子类必须重写此方法 """ raise NotImplementedError() def get_queryset_from_cache(self, cache_key): - ''' - 缓存页面数据 - :param cache_key: 缓存key - :return: - ''' + """ + 从缓存获取查询集数据 + Args: + cache_key: 缓存键 + Returns: + QuerySet: 文章查询集 + """ value = cache.get(cache_key) if value: logger.info('get view cache.key:{key}'.format(key=cache_key)) @@ -76,45 +86,52 @@ class ArticleListView(ListView): return article_list def get_queryset(self): - ''' - 重写默认,从缓存获取数据 - :return: - ''' + """ + 获取查询集 - 从缓存获取数据 + Returns: + QuerySet: 文章查询集 + """ key = self.get_queryset_cache_key() value = self.get_queryset_from_cache(key) return value def get_context_data(self, **kwargs): + """添加上下文数据""" kwargs['linktype'] = self.link_type return super(ArticleListView, self).get_context_data(**kwargs) class IndexView(ArticleListView): - ''' - 首页 - ''' + """ + 首页视图 + 显示最新的文章列表 + """ # 友情链接类型 link_type = LinkShowType.I def get_queryset_data(self): + """获取首页文章数据""" article_list = Article.objects.filter(type='a', status='p') return article_list def get_queryset_cache_key(self): + """获取首页缓存键""" cache_key = 'index_{page}'.format(page=self.page_number) return cache_key class ArticleDetailView(DetailView): - ''' - 文章详情页面 - ''' + """ + 文章详情页面视图 + 显示单篇文章的详细内容和评论 + """ template_name = 'blog/article_detail.html' model = Article pk_url_kwarg = 'article_id' context_object_name = "article" def get_context_data(self, **kwargs): + """添加上下文数据 - 文章详情和评论信息""" comment_form = CommentForm() article_comments = self.object.comment_list() @@ -162,12 +179,14 @@ class ArticleDetailView(DetailView): class CategoryDetailView(ArticleListView): - ''' - 分类目录列表 - ''' + """ + 分类目录列表视图 + 显示指定分类下的所有文章 + """ page_type = "分类目录归档" def get_queryset_data(self): + """获取分类文章数据""" slug = self.kwargs['category_name'] category = get_object_or_404(Category, slug=slug) @@ -180,6 +199,7 @@ class CategoryDetailView(ArticleListView): return article_list def get_queryset_cache_key(self): + """获取分类页面缓存键""" slug = self.kwargs['category_name'] category = get_object_or_404(Category, slug=slug) categoryname = category.name @@ -189,6 +209,7 @@ class CategoryDetailView(ArticleListView): return cache_key def get_context_data(self, **kwargs): + """添加上下文数据""" categoryname = self.categoryname try: @@ -201,12 +222,14 @@ class CategoryDetailView(ArticleListView): class AuthorDetailView(ArticleListView): - ''' - 作者详情页 - ''' + """ + 作者详情页视图 + 显示指定作者的所有文章 + """ page_type = '作者文章归档' def get_queryset_cache_key(self): + """获取作者页面缓存键""" from uuslug import slugify author_name = slugify(self.kwargs['author_name']) cache_key = 'author_{author_name}_{page}'.format( @@ -214,12 +237,14 @@ class AuthorDetailView(ArticleListView): return cache_key def get_queryset_data(self): + """获取作者文章数据""" author_name = self.kwargs['author_name'] article_list = Article.objects.filter( author__username=author_name, type='a', status='p') return article_list def get_context_data(self, **kwargs): + """添加上下文数据""" author_name = self.kwargs['author_name'] kwargs['page_type'] = AuthorDetailView.page_type kwargs['tag_name'] = author_name @@ -227,12 +252,14 @@ class AuthorDetailView(ArticleListView): class TagDetailView(ArticleListView): - ''' - 标签列表页面 - ''' + """ + 标签列表页面视图 + 显示指定标签下的所有文章 + """ page_type = '分类标签归档' def get_queryset_data(self): + """获取标签文章数据""" slug = self.kwargs['tag_name'] tag = get_object_or_404(Tag, slug=slug) tag_name = tag.name @@ -242,6 +269,7 @@ class TagDetailView(ArticleListView): return article_list def get_queryset_cache_key(self): + """获取标签页面缓存键""" slug = self.kwargs['tag_name'] tag = get_object_or_404(Tag, slug=slug) tag_name = tag.name @@ -251,6 +279,7 @@ class TagDetailView(ArticleListView): return cache_key def get_context_data(self, **kwargs): + """添加上下文数据""" # tag_name = self.kwargs['tag_name'] tag_name = self.name kwargs['page_type'] = TagDetailView.page_type @@ -259,32 +288,45 @@ class TagDetailView(ArticleListView): class ArchivesView(ArticleListView): - ''' - 文章归档页面 - ''' + """ + 文章归档页面视图 + 按时间顺序显示所有文章 + """ page_type = '文章归档' paginate_by = None page_kwarg = None template_name = 'blog/article_archives.html' def get_queryset_data(self): + """获取归档数据""" return Article.objects.filter(status='p').all() def get_queryset_cache_key(self): + """获取归档页面缓存键""" cache_key = 'archives' return cache_key class LinkListView(ListView): + """ + 友情链接列表视图 + 显示所有启用的友情链接 + """ model = Links template_name = 'blog/links_list.html' def get_queryset(self): + """获取启用的友情链接""" return Links.objects.filter(is_enable=True) class EsSearchView(SearchView): + """ + Elasticsearch搜索视图 + 扩展Haystack的搜索功能 + """ def get_context(self): + """获取搜索上下文数据""" paginator, page = self.build_page() context = { "query": self.query, @@ -303,9 +345,12 @@ class EsSearchView(SearchView): @csrf_exempt def fileupload(request): """ - 该方法需自己写调用端来上传图片,该方法仅提供图床功能 - :param request: - :return: + 文件上传视图 + 提供图床功能,支持图片和文件上传 + Args: + request: HTTP请求对象 + Returns: + HttpResponse: 上传结果 """ if request.method == 'POST': sign = request.GET.get('sign', None) @@ -344,6 +389,9 @@ def page_not_found_view( request, exception, template_name='blog/error_page.html'): + """ + 404页面未找到视图 + """ if exception: logger.error(exception) url = request.get_full_path() @@ -355,6 +403,9 @@ def page_not_found_view( def server_error_view(request, template_name='blog/error_page.html'): + """ + 500服务器错误视图 + """ return render(request, template_name, {'message': _('Sorry, the server is busy, please click the home page to see other?'), @@ -366,6 +417,9 @@ def permission_denied_view( request, exception, template_name='blog/error_page.html'): + """ + 403权限拒绝视图 + """ if exception: logger.error(exception) return render( @@ -375,5 +429,9 @@ def permission_denied_view( def clean_cache_view(request): + """ + 清理缓存视图 + 用于手动清理系统缓存 + """ cache.clear() return HttpResponse('ok') diff --git a/src/DjangoBlog/djangoblog/settings.py b/src/DjangoBlog/djangoblog/settings.py index 80e99a0..eee04a9 100644 --- a/src/DjangoBlog/djangoblog/settings.py +++ b/src/DjangoBlog/djangoblog/settings.py @@ -17,72 +17,82 @@ from django.utils.translation import gettext_lazy as _ def env_to_bool(env, default): + """ + 环境变量转布尔值工具函数 + 用于从环境变量读取配置,支持True/False字符串转换 + """ str_val = os.environ.get(env) return default if str_val is None else str_val == 'True' - +# 项目根目录路径配置 # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - +# 快速开发配置 - 生产环境不适用 # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ +# 安全警告:生产环境必须保护密钥! # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.environ.get( 'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6' + +# 安全警告:生产环境必须关闭调试模式! # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env_to_bool('DJANGO_DEBUG', True) # DEBUG = False TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test' +# 允许的主机名配置 # ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] # django 4.0新增配置 CSRF_TRUSTED_ORIGINS = ['http://example.com'] -# Application definition +# 应用定义 - 注册所有Django应用 +# Application definition INSTALLED_APPS = [ # 'django.contrib.admin', - 'django.contrib.admin.apps.SimpleAdminConfig', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django.contrib.sitemaps', - 'mdeditor', - 'haystack', - 'blog', - 'accounts', - 'comments', - 'oauth', - 'servermanager', - 'owntracks', - 'compressor', - 'djangoblog' + 'django.contrib.admin.apps.SimpleAdminConfig', # 简化版管理员后台 + 'django.contrib.auth', # 认证系统 + 'django.contrib.contenttypes', # 内容类型框架 + 'django.contrib.sessions', # 会话管理 + 'django.contrib.messages', # 消息框架 + 'django.contrib.staticfiles', # 静态文件管理 + 'django.contrib.sites', # 多站点支持 + 'django.contrib.sitemaps', # 站点地图 + 'mdeditor', # Markdown编辑器 + 'haystack', # 搜索功能 + 'blog', # 博客核心应用 + 'accounts', # 用户账户应用 + 'comments', # 评论系统应用 + 'oauth', # OAuth认证 + 'servermanager', # 服务器管理 + 'owntracks', # 位置跟踪 + 'compressor', # 静态文件压缩 + 'djangoblog' # 项目主应用 ] +# 中间件配置 - 请求处理管道 MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.gzip.GZipMiddleware', + 'django.middleware.security.SecurityMiddleware', # 安全中间件 + 'django.contrib.sessions.middleware.SessionMiddleware', # 会话中间件 + 'django.middleware.locale.LocaleMiddleware', # 国际化中间件 + 'django.middleware.gzip.GZipMiddleware', # Gzip压缩 # 'django.middleware.cache.UpdateCacheMiddleware', - 'django.middleware.common.CommonMiddleware', + 'django.middleware.common.CommonMiddleware', # 通用中间件 # 'django.middleware.cache.FetchFromCacheMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.http.ConditionalGetMiddleware', - 'blog.middleware.OnlineMiddleware' + 'django.middleware.csrf.CsrfViewMiddleware', # CSRF保护 + 'django.contrib.auth.middleware.AuthenticationMiddleware', # 认证中间件 + 'django.contrib.messages.middleware.MessageMiddleware', # 消息中间件 + 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 点击劫持保护 + 'django.middleware.http.ConditionalGetMiddleware', # 条件GET请求 + 'blog.middleware.OnlineMiddleware' # 自定义在线状态中间件 ] - +# 根URL配置 ROOT_URLCONF = 'djangoblog.urls' - +# 模板配置 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -99,9 +109,9 @@ TEMPLATES = [ }, }, ] - +# WSGI应用配置 WSGI_APPLICATION = 'djangoblog.wsgi.application' - +# 数据库配置 # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases @@ -117,7 +127,7 @@ DATABASES = { 'OPTIONS': { 'charset': 'utf8mb4'}, }} - +# 密码验证配置 # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators @@ -135,7 +145,7 @@ AUTH_PASSWORD_VALIDATORS = [ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] - +# 国际化配置 LANGUAGES = ( ('en', _('English')), ('zh-hans', _('Simplified Chinese')), @@ -154,43 +164,50 @@ USE_I18N = True USE_L10N = True USE_TZ = False - +# 静态文件配置 (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ - +# 搜索功能配置 - Haystack HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), }, } +# 自动更新搜索索引 # Automatically update searching index HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' +# 允许用户使用邮箱或用户名登录 # Allow user login with username and password AUTHENTICATION_BACKENDS = [ 'accounts.user_login_backend.EmailOrUsernameModelBackend'] - +# 静态文件收集目录 STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') - STATIC_URL = '/static/' STATICFILES = os.path.join(BASE_DIR, 'static') +# 自定义用户模型 AUTH_USER_MODEL = 'accounts.BlogUser' LOGIN_URL = '/login/' +# 时间格式配置 TIME_FORMAT = '%Y-%m-%d %H:%M:%S' DATE_TIME_FORMAT = '%Y-%m-%d' +# Bootstrap颜色样式 # bootstrap color styles BOOTSTRAP_COLOR_TYPES = [ 'default', 'primary', 'success', 'info', 'warning', 'danger' ] +# 分页配置 # paginate PAGINATE_BY = 10 +# HTTP缓存超时时间 # http cache timeout CACHE_CONTROL_MAX_AGE = 2592000 +# 缓存配置 # cache setting CACHES = { 'default': { @@ -207,12 +224,13 @@ if os.environ.get("DJANGO_REDIS_URL"): 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}', } } - +# 站点ID(多站点支持) SITE_ID = 1 +# 百度站长平台推送URL BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \ or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' -# Email: +# 邮件配置: EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True) @@ -222,12 +240,17 @@ EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') DEFAULT_FROM_EMAIL = EMAIL_HOST_USER SERVER_EMAIL = EMAIL_HOST_USER + +# 管理员邮箱配置(错误通知) # Setting debug=false did NOT handle except email notifications ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')] + +# 微信管理员密码(两次MD5加密) # WX ADMIN password(Two times md5) WXADMIN = os.environ.get( 'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7' +# 日志配置 LOG_PATH = os.path.join(BASE_DIR, 'logs') if not os.path.exists(LOG_PATH): os.makedirs(LOG_PATH, exist_ok=True) @@ -293,6 +316,7 @@ LOGGING = { } } +# 静态文件查找器配置 STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', @@ -302,7 +326,7 @@ STATICFILES_FINDERS = ( COMPRESS_ENABLED = True # COMPRESS_OFFLINE = True - +# 静态文件压缩配置 COMPRESS_CSS_FILTERS = [ # creates absolute urls from relative ones 'compressor.filters.css_default.CssAbsoluteFilter', @@ -312,13 +336,14 @@ COMPRESS_CSS_FILTERS = [ COMPRESS_JS_FILTERS = [ 'compressor.filters.jsmin.JSMinFilter' ] - +# 媒体文件配置 MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') MEDIA_URL = '/media/' X_FRAME_OPTIONS = 'SAMEORIGIN' - +# 默认主键字段类型(Django 3.2+) DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +# Elasticsearch搜索配置(如果配置了环境变量) if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): ELASTICSEARCH_DSL = { 'default': { @@ -331,7 +356,7 @@ if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): }, } -# Plugin System +# 插件系统配置 PLUGINS_DIR = BASE_DIR / 'plugins' ACTIVE_PLUGINS = [ 'article_copyright', diff --git a/src/DjangoBlog/djangoblog_complete.sql b/src/DjangoBlog/djangoblog_complete.sql new file mode 100644 index 0000000..423b3a4 Binary files /dev/null and b/src/DjangoBlog/djangoblog_complete.sql differ diff --git a/src/DjangoBlog/foodBlog/food_categories.json b/src/DjangoBlog/foodBlog/food_categories.json new file mode 100644 index 0000000..7e764a6 --- /dev/null +++ b/src/DjangoBlog/foodBlog/food_categories.json @@ -0,0 +1,30 @@ +[ + { + "model": "blog.category", + "pk": 1, + "fields": { + "name": "中式菜肴" + } + }, + { + "model": "blog.category", + "pk": 2, + "fields": { + "name": "西式美食" + } + }, + { + "model": "blog.category", + "pk": 3, + "fields": { + "name": "甜点烘焙" + } + }, + { + "model": "blog.category", + "pk": 4, + "fields": { + "name": "特色小吃" + } + } +] diff --git a/src/DjangoBlog/foodBlog/food_posts.json b/src/DjangoBlog/foodBlog/food_posts.json new file mode 100644 index 0000000..771e798 --- /dev/null +++ b/src/DjangoBlog/foodBlog/food_posts.json @@ -0,0 +1,56 @@ +[ + { + "model": "blog.Article", + "pk": 1, + "fields": { + "title": "北京胡同里的炸酱面探店", + "body": "在北京,胡同是这座城市的脉络,藏着老北京最地道的烟火气,也藏着不少令人惊艳的美食小店。这次,我就寻到了一家隐匿在胡同深处的炸酱面店,那味道,堪称老北京炸酱面的\"天花板\"。这家店位于东四附近的一条小胡同里,没有醒目的招牌,只有一块略显斑驳的木牌,上面用毛笔字写着\"老北京炸酱面\"。若不是本地朋友极力推荐,我大概率会与它擦肩而过。跟着导航七拐八绕,穿过两侧是灰墙灰瓦的狭窄胡同,耳边是自行车铃铛声、街坊邻里的谈笑声,仿佛一下子穿越回了老北京的旧时光,而那家炸酱面店,就安静地坐落在胡同中段。推开略显厚重的木门,店内空间不大,摆着几张木质方桌和长条凳,桌面上擦得干干净净。墙壁上挂着老北京的黑白照片,有胡同的全景,有孩童嬉戏的场景,还有老北京人吃炸酱面的模样,瞬间就把人拉进了充满回忆的氛围里。店里的食客不少,有附近的老街坊,也有像我一样慕名而来的游客,大家挤在一起,却丝毫不觉得拥挤,反而因为这浓浓的烟火气,多了几分亲近感。老板是位地道的北京大爷,穿着白色的厨师服,脸上总是挂着憨厚的笑容,看到新客进来,会热情地招呼:\"里面坐,炸酱面马上好!\"等了没几分钟,一碗热腾腾的炸酱面就端上了桌。先看那炸酱,盛在一个小巧的青花瓷碗里,酱色红亮,上面还点缀着星星点点的葱花。用筷子挑起一点,能看到酱里肥瘦相间的五花肉丁,颗粒饱满,大小均匀。老板说,这酱是用五花肉丁先煸炒出油,再加入黄酱和甜面酱慢慢熬制而成,期间要不断搅拌,这样才能让酱变得浓稠,而且不会糊锅。凑近闻一闻,酱香混合着肉香,直往鼻子里钻,却一点都不觉得腻。再看面条,是店家手工制作的手擀面。面条粗细均匀,色泽洁白,根根分明。用筷子夹起一筷子,能感觉到面条很有韧性,放进嘴里一尝,果然筋道弹牙,咀嚼起来充满了小麦的清香。老板告诉我,他们的面条都是当天现做的,和面时还加了适量的盐,这样能让面条更有嚼劲。炸酱面的配菜也很丰富,一小盘里装着黄瓜丝、胡萝卜丝、豆芽、青豆、心里美萝卜丝,色彩鲜艳,看着就很有食欲。这些配菜都切得很精细,黄瓜丝脆嫩,胡萝卜丝清甜,豆芽爽脆,和筋道的面条、香浓的炸酱搭配在一起,口感层次十分丰富。按照老北京的吃法,先把配菜倒进面条里,再挖上一大勺炸酱,然后用筷子快速地搅拌均匀。随着搅拌,面条被均匀地裹上了红亮的炸酱,配菜也与面条充分融合。夹起一筷子,送进嘴里,面条的筋道、炸酱的香浓、配菜的清爽,在口腔里交织碰撞,那味道,真是绝了!每一口都能感受到老北京美食的独特魅力,仿佛能尝到时光的味道。吃完一碗,意犹未尽,连碗底的酱汁都舍不得剩下,恨不得用馒头蘸着吃掉。这家藏在胡同里的炸酱面店,没有华丽的装修,没有精致的摆盘,却用最地道的味道,征服了食客的味蕾。在这里,吃的不仅是一碗炸酱面,更是老北京的烟火气,是胡同里的人情味。如果你也喜欢老北京美食,不妨来这条胡同里,寻找这家\"宝藏小店\",相信你也会和我一样,被这碗炸酱面深深吸引。", + "pub_time": "2025-10-08 12:00:00", + "status": "p", + "comment_status": "o", + "type": "a", + "views": 0, + "author": 1, + "article_order": 0, + "show_toc": false, + "category": 1, + "tags": [1] + } + }, + { + "model": "blog.Article", + "pk": 2, + "fields": { + "title": "《意式餐厅里的味蕾漫游》", + "body": "在城市喧嚣的角落,藏着一家充满意式风情的餐厅,推开那扇雕花木门,仿佛瞬间穿越到了亚平宁半岛。暖黄的灯光洒在铺着格子桌布的餐桌上,墙上挂着意大利各地的风景照,空气中弥漫着橄榄油、芝士与香草交融的香气,一场味蕾的漫游就此开启。首先上桌的是经典的意式番茄肉酱面。手工制作的意面粗细均匀,如丝绸般顺滑,裹满了浓郁的肉酱。肉酱由新鲜番茄慢炖而成,酸甜的汁水充分渗入肉质鲜嫩的牛肉末中,每一口都能感受到番茄的清新与牛肉的醇厚在舌尖碰撞,搭配帕玛森芝士碎,咸香与酸甜交织,层次丰富得让人着迷。接着是意式烤千层面,这简直是芝士爱好者的天堂。层层叠叠的面皮间,夹着细腻的牛肉馅、奶香十足的 béchamel 酱,最上层铺满了马苏里拉芝士。经烤箱烤制后,芝士呈现出诱人的焦黄色,轻轻用叉子一挑,芝士拉出长长的丝,入口即化,面皮的柔韧、馅料的鲜美与芝士的浓郁完美融合,每一层都带来不同的味觉惊喜。最后以一份提拉米苏收尾再合适不过。手指饼干浸润了咖啡酒,带着微微的苦涩,与上层绵密的马斯卡彭芝士奶油完美平衡,顶部撒的可可粉更添了一丝醇厚。用小勺挖上一口,冰凉丝滑的口感在口中散开,咖啡的苦、芝士的甜、饼干的绵,交织出意式甜品独有的浪漫与馥郁。在这家意式餐厅,每一道菜都像是一位意大利友人,用最地道的风味,诉说着亚平宁半岛的阳光与浪漫,让我在城市里,也能拥有一场酣畅淋漓的味蕾漫游。", + "pub_time": "2025-10-08 12:00:00", + "status": "p", + "comment_status": "o", + "type": "a", + "views": 0, + "author": 1, + "article_order": 0, + "show_toc": false, + "category": 2, + "tags": [3] + } + }, + { + "model": "blog.Article", + "pk": 3, + "fields": { + "title": "《法式甜品店的午后邂逅》", + "body": "在繁华街区的转角处,一家名为『甜蜜时光』的法式甜品店静静伫立。推开那扇镶嵌着琉璃的蓝色木门,清脆的风铃声伴随着扑面而来的黄油香气,瞬间将人带入一个甜蜜的梦境。店内装饰着复古镜框与蕾丝窗帘,大理石台面上陈列着琳琅满目的甜品,每一款都如同精雕细琢的艺术品,令人目不暇接。首先品尝的是经典的拿破仑千层酥。三层金黄酥脆的派皮之间,均匀地夹着香草籽点缀的卡仕达酱。用叉子轻轻一压,酥皮应声碎裂,发出悦耳的咔嚓声。入口时,奶油的丝滑与派皮的酥脆在口中交织,香草的芬芳缓缓释放,甜而不腻,层次分明,仿佛在舌尖演奏了一首优雅的法国香颂。接下来是外形精致的覆盆子慕斯。光滑的镜面淋面下,是轻盈如云的慕斯体,中间包裹着酸甜的覆盆子果酱夹心。用小勺挖开,慕斯的绵密与果酱的流动感形成美妙对比。覆盆子的微酸恰到好处地平衡了慕斯的甜度,入口即化,余味带着一丝果香的清新,令人回味无穷。最后以一杯手冲伯爵茶搭配柠檬玛德琳收尾。现烤的玛德琳蛋糕散发着柠檬的清香,标志性的贝壳造型可爱迷人。蛋糕体湿润柔软,边缘带着微微的焦脆。轻咬一口,黄油的浓郁与柠檬的清爽相得益彰,再啜饮一口佛手柑香气萦绕的伯爵茶,茶香与蛋糕的甜味在口中完美融合,为这个午后画上了一个圆满的句号。在这家法式甜品店,每一道甜品都是一首诗,用细腻的口感与精致的造型,诉说着法式浪漫的甜蜜絮语,让我在忙碌的生活中,寻得了一份难得的惬意与治愈。", + "pub_time": "2025-10-09 14:30:00", + "status": "p", + "comment_status": "o", + "type": "a", + "views": 0, + "author": 1, + "article_order": 0, + "show_toc": false, + "category": 3, + "tags": [3] + } + } +] \ No newline at end of file diff --git a/src/DjangoBlog/foodBlog/food_tags.json b/src/DjangoBlog/foodBlog/food_tags.json new file mode 100644 index 0000000..b650876 --- /dev/null +++ b/src/DjangoBlog/foodBlog/food_tags.json @@ -0,0 +1,30 @@ +[ +  { +    "model": "blog.tag", +    "pk": 1, +    "fields": { +      "name": "探店" +    } +  }, +  { +    "model": "blog.tag", +    "pk": 2, +    "fields": { +      "name": "家常菜" +    } +  }, +  { +    "model": "blog.tag", +    "pk": 3, +    "fields": { +      "name": "烘焙" +    } +  }, +  { +    "model": "blog.tag", +    "pk": 4, +    "fields": { +      "name": "小吃" +    } +  } +] \ No newline at end of file diff --git a/src/DjangoBlog/foodBlog/food_users.json b/src/DjangoBlog/foodBlog/food_users.json new file mode 100644 index 0000000..414abc5 --- /dev/null +++ b/src/DjangoBlog/foodBlog/food_users.json @@ -0,0 +1,13 @@ +[ +  { +    "model": "auth.user", +    "pk": 1, +    "fields": { +      "username": "hyt", +      "password": "cs202000520", +      "email": "691385292@qq.com", +      "is_staff": false, +      "is_superuser": false +    } +  } +] \ No newline at end of file diff --git a/src/DjangoBlog/templates/share_layout/adsense.html b/src/DjangoBlog/templates/share_layout/adsense.html index 8f99c55..b9a742d 100644 --- a/src/DjangoBlog/templates/share_layout/adsense.html +++ b/src/DjangoBlog/templates/share_layout/adsense.html @@ -1,6 +1,15 @@ -