wmw_branch
starrysky yuanye 3 months ago
parent f01bf37760
commit 515819bc3d

@ -0,0 +1,135 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect, get_object_or_404
from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy
from .models import Article, Category, Tag
from .forms import ArticleForm
class ArticleCreateView(LoginRequiredMixin, CreateView):
"""创建文章视图"""
model = Article
form_class = ArticleForm
template_name = 'blog/article_create.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_categories'] = Category.objects.all()
context['all_tags'] = Tag.objects.all()
return context
def form_valid(self, form):
# 先不保存表单,先处理分类
form.instance.author = self.request.user
# 处理分类
category_name = form.cleaned_data['category']
category, created = Category.objects.get_or_create(
name=category_name,
defaults={'parent_category': None}
)
# 直接设置分类对象,而不是名称
form.instance.category = category
# 先保存文章实例,以便可以添加多对多关系
# 但在保存前先从表单中移除category字段避免表单尝试保存它
category_temp = form.cleaned_data.pop('category')
tags_temp = form.cleaned_data.pop('tags')
response = super().form_valid(form)
# 处理标签
if tags_temp:
tag_names = [tag.strip() for tag in tags_temp.split(',') if tag.strip()]
for tag_name in tag_names:
tag, created = Tag.objects.get_or_create(name=tag_name)
form.instance.tags.add(tag)
return response
def get_success_url(self):
return reverse_lazy('blog:detailbyid', kwargs={
'article_id': self.object.id,
'year': self.object.creation_time.year,
'month': self.object.creation_time.month,
'day': self.object.creation_time.day
})
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
"""更新文章视图"""
model = Article
form_class = ArticleForm
template_name = 'blog/article_edit.html'
pk_url_kwarg = 'article_id'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_categories'] = Category.objects.all()
context['all_tags'] = Tag.objects.all()
return context
def get_initial(self):
initial = super().get_initial()
# 设置分类初始值
if self.object.category:
initial['category'] = self.object.category.name
# 设置标签初始值
if self.object.tags.exists():
tag_names = [tag.name for tag in self.object.tags.all()]
initial['tags'] = ', '.join(tag_names)
return initial
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
# 只有文章作者或管理员可以编辑
if obj.author != request.user and not request.user.is_superuser:
return redirect('blog:detailbyid',
article_id=obj.id,
year=obj.creation_time.year,
month=obj.creation_time.month,
day=obj.creation_time.day)
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
# 先不保存表单,先处理分类
form.instance.author = self.request.user
# 处理分类
category_name = form.cleaned_data['category']
category, created = Category.objects.get_or_create(
name=category_name,
defaults={'parent_category': None}
)
# 直接设置分类对象,而不是名称
form.instance.category = category
# 先保存文章实例,以便可以添加多对多关系
# 但在保存前先从表单中移除category字段避免表单尝试保存它
category_temp = form.cleaned_data.pop('category')
tags_temp = form.cleaned_data.pop('tags')
response = super().form_valid(form)
# 处理标签
if tags_temp:
tag_names = [tag.strip() for tag in tags_temp.split(',') if tag.strip()]
form.instance.tags.clear() # 清除现有标签
for tag_name in tag_names:
tag, created = Tag.objects.get_or_create(name=tag_name)
form.instance.tags.add(tag)
else:
form.instance.tags.clear() # 如果没有标签,清除所有标签
return response
def get_success_url(self):
return reverse_lazy('blog:detailbyid', kwargs={
'article_id': self.object.id,
'year': self.object.creation_time.year,
'month': self.object.creation_time.month,
'day': self.object.creation_time.day
})

@ -1,7 +1,9 @@
import logging
from django import forms
from django.utils.translation import gettext_lazy as _
from haystack.forms import SearchForm
from .models import Article, Category, Tag
logger = logging.getLogger(__name__)
@ -17,3 +19,62 @@ class BlogSearchForm(SearchForm):
if self.cleaned_data['querydata']:
logger.info(self.cleaned_data['querydata'])
return datas
class ArticleForm(forms.ModelForm):
"""文章表单"""
title = forms.CharField(label='标题', max_length=200, required=True)
body = forms.CharField(label='内容', widget=forms.Textarea, required=True)
status = forms.ChoiceField(
label='状态',
choices=[('p', '发布'), ('d', '草稿')],
initial='p',
widget=forms.Select,
required=True
)
comment_status = forms.ChoiceField(
label='评论状态',
choices=[('o', '开启'), ('c', '关闭')],
initial='o',
widget=forms.Select,
required=True
)
type = forms.ChoiceField(
label='类型',
choices=[('a', '文章'), ('p', '页面')],
initial='a',
widget=forms.Select,
required=True
)
show_toc = forms.BooleanField(label='显示目录', required=False)
category = forms.CharField(
label='分类',
required=True,
widget=forms.TextInput(attrs={
'class': 'form-control',
'list': 'category-list',
'placeholder': '选择或输入分类名称'
})
)
tags = forms.CharField(
label='标签',
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'list': 'tag-list',
'placeholder': '选择或输入标签,多个标签用逗号分隔'
})
)
class Meta:
model = Article
fields = ['title', 'body', 'status', 'comment_status', 'type', 'show_toc']
def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'class': 'form-control'})
self.fields['body'].widget.attrs.update({'class': 'form-control', 'rows': 20})
self.fields['status'].widget.attrs.update({'class': 'form-control'})
self.fields['comment_status'].widget.attrs.update({'class': 'form-control'})
self.fields['type'].widget.attrs.update({'class': 'form-control'})
self.fields['show_toc'].widget.attrs.update({'class': 'form-check-input'})

@ -0,0 +1,41 @@
from django.core.management.base import BaseCommand
from blog.models import Category
class Command(BaseCommand):
help = '创建文章分类'
def handle(self, *args, **options):
categories = [
'技术',
'生活',
'旅行',
'美食',
'摄影',
'读书',
'电影',
'音乐',
'编程',
'设计',
'健康',
'教育',
'职场',
'财经',
'历史',
'文化',
'体育',
'游戏',
'科技'
]
for name in categories:
category, created = Category.objects.get_or_create(
name=name,
defaults={'parent_category': None}
)
if created:
self.stdout.write(self.style.SUCCESS(f'创建分类: {name}'))
else:
self.stdout.write(self.style.WARNING(f'分类已存在: {name}'))
self.stdout.write(self.style.SUCCESS('分类创建完成'))

@ -0,0 +1,13 @@
from django.core.management.base import BaseCommand
from blog.models import Category
class Command(BaseCommand):
help = '列出所有文章分类'
def handle(self, *args, **options):
categories = Category.objects.all()
self.stdout.write('当前数据库中的分类:')
for category in categories:
self.stdout.write(f'- {category.name} (ID: {category.id})')
self.stdout.write(f'总计: {categories.count()} 个分类')

@ -2,6 +2,7 @@ from django.urls import path
from django.views.decorators.cache import cache_page
from . import views
from . import article_views
app_name = "blog"
urlpatterns = [
@ -17,6 +18,18 @@ urlpatterns = [
r'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html',
views.ArticleDetailView.as_view(),
name='detailbyid'),
path(
r'article/create/',
article_views.ArticleCreateView.as_view(),
name='article_create'),
path(
r'article/edit/<int:article_id>/',
article_views.ArticleUpdateView.as_view(),
name='article_edit'),
path(
r'my-articles/',
views.my_articles,
name='my_articles'),
path(
r'category/<slug:category_name>.html',
views.CategoryDetailView.as_view(),

@ -378,3 +378,82 @@ def permission_denied_view(
def clean_cache_view(request):
cache.clear()
return HttpResponse('ok')
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy
from .forms import ArticleForm
class ArticleCreateView(LoginRequiredMixin, CreateView):
"""创建文章视图"""
model = Article
form_class = ArticleForm
template_name = 'blog/article_create.html'
def form_valid(self, form):
form.instance.author = self.request.user
# 处理分类
category_name = form.cleaned_data['category']
category, created = Category.objects.get_or_create(
name=category_name,
defaults={'parent_category': None}
)
form.instance.category = category
# 处理标签
tags_str = form.cleaned_data['tags']
if tags_str:
tag_names = [tag.strip() for tag in tags_str.split(',') if tag.strip()]
form.instance.save() # 保存文章实例,以便可以添加多对多关系
for tag_name in tag_names:
tag, created = Tag.objects.get_or_create(name=tag_name)
form.instance.tags.add(tag)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('blog:detailbyid', kwargs={
'article_id': self.object.id,
'year': self.object.creation_time.year,
'month': self.object.creation_time.month,
'day': self.object.creation_time.day
})
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
"""更新文章视图"""
model = Article
form_class = ArticleForm
template_name = 'blog/article_edit.html'
pk_url_kwarg = 'article_id'
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
# 只有文章作者或管理员可以编辑
if obj.author != request.user and not request.user.is_superuser:
return redirect('blog:detailbyid',
article_id=obj.id,
year=obj.creation_time.year,
month=obj.creation_time.month,
day=obj.creation_time.day)
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return reverse_lazy('blog:detailbyid', kwargs={
'article_id': self.object.id,
'year': self.object.creation_time.year,
'month': self.object.creation_time.month,
'day': self.object.creation_time.day
})
@login_required
def my_articles(request):
"""显示当前用户的文章列表"""
articles = Article.objects.filter(author=request.user)
return render(request, 'blog/my_articles.html', {'articles': articles})

@ -665,3 +665,95 @@ msgstr "快捷登录"
#: .\templates\share_layout\nav.html:26
msgid "Article archive"
msgstr "文章归档"
#: .\templates\share_layout\nav.html:29
msgid "Create Article"
msgstr "创建文章"
#: .\templates\share_layout\nav.html:33
msgid "My Articles"
msgstr "我的文章"
#: .\templates\blog\article_create.html:5
msgid "Create Article"
msgstr "创建文章"
#: .\templates\blog\article_edit.html:5
msgid "Edit Article"
msgstr "编辑文章"
#: .\templates\blog\my_articles.html:5
msgid "My Articles"
msgstr "我的文章"
#: .\templates\share_layout\nav.html:29
msgid "发布文章"
msgstr "发布文章"
#: .\templates\share_layout\nav.html:33
msgid "我的文章"
msgstr "我的文章"
#: .\templates\blog\article_create.html:97
msgid "发布"
msgstr "发布"
#: .\templates\blog\article_create.html:98
msgid "取消"
msgstr "取消"
#: .\templates\blog\article_edit.html:97
msgid "更新"
msgstr "更新"
#: .\templates\blog\article_edit.html:98
msgid "取消"
msgstr "取消"
#: .\templates\blog\my_articles.html:13
msgid "发布新文章"
msgstr "发布新文章"
#: .\templates\blog\my_articles.html:22
msgid "标题"
msgstr "标题"
#: .\templates\blog\my_articles.html:23
msgid "状态"
msgstr "状态"
#: .\templates\blog\my_articles.html:24
msgid "分类"
msgstr "分类"
#: .\templates\blog\my_articles.html:25
msgid "创建时间"
msgstr "创建时间"
#: .\templates\blog\my_articles.html:26
msgid "浏览量"
msgstr "浏览量"
#: .\templates\blog\my_articles.html:27
msgid "操作"
msgstr "操作"
#: .\templates\blog\my_articles.html:38
msgid "已发布"
msgstr "已发布"
#: .\templates\blog\my_articles.html:40
msgid "草稿"
msgstr "草稿"
#: .\templates\blog\my_articles.html:48
msgid "编辑"
msgstr "编辑"
#: .\templates\blog\my_articles.html:58
msgid "您还没有创建任何文章。"
msgstr "您还没有创建任何文章。"
#: .\templates\blog\my_articles.html:59
msgid "创建您的第一篇文章"
msgstr "创建您的第一篇文章"

@ -0,0 +1,116 @@
{% extends "share_layout/base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "Create Article" %}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{% trans "Create Article" %}</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.title.id_for_label }}" class="form-label">{{ form.title.label }}</label>
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger">
{{ form.title.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.body.id_for_label }}" class="form-label">{{ form.body.label }}</label>
{{ form.body }}
{% if form.body.errors %}
<div class="text-danger">
{{ form.body.errors }}
</div>
{% endif %}
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="{{ form.status.id_for_label }}" class="form-label">{{ form.status.label }}</label>
{{ form.status }}
{% if form.status.errors %}
<div class="text-danger">
{{ form.status.errors }}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
<label for="{{ form.comment_status.id_for_label }}" class="form-label">{{ form.comment_status.label }}</label>
{{ form.comment_status }}
{% if form.comment_status.errors %}
<div class="text-danger">
{{ form.comment_status.errors }}
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="{{ form.type.id_for_label }}" class="form-label">{{ form.type.label }}</label>
{{ form.type }}
{% if form.type.errors %}
<div class="text-danger">
{{ form.type.errors }}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
<label for="{{ form.category.id_for_label }}" class="form-label">{{ form.category.label }}</label>
{{ form.category }}
<datalist id="category-list">
{% for category in all_categories %}
<option value="{{ category.name }}">
{% endfor %}
</datalist>
{% if form.category.errors %}
<div class="text-danger">
{{ form.category.errors }}
</div>
{% endif %}
</div>
</div>
<div class="mb-3">
<label for="{{ form.tags.id_for_label }}" class="form-label">{{ form.tags.label }}</label>
{{ form.tags }}
<datalist id="tag-list">
{% for tag in all_tags %}
<option value="{{ tag.name }}">
{% endfor %}
</datalist>
{% if form.tags.errors %}
<div class="text-danger">
{{ form.tags.errors }}
</div>
{% endif %}
</div>
<div class="mb-3 form-check">
{{ form.show_toc }}
<label for="{{ form.show_toc.id_for_label }}" class="form-check-label">{{ form.show_toc.label }}</label>
{% if form.show_toc.errors %}
<div class="text-danger">
{{ form.show_toc.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">{% trans "发布" %}</button>
<a href="{% url 'blog:index' %}" class="btn btn-secondary">{% trans "取消" %}</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,118 @@
{% extends "share_layout/base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "Edit Article" %}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{% trans "Edit Article" %}: {{ article.title }}</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.title.id_for_label }}" class="form-label">{{ form.title.label }}</label>
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger">
{{ form.title.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.body.id_for_label }}" class="form-label">{{ form.body.label }}</label>
{{ form.body }}
{% if form.body.errors %}
<div class="text-danger">
{{ form.body.errors }}
</div>
{% endif %}
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="{{ form.status.id_for_label }}" class="form-label">{{ form.status.label }}</label>
{{ form.status }}
{% if form.status.errors %}
<div class="text-danger">
{{ form.status.errors }}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
<label for="{{ form.comment_status.id_for_label }}" class="form-label">{{ form.comment_status.label }}</label>
{{ form.comment_status }}
{% if form.comment_status.errors %}
<div class="text-danger">
{{ form.comment_status.errors }}
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="{{ form.type.id_for_label }}" class="form-label">{{ form.type.label }}</label>
{{ form.type }}
{% if form.type.errors %}
<div class="text-danger">
{{ form.type.errors }}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
<label for="{{ form.category.id_for_label }}" class="form-label">{{ form.category.label }}</label>
{{ form.category }}
<datalist id="category-list">
{% for category in all_categories %}
<option value="{{ category.name }}">
{% endfor %}
</datalist>
<div class="form-text">选择现有分类或创建新分类</div>
{% if form.category.errors %}
<div class="text-danger">
{{ form.category.errors }}
</div>
{% endif %}
</div>
</div>
<div class="mb-3">
<label for="{{ form.tags.id_for_label }}" class="form-label">{{ form.tags.label }}</label>
{{ form.tags }}
<datalist id="tag-list">
{% for tag in all_tags %}
<option value="{{ tag.name }}">
{% endfor %}
</datalist>
<div class="form-text">选择现有标签或创建新标签,多个标签用逗号分隔</div>
{% if form.tags.errors %}
<div class="text-danger">
{{ form.tags.errors }}
</div>
{% endif %}
</div>
<div class="mb-3 form-check">
{{ form.show_toc }}
<label for="{{ form.show_toc.id_for_label }}" class="form-check-label">{{ form.show_toc.label }}</label>
{% if form.show_toc.errors %}
<div class="text-danger">
{{ form.show_toc.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">{% trans "更新" %}</button>
<a href="{{ article.get_absolute_url }}" class="btn btn-secondary">{% trans "取消" %}</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,66 @@
{% extends "share_layout/base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "My Articles" %}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>{% trans "My Articles" %}</h2>
<a href="{% url 'blog:article_create' %}" class="btn btn-primary">
<i class="fas fa-plus"></i> {% trans "发布新文章" %}
</a>
</div>
{% if articles %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "标题" %}</th>
<th>{% trans "状态" %}</th>
<th>{% trans "分类" %}</th>
<th>{% trans "创建时间" %}</th>
<th>{% trans "浏览量" %}</th>
<th>{% trans "操作" %}</th>
</tr>
</thead>
<tbody>
{% for article in articles %}
<tr>
<td>
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
</td>
<td>
{% if article.status == 'p' %}
<span class="badge bg-success">{% trans "已发布" %}</span>
{% else %}
<span class="badge bg-secondary">{% trans "草稿" %}</span>
{% endif %}
</td>
<td>{{ article.category.name }}</td>
<td>{{ article.creation_time|date:"Y-m-d H:i" }}</td>
<td>{{ article.views }}</td>
<td>
<a href="{% url 'blog:article_edit' article.id %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit"></i> {% trans "编辑" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info">
{% trans "您还没有创建任何文章。" %}
<a href="{% url 'blog:article_create' %}">{% trans "创建您的第一篇文章" %}</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

@ -50,6 +50,8 @@
</a>
{% if user.is_superuser %}
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a>
{% elif user.is_authenticated and user == article.author %}
<a href="{% url 'blog:article_edit' article.id %}">{% trans 'edit' %}</a>
{% endif %}
</span>
</footer><!-- .entry-meta -->

@ -119,6 +119,8 @@
<ul>
<li><a href="/admin/" rel="nofollow">{% trans 'management site' %}</a></li>
{% if user.is_authenticated %}
<li><a href="{% url "blog:article_create" %}" rel="nofollow">{% trans '发布文章' %}</a></li>
<li><a href="{% url "blog:my_articles" %}" rel="nofollow">{% trans '我的文章' %}</a></li>
<li><a href="{% url "account:logout" %}" rel="nofollow">{% trans 'logout' %}</a>
</li>

@ -25,6 +25,7 @@
<a href="{% url "blog:archives" %}">{% trans 'Article archive' %}</a>
</li>
</ul>
</div>
</nav><!-- #site-navigation -->
Loading…
Cancel
Save