Merge develop into main

This commit is contained in:
2025-07-27 23:15:02 +08:00
10 changed files with 561 additions and 105 deletions

View File

@@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from .models import Post from .models import Post, Category
from django.db import models from django.db import models
from mdeditor.widgets import MDEditorWidget from mdeditor.widgets import MDEditorWidget
@@ -18,3 +18,4 @@ class PostAdmin(admin.ModelAdmin):
# 注册自定义的PostAdmin # 注册自定义的PostAdmin
admin.site.register(Post, PostAdmin) admin.site.register(Post, PostAdmin)
admin.site.register(Category)

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.1 on 2025-07-27 13:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0005_remove_post_image'),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
('description', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
options={
'verbose_name_plural': 'Categories',
},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.1 on 2025-07-27 13:32
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0006_category'),
]
operations = [
migrations.AddField(
model_name='post',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='posts', to='blog.category'),
),
]

View File

@@ -6,15 +6,30 @@ from django.utils.safestring import mark_safe
from mdeditor.fields import MDTextField from mdeditor.fields import MDTextField
# 添加 Category 模型
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Categories"
class Post(models.Model): class Post(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
content = MDTextField() # ✅ 改成这里 content = MDTextField() # ✅ 改成这里
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
publish_date = models.DateTimeField(default=timezone.now) publish_date = models.DateTimeField(default=timezone.now)
# 添加分类字段,建立外键关系
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='posts')
def __str__(self): def __str__(self):
return f"{self.title} ({self.publish_date.strftime('%Y-%m-%d')})" return f"{self.title}"
def get_markdown_content(self): def get_markdown_content(self):
import re import re
@@ -63,8 +78,8 @@ class Post(models.Model):
# 先转换markdown到HTML # 先转换markdown到HTML
html_content = markdown.markdown(content) html_content = markdown.markdown(content)
# 将emoji shortcode转换为实际的emoji字符 # 将emoji shortcode转换为实际的emoji字符
html_content = emoji.emojize(html_content, language='alias') html_content = emoji.emojize(html_content, language='alias')
return mark_safe(html_content) return mark_safe(html_content)

View File

@@ -3,59 +3,223 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ post.title }}</title> <title>{{ post.title }}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.container {
display: flex;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
/ / : sticky min-height: 100 vh;
}
.sidebar {
width: 250px;
padding-right: 20px;
border-right: 1px solid #eee;
/ / : position: -webkit-sticky;
position: sticky;
top: 20px;
align-self: flex-start;
height: fit-content;
}
.sidebar h3 {
color: #333;
border-bottom: 2px solid #007cba;
padding-bottom: 10px;
}
.sidebar ul {
list-style: none;
padding: 0;
}
.sidebar ul li {
margin: 10px 0;
}
.sidebar ul li a {
text-decoration: none;
color: #666;
display: block;
padding: 8px 12px;
border-radius: 4px;
transition: all 0.3s;
}
.sidebar ul li a:hover,
.sidebar ul li a.active {
background-color: #007cba;
color: white;
}
.search-box {
margin-bottom: 20px;
}
.search-box input[type="text"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.search-box input[type="submit"] {
width: 100%;
padding: 8px;
background-color: #007cba;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 5px;
}
.search-box input[type="submit"]:hover {
background-color: #005a87;
}
.search-type {
margin: 10px 0;
}
.search-type label {
display: block;
margin: 5px 0;
font-size: 14px;
color: #666;
}
.search-type input[type="radio"] {
margin-right: 5px;
}
.main-content {
flex: 1;
padding-left: 20px;
}
.main-content h1 {
color: #333;
border-bottom: 2px solid #eee;
padding-bottom: 10px;
}
.post-meta {
font-size: 14px;
color: #999;
margin-bottom: 20px;
}
.post-content img {
max-width: 100%;
height: auto;
display: block;
margin: 10px 0;
}
.post-content pre {
background-color: #f4f4f4;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
overflow-x: auto;
margin: 10px 0;
}
.post-content code {
font-family: 'Courier New', Courier, monospace;
background-color: #f4f4f4;
padding: 2px 4px;
border-radius: 3px;
font-size: 0.9em;
}
.post-content pre code {
background-color: transparent;
padding: 0;
border-radius: 0;
font-size: 0.9em;
}
footer {
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
font-size: 12px;
color: #999;
background-color: white;
padding: 5px 0;
}
</style>
</head> </head>
<body> <body>
<!-- 添加样式使整个页面内容居中显示 --> <!-- 添加网站标题 -->
<div style="max-width: 800px; margin: 0 auto; padding: 0 20px;"> <div style="max-width: 1200px; margin: 0 auto; padding: 0 20px;">
<!-- 添加网站标题 -->
<h1 style="text-align: left;">六桂流芳的com</h1> <h1 style="text-align: left;">六桂流芳的com</h1>
<h1 style="text-align: center;">{{ post.title }}</h1>
<!-- 将返回首页链接放在发布时间的右侧 -->
<div style="display: flex; align-items: center; justify-content: space-between;">
<p>发布时间:{{ post.publish_date|date:"Y年n月j日 H:i" }}</p>
<a href="{% url 'index' %}" style="margin-left: 20px; white-space: nowrap;">返回首页</a>
</div>
<!-- 添加CSS样式限制图片大小 -->
<div style="max-width: 100%;">
<style>
.post-content img {
max-width: 100%;
height: auto;
display: block;
margin: 10px 0;
}
.post-content pre {
background-color: #f4f4f4;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
overflow-x: auto;
margin: 10px 0;
}
.post-content code {
font-family: 'Courier New', Courier, monospace;
background-color: #f4f4f4;
padding: 2px 4px;
border-radius: 3px;
font-size: 0.9em;
}
.post-content pre code {
background-color: transparent;
padding: 0;
border-radius: 0;
font-size: 0.9em;
}
</style>
<div class="post-content">{{ post.get_markdown_content }}</div>
</div>
<br>
</div> </div>
<footer style="position: fixed; bottom: 0; width: 100%; text-align: center; font-size: 12px; color: #999; background-color: white; padding: 5px 0;">
<div class="container">
<div class="sidebar">
<div class="search-box">
<form method="get" action="{% url 'index' %}">
<input type="text" name="q" placeholder="搜索文章..." value="{{ query|default:'' }}">
<div class="search-type">
<label>
<input type="radio" name="search_type" value="all" checked>
全文搜索
</label>
<label>
<input type="radio" name="search_type" value="title">
标题搜索
</label>
<label>
<input type="radio" name="search_type" value="content">
内容搜索
</label>
</div>
<input type="submit" value="搜索">
</form>
</div>
<h3>文章分类</h3>
<ul>
<li><a href="{% url 'index' %}" {% if not post.category %}class="active"{% endif %}>全部文章</a></li>
{% for category in categories %}
<li>
<a href="{% url 'index' %}?category={{ category.id }}"
{% if post.category and post.category.id == category.id %}class="active"{% endif %}>
{{ category.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="main-content">
<h1 style="text-align: center;">{{ post.title }}</h1>
<div class="post-meta">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>发布时间:{{ post.publish_date|date:"Y年n月j日 H:i" }}</span>
<a href="{% url 'index' %}" style="white-space: nowrap;">返回首页</a>
</div>
</div>
<div class="post-content">{{ post.get_markdown_content }}</div>
<br>
</div>
</div>
<footer>
<a href="https://beian.miit.gov.cn/" target="_blank">闽ICP备2023010767号-2</a> <a href="https://beian.miit.gov.cn/" target="_blank">闽ICP备2023010767号-2</a>
</footer> </footer>
</body> </body>
</html> </html>

View File

@@ -3,22 +3,236 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>六桂流芳的com</title> <title>六桂流芳的com</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.container {
display: flex;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
}
.sidebar {
width: 250px;
padding-right: 20px;
border-right: 1px solid #eee;
position: -webkit-sticky;
position: sticky;
top: 20px;
align-self: flex-start;
height: fit-content;
}
.sidebar h3 {
color: #333;
border-bottom: 2px solid #007cba;
padding-bottom: 10px;
}
.sidebar ul {
list-style: none;
padding: 0;
}
.sidebar ul li {
margin: 10px 0;
}
.sidebar ul li a {
text-decoration: none;
color: #666;
display: block;
padding: 8px 12px;
border-radius: 4px;
transition: all 0.3s;
}
.sidebar ul li a:hover,
.sidebar ul li a.active {
background-color: #007cba;
color: white;
}
.search-box {
margin-bottom: 20px;
}
.search-box input[type="text"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.search-box input[type="submit"] {
width: 100%;
padding: 8px;
background-color: #007cba;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 5px;
}
.search-box input[type="submit"]:hover {
background-color: #005a87;
}
.search-type {
margin: 10px 0;
}
.search-type label {
display: block;
margin: 5px 0;
font-size: 14px;
color: #666;
}
.search-type input[type="radio"] {
margin-right: 5px;
}
.main-content {
flex: 1;
padding-left: 20px;
}
.main-content h1 {
color: #333;
border-bottom: 2px solid #eee;
padding-bottom: 10px;
}
.posts-list {
list-style: none;
padding: 0;
}
.posts-list li {
padding: 15px 0;
border-bottom: 1px solid #eee;
}
.posts-list li:last-child {
border-bottom: none;
}
.post-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
}
.post-title a {
text-decoration: none;
color: #007cba;
}
.post-meta {
font-size: 14px;
color: #999;
}
.post-category {
display: inline-block;
background-color: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
margin-top: 5px;
}
.post-summary {
margin: 10px 0;
line-height: 1.6;
color: #666;
}
footer {
}
</style>
</head> </head>
<body> <body>
<!-- 修改样式使页面内容靠左对齐,标题显示在左上角 --> <!-- 添加网站标题 -->
<div style="max-width: 800px; margin: 0 auto; padding: 0 20px; text-align: left;"> <div style="max-width: 1200px; margin: 0 auto; padding: 0 20px;">
<h1 style="text-align: left;">六桂流芳的com</h1> <h1 style="text-align: left;">六桂流芳的com</h1>
<ul style="list-style: none; padding: 0;">
{% for post in posts %}
<li style="margin: 10px 0; text-align: left;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<a href="{% url 'detail' post.id %}">{{ post.title }}</a>
<span style="margin-left: 10px; white-space: nowrap;">发布时间:{{ post.publish_date|date:"Y年n月j日 H:i" }}</span>
</div>
</li>
{% endfor %}
</ul>
</div> </div>
<div class="container">
<div class="sidebar">
<div class="search-box">
<form method="get">
<input type="text" name="q" placeholder="搜索文章..." value="{{ query|default:'' }}">
<div class="search-type">
<label>
<input type="radio" name="search_type" value="all"
{% if search_type != 'title' and search_type != 'content' %}checked{% endif %}>
全文搜索
</label>
<label>
<input type="radio" name="search_type" value="title"
{% if search_type == 'title' %}checked{% endif %}>
标题搜索
</label>
<label>
<input type="radio" name="search_type" value="content"
{% if search_type == 'content' %}checked{% endif %}>
内容搜索
</label>
</div>
<input type="submit" value="搜索">
</form>
</div>
<h3>文章分类</h3>
<ul>
<li><a href="{% url 'index' %}" {% if not selected_category %}class="active"{% endif %}>全部文章</a></li>
{% for category in categories %}
<li>
<a href="?category={{ category.id }}"
{% if selected_category == category.id|stringformat:"s" %}class="active"{% endif %}>
{{ category.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="main-content">
<ul class="posts-list">
{% for post in posts %}
<li>
<div class="post-title">
<a href="{% url 'detail' post.id %}">{{ post.title }}</a>
</div>
<div class="post-meta">
发布时间:{{ post.publish_date|date:"Y年n月j日 H:i" }}
</div>
<div class="post-summary">
{{ post.summary|safe }}
</div>
{% if post.category %}
<div class="post-category">
{{ post.category.name }}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
<footer style="position: fixed; bottom: 0; width: 100%; text-align: center; font-size: 12px; color: #999; background-color: white; padding: 5px 0;"> <footer style="position: fixed; bottom: 0; width: 100%; text-align: center; font-size: 12px; color: #999; background-color: white; padding: 5px 0;">
<a href="https://beian.miit.gov.cn/" target="_blank">闽ICP备2023010767号-2</a> <a href="https://beian.miit.gov.cn/" target="_blank">闽ICP备2023010767号-2</a>
</footer> </footer>

View File

@@ -1,14 +1,56 @@
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from .models import Post from .models import Post, Category
# Create your views here. # Create your views here.
def index(request): def index(request):
posts = Post.objects.order_by('created_at') # 获取所有分类
return render(request, 'blog/index.html', {'posts': posts}) categories = Category.objects.all()
# 获取查询参数中的分类ID
category_id = request.GET.get('category')
# 获取搜索关键词
query = request.GET.get('q')
# 获取搜索类型参数
search_type = request.GET.get('search_type', 'all')
# 根据分类和搜索关键词筛选文章
if query:
# 根据搜索类型执行不同的搜索
if search_type == 'title':
posts = Post.objects.filter(title__icontains=query)
elif search_type == 'content':
posts = Post.objects.filter(content__icontains=query)
else:
# 默认按标题和内容搜索
posts = Post.objects.filter(title__icontains=query) | Post.objects.filter(content__icontains=query)
elif category_id:
posts = Post.objects.filter(category_id=category_id)
else:
posts = Post.objects.all()
posts = posts.order_by('-publish_date').distinct()
# 为每篇文章添加摘要前200个字符
for post in posts:
# 移除HTML标签并截取前200个字符作为摘要
import re
clean_content = re.sub(r'<[^>]+>', '', post.get_markdown_content())
post.summary = clean_content[:200] + '...' if len(clean_content) > 200 else clean_content
return render(request, 'blog/index.html', {
'posts': posts,
'categories': categories,
'selected_category': category_id,
'query': query,
'search_type': search_type
})
def detail(request, post_id): def detail(request, post_id):
post = get_object_or_404(Post, pk=post_id) post = get_object_or_404(Post, pk=post_id)
return render(request, 'blog/detail.html', {'post': post}) categories = Category.objects.all() # 获取所有分类用于侧边栏
return render(request, 'blog/detail.html', {'post': post, 'categories': categories})

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
""" """
from pathlib import Path from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@@ -112,7 +113,6 @@ USE_TZ = True
STATIC_URL = 'static/' STATIC_URL = 'static/'
# 添加媒体文件配置 # 添加媒体文件配置
import os
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
@@ -157,3 +157,10 @@ MDEDITOR_CONFIGS = {
} }
X_FRAME_OPTIONS = 'SAMEORIGIN' X_FRAME_OPTIONS = 'SAMEORIGIN'
# settings.py 末尾加上这段
try:
from .local_settings import *
except ImportError:
pass

View File

@@ -1,31 +0,0 @@
asgiref==3.9.1
asttokens==3.0.0
click==8.2.1
configparser==7.2.0
decorator==5.2.1
executing==2.2.0
h11==0.16.0
ipython==9.4.0
ipython_pygments_lexers==1.1.1
jedi==0.19.2
matplotlib-inline==0.1.7
numpy==2.3.2
packaging==25.0
pandas==2.3.1
parso==0.8.4
pexpect==4.9.0
prompt_toolkit==3.0.51
psutil==7.0.0
ptyprocess==0.7.0
pure_eval==0.2.3
Pygments==2.19.2
python-dateutil==2.9.0.post0
pytz==2025.2
six==1.17.0
sqlparse==0.5.3
stack-data==0.6.3
traitlets==5.14.3
tzdata==2025.2
uv==0.8.3
uvicorn==0.35.0
wcwidth==0.2.13

View File

@@ -5,9 +5,9 @@ certifi==2025.7.14
charset-normalizer==3.4.2 charset-normalizer==3.4.2
click==8.2.1 click==8.2.1
decorator==5.2.1 decorator==5.2.1
Django==5.2.4 Django==5.1
django-mdeditor==0.1.20 django-mdeditor==0.1.20
django-summernote==0.8.20.0 emoji==2.14.1
executing==2.2.0 executing==2.2.0
gunicorn==23.0.0 gunicorn==23.0.0
h11==0.16.0 h11==0.16.0
@@ -15,7 +15,7 @@ idna==3.10
ipython==9.4.0 ipython==9.4.0
ipython_pygments_lexers==1.1.1 ipython_pygments_lexers==1.1.1
jedi==0.19.2 jedi==0.19.2
Markdown Markdown==3.5.2
martor==1.6.45 martor==1.6.45
matplotlib-inline==0.1.7 matplotlib-inline==0.1.7
numpy==2.3.2 numpy==2.3.2