Add Search button
This commit is contained in:
@@ -9,32 +9,38 @@ import csv
|
||||
from django.http import HttpResponse
|
||||
import json
|
||||
|
||||
|
||||
# 创建自定义管理站点
|
||||
class NewsCnAdminSite(AdminSite):
|
||||
site_header = "新华网管理后台"
|
||||
site_title = "新华网管理"
|
||||
index_title = "新华网内容管理"
|
||||
|
||||
|
||||
class DongfangyancaoAdminSite(AdminSite):
|
||||
site_header = "东方烟草报管理后台"
|
||||
site_title = "东方烟草报管理"
|
||||
index_title = "东方烟草报内容管理"
|
||||
|
||||
|
||||
# 实例化管理站点
|
||||
news_cn_admin = NewsCnAdminSite(name='news_cn_admin')
|
||||
dongfangyancao_admin = DongfangyancaoAdminSite(name='dongfangyancao_admin')
|
||||
|
||||
|
||||
@admin.register(Website)
|
||||
class WebsiteAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'base_url', 'enabled')
|
||||
|
||||
|
||||
# 为ArticleAdmin添加自定义动作
|
||||
@admin.register(Article)
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'website', 'pub_date')
|
||||
search_fields = ('title', 'content')
|
||||
# 添加动作选项
|
||||
actions = ['delete_selected_articles', 'delete_dongfangyancao_articles', 'export_as_csv', 'export_as_json', 'export_as_word']
|
||||
actions = ['delete_selected_articles', 'delete_dongfangyancao_articles', 'export_as_csv', 'export_as_json',
|
||||
'export_as_word']
|
||||
|
||||
def delete_dongfangyancao_articles(self, request, queryset):
|
||||
"""一键删除东方烟草报的所有文章"""
|
||||
@@ -61,7 +67,8 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
|
||||
writer.writerow(field_names)
|
||||
for obj in queryset:
|
||||
row = [getattr(obj, field)() if callable(getattr(obj, field)) else getattr(obj, field) for field in field_names]
|
||||
row = [getattr(obj, field)() if callable(getattr(obj, field)) else getattr(obj, field) for field in
|
||||
field_names]
|
||||
writer.writerow(row)
|
||||
|
||||
return response
|
||||
@@ -114,7 +121,8 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
# 添加文章元数据
|
||||
doc.add_paragraph(f"网站: {article.website.name}")
|
||||
doc.add_paragraph(f"URL: {article.url}")
|
||||
doc.add_paragraph(f"发布时间: {article.pub_date.strftime('%Y-%m-%d %H:%M:%S') if article.pub_date else 'N/A'}")
|
||||
doc.add_paragraph(
|
||||
f"发布时间: {article.pub_date.strftime('%Y-%m-%d %H:%M:%S') if article.pub_date else 'N/A'}")
|
||||
doc.add_paragraph(f"创建时间: {article.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
# 添加文章内容
|
||||
@@ -190,12 +198,14 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
|
||||
# 创建HttpResponse
|
||||
from django.http import HttpResponse
|
||||
response = HttpResponse(buffer.getvalue(), content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
|
||||
response = HttpResponse(buffer.getvalue(),
|
||||
content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
|
||||
response['Content-Disposition'] = 'attachment; filename=articles.docx'
|
||||
return response
|
||||
|
||||
export_as_word.short_description = "导出选中文章为Word格式"
|
||||
|
||||
|
||||
# 为不同网站创建专门的文章管理类
|
||||
class NewsCnArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'pub_date')
|
||||
@@ -258,6 +268,7 @@ class NewsCnArticleAdmin(admin.ModelAdmin):
|
||||
|
||||
export_as_json.short_description = "导出选中文章为JSON格式"
|
||||
|
||||
|
||||
class DongfangyancaoArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'pub_date')
|
||||
search_fields = ('title', 'content')
|
||||
@@ -329,6 +340,7 @@ class DongfangyancaoArticleAdmin(admin.ModelAdmin):
|
||||
|
||||
export_as_json.short_description = "导出选中文章为JSON格式"
|
||||
|
||||
|
||||
# 在各自的管理站点中注册模型
|
||||
news_cn_admin.register(Website, WebsiteAdmin)
|
||||
news_cn_admin.register(Article, NewsCnArticleAdmin)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# core/management/commands/crawl_dongfangyancao.py
|
||||
from django.core.management.base import BaseCommand
|
||||
from core.models import Website
|
||||
from core.utils import full_site_crawler
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# core/management/commands/crawl_xinhua.py
|
||||
from django.core.management.base import BaseCommand
|
||||
from core.models import Website
|
||||
from core.utils import full_site_crawler
|
||||
|
||||
@@ -103,7 +103,8 @@ class Command(BaseCommand):
|
||||
writer.writeheader()
|
||||
for article_data in articles_data:
|
||||
# 将列表转换为字符串以便在CSV中存储
|
||||
article_data['media_files'] = ';'.join(article_data['media_files']) if article_data['media_files'] else ''
|
||||
article_data['media_files'] = ';'.join(article_data['media_files']) if article_data[
|
||||
'media_files'] else ''
|
||||
writer.writerow(article_data)
|
||||
|
||||
# 添加Word格式导出方法
|
||||
@@ -215,7 +216,8 @@ class Command(BaseCommand):
|
||||
writer = csv.DictWriter(csv_buffer, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
for article_data in articles_data:
|
||||
article_data['media_files'] = ';'.join(article_data['media_files']) if article_data['media_files'] else ''
|
||||
article_data['media_files'] = ';'.join(article_data['media_files']) if article_data[
|
||||
'media_files'] else ''
|
||||
writer.writerow(article_data)
|
||||
zipf.writestr(data_filename, csv_buffer.getvalue())
|
||||
# 添加Word格式支持
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.article-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
@@ -20,32 +21,38 @@
|
||||
padding: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background: #ecf0f1;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
@@ -55,6 +62,7 @@
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
@@ -20,18 +21,21 @@
|
||||
padding: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #f1f8ff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.filters a {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
@@ -41,38 +45,47 @@
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.filters a.active {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #2980b9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.pagination a {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
@@ -82,38 +95,101 @@
|
||||
border-radius: 4px;
|
||||
margin: 0 2px; /* 修改:调整页码间距 */
|
||||
}
|
||||
|
||||
.pagination a:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.pagination span {
|
||||
margin: 0 10px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 新增:当前页码样式 */
|
||||
.pagination .current {
|
||||
background-color: #2980b9;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* 新增:省略号样式 */
|
||||
.pagination .ellipsis {
|
||||
display: inline-block;
|
||||
padding: 8px 4px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 新增:搜索框样式 */
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #f1f8ff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.search-form input[type="text"] {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 300px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.search-form input[type="submit"] {
|
||||
padding: 8px 16px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-form input[type="submit"]:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.search-info {
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>绿色课堂文章列表</h1>
|
||||
|
||||
<!-- 新增:返回首页链接 -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
<a href="{% url 'article_list' %}" style="color: #3498db; text-decoration: none;">← 返回首页</a>
|
||||
</div>
|
||||
|
||||
<!-- 新增:搜索表单 -->
|
||||
<div class="search-form">
|
||||
<form method="get">
|
||||
<input type="text" name="q" placeholder="输入关键词搜索文章..." value="{{ search_query }}">
|
||||
{% if selected_website %}
|
||||
<input type="hidden" name="website" value="{{ selected_website.id }}">
|
||||
{% endif %}
|
||||
<input type="submit" value="搜索">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<strong>按网站筛选:</strong>
|
||||
<a href="{% url 'article_list' %}" {% if not selected_website %}class="active"{% endif %}>全部</a>
|
||||
<a href="{% url 'article_list' %}{% if search_query %}?q={{ search_query }}{% endif %}" {% if not selected_website %}class="active" {% endif %}>全部</a>
|
||||
{% for website in websites %}
|
||||
<a href="?website={{ website.id }}" {% if selected_website.id == website.id %}class="active"{% endif %}>{{ website.name }}</a>
|
||||
<a href="?website={{ website.id }}{% if search_query %}&q={{ search_query }}{% endif %}" {% if selected_website and selected_website.id == website.id %}class="active" {% endif %}>{{ website.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- 新增:搜索结果信息 -->
|
||||
{% if search_query %}
|
||||
<div class="search-info">
|
||||
搜索 "{{ search_query }}" 找到 {{ page_obj.paginator.count }} 篇文章
|
||||
<a href="{% if selected_website %}?website={{ selected_website.id }}{% else %}{% url 'article_list' %}{% endif %}">清除搜索</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<ul>
|
||||
{% for article in page_obj %}
|
||||
<li>
|
||||
@@ -128,11 +204,11 @@
|
||||
<div class="pagination">
|
||||
{% if page_obj.has_previous %}
|
||||
{% if selected_website %}
|
||||
<a href="?website={{ selected_website.id }}&page=1">« 首页</a>
|
||||
<a href="?website={{ selected_website.id }}&page={{ page_obj.previous_page_number }}">上一页</a>
|
||||
<a href="?website={{ selected_website.id }}{% if search_query %}&q={{ search_query }}{% endif %}&page=1">« 首页</a>
|
||||
<a href="?website={{ selected_website.id }}{% if search_query %}&q={{ search_query }}{% endif %}&page={{ page_obj.previous_page_number }}">上一页</a>
|
||||
{% else %}
|
||||
<a href="?page=1">« 首页</a>
|
||||
<a href="?page={{ page_obj.previous_page_number }}">上一页</a>
|
||||
<a href="?{% if search_query %}q={{ search_query }}&{% endif %}page=1">« 首页</a>
|
||||
<a href="?{% if search_query %}q={{ search_query }}&{% endif %}page={{ page_obj.previous_page_number }}">上一页</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -145,15 +221,15 @@
|
||||
<a href="#" class="current">{{ num }}</a>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
{% if selected_website %}
|
||||
<a href="?website={{ selected_website.id }}&page={{ num }}">{{ num }}</a>
|
||||
<a href="?website={{ selected_website.id }}{% if search_query %}&q={{ search_query }}{% endif %}&page={{ num }}">{{ num }}</a>
|
||||
{% else %}
|
||||
<a href="?page={{ num }}">{{ num }}</a>
|
||||
<a href="?{% if search_query %}q={{ search_query }}&{% endif %}page={{ num }}">{{ num }}</a>
|
||||
{% endif %}
|
||||
{% elif num == 1 or num == paginator.num_pages %}
|
||||
{% if selected_website %}
|
||||
<a href="?website={{ selected_website.id }}&page={{ num }}">{{ num }}</a>
|
||||
<a href="?website={{ selected_website.id }}{% if search_query %}&q={{ search_query }}{% endif %}&page={{ num }}">{{ num }}</a>
|
||||
{% else %}
|
||||
<a href="?page={{ num }}">{{ num }}</a>
|
||||
<a href="?{% if search_query %}q={{ search_query }}&{% endif %}page={{ num }}">{{ num }}</a>
|
||||
{% endif %}
|
||||
{% elif num == page_obj.number|add:'-3' or num == page_obj.number|add:'3' %}
|
||||
<span class="ellipsis">...</span>
|
||||
@@ -163,11 +239,11 @@
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
{% if selected_website %}
|
||||
<a href="?website={{ selected_website.id }}&page={{ page_obj.next_page_number }}">下一页</a>
|
||||
<a href="?website={{ selected_website.id }}&page={{ page_obj.paginator.num_pages }}">末页 »</a>
|
||||
<a href="?website={{ selected_website.id }}{% if search_query %}&q={{ search_query }}{% endif %}&page={{ page_obj.next_page_number }}">下一页</a>
|
||||
<a href="?website={{ selected_website.id }}{% if search_query %}&q={{ search_query }}{% endif %}&page={{ page_obj.paginator.num_pages }}">末页 »</a>
|
||||
{% else %}
|
||||
<a href="?page={{ page_obj.next_page_number }}">下一页</a>
|
||||
<a href="?page={{ page_obj.paginator.num_pages }}">末页 »</a>
|
||||
<a href="?{% if search_query %}q={{ search_query }}&{% endif %}page={{ page_obj.next_page_number }}">下一页</a>
|
||||
<a href="?{% if search_query %}q={{ search_query }}&{% endif %}page={{ page_obj.paginator.num_pages }}">末页 »</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,44 @@
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.shortcuts import render
|
||||
from django.core.paginator import Paginator
|
||||
from .models import Article
|
||||
|
||||
from .models import Article, Website
|
||||
|
||||
def article_list(request):
|
||||
"""
|
||||
显示文章列表的视图函数
|
||||
"""
|
||||
articles = Article.objects.all().order_by('-created_at')
|
||||
paginator = Paginator(articles, 20) # 每页显示10篇文章
|
||||
# 获取所有启用的网站
|
||||
websites = Website.objects.filter(enabled=True)
|
||||
|
||||
# 获取筛选网站
|
||||
selected_website = None
|
||||
articles = Article.objects.all()
|
||||
|
||||
website_id = request.GET.get('website')
|
||||
if website_id:
|
||||
try:
|
||||
selected_website = Website.objects.get(id=website_id)
|
||||
articles = articles.filter(website=selected_website)
|
||||
except Website.DoesNotExist:
|
||||
pass
|
||||
|
||||
# 新增:处理关键词搜索
|
||||
search_query = request.GET.get('q')
|
||||
if search_query:
|
||||
articles = articles.filter(title__icontains=search_query)
|
||||
|
||||
# 按创建时间倒序排列
|
||||
articles = articles.order_by('-created_at')
|
||||
|
||||
# 分页
|
||||
paginator = Paginator(articles, 10) # 每页显示10篇文章
|
||||
page_number = request.GET.get('page')
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
return render(request, 'core/article_list.html', {
|
||||
'page_obj': page_obj
|
||||
'page_obj': page_obj,
|
||||
'websites': websites,
|
||||
'selected_website': selected_website,
|
||||
# 新增:传递搜索关键词到模板
|
||||
'search_query': search_query
|
||||
})
|
||||
|
||||
|
||||
def article_detail(request, article_id):
|
||||
"""
|
||||
显示文章详情的视图函数
|
||||
"""
|
||||
article = get_object_or_404(Article, id=article_id)
|
||||
return render(request, 'core/article_detail.html', {
|
||||
'article': article
|
||||
})
|
||||
article = Article.objects.get(id=article_id)
|
||||
return render(request, 'core/article_detail.html', {'article': article})
|
||||
|
||||
31
requirements.txt
Normal file
31
requirements.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
asgiref==3.9.1
|
||||
asttokens==3.0.0
|
||||
beautifulsoup4==4.13.4
|
||||
bs4==0.0.2
|
||||
certifi==2025.8.3
|
||||
charset-normalizer==3.4.3
|
||||
decorator==5.2.1
|
||||
Django==5.1
|
||||
executing==2.2.0
|
||||
idna==3.10
|
||||
ipython==9.4.0
|
||||
ipython_pygments_lexers==1.1.1
|
||||
jedi==0.19.2
|
||||
lxml==6.0.0
|
||||
matplotlib-inline==0.1.7
|
||||
parso==0.8.4
|
||||
pexpect==4.9.0
|
||||
prompt_toolkit==3.0.51
|
||||
ptyprocess==0.7.0
|
||||
pure_eval==0.2.3
|
||||
Pygments==2.19.2
|
||||
python-docx==1.2.0
|
||||
requests==2.32.4
|
||||
soupsieve==2.7
|
||||
sqlparse==0.5.3
|
||||
stack-data==0.6.3
|
||||
traitlets==5.14.3
|
||||
typing_extensions==4.14.1
|
||||
urllib3==2.5.0
|
||||
uv==0.8.8
|
||||
wcwidth==0.2.13
|
||||
Reference in New Issue
Block a user