Compare commits
No commits in common. 'yxy_branch' and 'master' have entirely different histories.
yxy_branch
...
master
@ -1,7 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (PythonProject1)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (PythonProject1)" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Django" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="357be86a-1284-4635-a32e-bf0ce39e4b81" name="更改" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="PUSH_TAGS">
|
||||
<GitPushTagMode>
|
||||
<option name="argument" value="--follow-tags" />
|
||||
<option name="title" value="Current Branch" />
|
||||
</GitPushTagMode>
|
||||
</option>
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="master" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 3
|
||||
}</component>
|
||||
<component name="ProjectId" id="33xRndo8NxWInlIDa0Lp1mySOhn" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RunManager">
|
||||
<configuration name="zyl_django" type="Python.DjangoServer" factoryName="Django server">
|
||||
<module name="zyl_django" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="launchJavascriptDebuger" value="false" />
|
||||
<option name="port" value="8000" />
|
||||
<option name="host" value="" />
|
||||
<option name="additionalOptions" value="" />
|
||||
<option name="browserUrl" value="" />
|
||||
<option name="runTestServer" value="false" />
|
||||
<option name="runNoReload" value="false" />
|
||||
<option name="useCustomRunCommand" value="false" />
|
||||
<option name="customRunCommand" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-b598e85cdad2-JavaScript-PY-252.25557.178" />
|
||||
<option value="bundled-python-sdk-ce6832f46686-7b97d883f26b-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-252.25557.178" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="默认任务">
|
||||
<changelist id="357be86a-1284-4635-a32e-bf0ce39e4b81" name="更改" comment="" />
|
||||
<created>1760256904468</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1760256904468</updated>
|
||||
<workItem from="1760256905584" duration="153000" />
|
||||
<workItem from="1760257111491" duration="1866000" />
|
||||
<workItem from="1763889357146" duration="268000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BlogConfig(AppConfig):
|
||||
name = 'blog'
|
||||
@ -0,0 +1,43 @@
|
||||
import logging
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from djangoblog.utils import cache, get_blog_setting
|
||||
from .models import Category, Article
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def seo_processor(requests):
|
||||
key = 'seo_processor'
|
||||
value = cache.get(key)
|
||||
if value:
|
||||
return value
|
||||
else:
|
||||
logger.info('set processor cache.')
|
||||
setting = get_blog_setting()
|
||||
value = {
|
||||
'SITE_NAME': setting.site_name,
|
||||
'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,
|
||||
'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,
|
||||
'SITE_SEO_DESCRIPTION': setting.site_seo_description,
|
||||
'SITE_DESCRIPTION': setting.site_description,
|
||||
'SITE_KEYWORDS': setting.site_keywords,
|
||||
'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
|
||||
'ARTICLE_SUB_LENGTH': setting.article_sub_length,
|
||||
'nav_category_list': Category.objects.all(),
|
||||
'nav_pages': Article.objects.filter(
|
||||
type='p',
|
||||
status='p'),
|
||||
'OPEN_SITE_COMMENT': setting.open_site_comment,
|
||||
'BEIAN_CODE': setting.beian_code,
|
||||
'ANALYTICS_CODE': setting.analytics_code,
|
||||
"BEIAN_CODE_GONGAN": setting.gongan_beiancode,
|
||||
"SHOW_GONGAN_CODE": setting.show_gongan_code,
|
||||
"CURRENT_YEAR": timezone.now().year,
|
||||
"GLOBAL_HEADER": setting.global_header,
|
||||
"GLOBAL_FOOTER": setting.global_footer,
|
||||
"COMMENT_NEED_REVIEW": setting.comment_need_review,
|
||||
}
|
||||
cache.set(key, value, 60 * 60 * 10)
|
||||
return value
|
||||
@ -0,0 +1,213 @@
|
||||
import time
|
||||
|
||||
import elasticsearch.client
|
||||
from django.conf import settings
|
||||
from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean
|
||||
from elasticsearch_dsl.connections import connections
|
||||
|
||||
from blog.models import Article
|
||||
|
||||
ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL')
|
||||
|
||||
if ELASTICSEARCH_ENABLED:
|
||||
connections.create_connection(
|
||||
hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']])
|
||||
from elasticsearch import Elasticsearch
|
||||
|
||||
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||
from elasticsearch.client import IngestClient
|
||||
|
||||
c = IngestClient(es)
|
||||
try:
|
||||
c.get_pipeline('geoip')
|
||||
except elasticsearch.exceptions.NotFoundError:
|
||||
c.put_pipeline('geoip', body='''{
|
||||
"description" : "Add geoip info",
|
||||
"processors" : [
|
||||
{
|
||||
"geoip" : {
|
||||
"field" : "ip"
|
||||
}
|
||||
}
|
||||
]
|
||||
}''')
|
||||
|
||||
|
||||
class GeoIp(InnerDoc):
|
||||
continent_name = Keyword()
|
||||
country_iso_code = Keyword()
|
||||
country_name = Keyword()
|
||||
location = GeoPoint()
|
||||
|
||||
|
||||
class UserAgentBrowser(InnerDoc):
|
||||
Family = Keyword()
|
||||
Version = Keyword()
|
||||
|
||||
|
||||
class UserAgentOS(UserAgentBrowser):
|
||||
pass
|
||||
|
||||
|
||||
class UserAgentDevice(InnerDoc):
|
||||
Family = Keyword()
|
||||
Brand = Keyword()
|
||||
Model = Keyword()
|
||||
|
||||
|
||||
class UserAgent(InnerDoc):
|
||||
browser = Object(UserAgentBrowser, required=False)
|
||||
os = Object(UserAgentOS, required=False)
|
||||
device = Object(UserAgentDevice, required=False)
|
||||
string = Text()
|
||||
is_bot = Boolean()
|
||||
|
||||
|
||||
class ElapsedTimeDocument(Document):
|
||||
url = Keyword()
|
||||
time_taken = Long()
|
||||
log_datetime = Date()
|
||||
ip = Keyword()
|
||||
geoip = Object(GeoIp, required=False)
|
||||
useragent = Object(UserAgent, required=False)
|
||||
|
||||
class Index:
|
||||
name = 'performance'
|
||||
settings = {
|
||||
"number_of_shards": 1,
|
||||
"number_of_replicas": 0
|
||||
}
|
||||
|
||||
class Meta:
|
||||
doc_type = 'ElapsedTime'
|
||||
|
||||
|
||||
class ElaspedTimeDocumentManager:
|
||||
@staticmethod
|
||||
def build_index():
|
||||
from elasticsearch import Elasticsearch
|
||||
client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||
res = client.indices.exists(index="performance")
|
||||
if not res:
|
||||
ElapsedTimeDocument.init()
|
||||
|
||||
@staticmethod
|
||||
def delete_index():
|
||||
from elasticsearch import Elasticsearch
|
||||
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||
es.indices.delete(index='performance', ignore=[400, 404])
|
||||
|
||||
@staticmethod
|
||||
def create(url, time_taken, log_datetime, useragent, ip):
|
||||
ElaspedTimeDocumentManager.build_index()
|
||||
ua = UserAgent()
|
||||
ua.browser = UserAgentBrowser()
|
||||
ua.browser.Family = useragent.browser.family
|
||||
ua.browser.Version = useragent.browser.version_string
|
||||
|
||||
ua.os = UserAgentOS()
|
||||
ua.os.Family = useragent.os.family
|
||||
ua.os.Version = useragent.os.version_string
|
||||
|
||||
ua.device = UserAgentDevice()
|
||||
ua.device.Family = useragent.device.family
|
||||
ua.device.Brand = useragent.device.brand
|
||||
ua.device.Model = useragent.device.model
|
||||
ua.string = useragent.ua_string
|
||||
ua.is_bot = useragent.is_bot
|
||||
|
||||
doc = ElapsedTimeDocument(
|
||||
meta={
|
||||
'id': int(
|
||||
round(
|
||||
time.time() *
|
||||
1000))
|
||||
},
|
||||
url=url,
|
||||
time_taken=time_taken,
|
||||
log_datetime=log_datetime,
|
||||
useragent=ua, ip=ip)
|
||||
doc.save(pipeline="geoip")
|
||||
|
||||
|
||||
class ArticleDocument(Document):
|
||||
body = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
|
||||
title = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
|
||||
author = Object(properties={
|
||||
'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
|
||||
'id': Integer()
|
||||
})
|
||||
category = Object(properties={
|
||||
'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
|
||||
'id': Integer()
|
||||
})
|
||||
tags = Object(properties={
|
||||
'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
|
||||
'id': Integer()
|
||||
})
|
||||
|
||||
pub_time = Date()
|
||||
status = Text()
|
||||
comment_status = Text()
|
||||
type = Text()
|
||||
views = Integer()
|
||||
article_order = Integer()
|
||||
|
||||
class Index:
|
||||
name = 'blog'
|
||||
settings = {
|
||||
"number_of_shards": 1,
|
||||
"number_of_replicas": 0
|
||||
}
|
||||
|
||||
class Meta:
|
||||
doc_type = 'Article'
|
||||
|
||||
|
||||
class ArticleDocumentManager():
|
||||
|
||||
def __init__(self):
|
||||
self.create_index()
|
||||
|
||||
def create_index(self):
|
||||
ArticleDocument.init()
|
||||
|
||||
def delete_index(self):
|
||||
from elasticsearch import Elasticsearch
|
||||
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||
es.indices.delete(index='blog', ignore=[400, 404])
|
||||
|
||||
def convert_to_doc(self, articles):
|
||||
return [
|
||||
ArticleDocument(
|
||||
meta={
|
||||
'id': article.id},
|
||||
body=article.body,
|
||||
title=article.title,
|
||||
author={
|
||||
'nickname': article.author.username,
|
||||
'id': article.author.id},
|
||||
category={
|
||||
'name': article.category.name,
|
||||
'id': article.category.id},
|
||||
tags=[
|
||||
{
|
||||
'name': t.name,
|
||||
'id': t.id} for t in article.tags.all()],
|
||||
pub_time=article.pub_time,
|
||||
status=article.status,
|
||||
comment_status=article.comment_status,
|
||||
type=article.type,
|
||||
views=article.views,
|
||||
article_order=article.article_order) for article in articles]
|
||||
|
||||
def rebuild(self, articles=None):
|
||||
ArticleDocument.init()
|
||||
articles = articles if articles else Article.objects.all()
|
||||
docs = self.convert_to_doc(articles)
|
||||
for doc in docs:
|
||||
doc.save()
|
||||
|
||||
def update_docs(self, docs):
|
||||
for doc in docs:
|
||||
doc.save()
|
||||
@ -0,0 +1,19 @@
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
from haystack.forms import SearchForm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BlogSearchForm(SearchForm):
|
||||
querydata = forms.CharField(required=True)
|
||||
|
||||
def search(self):
|
||||
datas = super(BlogSearchForm, self).search()
|
||||
if not self.is_valid():
|
||||
return self.no_query_found()
|
||||
|
||||
if self.cleaned_data['querydata']:
|
||||
logger.info(self.cleaned_data['querydata'])
|
||||
return datas
|
||||
@ -0,0 +1,18 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \
|
||||
ELASTICSEARCH_ENABLED
|
||||
|
||||
|
||||
# TODO 参数化
|
||||
class Command(BaseCommand):
|
||||
help = 'build search index'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if ELASTICSEARCH_ENABLED:
|
||||
ElaspedTimeDocumentManager.build_index()
|
||||
manager = ElapsedTimeDocument()
|
||||
manager.init()
|
||||
manager = ArticleDocumentManager()
|
||||
manager.delete_index()
|
||||
manager.rebuild()
|
||||
@ -0,0 +1,13 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from blog.models import Tag, Category
|
||||
|
||||
|
||||
# TODO 参数化
|
||||
class Command(BaseCommand):
|
||||
help = 'build search words'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
datas = set([t.name for t in Tag.objects.all()] +
|
||||
[t.name for t in Category.objects.all()])
|
||||
print('\n'.join(datas))
|
||||
@ -0,0 +1,11 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from djangoblog.utils import cache
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'clear the whole cache'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
cache.clear()
|
||||
self.stdout.write(self.style.SUCCESS('Cleared cache\n'))
|
||||
@ -0,0 +1,40 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from blog.models import Article, Tag, Category
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'create test datas'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
user = get_user_model().objects.get_or_create(
|
||||
email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0]
|
||||
|
||||
pcategory = Category.objects.get_or_create(
|
||||
name='我是父类目', parent_category=None)[0]
|
||||
|
||||
category = Category.objects.get_or_create(
|
||||
name='子类目', parent_category=pcategory)[0]
|
||||
|
||||
category.save()
|
||||
basetag = Tag()
|
||||
basetag.name = "标签"
|
||||
basetag.save()
|
||||
for i in range(1, 20):
|
||||
article = Article.objects.get_or_create(
|
||||
category=category,
|
||||
title='nice title ' + str(i),
|
||||
body='nice content ' + str(i),
|
||||
author=user)[0]
|
||||
tag = Tag()
|
||||
tag.name = "标签" + str(i)
|
||||
tag.save()
|
||||
article.tags.add(tag)
|
||||
article.tags.add(basetag)
|
||||
article.save()
|
||||
|
||||
from djangoblog.utils import cache
|
||||
cache.clear()
|
||||
self.stdout.write(self.style.SUCCESS('created test datas \n'))
|
||||
@ -0,0 +1,50 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from djangoblog.spider_notify import SpiderNotify
|
||||
from djangoblog.utils import get_current_site
|
||||
from blog.models import Article, Tag, Category
|
||||
|
||||
site = get_current_site().domain
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'notify baidu url'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'data_type',
|
||||
type=str,
|
||||
choices=[
|
||||
'all',
|
||||
'article',
|
||||
'tag',
|
||||
'category'],
|
||||
help='article : all article,tag : all tag,category: all category,all: All of these')
|
||||
|
||||
def get_full_url(self, path):
|
||||
url = "https://{site}{path}".format(site=site, path=path)
|
||||
return url
|
||||
|
||||
def handle(self, *args, **options):
|
||||
type = options['data_type']
|
||||
self.stdout.write('start get %s' % type)
|
||||
|
||||
urls = []
|
||||
if type == 'article' or type == 'all':
|
||||
for article in Article.objects.filter(status='p'):
|
||||
urls.append(article.get_full_url())
|
||||
if type == 'tag' or type == 'all':
|
||||
for tag in Tag.objects.all():
|
||||
url = tag.get_absolute_url()
|
||||
urls.append(self.get_full_url(url))
|
||||
if type == 'category' or type == 'all':
|
||||
for category in Category.objects.all():
|
||||
url = category.get_absolute_url()
|
||||
urls.append(self.get_full_url(url))
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
'start notify %d urls' %
|
||||
len(urls)))
|
||||
SpiderNotify.baidu_notify(urls)
|
||||
self.stdout.write(self.style.SUCCESS('finish notify'))
|
||||
@ -0,0 +1,47 @@
|
||||
import requests
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.templatetags.static import static
|
||||
|
||||
from djangoblog.utils import save_user_avatar
|
||||
from oauth.models import OAuthUser
|
||||
from oauth.oauthmanager import get_manager_by_type
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'sync user avatar'
|
||||
|
||||
def test_picture(self, url):
|
||||
try:
|
||||
if requests.get(url, timeout=2).status_code == 200:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
static_url = static("../")
|
||||
users = OAuthUser.objects.all()
|
||||
self.stdout.write(f'开始同步{len(users)}个用户头像')
|
||||
for u in users:
|
||||
self.stdout.write(f'开始同步:{u.nickname}')
|
||||
url = u.picture
|
||||
if url:
|
||||
if url.startswith(static_url):
|
||||
if self.test_picture(url):
|
||||
continue
|
||||
else:
|
||||
if u.metadata:
|
||||
manage = get_manager_by_type(u.type)
|
||||
url = manage.get_picture(u.metadata)
|
||||
url = save_user_avatar(url)
|
||||
else:
|
||||
url = static('blog/img/avatar.png')
|
||||
else:
|
||||
url = save_user_avatar(url)
|
||||
else:
|
||||
url = static('blog/img/avatar.png')
|
||||
if url:
|
||||
self.stdout.write(
|
||||
f'结束同步:{u.nickname}.url:{url}')
|
||||
u.picture = url
|
||||
u.save()
|
||||
self.stdout.write('结束同步')
|
||||
@ -0,0 +1,42 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from ipware import get_client_ip
|
||||
from user_agents import parse
|
||||
|
||||
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OnlineMiddleware(object):
|
||||
def __init__(self, get_response=None):
|
||||
self.get_response = get_response
|
||||
super().__init__()
|
||||
|
||||
def __call__(self, request):
|
||||
''' page render time '''
|
||||
start_time = time.time()
|
||||
response = self.get_response(request)
|
||||
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||
ip, _ = get_client_ip(request)
|
||||
user_agent = parse(http_user_agent)
|
||||
if not response.streaming:
|
||||
try:
|
||||
cast_time = time.time() - start_time
|
||||
if ELASTICSEARCH_ENABLED:
|
||||
time_taken = round((cast_time) * 1000, 2)
|
||||
url = request.path
|
||||
from django.utils import timezone
|
||||
ElaspedTimeDocumentManager.create(
|
||||
url=url,
|
||||
time_taken=time_taken,
|
||||
log_datetime=timezone.now(),
|
||||
useragent=user_agent,
|
||||
ip=ip)
|
||||
response.content = response.content.replace(
|
||||
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
|
||||
except Exception as e:
|
||||
logger.error("Error OnlineMiddleware: %s" % e)
|
||||
|
||||
return response
|
||||
@ -0,0 +1,137 @@
|
||||
# Generated by Django 4.1.7 on 2023-03-02 07:14
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import mdeditor.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BlogSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')),
|
||||
('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')),
|
||||
('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')),
|
||||
('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')),
|
||||
('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')),
|
||||
('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')),
|
||||
('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')),
|
||||
('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')),
|
||||
('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')),
|
||||
('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')),
|
||||
('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')),
|
||||
('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')),
|
||||
('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')),
|
||||
('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')),
|
||||
('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '网站配置',
|
||||
'verbose_name_plural': '网站配置',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Links',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')),
|
||||
('link', models.URLField(verbose_name='链接地址')),
|
||||
('sequence', models.IntegerField(unique=True, verbose_name='排序')),
|
||||
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')),
|
||||
('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '友情链接',
|
||||
'verbose_name_plural': '友情链接',
|
||||
'ordering': ['sequence'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SideBar',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='标题')),
|
||||
('content', models.TextField(verbose_name='内容')),
|
||||
('sequence', models.IntegerField(unique=True, verbose_name='排序')),
|
||||
('is_enable', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '侧边栏',
|
||||
'verbose_name_plural': '侧边栏',
|
||||
'ordering': ['sequence'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')),
|
||||
('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '标签',
|
||||
'verbose_name_plural': '标签',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')),
|
||||
('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
|
||||
('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')),
|
||||
('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '分类',
|
||||
'verbose_name_plural': '分类',
|
||||
'ordering': ['-index'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Article',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||
('title', models.CharField(max_length=200, unique=True, verbose_name='标题')),
|
||||
('body', mdeditor.fields.MDTextField(verbose_name='正文')),
|
||||
('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')),
|
||||
('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')),
|
||||
('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')),
|
||||
('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')),
|
||||
('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')),
|
||||
('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')),
|
||||
('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')),
|
||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')),
|
||||
('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '文章',
|
||||
'verbose_name_plural': '文章',
|
||||
'ordering': ['-article_order', '-pub_time'],
|
||||
'get_latest_by': 'id',
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.1.7 on 2023-03-29 06:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='blogsettings',
|
||||
name='global_footer',
|
||||
field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='blogsettings',
|
||||
name='global_header',
|
||||
field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.2.1 on 2023-05-09 07:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('blog', '0002_blogsettings_global_footer_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='blogsettings',
|
||||
name='comment_need_review',
|
||||
field=models.BooleanField(default=False, verbose_name='评论是否需要审核'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,27 @@
|
||||
# Generated by Django 4.2.1 on 2023-05-09 07:51
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('blog', '0003_blogsettings_comment_need_review'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='blogsettings',
|
||||
old_name='analyticscode',
|
||||
new_name='analytics_code',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='blogsettings',
|
||||
old_name='beiancode',
|
||||
new_name='beian_code',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='blogsettings',
|
||||
old_name='sitename',
|
||||
new_name='site_name',
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,300 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-06 13:13
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import mdeditor.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='article',
|
||||
options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='category',
|
||||
options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='links',
|
||||
options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='sidebar',
|
||||
options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='tag',
|
||||
options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='article',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='article',
|
||||
name='last_mod_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='category',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='category',
|
||||
name='last_mod_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='links',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='sidebar',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tag',
|
||||
name='created_time',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tag',
|
||||
name='last_mod_time',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='category',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='category',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='links',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sidebar',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='creation_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='last_modify_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='article_order',
|
||||
field=models.IntegerField(default=0, verbose_name='order'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='body',
|
||||
field=mdeditor.fields.MDTextField(verbose_name='body'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='comment_status',
|
||||
field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='pub_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='show_toc',
|
||||
field=models.BooleanField(default=False, verbose_name='show toc'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='title',
|
||||
field=models.CharField(max_length=200, unique=True, verbose_name='title'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='views',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='views'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='article_comment_count',
|
||||
field=models.IntegerField(default=5, verbose_name='article comment count'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='article_sub_length',
|
||||
field=models.IntegerField(default=300, verbose_name='article sub length'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='google_adsense_codes',
|
||||
field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='open_site_comment',
|
||||
field=models.BooleanField(default=True, verbose_name='open site comment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='show_google_adsense',
|
||||
field=models.BooleanField(default=False, verbose_name='show adsense'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='sidebar_article_count',
|
||||
field=models.IntegerField(default=10, verbose_name='sidebar article count'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='sidebar_comment_count',
|
||||
field=models.IntegerField(default=5, verbose_name='sidebar comment count'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_description',
|
||||
field=models.TextField(default='', max_length=1000, verbose_name='site description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_keywords',
|
||||
field=models.TextField(default='', max_length=1000, verbose_name='site keywords'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_name',
|
||||
field=models.CharField(default='', max_length=200, verbose_name='site name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blogsettings',
|
||||
name='site_seo_description',
|
||||
field=models.TextField(default='', max_length=1000, verbose_name='site seo description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='category',
|
||||
name='index',
|
||||
field=models.IntegerField(default=0, verbose_name='index'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='category',
|
||||
name='name',
|
||||
field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='category',
|
||||
name='parent_category',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='is_enable',
|
||||
field=models.BooleanField(default=True, verbose_name='is show'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='last_mod_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='link',
|
||||
field=models.URLField(verbose_name='link'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='name',
|
||||
field=models.CharField(max_length=30, unique=True, verbose_name='link name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='sequence',
|
||||
field=models.IntegerField(unique=True, verbose_name='order'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='links',
|
||||
name='show_type',
|
||||
field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='content',
|
||||
field=models.TextField(verbose_name='content'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='is_enable',
|
||||
field=models.BooleanField(default=True, verbose_name='is enable'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='last_mod_time',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, verbose_name='title'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sidebar',
|
||||
name='sequence',
|
||||
field=models.IntegerField(unique=True, verbose_name='order'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tag',
|
||||
name='name',
|
||||
field=models.CharField(max_length=30, unique=True, verbose_name='tag name'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.2.7 on 2024-01-26 02:41
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0005_alter_article_options_alter_category_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='blogsettings',
|
||||
options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,376 @@
|
||||
import logging
|
||||
import re
|
||||
from abc import abstractmethod
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from mdeditor.fields import MDTextField
|
||||
from uuslug import slugify
|
||||
|
||||
from djangoblog.utils import cache_decorator, cache
|
||||
from djangoblog.utils import get_current_site
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LinkShowType(models.TextChoices):
|
||||
I = ('i', _('index'))
|
||||
L = ('l', _('list'))
|
||||
P = ('p', _('post'))
|
||||
A = ('a', _('all'))
|
||||
S = ('s', _('slide'))
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||
last_modify_time = models.DateTimeField(_('modify time'), default=now)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
is_update_views = isinstance(
|
||||
self,
|
||||
Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
|
||||
if is_update_views:
|
||||
Article.objects.filter(pk=self.pk).update(views=self.views)
|
||||
else:
|
||||
if 'slug' in self.__dict__:
|
||||
slug = getattr(
|
||||
self, 'title') if 'title' in self.__dict__ else getattr(
|
||||
self, 'name')
|
||||
setattr(self, 'slug', slugify(slug))
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_full_url(self):
|
||||
site = get_current_site().domain
|
||||
url = "https://{site}{path}".format(site=site,
|
||||
path=self.get_absolute_url())
|
||||
return url
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@abstractmethod
|
||||
def get_absolute_url(self):
|
||||
pass
|
||||
|
||||
|
||||
class Article(BaseModel):
|
||||
"""文章"""
|
||||
STATUS_CHOICES = (
|
||||
('d', _('Draft')),
|
||||
('p', _('Published')),
|
||||
)
|
||||
COMMENT_STATUS = (
|
||||
('o', _('Open')),
|
||||
('c', _('Close')),
|
||||
)
|
||||
TYPE = (
|
||||
('a', _('Article')),
|
||||
('p', _('Page')),
|
||||
)
|
||||
title = models.CharField(_('title'), max_length=200, unique=True)
|
||||
body = MDTextField(_('body'))
|
||||
pub_time = models.DateTimeField(
|
||||
_('publish time'), blank=False, null=False, default=now)
|
||||
status = models.CharField(
|
||||
_('status'),
|
||||
max_length=1,
|
||||
choices=STATUS_CHOICES,
|
||||
default='p')
|
||||
comment_status = models.CharField(
|
||||
_('comment status'),
|
||||
max_length=1,
|
||||
choices=COMMENT_STATUS,
|
||||
default='o')
|
||||
type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a')
|
||||
views = models.PositiveIntegerField(_('views'), default=0)
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
verbose_name=_('author'),
|
||||
blank=False,
|
||||
null=False,
|
||||
on_delete=models.CASCADE)
|
||||
article_order = models.IntegerField(
|
||||
_('order'), blank=False, null=False, default=0)
|
||||
show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False)
|
||||
category = models.ForeignKey(
|
||||
'Category',
|
||||
verbose_name=_('category'),
|
||||
on_delete=models.CASCADE,
|
||||
blank=False,
|
||||
null=False)
|
||||
tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True)
|
||||
|
||||
def body_to_string(self):
|
||||
return self.body
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
ordering = ['-article_order', '-pub_time']
|
||||
verbose_name = _('article')
|
||||
verbose_name_plural = verbose_name
|
||||
get_latest_by = 'id'
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('blog:detailbyid', kwargs={
|
||||
'article_id': self.id,
|
||||
'year': self.creation_time.year,
|
||||
'month': self.creation_time.month,
|
||||
'day': self.creation_time.day
|
||||
})
|
||||
|
||||
@cache_decorator(60 * 60 * 10)
|
||||
def get_category_tree(self):
|
||||
tree = self.category.get_category_tree()
|
||||
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
|
||||
|
||||
return names
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def viewed(self):
|
||||
self.views += 1
|
||||
self.save(update_fields=['views'])
|
||||
|
||||
def comment_list(self):
|
||||
cache_key = 'article_comments_{id}'.format(id=self.id)
|
||||
value = cache.get(cache_key)
|
||||
if value:
|
||||
logger.info('get article comments:{id}'.format(id=self.id))
|
||||
return value
|
||||
else:
|
||||
comments = self.comment_set.filter(is_enable=True).order_by('-id')
|
||||
cache.set(cache_key, comments, 60 * 100)
|
||||
logger.info('set article comments:{id}'.format(id=self.id))
|
||||
return comments
|
||||
|
||||
def get_admin_url(self):
|
||||
info = (self._meta.app_label, self._meta.model_name)
|
||||
return reverse('admin:%s_%s_change' % info, args=(self.pk,))
|
||||
|
||||
@cache_decorator(expiration=60 * 100)
|
||||
def next_article(self):
|
||||
# 下一篇
|
||||
return Article.objects.filter(
|
||||
id__gt=self.id, status='p').order_by('id').first()
|
||||
|
||||
@cache_decorator(expiration=60 * 100)
|
||||
def prev_article(self):
|
||||
# 前一篇
|
||||
return Article.objects.filter(id__lt=self.id, status='p').first()
|
||||
|
||||
def get_first_image_url(self):
|
||||
"""
|
||||
Get the first image url from article.body.
|
||||
:return:
|
||||
"""
|
||||
match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return ""
|
||||
|
||||
|
||||
class Category(BaseModel):
|
||||
"""文章分类"""
|
||||
name = models.CharField(_('category name'), max_length=30, unique=True)
|
||||
parent_category = models.ForeignKey(
|
||||
'self',
|
||||
verbose_name=_('parent category'),
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE)
|
||||
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
|
||||
index = models.IntegerField(default=0, verbose_name=_('index'))
|
||||
|
||||
class Meta:
|
||||
ordering = ['-index']
|
||||
verbose_name = _('category')
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
'blog:category_detail', kwargs={
|
||||
'category_name': self.slug})
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@cache_decorator(60 * 60 * 10)
|
||||
def get_category_tree(self):
|
||||
"""
|
||||
递归获得分类目录的父级
|
||||
:return:
|
||||
"""
|
||||
categorys = []
|
||||
|
||||
def parse(category):
|
||||
categorys.append(category)
|
||||
if category.parent_category:
|
||||
parse(category.parent_category)
|
||||
|
||||
parse(self)
|
||||
return categorys
|
||||
|
||||
@cache_decorator(60 * 60 * 10)
|
||||
def get_sub_categorys(self):
|
||||
"""
|
||||
获得当前分类目录所有子集
|
||||
:return:
|
||||
"""
|
||||
categorys = []
|
||||
all_categorys = Category.objects.all()
|
||||
|
||||
def parse(category):
|
||||
if category not in categorys:
|
||||
categorys.append(category)
|
||||
childs = all_categorys.filter(parent_category=category)
|
||||
for child in childs:
|
||||
if category not in categorys:
|
||||
categorys.append(child)
|
||||
parse(child)
|
||||
|
||||
parse(self)
|
||||
return categorys
|
||||
|
||||
|
||||
class Tag(BaseModel):
|
||||
"""文章标签"""
|
||||
name = models.CharField(_('tag name'), max_length=30, unique=True)
|
||||
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
|
||||
|
||||
@cache_decorator(60 * 60 * 10)
|
||||
def get_article_count(self):
|
||||
return Article.objects.filter(tags__name=self.name).distinct().count()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
verbose_name = _('tag')
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
class Links(models.Model):
|
||||
"""友情链接"""
|
||||
|
||||
name = models.CharField(_('link name'), max_length=30, unique=True)
|
||||
link = models.URLField(_('link'))
|
||||
sequence = models.IntegerField(_('order'), unique=True)
|
||||
is_enable = models.BooleanField(
|
||||
_('is show'), default=True, blank=False, null=False)
|
||||
show_type = models.CharField(
|
||||
_('show type'),
|
||||
max_length=1,
|
||||
choices=LinkShowType.choices,
|
||||
default=LinkShowType.I)
|
||||
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||
last_mod_time = models.DateTimeField(_('modify time'), default=now)
|
||||
|
||||
class Meta:
|
||||
ordering = ['sequence']
|
||||
verbose_name = _('link')
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class SideBar(models.Model):
|
||||
"""侧边栏,可以展示一些html内容"""
|
||||
name = models.CharField(_('title'), max_length=100)
|
||||
content = models.TextField(_('content'))
|
||||
sequence = models.IntegerField(_('order'), unique=True)
|
||||
is_enable = models.BooleanField(_('is enable'), default=True)
|
||||
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||
last_mod_time = models.DateTimeField(_('modify time'), default=now)
|
||||
|
||||
class Meta:
|
||||
ordering = ['sequence']
|
||||
verbose_name = _('sidebar')
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class BlogSettings(models.Model):
|
||||
"""blog的配置"""
|
||||
site_name = models.CharField(
|
||||
_('site name'),
|
||||
max_length=200,
|
||||
null=False,
|
||||
blank=False,
|
||||
default='')
|
||||
site_description = models.TextField(
|
||||
_('site description'),
|
||||
max_length=1000,
|
||||
null=False,
|
||||
blank=False,
|
||||
default='')
|
||||
site_seo_description = models.TextField(
|
||||
_('site seo description'), max_length=1000, null=False, blank=False, default='')
|
||||
site_keywords = models.TextField(
|
||||
_('site keywords'),
|
||||
max_length=1000,
|
||||
null=False,
|
||||
blank=False,
|
||||
default='')
|
||||
article_sub_length = models.IntegerField(_('article sub length'), default=300)
|
||||
sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10)
|
||||
sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5)
|
||||
article_comment_count = models.IntegerField(_('article comment count'), default=5)
|
||||
show_google_adsense = models.BooleanField(_('show adsense'), default=False)
|
||||
google_adsense_codes = models.TextField(
|
||||
_('adsense code'), max_length=2000, null=True, blank=True, default='')
|
||||
open_site_comment = models.BooleanField(_('open site comment'), default=True)
|
||||
global_header = models.TextField("公共头部", null=True, blank=True, default='')
|
||||
global_footer = models.TextField("公共尾部", null=True, blank=True, default='')
|
||||
beian_code = models.CharField(
|
||||
'备案号',
|
||||
max_length=2000,
|
||||
null=True,
|
||||
blank=True,
|
||||
default='')
|
||||
analytics_code = models.TextField(
|
||||
"网站统计代码",
|
||||
max_length=1000,
|
||||
null=False,
|
||||
blank=False,
|
||||
default='')
|
||||
show_gongan_code = models.BooleanField(
|
||||
'是否显示公安备案号', default=False, null=False)
|
||||
gongan_beiancode = models.TextField(
|
||||
'公安备案号',
|
||||
max_length=2000,
|
||||
null=True,
|
||||
blank=True,
|
||||
default='')
|
||||
comment_need_review = models.BooleanField(
|
||||
'评论是否需要审核', default=False, null=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Website configuration')
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.site_name
|
||||
|
||||
def clean(self):
|
||||
if BlogSettings.objects.exclude(id=self.id).count():
|
||||
raise ValidationError(_('There can only be one configuration'))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
from djangoblog.utils import cache
|
||||
cache.clear()
|
||||
@ -0,0 +1,13 @@
|
||||
from haystack import indexes
|
||||
|
||||
from blog.models import Article
|
||||
|
||||
|
||||
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True)
|
||||
|
||||
def get_model(self):
|
||||
return Article
|
||||
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.filter(status='p')
|
||||
@ -0,0 +1,232 @@
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.management import call_command
|
||||
from django.core.paginator import Paginator
|
||||
from django.templatetags.static import static
|
||||
from django.test import Client, RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from accounts.models import BlogUser
|
||||
from blog.forms import BlogSearchForm
|
||||
from blog.models import Article, Category, Tag, SideBar, Links
|
||||
from blog.templatetags.blog_tags import load_pagination_info, load_articletags
|
||||
from djangoblog.utils import get_current_site, get_sha256
|
||||
from oauth.models import OAuthUser, OAuthConfig
|
||||
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
class ArticleTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_validate_article(self):
|
||||
site = get_current_site().domain
|
||||
user = BlogUser.objects.get_or_create(
|
||||
email="liangliangyy@gmail.com",
|
||||
username="liangliangyy")[0]
|
||||
user.set_password("liangliangyy")
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
response = self.client.get(user.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get('/admin/servermanager/emailsendlog/')
|
||||
response = self.client.get('admin/admin/logentry/')
|
||||
s = SideBar()
|
||||
s.sequence = 1
|
||||
s.name = 'test'
|
||||
s.content = 'test content'
|
||||
s.is_enable = True
|
||||
s.save()
|
||||
|
||||
category = Category()
|
||||
category.name = "category"
|
||||
category.creation_time = timezone.now()
|
||||
category.last_mod_time = timezone.now()
|
||||
category.save()
|
||||
|
||||
tag = Tag()
|
||||
tag.name = "nicetag"
|
||||
tag.save()
|
||||
|
||||
article = Article()
|
||||
article.title = "nicetitle"
|
||||
article.body = "nicecontent"
|
||||
article.author = user
|
||||
article.category = category
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
|
||||
article.save()
|
||||
self.assertEqual(0, article.tags.count())
|
||||
article.tags.add(tag)
|
||||
article.save()
|
||||
self.assertEqual(1, article.tags.count())
|
||||
|
||||
for i in range(20):
|
||||
article = Article()
|
||||
article.title = "nicetitle" + str(i)
|
||||
article.body = "nicetitle" + str(i)
|
||||
article.author = user
|
||||
article.category = category
|
||||
article.type = 'a'
|
||||
article.status = 'p'
|
||||
article.save()
|
||||
article.tags.add(tag)
|
||||
article.save()
|
||||
from blog.documents import ELASTICSEARCH_ENABLED
|
||||
if ELASTICSEARCH_ENABLED:
|
||||
call_command("build_index")
|
||||
response = self.client.get('/search', {'q': 'nicetitle'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(article.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
from djangoblog.spider_notify import SpiderNotify
|
||||
SpiderNotify.notify(article.get_absolute_url())
|
||||
response = self.client.get(tag.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(category.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get('/search', {'q': 'django'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
s = load_articletags(article)
|
||||
self.assertIsNotNone(s)
|
||||
|
||||
self.client.login(username='liangliangyy', password='liangliangyy')
|
||||
|
||||
response = self.client.get(reverse('blog:archives'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
p = Paginator(Article.objects.all(), settings.PAGINATE_BY)
|
||||
self.check_pagination(p, '', '')
|
||||
|
||||
p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY)
|
||||
self.check_pagination(p, '分类标签归档', tag.slug)
|
||||
|
||||
p = Paginator(
|
||||
Article.objects.filter(
|
||||
author__username='liangliangyy'), settings.PAGINATE_BY)
|
||||
self.check_pagination(p, '作者文章归档', 'liangliangyy')
|
||||
|
||||
p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY)
|
||||
self.check_pagination(p, '分类目录归档', category.slug)
|
||||
|
||||
f = BlogSearchForm()
|
||||
f.search()
|
||||
# self.client.login(username='liangliangyy', password='liangliangyy')
|
||||
from djangoblog.spider_notify import SpiderNotify
|
||||
SpiderNotify.baidu_notify([article.get_full_url()])
|
||||
|
||||
from blog.templatetags.blog_tags import gravatar_url, gravatar
|
||||
u = gravatar_url('liangliangyy@gmail.com')
|
||||
u = gravatar('liangliangyy@gmail.com')
|
||||
|
||||
link = Links(
|
||||
sequence=1,
|
||||
name="lylinux",
|
||||
link='https://wwww.lylinux.net')
|
||||
link.save()
|
||||
response = self.client.get('/links.html')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get('/feed/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get('/sitemap.xml')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.get("/admin/blog/article/1/delete/")
|
||||
self.client.get('/admin/servermanager/emailsendlog/')
|
||||
self.client.get('/admin/admin/logentry/')
|
||||
self.client.get('/admin/admin/logentry/1/change/')
|
||||
|
||||
def check_pagination(self, p, type, value):
|
||||
for page in range(1, p.num_pages + 1):
|
||||
s = load_pagination_info(p.page(page), type, value)
|
||||
self.assertIsNotNone(s)
|
||||
if s['previous_url']:
|
||||
response = self.client.get(s['previous_url'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
if s['next_url']:
|
||||
response = self.client.get(s['next_url'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_image(self):
|
||||
import requests
|
||||
rsp = requests.get(
|
||||
'https://www.python.org/static/img/python-logo.png')
|
||||
imagepath = os.path.join(settings.BASE_DIR, 'python.png')
|
||||
with open(imagepath, 'wb') as file:
|
||||
file.write(rsp.content)
|
||||
rsp = self.client.post('/upload')
|
||||
self.assertEqual(rsp.status_code, 403)
|
||||
sign = get_sha256(get_sha256(settings.SECRET_KEY))
|
||||
with open(imagepath, 'rb') as file:
|
||||
imgfile = SimpleUploadedFile(
|
||||
'python.png', file.read(), content_type='image/jpg')
|
||||
form_data = {'python.png': imgfile}
|
||||
rsp = self.client.post(
|
||||
'/upload?sign=' + sign, form_data, follow=True)
|
||||
self.assertEqual(rsp.status_code, 200)
|
||||
os.remove(imagepath)
|
||||
from djangoblog.utils import save_user_avatar, send_email
|
||||
send_email(['qq@qq.com'], 'testTitle', 'testContent')
|
||||
save_user_avatar(
|
||||
'https://www.python.org/static/img/python-logo.png')
|
||||
|
||||
def test_errorpage(self):
|
||||
rsp = self.client.get('/eee')
|
||||
self.assertEqual(rsp.status_code, 404)
|
||||
|
||||
def test_commands(self):
|
||||
user = BlogUser.objects.get_or_create(
|
||||
email="liangliangyy@gmail.com",
|
||||
username="liangliangyy")[0]
|
||||
user.set_password("liangliangyy")
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
|
||||
c = OAuthConfig()
|
||||
c.type = 'qq'
|
||||
c.appkey = 'appkey'
|
||||
c.appsecret = 'appsecret'
|
||||
c.save()
|
||||
|
||||
u = OAuthUser()
|
||||
u.type = 'qq'
|
||||
u.openid = 'openid'
|
||||
u.user = user
|
||||
u.picture = static("/blog/img/avatar.png")
|
||||
u.metadata = '''
|
||||
{
|
||||
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
|
||||
}'''
|
||||
u.save()
|
||||
|
||||
u = OAuthUser()
|
||||
u.type = 'qq'
|
||||
u.openid = 'openid1'
|
||||
u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30'
|
||||
u.metadata = '''
|
||||
{
|
||||
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
|
||||
}'''
|
||||
u.save()
|
||||
|
||||
from blog.documents import ELASTICSEARCH_ENABLED
|
||||
if ELASTICSEARCH_ENABLED:
|
||||
call_command("build_index")
|
||||
call_command("ping_baidu", "all")
|
||||
call_command("create_testdata")
|
||||
call_command("clear_cache")
|
||||
call_command("sync_user_avatar")
|
||||
call_command("build_search_words")
|
||||
@ -0,0 +1,102 @@
|
||||
# Generated by Django 5.2.4 on 2025-11-23 23:26
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blog', '0006_alter_blogsettings_options'),
|
||||
('comments', '0003_alter_comment_options_remove_comment_created_time_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CommentLike',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created time')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'comment like',
|
||||
'verbose_name_plural': 'comment like',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CommentReport',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reason', models.CharField(choices=[('spam', 'Spam'), ('abuse', 'Abuse'), ('inappropriate', 'Inappropriate Content'), ('other', 'Other')], default='other', max_length=20, verbose_name='reason')),
|
||||
('description', models.TextField(blank=True, max_length=500, verbose_name='description')),
|
||||
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created time')),
|
||||
('is_handled', models.BooleanField(default=False, verbose_name='is handled')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'comment report',
|
||||
'verbose_name_plural': 'comment report',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='comment',
|
||||
name='like_count',
|
||||
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='like count'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='comment',
|
||||
name='report_count',
|
||||
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='report count'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='comment',
|
||||
index=models.Index(fields=['-creation_time'], name='comments_co_creatio_444c12_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='comment',
|
||||
index=models.Index(fields=['-like_count'], name='comments_co_like_co_572784_idx'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='commentlike',
|
||||
name='comment',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes', to='comments.comment', verbose_name='comment'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='commentlike',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='commentreport',
|
||||
name='comment',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reports', to='comments.comment', verbose_name='comment'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='commentreport',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='commentlike',
|
||||
index=models.Index(fields=['comment', 'user'], name='comments_co_comment_e08f6a_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='commentlike',
|
||||
unique_together={('user', 'comment')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='commentreport',
|
||||
index=models.Index(fields=['comment', 'user'], name='comments_co_comment_22fd70_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='commentreport',
|
||||
index=models.Index(fields=['is_handled'], name='comments_co_is_hand_8df8dc_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='commentreport',
|
||||
unique_together={('user', 'comment')},
|
||||
),
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue