forked from puhanfmc3/tentest
Compare commits
114 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9be7adbd14 | 2 months ago |
|
|
beb070948b | 2 months ago |
|
|
322e77599c | 2 months ago |
|
|
3808109f8a | 2 months ago |
|
|
9b51b07881 | 2 months ago |
|
|
466a6864bc | 2 months ago |
|
|
8bc899d943 | 2 months ago |
|
|
fb5f469f4a | 2 months ago |
|
|
30400814ab | 2 months ago |
|
|
2cb5514f11 | 2 months ago |
|
|
54ab827919 | 2 months ago |
|
|
9c3bf6e105 | 2 months ago |
|
|
4abe1ee4a9 | 2 months ago |
|
|
1c77fe8e22 | 2 months ago |
|
|
184d73b9e4 | 2 months ago |
|
|
75628202e3 | 2 months ago |
|
|
5c389a179c | 2 months ago |
|
|
c47c8ea166 | 2 months ago |
|
|
9928651766 | 2 months ago |
|
|
24c10c00ce | 2 months ago |
|
|
6352e52bed | 2 months ago |
|
|
f9bbb4c87e | 2 months ago |
|
|
26d4c9752f | 2 months ago |
|
|
87d4d4e590 | 2 months ago |
|
|
c8413e7577 | 2 months ago |
|
|
c51b8b0c56 | 2 months ago |
|
|
cedd0a20cf | 2 months ago |
|
|
72b8e67060 | 2 months ago |
|
|
18e424dd97 | 2 months ago |
|
|
d52dc2800c | 2 months ago |
|
|
e9ed2007f8 | 2 months ago |
|
|
3ad5c5a754 | 2 months ago |
|
|
dfe9b32351 | 2 months ago |
|
|
48708d60dd | 2 months ago |
|
|
d6d92db1f3 | 2 months ago |
|
|
8641273b52 | 2 months ago |
|
|
d888017d59 | 2 months ago |
|
|
afe4e723e0 | 2 months ago |
|
|
4c2e221de9 | 2 months ago |
|
|
ebd9538c68 | 2 months ago |
|
|
0cc903fbc4 | 2 months ago |
|
|
8380c07eab | 2 months ago |
|
|
2da8518e4f | 2 months ago |
|
|
37d35baab4 | 2 months ago |
|
|
db19f9ab12 | 2 months ago |
|
|
de046165a5 | 2 months ago |
|
|
d4b3780968 | 2 months ago |
|
|
763ee35ee5 | 2 months ago |
|
|
c36961b293 | 2 months ago |
|
|
6143a4a432 | 2 months ago |
|
|
f91eca2d4d | 2 months ago |
|
|
51967e47d9 | 2 months ago |
|
|
0d52b1cd2e | 2 months ago |
|
|
12ee321c7f | 2 months ago |
|
|
017f932fe9 | 2 months ago |
|
|
506541d6f7 | 2 months ago |
|
|
c06d15ba90 | 3 months ago |
|
|
074e9f5476 | 3 months ago |
|
|
dcbee62795 | 3 months ago |
|
|
58fcb6b24a | 3 months ago |
|
|
9e1408389e | 3 months ago |
|
|
746a6adc0b | 3 months ago |
|
|
d208e833ff | 3 months ago |
|
|
2c96334b4b | 3 months ago |
|
|
260a92ff65 | 3 months ago |
|
|
7fd7fde93d | 3 months ago |
|
|
f2cdcfba87 | 3 months ago |
|
|
245a18b53f | 3 months ago |
|
|
b59800b7a9 | 3 months ago |
|
|
509ec8360d | 3 months ago |
|
|
32dd36a0e2 | 3 months ago |
|
|
8e8d9ea64c | 3 months ago |
|
|
e899883087 | 3 months ago |
|
|
5be757fbbf | 3 months ago |
|
|
59897191ac | 3 months ago |
|
|
a07a4e068b | 3 months ago |
|
|
9792d18d9a | 3 months ago |
|
|
ee8fcfefcb | 3 months ago |
|
|
3e0fc26d2c | 3 months ago |
|
|
22a24281be | 3 months ago |
|
|
62e81357dd | 3 months ago |
|
|
15da01e6e9 | 3 months ago |
|
|
84f4c9e8f3 | 3 months ago |
|
|
77df3e9e9e | 3 months ago |
|
|
ac177291fe | 3 months ago |
|
|
43fabaeb51 | 3 months ago |
|
|
580968353e | 3 months ago |
|
|
65f769084f | 3 months ago |
|
|
a7541d3093 | 3 months ago |
|
|
1a332d9c92 | 3 months ago |
|
|
ed17f8fd02 | 3 months ago |
|
|
9bdaac90a5 | 3 months ago |
|
|
afc75321c9 | 3 months ago |
|
|
3b612a260f | 3 months ago |
|
|
04a70ac42b | 3 months ago |
|
|
feddbef978 | 3 months ago |
|
|
cf77dfa0e7 | 3 months ago |
|
|
2bb0abdc49 | 3 months ago |
|
|
cca871a53b | 3 months ago |
|
|
4762f52d39 | 3 months ago |
|
|
cc874b667f | 3 months ago |
|
|
797f57fe57 | 3 months ago |
|
|
45f725295e | 3 months ago |
|
|
2b8a829300 | 3 months ago |
|
|
c6fb2238ac | 3 months ago |
|
|
d4f097745d | 3 months ago |
|
|
fd175c6b8f | 3 months ago |
|
|
731d3625dd | 3 months ago |
|
|
d0115e3660 | 3 months ago |
|
|
735a5e5a66 | 4 months ago |
|
|
8583e895d3 | 4 months ago |
|
|
c138ab8d57 | 4 months ago |
|
|
c3c378b76b | 4 months ago |
|
|
51df660506 | 4 months ago |
@ -1,3 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
name = 'accounts'
|
||||
@ -1,26 +0,0 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
|
||||
class EmailOrUsernameModelBackend(ModelBackend):
|
||||
"""
|
||||
允许使用用户名或邮箱登录
|
||||
"""
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
if '@' in username:
|
||||
kwargs = {'email': username}
|
||||
else:
|
||||
kwargs = {'username': username}
|
||||
try:
|
||||
user = get_user_model().objects.get(**kwargs)
|
||||
if user.check_password(password):
|
||||
return user
|
||||
except get_user_model().DoesNotExist:
|
||||
return None
|
||||
|
||||
def get_user(self, username):
|
||||
try:
|
||||
return get_user_model().objects.get(pk=username)
|
||||
except get_user_model().DoesNotExist:
|
||||
return None
|
||||
@ -1,49 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def disable_commentstatus(modeladmin, request, queryset):
|
||||
queryset.update(is_enable=False)
|
||||
|
||||
|
||||
def enable_commentstatus(modeladmin, request, queryset):
|
||||
queryset.update(is_enable=True)
|
||||
|
||||
|
||||
disable_commentstatus.short_description = _('Disable comments')
|
||||
enable_commentstatus.short_description = _('Enable comments')
|
||||
|
||||
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_per_page = 20
|
||||
list_display = (
|
||||
'id',
|
||||
'body',
|
||||
'link_to_userinfo',
|
||||
'link_to_article',
|
||||
'is_enable',
|
||||
'creation_time')
|
||||
list_display_links = ('id', 'body', 'is_enable')
|
||||
list_filter = ('is_enable',)
|
||||
exclude = ('creation_time', 'last_modify_time')
|
||||
actions = [disable_commentstatus, enable_commentstatus]
|
||||
raw_id_fields = ('author', 'article')
|
||||
search_fields = ('body',)
|
||||
|
||||
def link_to_userinfo(self, obj):
|
||||
info = (obj.author._meta.app_label, obj.author._meta.model_name)
|
||||
link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,))
|
||||
return format_html(
|
||||
u'<a href="%s">%s</a>' %
|
||||
(link, obj.author.nickname if obj.author.nickname else obj.author.email))
|
||||
|
||||
def link_to_article(self, obj):
|
||||
info = (obj.article._meta.app_label, obj.article._meta.model_name)
|
||||
link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,))
|
||||
return format_html(
|
||||
u'<a href="%s">%s</a>' % (link, obj.article.title))
|
||||
|
||||
link_to_userinfo.short_description = _('User')
|
||||
link_to_article.short_description = _('Article')
|
||||
@ -1,5 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CommentsConfig(AppConfig):
|
||||
name = 'comments'
|
||||
@ -1,13 +0,0 @@
|
||||
from django import forms
|
||||
from django.forms import ModelForm
|
||||
|
||||
from .models import Comment
|
||||
|
||||
|
||||
class CommentForm(ModelForm):
|
||||
parent_comment_id = forms.IntegerField(
|
||||
widget=forms.HiddenInput, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = ['body']
|
||||
@ -1,39 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from blog.models import Article
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class Comment(models.Model):
|
||||
body = models.TextField('正文', max_length=300)
|
||||
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||
last_modify_time = models.DateTimeField(_('last modify time'), default=now)
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
verbose_name=_('author'),
|
||||
on_delete=models.CASCADE)
|
||||
article = models.ForeignKey(
|
||||
Article,
|
||||
verbose_name=_('article'),
|
||||
on_delete=models.CASCADE)
|
||||
parent_comment = models.ForeignKey(
|
||||
'self',
|
||||
verbose_name=_('parent comment'),
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE)
|
||||
is_enable = models.BooleanField(_('enable'),
|
||||
default=False, blank=False, null=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-id']
|
||||
verbose_name = _('comment')
|
||||
verbose_name_plural = verbose_name
|
||||
get_latest_by = 'id'
|
||||
|
||||
def __str__(self):
|
||||
return self.body
|
||||
@ -1,109 +0,0 @@
|
||||
from django.test import Client, RequestFactory, TransactionTestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from accounts.models import BlogUser
|
||||
from blog.models import Category, Article
|
||||
from comments.models import Comment
|
||||
from comments.templatetags.comments_tags import *
|
||||
from djangoblog.utils import get_max_articleid_commentid
|
||||
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class CommentsTest(TransactionTestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
from blog.models import BlogSettings
|
||||
value = BlogSettings()
|
||||
value.comment_need_review = True
|
||||
value.save()
|
||||
|
||||
self.user = BlogUser.objects.create_superuser(
|
||||
email="liangliangyy1@gmail.com",
|
||||
username="liangliangyy1",
|
||||
password="liangliangyy1")
|
||||
|
||||
def update_article_comment_status(self, article):
|
||||
comments = article.comment_set.all()
|
||||
for comment in comments:
|
||||
comment.is_enable = True
|
||||
comment.save()
|
||||
|
||||
def test_validate_comment(self):
|
||||
self.client.login(username='liangliangyy1', password='liangliangyy1')
|
||||
|
||||
category = Category()
|
||||
category.name = "categoryccc"
|
||||
category.save()
|
||||
|
||||
article = Article()
|
||||
article.title = "nicetitleccc"
|
||||
article.body = "nicecontentccc"
|
||||
article.author = self.user
|
||||
article.category = category
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
article.save()
|
||||
|
||||
comment_url = reverse(
|
||||
'comments:postcomment', kwargs={
|
||||
'article_id': article.id})
|
||||
|
||||
response = self.client.post(comment_url,
|
||||
{
|
||||
'body': '123ffffffffff'
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.assertEqual(len(article.comment_list()), 0)
|
||||
self.update_article_comment_status(article)
|
||||
|
||||
self.assertEqual(len(article.comment_list()), 1)
|
||||
|
||||
response = self.client.post(comment_url,
|
||||
{
|
||||
'body': '123ffffffffff',
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.update_article_comment_status(article)
|
||||
self.assertEqual(len(article.comment_list()), 2)
|
||||
parent_comment_id = article.comment_list()[0].id
|
||||
|
||||
response = self.client.post(comment_url,
|
||||
{
|
||||
'body': '''
|
||||
# Title1
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
[url](https://www.lylinux.net/)
|
||||
|
||||
[ddd](http://www.baidu.com)
|
||||
|
||||
|
||||
''',
|
||||
'parent_comment_id': parent_comment_id
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.update_article_comment_status(article)
|
||||
article = Article.objects.get(pk=article.pk)
|
||||
self.assertEqual(len(article.comment_list()), 3)
|
||||
comment = Comment.objects.get(id=parent_comment_id)
|
||||
tree = parse_commenttree(article.comment_list(), comment)
|
||||
self.assertEqual(len(tree), 1)
|
||||
data = show_comment_item(comment, True)
|
||||
self.assertIsNotNone(data)
|
||||
s = get_max_articleid_commentid()
|
||||
self.assertIsNotNone(s)
|
||||
|
||||
from comments.utils import send_comment_email
|
||||
send_comment_email(comment)
|
||||
@ -1,11 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "comments"
|
||||
urlpatterns = [
|
||||
path(
|
||||
'article/<int:article_id>/postcomment',
|
||||
views.CommentPostView.as_view(),
|
||||
name='postcomment'),
|
||||
]
|
||||
@ -1,38 +0,0 @@
|
||||
import logging
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from djangoblog.utils import get_current_site
|
||||
from djangoblog.utils import send_email
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def send_comment_email(comment):
|
||||
site = get_current_site().domain
|
||||
subject = _('Thanks for your comment')
|
||||
article_url = f"https://{site}{comment.article.get_absolute_url()}"
|
||||
html_content = _("""<p>Thank you very much for your comments on this site</p>
|
||||
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
|
||||
to review your comments,
|
||||
Thank you again!
|
||||
<br />
|
||||
If the link above cannot be opened, please copy this link to your browser.
|
||||
%(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
|
||||
tomail = comment.author.email
|
||||
send_email([tomail], subject, html_content)
|
||||
try:
|
||||
if comment.parent_comment:
|
||||
html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
|
||||
received a reply. <br/> %(comment_body)s
|
||||
<br/>
|
||||
go check it out!
|
||||
<br/>
|
||||
If the link above cannot be opened, please copy this link to your browser.
|
||||
%(article_url)s
|
||||
""") % {'article_url': article_url, 'article_title': comment.article.title,
|
||||
'comment_body': comment.parent_comment.body}
|
||||
tomail = comment.parent_comment.author.email
|
||||
send_email([tomail], subject, html_content)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
@ -1,63 +0,0 @@
|
||||
# Create your views here.
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from accounts.models import BlogUser
|
||||
from blog.models import Article
|
||||
from .forms import CommentForm
|
||||
from .models import Comment
|
||||
|
||||
|
||||
class CommentPostView(FormView):
|
||||
form_class = CommentForm
|
||||
template_name = 'blog/article_detail.html'
|
||||
|
||||
@method_decorator(csrf_protect)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(CommentPostView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
article_id = self.kwargs['article_id']
|
||||
article = get_object_or_404(Article, pk=article_id)
|
||||
url = article.get_absolute_url()
|
||||
return HttpResponseRedirect(url + "#comments")
|
||||
|
||||
def form_invalid(self, form):
|
||||
article_id = self.kwargs['article_id']
|
||||
article = get_object_or_404(Article, pk=article_id)
|
||||
|
||||
return self.render_to_response({
|
||||
'form': form,
|
||||
'article': article
|
||||
})
|
||||
|
||||
def form_valid(self, form):
|
||||
"""提交的数据验证合法后的逻辑"""
|
||||
user = self.request.user
|
||||
author = BlogUser.objects.get(pk=user.pk)
|
||||
article_id = self.kwargs['article_id']
|
||||
article = get_object_or_404(Article, pk=article_id)
|
||||
|
||||
if article.comment_status == 'c' or article.status == 'c':
|
||||
raise ValidationError("该文章评论已关闭.")
|
||||
comment = form.save(False)
|
||||
comment.article = article
|
||||
from djangoblog.utils import get_blog_setting
|
||||
settings = get_blog_setting()
|
||||
if not settings.comment_need_review:
|
||||
comment.is_enable = True
|
||||
comment.author = author
|
||||
|
||||
if form.cleaned_data['parent_comment_id']:
|
||||
parent_comment = Comment.objects.get(
|
||||
pk=form.cleaned_data['parent_comment_id'])
|
||||
comment.parent_comment = parent_comment
|
||||
|
||||
comment.save(True)
|
||||
return HttpResponseRedirect(
|
||||
"%s#div-comment-%d" %
|
||||
(article.get_absolute_url(), comment.pk))
|
||||
@ -1 +0,0 @@
|
||||
default_app_config = 'djangoblog.apps.DjangoblogAppConfig'
|
||||
@ -1,64 +0,0 @@
|
||||
from django.contrib.admin import AdminSite
|
||||
from django.contrib.admin.models import LogEntry
|
||||
from django.contrib.sites.admin import SiteAdmin
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from accounts.admin import *
|
||||
from blog.admin import *
|
||||
from blog.models import *
|
||||
from comments.admin import *
|
||||
from comments.models import *
|
||||
from djangoblog.logentryadmin import LogEntryAdmin
|
||||
from oauth.admin import *
|
||||
from oauth.models import *
|
||||
from owntracks.admin import *
|
||||
from owntracks.models import *
|
||||
from servermanager.admin import *
|
||||
from servermanager.models import *
|
||||
|
||||
|
||||
class DjangoBlogAdminSite(AdminSite):
|
||||
site_header = 'djangoblog administration'
|
||||
site_title = 'djangoblog site admin'
|
||||
|
||||
def __init__(self, name='admin'):
|
||||
super().__init__(name)
|
||||
|
||||
def has_permission(self, request):
|
||||
return request.user.is_superuser
|
||||
|
||||
# def get_urls(self):
|
||||
# urls = super().get_urls()
|
||||
# from django.urls import path
|
||||
# from blog.views import refresh_memcache
|
||||
#
|
||||
# my_urls = [
|
||||
# path('refresh/', self.admin_view(refresh_memcache), name="refresh"),
|
||||
# ]
|
||||
# return urls + my_urls
|
||||
|
||||
|
||||
admin_site = DjangoBlogAdminSite(name='admin')
|
||||
|
||||
admin_site.register(Article, ArticlelAdmin)
|
||||
admin_site.register(Category, CategoryAdmin)
|
||||
admin_site.register(Tag, TagAdmin)
|
||||
admin_site.register(Links, LinksAdmin)
|
||||
admin_site.register(SideBar, SideBarAdmin)
|
||||
admin_site.register(BlogSettings, BlogSettingsAdmin)
|
||||
|
||||
admin_site.register(commands, CommandsAdmin)
|
||||
admin_site.register(EmailSendLog, EmailSendLogAdmin)
|
||||
|
||||
admin_site.register(BlogUser, BlogUserAdmin)
|
||||
|
||||
admin_site.register(Comment, CommentAdmin)
|
||||
|
||||
admin_site.register(OAuthUser, OAuthUserAdmin)
|
||||
admin_site.register(OAuthConfig, OAuthConfigAdmin)
|
||||
|
||||
admin_site.register(OwnTrackLog, OwnTrackLogsAdmin)
|
||||
|
||||
admin_site.register(Site, SiteAdmin)
|
||||
|
||||
admin_site.register(LogEntry, LogEntryAdmin)
|
||||
@ -1,11 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class DjangoblogAppConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'djangoblog'
|
||||
|
||||
def ready(self):
|
||||
super().ready()
|
||||
# Import and load plugins here
|
||||
from .plugin_manage.loader import load_plugins
|
||||
load_plugins()
|
||||
@ -1,122 +0,0 @@
|
||||
import _thread
|
||||
import logging
|
||||
|
||||
import django.dispatch
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.models import LogEntry
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from comments.models import Comment
|
||||
from comments.utils import send_comment_email
|
||||
from djangoblog.spider_notify import SpiderNotify
|
||||
from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache
|
||||
from djangoblog.utils import get_current_site
|
||||
from oauth.models import OAuthUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
oauth_user_login_signal = django.dispatch.Signal(['id'])
|
||||
send_email_signal = django.dispatch.Signal(
|
||||
['emailto', 'title', 'content'])
|
||||
|
||||
|
||||
@receiver(send_email_signal)
|
||||
def send_email_signal_handler(sender, **kwargs):
|
||||
emailto = kwargs['emailto']
|
||||
title = kwargs['title']
|
||||
content = kwargs['content']
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
title,
|
||||
content,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
to=emailto)
|
||||
msg.content_subtype = "html"
|
||||
|
||||
from servermanager.models import EmailSendLog
|
||||
log = EmailSendLog()
|
||||
log.title = title
|
||||
log.content = content
|
||||
log.emailto = ','.join(emailto)
|
||||
|
||||
try:
|
||||
result = msg.send()
|
||||
log.send_result = result > 0
|
||||
except Exception as e:
|
||||
logger.error(f"失败邮箱号: {emailto}, {e}")
|
||||
log.send_result = False
|
||||
log.save()
|
||||
|
||||
|
||||
@receiver(oauth_user_login_signal)
|
||||
def oauth_user_login_signal_handler(sender, **kwargs):
|
||||
id = kwargs['id']
|
||||
oauthuser = OAuthUser.objects.get(id=id)
|
||||
site = get_current_site().domain
|
||||
if oauthuser.picture and not oauthuser.picture.find(site) >= 0:
|
||||
from djangoblog.utils import save_user_avatar
|
||||
oauthuser.picture = save_user_avatar(oauthuser.picture)
|
||||
oauthuser.save()
|
||||
|
||||
delete_sidebar_cache()
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
def model_post_save_callback(
|
||||
sender,
|
||||
instance,
|
||||
created,
|
||||
raw,
|
||||
using,
|
||||
update_fields,
|
||||
**kwargs):
|
||||
clearcache = False
|
||||
if isinstance(instance, LogEntry):
|
||||
return
|
||||
if 'get_full_url' in dir(instance):
|
||||
is_update_views = update_fields == {'views'}
|
||||
if not settings.TESTING and not is_update_views:
|
||||
try:
|
||||
notify_url = instance.get_full_url()
|
||||
SpiderNotify.baidu_notify([notify_url])
|
||||
except Exception as ex:
|
||||
logger.error("notify sipder", ex)
|
||||
if not is_update_views:
|
||||
clearcache = True
|
||||
|
||||
if isinstance(instance, Comment):
|
||||
if instance.is_enable:
|
||||
path = instance.article.get_absolute_url()
|
||||
site = get_current_site().domain
|
||||
if site.find(':') > 0:
|
||||
site = site[0:site.find(':')]
|
||||
|
||||
expire_view_cache(
|
||||
path,
|
||||
servername=site,
|
||||
serverport=80,
|
||||
key_prefix='blogdetail')
|
||||
if cache.get('seo_processor'):
|
||||
cache.delete('seo_processor')
|
||||
comment_cache_key = 'article_comments_{id}'.format(
|
||||
id=instance.article.id)
|
||||
cache.delete(comment_cache_key)
|
||||
delete_sidebar_cache()
|
||||
delete_view_cache('article_comments', [str(instance.article.pk)])
|
||||
|
||||
_thread.start_new_thread(send_comment_email, (instance,))
|
||||
|
||||
if clearcache:
|
||||
cache.clear()
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
@receiver(user_logged_out)
|
||||
def user_auth_callback(sender, request, user, **kwargs):
|
||||
if user and user.username:
|
||||
logger.info(user)
|
||||
delete_sidebar_cache()
|
||||
# cache.clear()
|
||||
@ -1,183 +0,0 @@
|
||||
from django.utils.encoding import force_str
|
||||
from elasticsearch_dsl import Q
|
||||
from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query
|
||||
from haystack.forms import ModelSearchForm
|
||||
from haystack.models import SearchResult
|
||||
from haystack.utils import log as logging
|
||||
|
||||
from blog.documents import ArticleDocument, ArticleDocumentManager
|
||||
from blog.models import Article
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ElasticSearchBackend(BaseSearchBackend):
|
||||
def __init__(self, connection_alias, **connection_options):
|
||||
super(
|
||||
ElasticSearchBackend,
|
||||
self).__init__(
|
||||
connection_alias,
|
||||
**connection_options)
|
||||
self.manager = ArticleDocumentManager()
|
||||
self.include_spelling = True
|
||||
|
||||
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
|
||||
|
||||
def _create(self, models):
|
||||
self.manager.create_index()
|
||||
docs = self._get_models(models)
|
||||
self.manager.rebuild(docs)
|
||||
|
||||
def _delete(self, models):
|
||||
for m in models:
|
||||
m.delete()
|
||||
return True
|
||||
|
||||
def _rebuild(self, models):
|
||||
models = models if models else Article.objects.all()
|
||||
docs = self.manager.convert_to_doc(models)
|
||||
self.manager.update_docs(docs)
|
||||
|
||||
def update(self, index, iterable, commit=True):
|
||||
|
||||
models = self._get_models(iterable)
|
||||
self.manager.update_docs(models)
|
||||
|
||||
def remove(self, obj_or_string):
|
||||
models = self._get_models([obj_or_string])
|
||||
self._delete(models)
|
||||
|
||||
def clear(self, models=None, commit=True):
|
||||
self.remove(None)
|
||||
|
||||
@staticmethod
|
||||
def get_suggestion(query: str) -> str:
|
||||
"""获取推荐词, 如果没有找到添加原搜索词"""
|
||||
|
||||
search = ArticleDocument.search() \
|
||||
.query("match", body=query) \
|
||||
.suggest('suggest_search', query, term={'field': 'body'}) \
|
||||
.execute()
|
||||
|
||||
keywords = []
|
||||
for suggest in search.suggest.suggest_search:
|
||||
if suggest["options"]:
|
||||
keywords.append(suggest["options"][0]["text"])
|
||||
else:
|
||||
keywords.append(suggest["text"])
|
||||
|
||||
return ' '.join(keywords)
|
||||
|
||||
@log_query
|
||||
def search(self, query_string, **kwargs):
|
||||
logger.info('search query_string:' + query_string)
|
||||
|
||||
start_offset = kwargs.get('start_offset')
|
||||
end_offset = kwargs.get('end_offset')
|
||||
|
||||
# 推荐词搜索
|
||||
if getattr(self, "is_suggest", None):
|
||||
suggestion = self.get_suggestion(query_string)
|
||||
else:
|
||||
suggestion = query_string
|
||||
|
||||
q = Q('bool',
|
||||
should=[Q('match', body=suggestion), Q('match', title=suggestion)],
|
||||
minimum_should_match="70%")
|
||||
|
||||
search = ArticleDocument.search() \
|
||||
.query('bool', filter=[q]) \
|
||||
.filter('term', status='p') \
|
||||
.filter('term', type='a') \
|
||||
.source(False)[start_offset: end_offset]
|
||||
|
||||
results = search.execute()
|
||||
hits = results['hits'].total
|
||||
raw_results = []
|
||||
for raw_result in results['hits']['hits']:
|
||||
app_label = 'blog'
|
||||
model_name = 'Article'
|
||||
additional_fields = {}
|
||||
|
||||
result_class = SearchResult
|
||||
|
||||
result = result_class(
|
||||
app_label,
|
||||
model_name,
|
||||
raw_result['_id'],
|
||||
raw_result['_score'],
|
||||
**additional_fields)
|
||||
raw_results.append(result)
|
||||
facets = {}
|
||||
spelling_suggestion = None if query_string == suggestion else suggestion
|
||||
|
||||
return {
|
||||
'results': raw_results,
|
||||
'hits': hits,
|
||||
'facets': facets,
|
||||
'spelling_suggestion': spelling_suggestion,
|
||||
}
|
||||
|
||||
|
||||
class ElasticSearchQuery(BaseSearchQuery):
|
||||
def _convert_datetime(self, date):
|
||||
if hasattr(date, 'hour'):
|
||||
return force_str(date.strftime('%Y%m%d%H%M%S'))
|
||||
else:
|
||||
return force_str(date.strftime('%Y%m%d000000'))
|
||||
|
||||
def clean(self, query_fragment):
|
||||
"""
|
||||
Provides a mechanism for sanitizing user input before presenting the
|
||||
value to the backend.
|
||||
|
||||
Whoosh 1.X differs here in that you can no longer use a backslash
|
||||
to escape reserved characters. Instead, the whole word should be
|
||||
quoted.
|
||||
"""
|
||||
words = query_fragment.split()
|
||||
cleaned_words = []
|
||||
|
||||
for word in words:
|
||||
if word in self.backend.RESERVED_WORDS:
|
||||
word = word.replace(word, word.lower())
|
||||
|
||||
for char in self.backend.RESERVED_CHARACTERS:
|
||||
if char in word:
|
||||
word = "'%s'" % word
|
||||
break
|
||||
|
||||
cleaned_words.append(word)
|
||||
|
||||
return ' '.join(cleaned_words)
|
||||
|
||||
def build_query_fragment(self, field, filter_type, value):
|
||||
return value.query_string
|
||||
|
||||
def get_count(self):
|
||||
results = self.get_results()
|
||||
return len(results) if results else 0
|
||||
|
||||
def get_spelling_suggestion(self, preferred_query=None):
|
||||
return self._spelling_suggestion
|
||||
|
||||
def build_params(self, spelling_query=None):
|
||||
kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query)
|
||||
return kwargs
|
||||
|
||||
|
||||
class ElasticSearchModelSearchForm(ModelSearchForm):
|
||||
|
||||
def search(self):
|
||||
# 是否建议搜索
|
||||
self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no"
|
||||
sqs = super().search()
|
||||
return sqs
|
||||
|
||||
|
||||
class ElasticSearchEngine(BaseEngine):
|
||||
backend = ElasticSearchBackend
|
||||
query = ElasticSearchQuery
|
||||
@ -1,40 +0,0 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.utils import timezone
|
||||
from django.utils.feedgenerator import Rss201rev2Feed
|
||||
|
||||
from blog.models import Article
|
||||
from djangoblog.utils import CommonMarkdown
|
||||
|
||||
|
||||
class DjangoBlogFeed(Feed):
|
||||
feed_type = Rss201rev2Feed
|
||||
|
||||
description = '大巧无工,重剑无锋.'
|
||||
title = "且听风吟 大巧无工,重剑无锋. "
|
||||
link = "/feed/"
|
||||
|
||||
def author_name(self):
|
||||
return get_user_model().objects.first().nickname
|
||||
|
||||
def author_link(self):
|
||||
return get_user_model().objects.first().get_absolute_url()
|
||||
|
||||
def items(self):
|
||||
return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5]
|
||||
|
||||
def item_title(self, item):
|
||||
return item.title
|
||||
|
||||
def item_description(self, item):
|
||||
return CommonMarkdown.get_markdown(item.body)
|
||||
|
||||
def feed_copyright(self):
|
||||
now = timezone.now()
|
||||
return "Copyright© {year} 且听风吟".format(year=now.year)
|
||||
|
||||
def item_link(self, item):
|
||||
return item.get_absolute_url()
|
||||
|
||||
def item_guid(self, item):
|
||||
return
|
||||
@ -1,91 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.models import DELETION
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse, NoReverseMatch
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class LogEntryAdmin(admin.ModelAdmin):
|
||||
list_filter = [
|
||||
'content_type'
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'object_repr',
|
||||
'change_message'
|
||||
]
|
||||
|
||||
list_display_links = [
|
||||
'action_time',
|
||||
'get_change_message',
|
||||
]
|
||||
list_display = [
|
||||
'action_time',
|
||||
'user_link',
|
||||
'content_type',
|
||||
'object_link',
|
||||
'get_change_message',
|
||||
]
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
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'
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def object_link(self, obj):
|
||||
object_link = escape(obj.object_repr)
|
||||
content_type = obj.content_type
|
||||
|
||||
if obj.action_flag != DELETION and content_type is not None:
|
||||
# try returning an actual link instead of object repr string
|
||||
try:
|
||||
url = reverse(
|
||||
'admin:{}_{}_change'.format(content_type.app_label,
|
||||
content_type.model),
|
||||
args=[obj.object_id]
|
||||
)
|
||||
object_link = '<a href="{}">{}</a>'.format(url, object_link)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
return mark_safe(object_link)
|
||||
|
||||
object_link.admin_order_field = 'object_repr'
|
||||
object_link.short_description = _('object')
|
||||
|
||||
def user_link(self, obj):
|
||||
content_type = ContentType.objects.get_for_model(type(obj.user))
|
||||
user_link = escape(force_str(obj.user))
|
||||
try:
|
||||
# try returning an actual link instead of object repr string
|
||||
url = reverse(
|
||||
'admin:{}_{}_change'.format(content_type.app_label,
|
||||
content_type.model),
|
||||
args=[obj.user.pk]
|
||||
)
|
||||
user_link = '<a href="{}">{}</a>'.format(url, user_link)
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
return mark_safe(user_link)
|
||||
|
||||
user_link.admin_order_field = 'user'
|
||||
user_link.short_description = _('user')
|
||||
|
||||
def get_queryset(self, request):
|
||||
queryset = super(LogEntryAdmin, self).get_queryset(request)
|
||||
return queryset.prefetch_related('content_type')
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super(LogEntryAdmin, self).get_actions(request)
|
||||
if 'delete_selected' in actions:
|
||||
del actions['delete_selected']
|
||||
return actions
|
||||
@ -1,194 +0,0 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BasePlugin:
|
||||
# 插件元数据
|
||||
PLUGIN_NAME = None
|
||||
PLUGIN_DESCRIPTION = None
|
||||
PLUGIN_VERSION = None
|
||||
PLUGIN_AUTHOR = None
|
||||
|
||||
# 插件配置
|
||||
SUPPORTED_POSITIONS = [] # 支持的显示位置
|
||||
DEFAULT_PRIORITY = 100 # 默认优先级(数字越小优先级越高)
|
||||
POSITION_PRIORITIES = {} # 各位置的优先级 {'sidebar': 50, 'article_bottom': 80}
|
||||
|
||||
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.plugin_dir = self._get_plugin_directory()
|
||||
self.plugin_slug = self._get_plugin_slug()
|
||||
|
||||
self.init_plugin()
|
||||
self.register_hooks()
|
||||
|
||||
def _get_plugin_directory(self):
|
||||
"""获取插件目录路径"""
|
||||
import inspect
|
||||
plugin_file = inspect.getfile(self.__class__)
|
||||
return Path(plugin_file).parent
|
||||
|
||||
def _get_plugin_slug(self):
|
||||
"""获取插件标识符(目录名)"""
|
||||
return self.plugin_dir.name
|
||||
|
||||
def init_plugin(self):
|
||||
"""
|
||||
插件初始化逻辑
|
||||
子类可以重写此方法来实现特定的初始化操作
|
||||
"""
|
||||
logger.info(f'{self.PLUGIN_NAME} initialized.')
|
||||
|
||||
def register_hooks(self):
|
||||
"""
|
||||
注册插件钩子
|
||||
子类可以重写此方法来注册特定的钩子
|
||||
"""
|
||||
pass
|
||||
|
||||
# === 位置渲染系统 ===
|
||||
def render_position_widget(self, position, context, **kwargs):
|
||||
"""
|
||||
根据位置渲染插件组件
|
||||
|
||||
Args:
|
||||
position: 位置标识
|
||||
context: 模板上下文
|
||||
**kwargs: 额外参数
|
||||
|
||||
Returns:
|
||||
dict: {'html': 'HTML内容', 'priority': 优先级} 或 None
|
||||
"""
|
||||
if position not in self.SUPPORTED_POSITIONS:
|
||||
return None
|
||||
|
||||
# 检查条件显示
|
||||
if not self.should_display(position, context, **kwargs):
|
||||
return None
|
||||
|
||||
# 调用具体的位置渲染方法
|
||||
method_name = f'render_{position}_widget'
|
||||
if hasattr(self, method_name):
|
||||
html = getattr(self, method_name)(context, **kwargs)
|
||||
if html:
|
||||
priority = self.POSITION_PRIORITIES.get(position, self.DEFAULT_PRIORITY)
|
||||
return {
|
||||
'html': html,
|
||||
'priority': priority,
|
||||
'plugin_name': self.PLUGIN_NAME
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def should_display(self, position, context, **kwargs):
|
||||
"""
|
||||
判断插件是否应该在指定位置显示
|
||||
子类可重写此方法实现条件显示逻辑
|
||||
|
||||
Args:
|
||||
position: 位置标识
|
||||
context: 模板上下文
|
||||
**kwargs: 额外参数
|
||||
|
||||
Returns:
|
||||
bool: 是否显示
|
||||
"""
|
||||
return True
|
||||
|
||||
# === 各位置渲染方法 - 子类重写 ===
|
||||
def render_sidebar_widget(self, context, **kwargs):
|
||||
"""渲染侧边栏组件"""
|
||||
return None
|
||||
|
||||
def render_article_bottom_widget(self, context, **kwargs):
|
||||
"""渲染文章底部组件"""
|
||||
return None
|
||||
|
||||
def render_article_top_widget(self, context, **kwargs):
|
||||
"""渲染文章顶部组件"""
|
||||
return None
|
||||
|
||||
def render_header_widget(self, context, **kwargs):
|
||||
"""渲染页头组件"""
|
||||
return None
|
||||
|
||||
def render_footer_widget(self, context, **kwargs):
|
||||
"""渲染页脚组件"""
|
||||
return None
|
||||
|
||||
def render_comment_before_widget(self, context, **kwargs):
|
||||
"""渲染评论前组件"""
|
||||
return None
|
||||
|
||||
def render_comment_after_widget(self, context, **kwargs):
|
||||
"""渲染评论后组件"""
|
||||
return None
|
||||
|
||||
# === 模板系统 ===
|
||||
def render_template(self, template_name, context=None):
|
||||
"""
|
||||
渲染插件模板
|
||||
|
||||
Args:
|
||||
template_name: 模板文件名
|
||||
context: 模板上下文
|
||||
|
||||
Returns:
|
||||
HTML字符串
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
template_path = f"plugins/{self.plugin_slug}/{template_name}"
|
||||
|
||||
try:
|
||||
return render_to_string(template_path, context)
|
||||
except TemplateDoesNotExist:
|
||||
logger.warning(f"Plugin template not found: {template_path}")
|
||||
return ""
|
||||
|
||||
# === 静态资源系统 ===
|
||||
def get_static_url(self, static_file):
|
||||
"""获取插件静态文件URL"""
|
||||
from django.templatetags.static import static
|
||||
return static(f"{self.plugin_slug}/static/{self.plugin_slug}/{static_file}")
|
||||
|
||||
def get_css_files(self):
|
||||
"""获取插件CSS文件列表"""
|
||||
return []
|
||||
|
||||
def get_js_files(self):
|
||||
"""获取插件JavaScript文件列表"""
|
||||
return []
|
||||
|
||||
def get_head_html(self, context=None):
|
||||
"""获取需要插入到<head>中的HTML内容"""
|
||||
return ""
|
||||
|
||||
def get_body_html(self, context=None):
|
||||
"""获取需要插入到<body>底部的HTML内容"""
|
||||
return ""
|
||||
|
||||
def get_plugin_info(self):
|
||||
"""
|
||||
获取插件信息
|
||||
:return: 包含插件元数据的字典
|
||||
"""
|
||||
return {
|
||||
'name': self.PLUGIN_NAME,
|
||||
'description': self.PLUGIN_DESCRIPTION,
|
||||
'version': self.PLUGIN_VERSION,
|
||||
'author': self.PLUGIN_AUTHOR,
|
||||
'slug': self.plugin_slug,
|
||||
'directory': str(self.plugin_dir),
|
||||
'supported_positions': self.SUPPORTED_POSITIONS,
|
||||
'priorities': self.POSITION_PRIORITIES
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
ARTICLE_DETAIL_LOAD = 'article_detail_load'
|
||||
ARTICLE_CREATE = 'article_create'
|
||||
ARTICLE_UPDATE = 'article_update'
|
||||
ARTICLE_DELETE = 'article_delete'
|
||||
|
||||
ARTICLE_CONTENT_HOOK_NAME = "the_content"
|
||||
|
||||
# 位置钩子常量
|
||||
POSITION_HOOKS = {
|
||||
'article_top': 'article_top_widgets',
|
||||
'article_bottom': 'article_bottom_widgets',
|
||||
'sidebar': 'sidebar_widgets',
|
||||
'header': 'header_widgets',
|
||||
'footer': 'footer_widgets',
|
||||
'comment_before': 'comment_before_widgets',
|
||||
'comment_after': 'comment_after_widgets',
|
||||
}
|
||||
|
||||
# 资源注入钩子
|
||||
HEAD_RESOURCES_HOOK = 'head_resources'
|
||||
BODY_RESOURCES_HOOK = 'body_resources'
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_hooks = {}
|
||||
|
||||
|
||||
def register(hook_name: str, callback: callable):
|
||||
"""
|
||||
注册一个钩子回调。
|
||||
"""
|
||||
if hook_name not in _hooks:
|
||||
_hooks[hook_name] = []
|
||||
_hooks[hook_name].append(callback)
|
||||
logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'")
|
||||
|
||||
|
||||
def run_action(hook_name: str, *args, **kwargs):
|
||||
"""
|
||||
执行一个 Action Hook。
|
||||
它会按顺序执行所有注册到该钩子上的回调函数。
|
||||
"""
|
||||
if hook_name in _hooks:
|
||||
logger.debug(f"Running action hook '{hook_name}'")
|
||||
for callback in _hooks[hook_name]:
|
||||
try:
|
||||
callback(*args, **kwargs)
|
||||
except Exception as e:
|
||||
logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True)
|
||||
|
||||
|
||||
def apply_filters(hook_name: str, value, *args, **kwargs):
|
||||
"""
|
||||
执行一个 Filter Hook。
|
||||
它会把 value 依次传递给所有注册的回调函数进行处理。
|
||||
"""
|
||||
if hook_name in _hooks:
|
||||
logger.debug(f"Applying filter hook '{hook_name}'")
|
||||
for callback in _hooks[hook_name]:
|
||||
try:
|
||||
value = callback(value, *args, **kwargs)
|
||||
except Exception as e:
|
||||
logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True)
|
||||
return value
|
||||
@ -1,64 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 全局插件注册表
|
||||
_loaded_plugins = []
|
||||
|
||||
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.
|
||||
"""
|
||||
global _loaded_plugins
|
||||
_loaded_plugins = []
|
||||
|
||||
for plugin_name in settings.ACTIVE_PLUGINS:
|
||||
plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name)
|
||||
if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')):
|
||||
try:
|
||||
# 导入插件模块
|
||||
plugin_module = __import__(f'plugins.{plugin_name}.plugin', fromlist=['plugin'])
|
||||
|
||||
# 获取插件实例
|
||||
if hasattr(plugin_module, 'plugin'):
|
||||
plugin_instance = plugin_module.plugin
|
||||
_loaded_plugins.append(plugin_instance)
|
||||
logger.info(f"Successfully loaded plugin: {plugin_name} - {plugin_instance.PLUGIN_NAME}")
|
||||
else:
|
||||
logger.warning(f"Plugin {plugin_name} does not have 'plugin' instance")
|
||||
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e)
|
||||
except AttributeError as e:
|
||||
logger.error(f"Failed to get plugin instance: {plugin_name}", exc_info=e)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error loading plugin: {plugin_name}", exc_info=e)
|
||||
|
||||
def get_loaded_plugins():
|
||||
"""获取所有已加载的插件"""
|
||||
return _loaded_plugins
|
||||
|
||||
def get_plugin_by_name(plugin_name):
|
||||
"""根据名称获取插件"""
|
||||
for plugin in _loaded_plugins:
|
||||
if plugin.plugin_slug == plugin_name:
|
||||
return plugin
|
||||
return None
|
||||
|
||||
def get_plugin_by_slug(plugin_slug):
|
||||
"""根据slug获取插件"""
|
||||
for plugin in _loaded_plugins:
|
||||
if plugin.plugin_slug == plugin_slug:
|
||||
return plugin
|
||||
return None
|
||||
|
||||
def get_plugins_info():
|
||||
"""获取所有插件的信息"""
|
||||
return [plugin.get_plugin_info() for plugin in _loaded_plugins]
|
||||
|
||||
def get_plugins_by_position(position):
|
||||
"""获取支持指定位置的插件"""
|
||||
return [plugin for plugin in _loaded_plugins if position in plugin.SUPPORTED_POSITIONS]
|
||||
@ -1,59 +0,0 @@
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from django.urls import reverse
|
||||
|
||||
from blog.models import Article, Category, Tag
|
||||
|
||||
|
||||
class StaticViewSitemap(Sitemap):
|
||||
priority = 0.5
|
||||
changefreq = 'daily'
|
||||
|
||||
def items(self):
|
||||
return ['blog:index', ]
|
||||
|
||||
def location(self, item):
|
||||
return reverse(item)
|
||||
|
||||
|
||||
class ArticleSiteMap(Sitemap):
|
||||
changefreq = "monthly"
|
||||
priority = "0.6"
|
||||
|
||||
def items(self):
|
||||
return Article.objects.filter(status='p')
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.last_modify_time
|
||||
|
||||
|
||||
class CategorySiteMap(Sitemap):
|
||||
changefreq = "Weekly"
|
||||
priority = "0.6"
|
||||
|
||||
def items(self):
|
||||
return Category.objects.all()
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.last_modify_time
|
||||
|
||||
|
||||
class TagSiteMap(Sitemap):
|
||||
changefreq = "Weekly"
|
||||
priority = "0.3"
|
||||
|
||||
def items(self):
|
||||
return Tag.objects.all()
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.last_modify_time
|
||||
|
||||
|
||||
class UserSiteMap(Sitemap):
|
||||
changefreq = "Weekly"
|
||||
priority = "0.3"
|
||||
|
||||
def items(self):
|
||||
return list(set(map(lambda x: x.author, Article.objects.all())))
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.date_joined
|
||||
@ -1,21 +0,0 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SpiderNotify():
|
||||
@staticmethod
|
||||
def baidu_notify(urls):
|
||||
try:
|
||||
data = '\n'.join(urls)
|
||||
result = requests.post(settings.BAIDU_NOTIFY_URL, data=data)
|
||||
logger.info(result.text)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
@staticmethod
|
||||
def notify(url):
|
||||
SpiderNotify.baidu_notify(url)
|
||||
@ -1,32 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from djangoblog.utils import *
|
||||
|
||||
|
||||
class DjangoBlogTest(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_utils(self):
|
||||
md5 = get_sha256('test')
|
||||
self.assertIsNotNone(md5)
|
||||
c = CommonMarkdown.get_markdown('''
|
||||
# Title1
|
||||
|
||||
```python
|
||||
import os
|
||||
```
|
||||
|
||||
[url](https://www.lylinux.net/)
|
||||
|
||||
[ddd](http://www.baidu.com)
|
||||
|
||||
|
||||
''')
|
||||
self.assertIsNotNone(c)
|
||||
d = {
|
||||
'd': 'key1',
|
||||
'd2': 'key2'
|
||||
}
|
||||
data = parse_dict_to_url(d)
|
||||
self.assertIsNotNone(data)
|
||||
@ -1,78 +0,0 @@
|
||||
"""djangoblog URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/1.10/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.conf.urls import url, include
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from django.urls import path, include
|
||||
from django.urls import re_path
|
||||
from haystack.views import search_view_factory
|
||||
from django.http import JsonResponse
|
||||
import time
|
||||
|
||||
from blog.views import EsSearchView
|
||||
from djangoblog.admin_site import admin_site
|
||||
from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm
|
||||
from djangoblog.feeds import DjangoBlogFeed
|
||||
from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap
|
||||
|
||||
sitemaps = {
|
||||
|
||||
'blog': ArticleSiteMap,
|
||||
'Category': CategorySiteMap,
|
||||
'Tag': TagSiteMap,
|
||||
'User': UserSiteMap,
|
||||
'static': StaticViewSitemap
|
||||
}
|
||||
|
||||
handler404 = 'blog.views.page_not_found_view'
|
||||
handler500 = 'blog.views.server_error_view'
|
||||
handle403 = 'blog.views.permission_denied_view'
|
||||
|
||||
|
||||
def health_check(request):
|
||||
"""
|
||||
健康检查接口
|
||||
简单返回服务健康状态
|
||||
"""
|
||||
return JsonResponse({
|
||||
'status': 'healthy',
|
||||
'timestamp': time.time()
|
||||
})
|
||||
|
||||
urlpatterns = [
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
path('health/', health_check, name='health_check'),
|
||||
]
|
||||
urlpatterns += i18n_patterns(
|
||||
re_path(r'^admin/', admin_site.urls),
|
||||
re_path(r'', include('blog.urls', namespace='blog')),
|
||||
re_path(r'mdeditor/', include('mdeditor.urls')),
|
||||
re_path(r'', include('comments.urls', namespace='comment')),
|
||||
re_path(r'', include('accounts.urls', namespace='account')),
|
||||
re_path(r'', include('oauth.urls', namespace='oauth')),
|
||||
re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
|
||||
name='django.contrib.sitemaps.views.sitemap'),
|
||||
re_path(r'^feed/$', DjangoBlogFeed()),
|
||||
re_path(r'^rss/$', DjangoBlogFeed()),
|
||||
re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm),
|
||||
name='search'),
|
||||
re_path(r'', include('servermanager.urls', namespace='servermanager')),
|
||||
re_path(r'', include('owntracks.urls', namespace='owntracks'))
|
||||
, prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL,
|
||||
document_root=settings.MEDIA_ROOT)
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
||||
"""
|
||||
WSGI config for djangoblog project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
@ -1,25 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "oauth"
|
||||
urlpatterns = [
|
||||
path(
|
||||
r'oauth/authorize',
|
||||
views.authorize),
|
||||
path(
|
||||
r'oauth/requireemail/<int:oauthid>.html',
|
||||
views.RequireEmailView.as_view(),
|
||||
name='require_email'),
|
||||
path(
|
||||
r'oauth/emailconfirm/<int:id>/<sign>.html',
|
||||
views.emailconfirm,
|
||||
name='email_confirm'),
|
||||
path(
|
||||
r'oauth/bindsuccess/<int:oauthid>.html',
|
||||
views.bindsuccess,
|
||||
name='bindsuccess'),
|
||||
path(
|
||||
r'oauth/oauthlogin',
|
||||
views.oauthlogin,
|
||||
name='oauthlogin')]
|
||||
@ -0,0 +1,26 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
|
||||
class EmailOrUsernameModelBackend(ModelBackend):
|
||||
#sjt 自定义认证后端:支持使用用户名或邮箱登录
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
#sjt 认证逻辑:根据输入判断是邮箱还是用户名,验证密码
|
||||
if '@' in username: # sjt 包含@符号则视为邮箱登录
|
||||
kwargs = {'email': username}
|
||||
else: # sjt 否则视为用户名登录
|
||||
kwargs = {'username': username}
|
||||
try:
|
||||
user = get_user_model().objects.get(** kwargs) # sjt 查询用户
|
||||
if user.check_password(password): # sjt 验证密码
|
||||
return user
|
||||
except get_user_model().DoesNotExist: # sjt 用户不存在
|
||||
return None
|
||||
|
||||
def get_user(self, username):
|
||||
#sjt 根据用户ID获取用户对象
|
||||
try:
|
||||
return get_user_model().objects.get(pk=username)
|
||||
except get_user_model().DoesNotExist:
|
||||
return None
|
||||
|
Before Width: | Height: | Size: 221 B After Width: | Height: | Size: 221 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue